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