1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.deconz;
10 name = "deconz";
11 stateDir = "/var/lib/${name}";
12 # ref. upstream deconz.service
13 capabilities =
14 lib.optionals (cfg.httpPort < 1024 || cfg.wsPort < 1024) [ "CAP_NET_BIND_SERVICE" ]
15 ++ lib.optionals (cfg.allowRebootSystem) [ "CAP_SYS_BOOT" ]
16 ++ lib.optionals (cfg.allowRestartService) [ "CAP_KILL" ]
17 ++ lib.optionals (cfg.allowSetSystemTime) [ "CAP_SYS_TIME" ];
18in
19{
20 options.services.deconz = {
21
22 enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee/RaspBee hardware (https://phoscon.de/)";
23
24 package = lib.mkOption {
25 type = lib.types.package;
26 default = pkgs.deconz;
27 defaultText = lib.literalExpression "pkgs.deconz";
28 description = "Which deCONZ package to use.";
29 };
30
31 device = lib.mkOption {
32 type = lib.types.nullOr lib.types.str;
33 default = null;
34 description = ''
35 Force deCONZ to use a specific USB device (e.g. /dev/ttyACM0). By
36 default it does a search.
37 '';
38 };
39
40 listenAddress = lib.mkOption {
41 type = lib.types.str;
42 default = "127.0.0.1";
43 description = ''
44 Pin deCONZ to the network interface specified through the provided IP
45 address. This applies for the webserver as well as the websocket
46 notifications.
47 '';
48 };
49
50 httpPort = lib.mkOption {
51 type = lib.types.port;
52 default = 80;
53 description = "TCP port for the web server.";
54 };
55
56 wsPort = lib.mkOption {
57 type = lib.types.port;
58 default = 443;
59 description = "TCP port for the WebSocket.";
60 };
61
62 openFirewall = lib.mkEnableOption "opening up the service ports in the firewall";
63
64 allowRebootSystem = lib.mkEnableOption "rebooting the system";
65
66 allowRestartService = lib.mkEnableOption "killing/restarting processes";
67
68 allowSetSystemTime = lib.mkEnableOption "setting the system time";
69
70 extraArgs = lib.mkOption {
71 type = lib.types.listOf lib.types.str;
72 default = [ ];
73 example = [
74 "--dbg-info=1"
75 "--dbg-err=2"
76 ];
77 description = ''
78 Extra command line arguments for deCONZ, see
79 <https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/deCONZ-command-line-parameters>.
80 '';
81 };
82 };
83
84 config = lib.mkIf cfg.enable {
85
86 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
87 cfg.httpPort
88 cfg.wsPort
89 ];
90
91 services.udev.packages = [ cfg.package ];
92
93 systemd.services.deconz = {
94 description = "deCONZ Zigbee gateway";
95 wantedBy = [ "multi-user.target" ];
96 preStart = ''
97 # The service puts a nix store path reference in here, and that path can
98 # be garbage collected. Ensure the file gets "refreshed" on every start.
99 rm -f ${stateDir}/.local/share/dresden-elektronik/deCONZ/zcldb.txt
100 '';
101 postStart = ''
102 # Delay signalling service readiness until it's actually up.
103 while ! "${lib.getExe pkgs.curl}" -sSfL -o /dev/null "http://${cfg.listenAddress}:${toString cfg.httpPort}"; do
104 echo "Waiting for TCP port ${toString cfg.httpPort} to be open..."
105 sleep 1
106 done
107 '';
108 environment = {
109 HOME = stateDir;
110 XDG_RUNTIME_DIR = "/run/${name}";
111 };
112 serviceConfig = {
113 ExecStart =
114 "${lib.getExe cfg.package}"
115 + " -platform minimal"
116 + " --http-listen=${cfg.listenAddress}"
117 + " --http-port=${toString cfg.httpPort}"
118 + " --ws-port=${toString cfg.wsPort}"
119 + " --auto-connect=1"
120 + (lib.optionalString (cfg.device != null) " --dev=${cfg.device}")
121 + " "
122 + (lib.escapeShellArgs cfg.extraArgs);
123 Restart = "on-failure";
124 AmbientCapabilities = capabilities;
125 CapabilityBoundingSet = capabilities;
126 UMask = "0027";
127 DynamicUser = true;
128 RuntimeDirectory = name;
129 RuntimeDirectoryMode = "0700";
130 StateDirectory = name;
131 SuccessExitStatus = [ 143 ];
132 WorkingDirectory = stateDir;
133 # For access to /dev/ttyACM0 (ConBee).
134 SupplementaryGroups = [ "dialout" ];
135 ProtectHome = true;
136 };
137 };
138 };
139}