at 23.11-pre 4.2 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 inherit (lib) 5 literalExpression 6 maintainers 7 mkEnableOption 8 mkIf 9 mkOption 10 mdDoc 11 types 12 ; 13 14 cfg = config.services.esphome; 15 16 stateDir = "/var/lib/esphome"; 17 18 esphomeParams = 19 if cfg.enableUnixSocket 20 then "--socket /run/esphome/esphome.sock" 21 else "--address ${cfg.address} --port ${toString cfg.port}"; 22in 23{ 24 meta.maintainers = with maintainers; [ oddlama ]; 25 26 options.services.esphome = { 27 enable = mkEnableOption (mdDoc "esphome"); 28 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 }; 35 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 }; 41 42 address = mkOption { 43 type = types.str; 44 default = "localhost"; 45 description = mdDoc "esphome address"; 46 }; 47 48 port = mkOption { 49 type = types.port; 50 default = 6052; 51 description = mdDoc "esphome port"; 52 }; 53 54 openFirewall = mkOption { 55 default = false; 56 type = types.bool; 57 description = mdDoc "Whether to open the firewall for the specified port."; 58 }; 59 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 }; 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 # platformio fails to determine the home directory when using DynamicUser 83 environment.PLATFORMIO_CORE_DIR = "${stateDir}/.platformio"; 84 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"; 96 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 = "pid"; 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 }; 136}