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 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 };
73
74 systemd.services.zigbee2mqtt = {
75 description = "Zigbee2mqtt Service";
76 wantedBy = [ "multi-user.target" ];
77 after = [ "network.target" ];
78 environment.ZIGBEE2MQTT_DATA = cfg.dataDir;
79 serviceConfig = {
80 ExecStart = "${cfg.package}/bin/zigbee2mqtt";
81 User = "zigbee2mqtt";
82 Group = "zigbee2mqtt";
83 WorkingDirectory = cfg.dataDir;
84 Restart = "on-failure";
85
86 # Hardening
87 CapabilityBoundingSet = "";
88 DeviceAllow = [
89 config.services.zigbee2mqtt.settings.serial.port
90 ];
91 DevicePolicy = "closed";
92 LockPersonality = true;
93 MemoryDenyWriteExecute = false;
94 NoNewPrivileges = true;
95 PrivateDevices = false; # prevents access to /dev/serial, because it is set 0700 root:root
96 PrivateUsers = true;
97 PrivateTmp = true;
98 ProtectClock = true;
99 ProtectControlGroups = true;
100 ProtectHome = true;
101 ProtectHostname = true;
102 ProtectKernelLogs = true;
103 ProtectKernelModules = true;
104 ProtectKernelTunables = true;
105 ProtectProc = "invisible";
106 ProcSubset = "pid";
107 ProtectSystem = "strict";
108 ReadWritePaths = cfg.dataDir;
109 RemoveIPC = true;
110 RestrictAddressFamilies = [
111 "AF_INET"
112 "AF_INET6"
113 ];
114 RestrictNamespaces = true;
115 RestrictRealtime = true;
116 RestrictSUIDSGID = true;
117 SupplementaryGroups = [
118 "dialout"
119 ];
120 SystemCallArchitectures = "native";
121 SystemCallFilter = [
122 "@system-service @pkey"
123 "~@privileged @resources"
124 ];
125 UMask = "0077";
126 };
127 preStart = ''
128 cp --no-preserve=mode ${configFile} "${cfg.dataDir}/configuration.yaml"
129 '';
130 };
131
132 users.users.zigbee2mqtt = {
133 home = cfg.dataDir;
134 createHome = true;
135 group = "zigbee2mqtt";
136 uid = config.ids.uids.zigbee2mqtt;
137 };
138
139 users.groups.zigbee2mqtt.gid = config.ids.gids.zigbee2mqtt;
140 };
141}