grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / home-automation / esphome.nix
blobfaae5ec8ff45cebe0fd113ac832331887a8d2957
1 { config, lib, pkgs, ... }:
3 let
4   inherit (lib)
5     literalExpression
6     maintainers
7     mkEnableOption
8     mkIf
9     mkOption
10     types
11     ;
13   cfg = config.services.esphome;
15   stateDir = "/var/lib/esphome";
17   esphomeParams =
18     if cfg.enableUnixSocket
19     then "--socket /run/esphome/esphome.sock"
20     else "--address ${cfg.address} --port ${toString cfg.port}";
23   meta.maintainers = with maintainers; [ oddlama ];
25   options.services.esphome = {
26     enable = mkEnableOption "esphome, for making custom firmwares for ESP32/ESP8266";
28     package = lib.mkPackageOption pkgs "esphome" { };
30     enableUnixSocket = mkOption {
31       type = types.bool;
32       default = false;
33       description = "Listen on a unix socket `/run/esphome/esphome.sock` instead of the TCP port.";
34     };
36     address = mkOption {
37       type = types.str;
38       default = "localhost";
39       description = "esphome address";
40     };
42     port = mkOption {
43       type = types.port;
44       default = 6052;
45       description = "esphome port";
46     };
48     openFirewall = mkOption {
49       default = false;
50       type = types.bool;
51       description = "Whether to open the firewall for the specified port.";
52     };
54     allowedDevices = mkOption {
55       default = ["char-ttyS" "char-ttyUSB"];
56       example = ["/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"];
57       description = ''
58         A list of device nodes to which {command}`esphome` has access to.
59         Refer to DeviceAllow in systemd.resource-control(5) for more information.
60         Beware that if a device is referred to by an absolute path instead of a device category,
61         it will only allow devices that already are plugged in when the service is started.
62       '';
63       type = types.listOf types.str;
64     };
66     usePing = mkOption {
67       default = false;
68       type = types.bool;
69       description = "Use ping to check online status of devices instead of mDNS";
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       environment = {
83         # platformio fails to determine the home directory when using DynamicUser
84         PLATFORMIO_CORE_DIR = "${stateDir}/.platformio";
85       } // lib.optionalAttrs cfg.usePing { ESPHOME_DASHBOARD_USE_PING = "true"; };
87       serviceConfig = {
88         ExecStart = "${cfg.package}/bin/esphome dashboard ${esphomeParams} ${stateDir}";
89         DynamicUser = true;
90         User = "esphome";
91         Group = "esphome";
92         WorkingDirectory = stateDir;
93         StateDirectory = "esphome";
94         StateDirectoryMode = "0750";
95         Restart = "on-failure";
96         RuntimeDirectory = mkIf cfg.enableUnixSocket "esphome";
97         RuntimeDirectoryMode = "0750";
99         # Hardening
100         CapabilityBoundingSet = "";
101         LockPersonality = true;
102         MemoryDenyWriteExecute = true;
103         DevicePolicy = "closed";
104         DeviceAllow = map (d: "${d} rw") cfg.allowedDevices;
105         SupplementaryGroups = ["dialout"];
106         #NoNewPrivileges = true; # Implied by DynamicUser
107         PrivateUsers = true;
108         #PrivateTmp = true; # Implied by DynamicUser
109         ProtectClock = true;
110         ProtectControlGroups = true;
111         ProtectHome = true;
112         ProtectHostname = false; # breaks bwrap
113         ProtectKernelLogs = false; # breaks bwrap
114         ProtectKernelModules = true;
115         ProtectKernelTunables = false; # breaks bwrap
116         ProtectProc = "invisible";
117         ProcSubset = "all"; # Using "pid" breaks bwrap
118         ProtectSystem = "strict";
119         #RemoveIPC = true; # Implied by DynamicUser
120         RestrictAddressFamilies = [
121           "AF_INET"
122           "AF_INET6"
123           "AF_NETLINK"
124           "AF_UNIX"
125         ];
126         RestrictNamespaces = false; # Required by platformio for chroot
127         RestrictRealtime = true;
128         #RestrictSUIDSGID = true; # Implied by DynamicUser
129         SystemCallArchitectures = "native";
130         SystemCallFilter = [
131           "@system-service"
132           "@mount" # Required by platformio for chroot
133         ];
134         UMask = "0077";
135       };
136     };
137   };