at 24.11-pre 4.3 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 inherit (lib) 5 literalExpression 6 maintainers 7 mkEnableOption 8 mkIf 9 mkOption 10 types 11 ; 12 13 cfg = config.services.esphome; 14 15 stateDir = "/var/lib/esphome"; 16 17 esphomeParams = 18 if cfg.enableUnixSocket 19 then "--socket /run/esphome/esphome.sock" 20 else "--address ${cfg.address} --port ${toString cfg.port}"; 21in 22{ 23 meta.maintainers = with maintainers; [ oddlama ]; 24 25 options.services.esphome = { 26 enable = mkEnableOption "esphome, for making custom firmwares for ESP32/ESP8266"; 27 28 package = lib.mkPackageOption pkgs "esphome" { }; 29 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 }; 35 36 address = mkOption { 37 type = types.str; 38 default = "localhost"; 39 description = "esphome address"; 40 }; 41 42 port = mkOption { 43 type = types.port; 44 default = 6052; 45 description = "esphome port"; 46 }; 47 48 openFirewall = mkOption { 49 default = false; 50 type = types.bool; 51 description = "Whether to open the firewall for the specified port."; 52 }; 53 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 }; 65 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 }; 72 73 config = mkIf cfg.enable { 74 networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall && !cfg.enableUnixSocket) [cfg.port]; 75 76 systemd.services.esphome = { 77 description = "ESPHome dashboard"; 78 after = ["network.target"]; 79 wantedBy = ["multi-user.target"]; 80 path = [cfg.package]; 81 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"; }; 86 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"; 98 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 }; 138}