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