1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.zigbee2mqtt;
7
8 format = pkgs.formats.yaml { };
9 configFile = format.generate "zigbee2mqtt.yaml" cfg.settings;
10
11in
12{
13 meta.maintainers = with maintainers; [ sweber hexa ];
14
15 imports = [
16 # Remove warning before the 21.11 release
17 (mkRenamedOptionModule [ "services" "zigbee2mqtt" "config" ] [ "services" "zigbee2mqtt" "settings" ])
18 ];
19
20 options.services.zigbee2mqtt = {
21 enable = mkEnableOption (lib.mdDoc "zigbee2mqtt service");
22
23 package = mkOption {
24 description = lib.mdDoc "Zigbee2mqtt package to use";
25 default = pkgs.zigbee2mqtt;
26 defaultText = literalExpression ''
27 pkgs.zigbee2mqtt
28 '';
29 type = types.package;
30 };
31
32 dataDir = mkOption {
33 description = lib.mdDoc "Zigbee2mqtt data directory";
34 default = "/var/lib/zigbee2mqtt";
35 type = types.path;
36 };
37
38 settings = mkOption {
39 type = format.type;
40 default = { };
41 example = literalExpression ''
42 {
43 homeassistant = config.services.home-assistant.enable;
44 permit_join = true;
45 serial = {
46 port = "/dev/ttyACM1";
47 };
48 }
49 '';
50 description = lib.mdDoc ''
51 Your {file}`configuration.yaml` as a Nix attribute set.
52 Check the [documentation](https://www.zigbee2mqtt.io/information/configuration.html)
53 for possible options.
54 '';
55 };
56 };
57
58 config = mkIf (cfg.enable) {
59
60 # preset config values
61 services.zigbee2mqtt.settings = {
62 homeassistant = mkDefault config.services.home-assistant.enable;
63 permit_join = mkDefault false;
64 mqtt = {
65 base_topic = mkDefault "zigbee2mqtt";
66 server = mkDefault "mqtt://localhost:1883";
67 };
68 serial.port = mkDefault "/dev/ttyACM0";
69 # reference device/group configuration, that is kept in a separate file
70 # to prevent it being overwritten in the units ExecStartPre script
71 devices = mkDefault "devices.yaml";
72 groups = mkDefault "groups.yaml";
73 };
74
75 systemd.services.zigbee2mqtt = {
76 description = "Zigbee2mqtt Service";
77 wantedBy = [ "multi-user.target" ];
78 after = [ "network.target" ];
79 environment.ZIGBEE2MQTT_DATA = cfg.dataDir;
80 serviceConfig = {
81 ExecStart = "${cfg.package}/bin/zigbee2mqtt";
82 User = "zigbee2mqtt";
83 Group = "zigbee2mqtt";
84 WorkingDirectory = cfg.dataDir;
85 Restart = "on-failure";
86
87 # Hardening
88 CapabilityBoundingSet = "";
89 DeviceAllow = [
90 config.services.zigbee2mqtt.settings.serial.port
91 ];
92 DevicePolicy = "closed";
93 LockPersonality = true;
94 MemoryDenyWriteExecute = false;
95 NoNewPrivileges = true;
96 PrivateDevices = false; # prevents access to /dev/serial, because it is set 0700 root:root
97 PrivateUsers = true;
98 PrivateTmp = true;
99 ProtectClock = true;
100 ProtectControlGroups = true;
101 ProtectHome = true;
102 ProtectHostname = true;
103 ProtectKernelLogs = true;
104 ProtectKernelModules = true;
105 ProtectKernelTunables = true;
106 ProtectProc = "invisible";
107 ProcSubset = "pid";
108 ProtectSystem = "strict";
109 ReadWritePaths = cfg.dataDir;
110 RemoveIPC = true;
111 RestrictAddressFamilies = [
112 "AF_INET"
113 "AF_INET6"
114 ];
115 RestrictNamespaces = true;
116 RestrictRealtime = true;
117 RestrictSUIDSGID = true;
118 SupplementaryGroups = [
119 "dialout"
120 ];
121 SystemCallArchitectures = "native";
122 SystemCallFilter = [
123 "@system-service @pkey"
124 "~@privileged @resources"
125 ];
126 UMask = "0077";
127 };
128 preStart = ''
129 cp --no-preserve=mode ${configFile} "${cfg.dataDir}/configuration.yaml"
130 '';
131 };
132
133 users.users.zigbee2mqtt = {
134 home = cfg.dataDir;
135 createHome = true;
136 group = "zigbee2mqtt";
137 uid = config.ids.uids.zigbee2mqtt;
138 };
139
140 users.groups.zigbee2mqtt.gid = config.ids.gids.zigbee2mqtt;
141 };
142}