forked from mirrors_public/oddlama_nix-config
feat: modulize esphome
This commit is contained in:
parent
8545dff4e7
commit
5d8c1c902d
4 changed files with 241 additions and 116 deletions
12
flake.lock
generated
12
flake.lock
generated
|
@ -166,11 +166,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1679265143,
|
"lastModified": 1679480702,
|
||||||
"narHash": "sha256-5RDMW+O4owjdPz7t4K4YxH2fOHCNOcyVmSiKRUikiv0=",
|
"narHash": "sha256-npuRD61YmxUPitI1TqKwlxLrU6iGl5E+BPT196LgUDo=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "1b8bf5c3270386a1b6850bd77d79dbdbaf0d7a7c",
|
"rev": "363c46b2480f1b73ec37cf68caac61f5daa82a2e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -211,11 +211,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1679172431,
|
"lastModified": 1679262748,
|
||||||
"narHash": "sha256-XEh5gIt5otaUbEAPUY5DILUTyWe1goAyeqQtmwaFPyI=",
|
"narHash": "sha256-DQCrrAFrkxijC6haUzOC5ZoFqpcv/tg2WxnyW3np1Cc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1603d11595a232205f03d46e635d919d1e1ec5b9",
|
"rev": "60c1d71f2ba4c80178ec84523c2ca0801522e0a6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -1,68 +1,17 @@
|
||||||
{
|
{nodeSecrets, ...}: {
|
||||||
lib,
|
imports = [../../modules/esphome.nix];
|
||||||
config,
|
|
||||||
nixos-hardware,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
dataDir = "/var/lib/esphome";
|
|
||||||
in {
|
|
||||||
systemd.services.esphome = {
|
|
||||||
description = "ESPHome Service";
|
|
||||||
wantedBy = ["multi-user.target"];
|
|
||||||
after = ["network.target"];
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart = "${pkgs.esphome}/bin/esphome dashboard --socket /run/esphome/esphome.sock ${dataDir}";
|
|
||||||
User = "esphome";
|
|
||||||
Group = "esphome";
|
|
||||||
WorkingDirectory = dataDir;
|
|
||||||
RuntimeDirectory = "esphome";
|
|
||||||
Restart = "on-failure";
|
|
||||||
|
|
||||||
# Hardening
|
services.esphome = {
|
||||||
CapabilityBoundingSet = "";
|
enable = true;
|
||||||
LockPersonality = true;
|
enableUnixSocket = true;
|
||||||
MemoryDenyWriteExecute = true;
|
allowedDevices = [
|
||||||
DevicePolicy = "closed";
|
{
|
||||||
DeviceAllow = "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0";
|
node = "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0";
|
||||||
SupplementaryGroups = ["dialout"];
|
modifier = "rw";
|
||||||
NoNewPrivileges = true;
|
}
|
||||||
PrivateUsers = true;
|
];
|
||||||
PrivateTmp = true;
|
|
||||||
ProtectClock = true;
|
|
||||||
ProtectControlGroups = true;
|
|
||||||
ProtectHome = true;
|
|
||||||
ProtectHostname = true;
|
|
||||||
ProtectKernelLogs = true;
|
|
||||||
ProtectKernelModules = true;
|
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectProc = "invisible";
|
|
||||||
ProcSubset = "pid";
|
|
||||||
ProtectSystem = "strict";
|
|
||||||
ReadWritePaths = dataDir;
|
|
||||||
RemoveIPC = true;
|
|
||||||
RestrictAddressFamilies = ["AF_UNIX" "AF_NETLINK" "AF_INET" "AF_INET6"];
|
|
||||||
RestrictNamespaces = false; # Required by platformio for chroot
|
|
||||||
RestrictRealtime = true;
|
|
||||||
RestrictSUIDSGID = true;
|
|
||||||
SystemCallArchitectures = "native";
|
|
||||||
SystemCallFilter = [
|
|
||||||
"@system-service"
|
|
||||||
"@mount" # Required by platformio for chroot
|
|
||||||
];
|
|
||||||
UMask = "0077";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
users.users.esphome = {
|
|
||||||
home = dataDir;
|
|
||||||
createHome = true;
|
|
||||||
group = "esphome";
|
|
||||||
uid = 316;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups.esphome.gid = 316;
|
|
||||||
|
|
||||||
# TODO esphome.sock permissions pls nginx currently world writable
|
# TODO esphome.sock permissions pls nginx currently world writable
|
||||||
services.nginx.upstreams = {
|
services.nginx.upstreams = {
|
||||||
"esphome" = {
|
"esphome" = {
|
||||||
|
|
153
modules/esphome.nix
Normal file
153
modules/esphome.nix
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(lib)
|
||||||
|
literalExpression
|
||||||
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
mkOption
|
||||||
|
mdDoc
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
cfg = config.services.esphome;
|
||||||
|
|
||||||
|
name = "esphome";
|
||||||
|
|
||||||
|
stateDir = "/var/lib/${name}";
|
||||||
|
in {
|
||||||
|
options.services.esphome = {
|
||||||
|
enable = mkEnableOption (mdDoc "esphome");
|
||||||
|
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.esphome;
|
||||||
|
defaultText = literalExpression "pkgs.esphome";
|
||||||
|
description = mdDoc "The package to use for the esphome command.";
|
||||||
|
};
|
||||||
|
|
||||||
|
enableUnixSocket = mkEnableOption (lib.mdDoc ''
|
||||||
|
Expose a unix socket under /run/esphome/esphome.sock instead of using a TCP socket.
|
||||||
|
'');
|
||||||
|
|
||||||
|
address = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "localhost";
|
||||||
|
description = mdDoc "esphome address";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 6052;
|
||||||
|
description = mdDoc "esphome port";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Whether to open the firewall for the specified port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowedDevices = mkOption {
|
||||||
|
default = [];
|
||||||
|
example = [
|
||||||
|
{
|
||||||
|
node = "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0";
|
||||||
|
modifier = "rw";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
A list of device nodes to which {command}`esphome` has access to.
|
||||||
|
Beware that permissions are not added dynamically when a device
|
||||||
|
is plugged in while the service is already running.
|
||||||
|
'';
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
node = mkOption {
|
||||||
|
example = "/dev/ttyUSB*";
|
||||||
|
type = types.str;
|
||||||
|
description = lib.mdDoc "Path to device node";
|
||||||
|
};
|
||||||
|
modifier = mkOption {
|
||||||
|
example = "rw";
|
||||||
|
type = types.str;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Device node access modifier. Takes a combination
|
||||||
|
`r` (read), `w` (write), and `m` (mknod). See the
|
||||||
|
`systemd.resource-control(5)` man page for more
|
||||||
|
information.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall && !cfg.enableUnixSocket) [cfg.port];
|
||||||
|
|
||||||
|
systemd.services.esphome = {
|
||||||
|
description = "ESPHome dashboard";
|
||||||
|
after = ["network.target"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
path = [cfg.package];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = let
|
||||||
|
extraParams =
|
||||||
|
if cfg.enableUnixSocket
|
||||||
|
then "--socket /run/${name}/esphome.sock"
|
||||||
|
else "--address ${cfg.address} --port ${toString cfg.port}";
|
||||||
|
in "${cfg.package}/bin/esphome dashboard ${extraParams} ${stateDir}";
|
||||||
|
DynamicUser = true;
|
||||||
|
WorkingDirectory = stateDir;
|
||||||
|
StateDirectory = name;
|
||||||
|
StateDirectoryMode = "0750";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RuntimeDirectory = mkIf cfg.enableUnixSocket name;
|
||||||
|
RuntimeDirectoryMode = "0750";
|
||||||
|
|
||||||
|
# Hardening
|
||||||
|
CapabilityBoundingSet = "";
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
DevicePolicy = "closed";
|
||||||
|
DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices;
|
||||||
|
SupplementaryGroups = ["dialout"];
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
ProcSubset = "pid";
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictAddressFamilies = [
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
"AF_NETLINK"
|
||||||
|
"AF_UNIX"
|
||||||
|
];
|
||||||
|
RestrictNamespaces = false; # Required by platformio for chroot
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"@mount" # Required by platformio for chroot
|
||||||
|
];
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -7,21 +7,22 @@
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
any
|
attrNames
|
||||||
attrValues
|
attrValues
|
||||||
concatLists
|
concatLists
|
||||||
|
concatMap
|
||||||
concatMapStrings
|
concatMapStrings
|
||||||
concatStringsSep
|
concatStringsSep
|
||||||
count
|
count
|
||||||
escapeShellArg
|
escapeShellArg
|
||||||
filter
|
filter
|
||||||
|
getAttr
|
||||||
literalExpression
|
literalExpression
|
||||||
mapAttrsToList
|
mapAttrsToList
|
||||||
mdDoc
|
mdDoc
|
||||||
mkIf
|
mkIf
|
||||||
mkOption
|
mkOption
|
||||||
optional
|
optional
|
||||||
optionals
|
|
||||||
optionalString
|
optionalString
|
||||||
stringLength
|
stringLength
|
||||||
toLower
|
toLower
|
||||||
|
@ -30,35 +31,6 @@
|
||||||
|
|
||||||
cfg = config.services.hostapd;
|
cfg = config.services.hostapd;
|
||||||
|
|
||||||
# Maps the specified acl mode to values understood by hostapd
|
|
||||||
macaddrAclModes = {
|
|
||||||
"allow" = "0";
|
|
||||||
"deny" = "1";
|
|
||||||
"radius" = "2";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Maps the specified ignore broadcast ssid mode to values understood by hostapd
|
|
||||||
ignoreBroadcastSsidModes = {
|
|
||||||
"disabled" = "0";
|
|
||||||
"empty" = "1";
|
|
||||||
"clear" = "2";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Maps the specified vht and he channel widths to values understood by hostapd
|
|
||||||
operatingChannelWidth = {
|
|
||||||
"20or40" = "0";
|
|
||||||
"80" = "1";
|
|
||||||
"160" = "2";
|
|
||||||
"80+80" = "3";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Maps the specified vht and he channel widths to values understood by hostapd
|
|
||||||
managementFrameProtection = {
|
|
||||||
"disabled" = "0";
|
|
||||||
"optional" = "1";
|
|
||||||
"required" = "2";
|
|
||||||
};
|
|
||||||
|
|
||||||
bool01 = b:
|
bool01 = b:
|
||||||
if b
|
if b
|
||||||
then "1"
|
then "1"
|
||||||
|
@ -91,7 +63,7 @@
|
||||||
channel=${toString ifcfg.channel}
|
channel=${toString ifcfg.channel}
|
||||||
noscan=${bool01 ifcfg.noScan}
|
noscan=${bool01 ifcfg.noScan}
|
||||||
# Set the MAC-address access control mode
|
# Set the MAC-address access control mode
|
||||||
macaddr_acl=${macaddrAclModes.${ifcfg.macAcl}}
|
macaddr_acl=${ifcfg.macAcl}
|
||||||
${optionalString (ifcfg.macAllow != [] || ifcfg.macAllowFile != null || ifcfg.authentication.saeAddToMacAllow) ''
|
${optionalString (ifcfg.macAllow != [] || ifcfg.macAllowFile != null || ifcfg.authentication.saeAddToMacAllow) ''
|
||||||
accept_mac_file=/run/hostapd/${interface}.mac.allow
|
accept_mac_file=/run/hostapd/${interface}.mac.allow
|
||||||
''}
|
''}
|
||||||
|
@ -100,7 +72,7 @@
|
||||||
''}
|
''}
|
||||||
# Only allow WPA, disable insecure WEP
|
# Only allow WPA, disable insecure WEP
|
||||||
auth_algs=1
|
auth_algs=1
|
||||||
ignore_broadcast_ssid=${ignoreBroadcastSsidModes.${ifcfg.ignoreBroadcastSsid}}
|
ignore_broadcast_ssid=${ifcfg.ignoreBroadcastSsid}
|
||||||
# Always enable QoS, which is required for 802.11n and above
|
# Always enable QoS, which is required for 802.11n and above
|
||||||
wmm_enabled=1
|
wmm_enabled=1
|
||||||
ap_isolate=${bool01 ifcfg.apIsolate}
|
ap_isolate=${bool01 ifcfg.apIsolate}
|
||||||
|
@ -119,14 +91,14 @@
|
||||||
ieee80211ac=1
|
ieee80211ac=1
|
||||||
vht_capab=${concatMapStrings (x: "[${x}]") ifcfg.wifi5.capabilities}
|
vht_capab=${concatMapStrings (x: "[${x}]") ifcfg.wifi5.capabilities}
|
||||||
require_vht=${bool01 ifcfg.wifi5.require}
|
require_vht=${bool01 ifcfg.wifi5.require}
|
||||||
vht_oper_chwidth=${operatingChannelWidth.${ifcfg.wifi5.operatingChannelWidth}}
|
vht_oper_chwidth=${ifcfg.wifi5.operatingChannelWidth}
|
||||||
''}
|
''}
|
||||||
${optionalString ifcfg.wifi6.enable ''
|
${optionalString ifcfg.wifi6.enable ''
|
||||||
##### IEEE 802.11ax (WiFi 6) related configuration #####################################
|
##### IEEE 802.11ax (WiFi 6) related configuration #####################################
|
||||||
|
|
||||||
ieee80211ax=1
|
ieee80211ax=1
|
||||||
require_he=${bool01 ifcfg.wifi6.require}
|
require_he=${bool01 ifcfg.wifi6.require}
|
||||||
he_oper_chwidth=${operatingChannelWidth.${ifcfg.wifi6.operatingChannelWidth}}
|
he_oper_chwidth=${ifcfg.wifi6.operatingChannelWidth}
|
||||||
he_su_beamformer=${bool01 ifcfg.wifi6.singleUserBeamformer}
|
he_su_beamformer=${bool01 ifcfg.wifi6.singleUserBeamformer}
|
||||||
he_su_beamformee=${bool01 ifcfg.wifi6.singleUserBeamformee}
|
he_su_beamformee=${bool01 ifcfg.wifi6.singleUserBeamformee}
|
||||||
he_mu_beamformer=${bool01 ifcfg.wifi6.multiUserBeamformer}
|
he_mu_beamformer=${bool01 ifcfg.wifi6.multiUserBeamformer}
|
||||||
|
@ -135,7 +107,7 @@
|
||||||
##### IEEE 802.11be (WiFi 7) related configuration #####################################
|
##### IEEE 802.11be (WiFi 7) related configuration #####################################
|
||||||
|
|
||||||
ieee80211be=1
|
ieee80211be=1
|
||||||
eht_oper_chwidth=${operatingChannelWidth.${ifcfg.wifi7.operatingChannelWidth}}
|
eht_oper_chwidth=${ifcfg.wifi7.operatingChannelWidth}
|
||||||
eht_su_beamformer=${bool01 ifcfg.wifi7.singleUserBeamformer}
|
eht_su_beamformer=${bool01 ifcfg.wifi7.singleUserBeamformer}
|
||||||
eht_su_beamformee=${bool01 ifcfg.wifi7.singleUserBeamformee}
|
eht_su_beamformee=${bool01 ifcfg.wifi7.singleUserBeamformee}
|
||||||
eht_mu_beamformer=${bool01 ifcfg.wifi7.multiUserBeamformer}
|
eht_mu_beamformer=${bool01 ifcfg.wifi7.multiUserBeamformer}
|
||||||
|
@ -144,7 +116,7 @@
|
||||||
##### WPA/IEEE 802.11i configuration ##########################################
|
##### WPA/IEEE 802.11i configuration ##########################################
|
||||||
|
|
||||||
# Encrypt management frames to protect against deauthentication and similar attacks
|
# Encrypt management frames to protect against deauthentication and similar attacks
|
||||||
ieee80211w=${managementFrameProtection.${ifcfg.managementFrameProtection}}
|
ieee80211w=${ifcfg.managementFrameProtection}
|
||||||
${optionalString (ifcfg.authentication.mode == "none") ''
|
${optionalString (ifcfg.authentication.mode == "none") ''
|
||||||
wpa=0
|
wpa=0
|
||||||
''}
|
''}
|
||||||
|
@ -424,6 +396,12 @@ in {
|
||||||
macAcl = mkOption {
|
macAcl = mkOption {
|
||||||
default = "allow";
|
default = "allow";
|
||||||
type = types.enum ["allow" "deny" "radius"];
|
type = types.enum ["allow" "deny" "radius"];
|
||||||
|
apply = x:
|
||||||
|
getAttr x {
|
||||||
|
"allow" = "0";
|
||||||
|
"deny" = "1";
|
||||||
|
"radius" = "2";
|
||||||
|
};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
Station MAC address -based authentication. The following modes are available:
|
Station MAC address -based authentication. The following modes are available:
|
||||||
|
|
||||||
|
@ -484,6 +462,12 @@ in {
|
||||||
ignoreBroadcastSsid = mkOption {
|
ignoreBroadcastSsid = mkOption {
|
||||||
default = "disabled";
|
default = "disabled";
|
||||||
type = types.enum ["disabled" "empty" "clear"];
|
type = types.enum ["disabled" "empty" "clear"];
|
||||||
|
apply = x:
|
||||||
|
getAttr x {
|
||||||
|
"disabled" = "0";
|
||||||
|
"empty" = "1";
|
||||||
|
"clear" = "2";
|
||||||
|
};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
Send empty SSID in beacons and ignore probe request frames that do not
|
Send empty SSID in beacons and ignore probe request frames that do not
|
||||||
specify full SSID, i.e., require stations to know SSID. Note that this does
|
specify full SSID, i.e., require stations to know SSID. Note that this does
|
||||||
|
@ -717,6 +701,12 @@ in {
|
||||||
managementFrameProtection = mkOption {
|
managementFrameProtection = mkOption {
|
||||||
default = "required";
|
default = "required";
|
||||||
type = types.enum ["disabled" "optional" "required"];
|
type = types.enum ["disabled" "optional" "required"];
|
||||||
|
apply = x:
|
||||||
|
getAttr x {
|
||||||
|
"disabled" = "0";
|
||||||
|
"optional" = "1";
|
||||||
|
"required" = "2";
|
||||||
|
};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
Management frame protection (MFP) authenticates management frames
|
Management frame protection (MFP) authenticates management frames
|
||||||
to prevent deauthentication (or related) attacks.
|
to prevent deauthentication (or related) attacks.
|
||||||
|
@ -789,6 +779,13 @@ in {
|
||||||
operatingChannelWidth = mkOption {
|
operatingChannelWidth = mkOption {
|
||||||
default = "20or40";
|
default = "20or40";
|
||||||
type = types.enum ["20or40" "80" "160" "80+80"];
|
type = types.enum ["20or40" "80" "160" "80+80"];
|
||||||
|
apply = x:
|
||||||
|
getAttr x {
|
||||||
|
"20or40" = "0";
|
||||||
|
"80" = "1";
|
||||||
|
"160" = "2";
|
||||||
|
"80+80" = "3";
|
||||||
|
};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
Determines the operating channel width for VHT.
|
Determines the operating channel width for VHT.
|
||||||
|
|
||||||
|
@ -837,6 +834,13 @@ in {
|
||||||
operatingChannelWidth = mkOption {
|
operatingChannelWidth = mkOption {
|
||||||
default = "20or40";
|
default = "20or40";
|
||||||
type = types.enum ["20or40" "80" "160" "80+80"];
|
type = types.enum ["20or40" "80" "160" "80+80"];
|
||||||
|
apply = x:
|
||||||
|
getAttr x {
|
||||||
|
"20or40" = "0";
|
||||||
|
"80" = "1";
|
||||||
|
"160" = "2";
|
||||||
|
"80+80" = "3";
|
||||||
|
};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
Determines the operating channel width for HE.
|
Determines the operating channel width for HE.
|
||||||
|
|
||||||
|
@ -882,6 +886,13 @@ in {
|
||||||
operatingChannelWidth = mkOption {
|
operatingChannelWidth = mkOption {
|
||||||
default = "20or40";
|
default = "20or40";
|
||||||
type = types.enum ["20or40" "80" "160" "80+80"];
|
type = types.enum ["20or40" "80" "160" "80+80"];
|
||||||
|
apply = x:
|
||||||
|
getAttr x {
|
||||||
|
"20or40" = "0";
|
||||||
|
"80" = "1";
|
||||||
|
"160" = "2";
|
||||||
|
"80+80" = "3";
|
||||||
|
};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
Determines the operating channel width for EHT.
|
Determines the operating channel width for EHT.
|
||||||
|
|
||||||
|
@ -908,14 +919,18 @@ in {
|
||||||
]
|
]
|
||||||
# Interface warnings
|
# Interface warnings
|
||||||
++ (concatLists (mapAttrsToList (interface: ifcfg: let
|
++ (concatLists (mapAttrsToList (interface: ifcfg: let
|
||||||
countWpaPasswordDefinitions = count (x: x != null) [ifcfg.authentication.wpaPassword ifcfg.authentication.wpaPasswordFile ifcfg.authentication.wpaPskFile];
|
countWpaPasswordDefinitions = count (x: x != null) [
|
||||||
|
ifcfg.authentication.wpaPassword
|
||||||
|
ifcfg.authentication.wpaPasswordFile
|
||||||
|
ifcfg.authentication.wpaPskFile
|
||||||
|
];
|
||||||
in [
|
in [
|
||||||
{
|
{
|
||||||
assertion = ifcfg.authentication.mode == "wpa3-sae" -> ifcfg.managementFrameProtection == "required";
|
assertion = ifcfg.authentication.mode == "wpa3-sae" -> ifcfg.managementFrameProtection == "2";
|
||||||
message = ''hostapd interface ${interface} uses WPA3-SAE which requires managementFrameProtection="required"'';
|
message = ''hostapd interface ${interface} uses WPA3-SAE which requires managementFrameProtection="required"'';
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
assertion = ifcfg.authentication.mode == "wpa3-sae-transition" -> ifcfg.managementFrameProtection != "disabled";
|
assertion = ifcfg.authentication.mode == "wpa3-sae-transition" -> ifcfg.managementFrameProtection != "0";
|
||||||
message = ''hostapd interface ${interface} uses WPA3-SAE in transition mode with WPA2-SHA256, which requires managementFrameProtection="optional" or ="required"'';
|
message = ''hostapd interface ${interface} uses WPA3-SAE in transition mode with WPA2-SHA256, which requires managementFrameProtection="optional" or ="required"'';
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -947,15 +962,14 @@ in {
|
||||||
|
|
||||||
environment.systemPackages = [pkgs.hostapd];
|
environment.systemPackages = [pkgs.hostapd];
|
||||||
|
|
||||||
services.udev.packages = optionals (any (i: i.countryCode != null) (attrValues cfg.interfaces)) [pkgs.crda];
|
services.udev.packages = with pkgs; [crda];
|
||||||
|
|
||||||
systemd.services.hostapd = {
|
systemd.services.hostapd = {
|
||||||
description = "Hostapd IEEE 802.11 AP Daemon";
|
description = "IEEE 802.11 Host Access-Point Daemon";
|
||||||
|
|
||||||
path = [pkgs.hostapd];
|
path = [pkgs.hostapd];
|
||||||
after = mapAttrsToList (interface: _: "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device") cfg.interfaces;
|
after = map (interface: "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device") (attrNames cfg.interfaces);
|
||||||
bindsTo = mapAttrsToList (interface: _: "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device") cfg.interfaces;
|
bindsTo = map (interface: "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device") (attrNames cfg.interfaces);
|
||||||
requiredBy = mapAttrsToList (interface: _: "network-link-${interface}.service") cfg.interfaces;
|
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = ["multi-user.target"];
|
||||||
|
|
||||||
# Create merged configuration and acl files for each interface prior to starting
|
# Create merged configuration and acl files for each interface prior to starting
|
||||||
|
@ -973,7 +987,7 @@ in {
|
||||||
DevicePolicy = "closed";
|
DevicePolicy = "closed";
|
||||||
DeviceAllow = "/dev/rfkill rw";
|
DeviceAllow = "/dev/rfkill rw";
|
||||||
NoNewPrivileges = true;
|
NoNewPrivileges = true;
|
||||||
PrivateUsers = false; # hostapd requires real system root access.
|
PrivateUsers = false; # hostapd requires true root access.
|
||||||
PrivateTmp = true;
|
PrivateTmp = true;
|
||||||
ProtectClock = true;
|
ProtectClock = true;
|
||||||
ProtectControlGroups = true;
|
ProtectControlGroups = true;
|
||||||
|
@ -985,12 +999,21 @@ in {
|
||||||
ProtectProc = "invisible";
|
ProtectProc = "invisible";
|
||||||
ProcSubset = "pid";
|
ProcSubset = "pid";
|
||||||
ProtectSystem = "strict";
|
ProtectSystem = "strict";
|
||||||
RestrictAddressFamilies = ["AF_UNIX" "AF_NETLINK" "AF_INET" "AF_INET6"];
|
RestrictAddressFamilies = [
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
"AF_NETLINK"
|
||||||
|
"AF_UNIX"
|
||||||
|
];
|
||||||
RestrictNamespaces = true;
|
RestrictNamespaces = true;
|
||||||
RestrictRealtime = true;
|
RestrictRealtime = true;
|
||||||
RestrictSUIDSGID = true;
|
RestrictSUIDSGID = true;
|
||||||
SystemCallArchitectures = "native";
|
SystemCallArchitectures = "native";
|
||||||
SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"~@privileged"
|
||||||
|
"@chown"
|
||||||
|
];
|
||||||
UMask = "0077";
|
UMask = "0077";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue