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