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