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