about summary refs log tree commit diff
path: root/nixos/modules/services/networking/deconz.nix
blob: eaa7759d0407c7f19b340fd60b4dfc19641a6883 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{ config, lib, pkgs, ... }:

let
  cfg = config.services.deconz;
  name = "deconz";
  stateDir = "/var/lib/${name}";
  # ref. upstream deconz.service
  capabilities =
    lib.optionals (cfg.httpPort < 1024 || cfg.wsPort < 1024) [ "CAP_NET_BIND_SERVICE" ]
    ++ lib.optionals (cfg.allowRebootSystem) [ "CAP_SYS_BOOT" ]
    ++ lib.optionals (cfg.allowRestartService) [ "CAP_KILL" ]
    ++ lib.optionals (cfg.allowSetSystemTime) [ "CAP_SYS_TIME" ];
in
{
  options.services.deconz = {

    enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee hardware (https://phoscon.de/en/conbee2)";

    package = lib.mkOption {
      type = lib.types.package;
      default = pkgs.deconz;
      defaultText = lib.literalExpression "pkgs.deconz";
      description = "Which deCONZ package to use.";
    };

    device = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = ''
        Force deCONZ to use a specific USB device (e.g. /dev/ttyACM0). By
        default it does a search.
      '';
    };

    listenAddress = lib.mkOption {
      type = lib.types.str;
      default = "127.0.0.1";
      description = ''
        Pin deCONZ to the network interface specified through the provided IP
        address. This applies for the webserver as well as the websocket
        notifications.
      '';
    };

    httpPort = lib.mkOption {
      type = lib.types.port;
      default = 80;
      description = "TCP port for the web server.";
    };

    wsPort = lib.mkOption {
      type = lib.types.port;
      default = 443;
      description = "TCP port for the WebSocket.";
    };

    openFirewall = lib.mkEnableOption "opening up the service ports in the firewall";

    allowRebootSystem = lib.mkEnableOption "rebooting the system";

    allowRestartService = lib.mkEnableOption "killing/restarting processes";

    allowSetSystemTime = lib.mkEnableOption "setting the system time";

    extraArgs = lib.mkOption {
      type = lib.types.listOf lib.types.str;
      default = [ ];
      example = [
        "--dbg-info=1"
        "--dbg-err=2"
      ];
      description = ''
        Extra command line arguments for deCONZ, see
        https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/deCONZ-command-line-parameters.
      '';
    };
  };

  config = lib.mkIf cfg.enable {

    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
      cfg.httpPort
      cfg.wsPort
    ];

    services.udev.packages = [ cfg.package ];

    systemd.services.deconz = {
      description = "deCONZ Zigbee gateway";
      wantedBy = [ "multi-user.target" ];
      preStart = ''
        # The service puts a nix store path reference in here, and that path can
        # be garbage collected. Ensure the file gets "refreshed" on every start.
        rm -f ${stateDir}/.local/share/dresden-elektronik/deCONZ/zcldb.txt
      '';
      postStart = ''
        # Delay signalling service readiness until it's actually up.
        while ! "${lib.getExe pkgs.curl}" -sSfl -o /dev/null "http://${cfg.listenAddress}:${toString cfg.httpPort}"; do
            echo "Waiting for TCP port ${toString cfg.httpPort} to be open..."
            sleep 1
        done
      '';
      environment = {
        HOME = stateDir;
        XDG_RUNTIME_DIR = "/run/${name}";
      };
      serviceConfig = {
        ExecStart =
          "${lib.getExe cfg.package}"
          + " -platform minimal"
          + " --http-listen=${cfg.listenAddress}"
          + " --http-port=${toString cfg.httpPort}"
          + " --ws-port=${toString cfg.wsPort}"
          + " --auto-connect=1"
          + (lib.optionalString (cfg.device != null) " --dev=${cfg.device}")
          + " " + (lib.escapeShellArgs cfg.extraArgs);
        Restart = "on-failure";
        AmbientCapabilities = capabilities;
        CapabilityBoundingSet = capabilities;
        UMask = "0027";
        DynamicUser = true;
        RuntimeDirectory = name;
        RuntimeDirectoryMode = "0700";
        StateDirectory = name;
        WorkingDirectory = stateDir;
        # For access to /dev/ttyACM0 (ConBee).
        SupplementaryGroups = [ "dialout" ];
        ProtectHome = true;
      };
    };
  };
}