nixos/preload: init
[NixPkgs.git] / nixos / modules / services / home-automation / esphome.nix
blob080c8876382f9223a5adacc6c52bfdd8948fdf91
1 { config, lib, pkgs, ... }:
3 let
4   inherit (lib)
5     literalExpression
6     maintainers
7     mkEnableOption
8     mkIf
9     mkOption
10     mdDoc
11     types
12     ;
14   cfg = config.services.esphome;
16   stateDir = "/var/lib/esphome";
18   esphomeParams =
19     if cfg.enableUnixSocket
20     then "--socket /run/esphome/esphome.sock"
21     else "--address ${cfg.address} --port ${toString cfg.port}";
24   meta.maintainers = with maintainers; [ oddlama ];
26   options.services.esphome = {
27     enable = mkEnableOption (mdDoc "esphome");
29     package = mkOption {
30       type = types.package;
31       default = pkgs.esphome;
32       defaultText = literalExpression "pkgs.esphome";
33       description = mdDoc "The package to use for the esphome command.";
34     };
36     enableUnixSocket = mkOption {
37       type = types.bool;
38       default = false;
39       description = lib.mdDoc "Listen on a unix socket `/run/esphome/esphome.sock` instead of the TCP port.";
40     };
42     address = mkOption {
43       type = types.str;
44       default = "localhost";
45       description = mdDoc "esphome address";
46     };
48     port = mkOption {
49       type = types.port;
50       default = 6052;
51       description = mdDoc "esphome port";
52     };
54     openFirewall = mkOption {
55       default = false;
56       type = types.bool;
57       description = mdDoc "Whether to open the firewall for the specified port.";
58     };
60     allowedDevices = mkOption {
61       default = ["char-ttyS" "char-ttyUSB"];
62       example = ["/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"];
63       description = lib.mdDoc ''
64         A list of device nodes to which {command}`esphome` has access to.
65         Refer to DeviceAllow in systemd.resource-control(5) for more information.
66         Beware that if a device is referred to by an absolute path instead of a device category,
67         it will only allow devices that already are plugged in when the service is started.
68       '';
69       type = types.listOf types.str;
70     };
71   };
73   config = mkIf cfg.enable {
74     networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall && !cfg.enableUnixSocket) [cfg.port];
76     systemd.services.esphome = {
77       description = "ESPHome dashboard";
78       after = ["network.target"];
79       wantedBy = ["multi-user.target"];
80       path = [cfg.package];
82       # platformio fails to determine the home directory when using DynamicUser
83       environment.PLATFORMIO_CORE_DIR = "${stateDir}/.platformio";
85       serviceConfig = {
86         ExecStart = "${cfg.package}/bin/esphome dashboard ${esphomeParams} ${stateDir}";
87         DynamicUser = true;
88         User = "esphome";
89         Group = "esphome";
90         WorkingDirectory = stateDir;
91         StateDirectory = "esphome";
92         StateDirectoryMode = "0750";
93         Restart = "on-failure";
94         RuntimeDirectory = mkIf cfg.enableUnixSocket "esphome";
95         RuntimeDirectoryMode = "0750";
97         # Hardening
98         CapabilityBoundingSet = "";
99         LockPersonality = true;
100         MemoryDenyWriteExecute = true;
101         DevicePolicy = "closed";
102         DeviceAllow = map (d: "${d} rw") cfg.allowedDevices;
103         SupplementaryGroups = ["dialout"];
104         #NoNewPrivileges = true; # Implied by DynamicUser
105         PrivateUsers = true;
106         #PrivateTmp = true; # Implied by DynamicUser
107         ProtectClock = true;
108         ProtectControlGroups = true;
109         ProtectHome = true;
110         ProtectHostname = true;
111         ProtectKernelLogs = true;
112         ProtectKernelModules = true;
113         ProtectKernelTunables = true;
114         ProtectProc = "invisible";
115         ProcSubset = "all"; # Using "pid" breaks bwrap
116         ProtectSystem = "strict";
117         #RemoveIPC = true; # Implied by DynamicUser
118         RestrictAddressFamilies = [
119           "AF_INET"
120           "AF_INET6"
121           "AF_NETLINK"
122           "AF_UNIX"
123         ];
124         RestrictNamespaces = false; # Required by platformio for chroot
125         RestrictRealtime = true;
126         #RestrictSUIDSGID = true; # Implied by DynamicUser
127         SystemCallArchitectures = "native";
128         SystemCallFilter = [
129           "@system-service"
130           "@mount" # Required by platformio for chroot
131         ];
132         UMask = "0077";
133       };
134     };
135   };