diff options
| author | Alejandro Soto <alejandro@34project.org> | 2025-08-24 18:55:06 -0600 |
|---|---|---|
| committer | Alejandro Soto <alejandro@34project.org> | 2025-08-24 18:55:06 -0600 |
| commit | d7ac88762db111a7962c4e14b5f4e37ab85ccac7 (patch) | |
| tree | 0c2c8c4383bef74215e3b7c48a2f6b0117f084bc /sys | |
| parent | 504589d1035f27b766bd33040b415b2725ece4ca (diff) | |
tree-wide: reformat using alejandra after enabling trivionomicon
Diffstat (limited to '')
60 files changed, 1910 insertions, 1591 deletions
diff --git a/sys/auth/login.nix b/sys/auth/login.nix index 5bc8f2e..f252c1c 100644 --- a/sys/auth/login.nix +++ b/sys/auth/login.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; { # TODO config = mkIf true { diff --git a/sys/auth/oath.nix b/sys/auth/oath.nix index 7030bab..6b00680 100644 --- a/sys/auth/oath.nix +++ b/sys/auth/oath.nix @@ -1,8 +1,12 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.auth.oath; -in -{ +in { options.local.auth.oath = { enable = lib.mkEnableOption "pam-oath"; }; diff --git a/sys/auth/openssh.nix b/sys/auth/openssh.nix index 07e6977..44fb49a 100644 --- a/sys/auth/openssh.nix +++ b/sys/auth/openssh.nix @@ -1,15 +1,22 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.auth.openssh; withOath = config.local.auth.oath.enable; withPassword = config.local.auth.openssh.passwordAuthentication; - port = if cfg.shiftPortNumber then 2234 else 22; + port = + if cfg.shiftPortNumber + then 2234 + else 22; restrict = cfg.restrictListen; exemptList = optionals config.local.net.fail2ban.enable config.services.fail2ban.ignoreIP; -in -{ +in { options.local.auth.openssh = { enable = mkEnableOption "openssh"; tunnel.enable = mkEnableOption "ssh tunnel user"; @@ -23,28 +30,29 @@ in type = types.bool; default = false; }; - }) [ "ecdsa" "ed25519" "rsa" ]); + }) ["ecdsa" "ed25519" "rsa"]); restrictListen = mkOption { default = null; - type = with types; nullOr (submodule { - options = { - addresses = mkOption { - type = listOf str; + type = with types; + nullOr (submodule { + options = { + addresses = mkOption { + type = listOf str; + }; + + interface = mkOption { + type = nullOr str; + default = null; + }; + + vsockCid = mkOption { + type = nullOr ints.u32; + default = null; + }; }; - - interface = mkOption { - type = nullOr str; - default = null; - }; - - vsockCid = mkOption { - type = nullOr ints.u32; - default = null; - }; - }; - }); + }); }; passwordAuthentication = mkOption { @@ -70,7 +78,7 @@ in message = "SSH tunnel requires oath"; } { - assertion = restrict != null -> (restrict.vsockCid != null -> (restrict.interface == null && restrict.addresses == [ ])); + assertion = restrict != null -> (restrict.vsockCid != null -> (restrict.interface == null && restrict.addresses == [])); message = "SSH vsock restrict requires disabling inet"; } { @@ -88,11 +96,11 @@ in ]; local.boot.impermanence.files = - flatten (map (key: [ key.path "${key.path}.pub" ]) config.services.openssh.hostKeys); + flatten (map (key: [key.path "${key.path}.pub"]) config.services.openssh.hostKeys); networking.firewall = { interfaces = optionalAttrs (restrict != null && restrict.interface != null) { - ${restrict.interface}.allowedTCPPorts = [ port ]; + ${restrict.interface}.allowedTCPPorts = [port]; }; allowedTCPPorts = optional (restrict == null || restrict.interface == null) port; @@ -101,36 +109,41 @@ in services.openssh = { enable = true; - ports = optional (restrict != null -> restrict.addresses != [ ]) port; + ports = optional (restrict != null -> restrict.addresses != []) port; startWhenNeeded = mkDefault (!config.services.fail2ban.enable); - extraConfig = optionalString (exemptList != [ ]) '' - PerSourcePenaltyExemptList ${concatStringsSep "," exemptList} - '' + optionalString cfg.tunnel.enable '' - # User 'tunnel' has no password. Use PAM OATH - # and connect with -N, forward with -R. - Match User tunnel - AllowTcpForwarding remote - AllowStreamLocalForwarding no - X11Forwarding no - PermitTunnel no - GatewayPorts no - AllowAgentForwarding no - PermitOpen none - PermitListen 60220 60221 60222 60223 60224 60225 60226 60227 60228 60229 - - Banner ${pkgs.writeText "tunnel-banner" '' - This is a reverse tunnel - ''} - ''; - - hostKeys = map - (name: { - path = "/etc/ssh/ssh_host_${name}_key"; - type = name; - } // optionalAttrs (name == "rsa") { - bits = 4096; - }) + extraConfig = + optionalString (exemptList != []) '' + PerSourcePenaltyExemptList ${concatStringsSep "," exemptList} + '' + + optionalString cfg.tunnel.enable '' + # User 'tunnel' has no password. Use PAM OATH + # and connect with -N, forward with -R. + Match User tunnel + AllowTcpForwarding remote + AllowStreamLocalForwarding no + X11Forwarding no + PermitTunnel no + GatewayPorts no + AllowAgentForwarding no + PermitOpen none + PermitListen 60220 60221 60222 60223 60224 60225 60226 60227 60228 60229 + + Banner ${pkgs.writeText "tunnel-banner" '' + This is a reverse tunnel + ''} + ''; + + hostKeys = + map + (name: + { + path = "/etc/ssh/ssh_host_${name}_key"; + type = name; + } + // optionalAttrs (name == "rsa") { + bits = 4096; + }) (attrNames (filterAttrs (name: enable: enable) cfg.hostKeys)); settings = { @@ -139,26 +152,29 @@ in PasswordAuthentication = withOath || withPassword; # Necesario para oath, no reemplaza a oath }; - listenAddresses = mkIf (restrict != null) - (map (addr: { inherit addr; }) restrict.addresses); + listenAddresses = + mkIf (restrict != null) + (map (addr: {inherit addr;}) restrict.addresses); }; systemd.sockets = mkIf (restrict != null && restrict.vsockCid != null) { - sshd = - let - kernelMod = "modprobe@${if restrict.vsockCid == 2 then "vhost_" else ""}vsock.service"; - in - { - after = [ kernelMod ]; - wants = [ kernelMod ]; - - socketConfig.ListenStream = mkForce [ "vsock:${toString restrict.vsockCid}:${toString port}" ]; - }; + sshd = let + kernelMod = "modprobe@${ + if restrict.vsockCid == 2 + then "vhost_" + else "" + }vsock.service"; + in { + after = [kernelMod]; + wants = [kernelMod]; + + socketConfig.ListenStream = mkForce ["vsock:${toString restrict.vsockCid}:${toString port}"]; + }; }; users.users = { root = mkIf cfg.withDeployKeys { - openssh.authorizedKeys.keyFiles = [ ./ssh-key.pub ]; + openssh.authorizedKeys.keyFiles = [./ssh-key.pub]; }; tunnel = mkIf cfg.tunnel.enable { diff --git a/sys/baseline/default.nix b/sys/baseline/default.nix index 3a425f7..96654d8 100644 --- a/sys/baseline/default.nix +++ b/sys/baseline/default.nix @@ -1,4 +1,10 @@ -{ config, flakes, lib, pkgs, ... }: +{ + config, + flakes, + lib, + pkgs, + ... +}: with lib; { config = { # This value determines the NixOS release from which the default @@ -10,30 +16,32 @@ with lib; { system.stateVersion = "21.11"; # Did you read the comment? environment = { - pathsToLink = [ "/share/zsh" ]; - - systemPackages = with pkgs; [ - git - ] ++ optionals (!config.boot.isContainer) [ - lm_sensors - lshw - parted - pciutils - smartmontools - usbutils - ]; + pathsToLink = ["/share/zsh"]; + + systemPackages = with pkgs; + [ + git + ] + ++ optionals (!config.boot.isContainer) [ + lm_sensors + lshw + parted + pciutils + smartmontools + usbutils + ]; }; home-manager = { useGlobalPkgs = true; useUserPackages = true; - extraSpecialArgs = { inherit flakes; }; + extraSpecialArgs = {inherit flakes;}; }; lib.local = pkgs.local.lib; - local.boot.impermanence.directories = [ "/var/lib/dhparams" ]; + local.boot.impermanence.directories = ["/var/lib/dhparams"]; nix = { package = pkgs.nix; diff --git a/sys/boot/chain.nix b/sys/boot/chain.nix index aeb3bbe..43edcb4 100644 --- a/sys/boot/chain.nix +++ b/sys/boot/chain.nix @@ -1,11 +1,15 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.boot; -in -{ +in { options.local.boot = { loader = mkOption { - type = types.enum [ "none" "grub" "systemd-boot" ]; + type = types.enum ["none" "grub" "systemd-boot"]; }; kernel = mkOption { @@ -18,13 +22,15 @@ in kernelPackages = cfg.kernel; loader = - if cfg.loader == "grub" then { + if cfg.loader == "grub" + then { grub = { enable = true; device = "nodev"; efiSupport = true; }; - } else { + } + else { systemd-boot = { enable = true; editor = true; diff --git a/sys/boot/detached-luks.nix b/sys/boot/detached-luks.nix index 8be7de1..78ae35c 100644 --- a/sys/boot/detached-luks.nix +++ b/sys/boot/detached-luks.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.boot.detachedLuks; @@ -6,8 +11,7 @@ with lib; let tpmInitrd = config.local.boot.tpm.initrd.enable; pcrList = concatStringsSep "," (map toString config.local.boot.tpm.initrd.pcrs); -in -{ +in { options.local.boot.detachedLuks = { enable = mkEnableOption "detached LUKS header in initrd"; @@ -30,43 +34,43 @@ in }; config = mkIf cfg.enable { - boot.initrd = - let - headerPath = "/initrd-boot/${cfg.headerFromBoot}"; - headerPathEscaped = escapeShellArg headerPath; - - tpmPath = escapeShellArg "/initrd-boot/${cfg.tpmStorageFromBoot}"; - hardwareKeyPath = "/tpm/unsealed.luks-key"; - in - { - preDeviceCommands = '' - mkdir -p `dirname ${headerPathEscaped}` - touch ${headerPathEscaped} - ''; - - postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' - # Set the system time from the hardware clock to work around a - # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised - # to the *boot time* of the host). - hwclock -s - ''; - - #FIXME: Demasiado vulgar - preLVMCommands = optionalString (config.local.boot.efi.enable && config.local.boot.efi.removable) '' - sleep 2 - ''; - - luks.devices.${cfg.target} = { - device = cfg.crypt; - header = headerPath; - preLVM = false; - - keyFile = mkIf tpmInitrd hardwareKeyPath; - fallbackToPassword = tpmInitrd; - - preOpenCommands = '' + boot.initrd = let + headerPath = "/initrd-boot/${cfg.headerFromBoot}"; + headerPathEscaped = escapeShellArg headerPath; + + tpmPath = escapeShellArg "/initrd-boot/${cfg.tpmStorageFromBoot}"; + hardwareKeyPath = "/tpm/unsealed.luks-key"; + in { + preDeviceCommands = '' + mkdir -p `dirname ${headerPathEscaped}` + touch ${headerPathEscaped} + ''; + + postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' + # Set the system time from the hardware clock to work around a + # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised + # to the *boot time* of the host). + hwclock -s + ''; + + #FIXME: Demasiado vulgar + preLVMCommands = optionalString (config.local.boot.efi.enable && config.local.boot.efi.removable) '' + sleep 2 + ''; + + luks.devices.${cfg.target} = { + device = cfg.crypt; + header = headerPath; + preLVM = false; + + keyFile = mkIf tpmInitrd hardwareKeyPath; + fallbackToPassword = tpmInitrd; + + preOpenCommands = + '' mount -o ro -t ${bootFs.fsType} ${bootFs.device} /initrd-boot - '' + optionalString tpmInitrd '' + '' + + optionalString tpmInitrd '' mkdir /tpm touch ${escapeShellArg hardwareKeyPath} @@ -92,18 +96,19 @@ in unseal_tpm_key ''; - postOpenCommands = mkBefore ('' + postOpenCommands = mkBefore ('' umount /initrd-boot - '' + optionalString tpmInitrd '' + '' + + optionalString tpmInitrd '' rm -r /tpm ''); - }; }; + }; local.boot = { stack = { btrfsToplevelMultidrive.toplevel.device = "/dev/mapper/${cfg.target}"; - luksExt4FscryptImpermanence = { inherit (cfg) target; }; + luksExt4FscryptImpermanence = {inherit (cfg) target;}; }; tpm.initrd.enable = mkDefault config.local.boot.tpm.enable; diff --git a/sys/boot/efi.nix b/sys/boot/efi.nix index cbcefd9..71c42c8 100644 --- a/sys/boot/efi.nix +++ b/sys/boot/efi.nix @@ -1,14 +1,17 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.boot.efi; -in -{ +in { options.local.boot.efi = { enable = mkEnableOption "EFI with FAT32 system partition"; esp = { mountpoint = mkOption { - type = types.enum [ "/boot" "/boot/efi" ]; + type = types.enum ["/boot" "/boot/efi"]; default = "/boot"; }; @@ -24,7 +27,7 @@ in config = mkIf cfg.enable { boot = { - initrd.supportedFilesystems = [ "vfat" ]; + initrd.supportedFilesystems = ["vfat"]; loader = { efi = { @@ -39,7 +42,7 @@ in fileSystems.${cfg.esp.mountpoint} = { device = "/dev/disk/by-uuid/${cfg.esp.uuid}"; fsType = "vfat"; - options = [ "noatime" "umask=027" "sync" ]; + options = ["noatime" "umask=027" "sync"]; neededForBoot = true; }; }; diff --git a/sys/boot/firmware.nix b/sys/boot/firmware.nix index 70a3c4b..b3598a7 100644 --- a/sys/boot/firmware.nix +++ b/sys/boot/firmware.nix @@ -1,15 +1,19 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.boot.firmware; -in -{ +in { options.local.boot.firmware = { mode = mkOption { - type = types.enum [ "none" "redistributable" "all" ]; + type = types.enum ["none" "redistributable" "all"]; }; cpuVendor = mkOption { - type = types.enum [ "amd" "intel" ]; + type = types.enum ["amd" "intel"]; }; }; diff --git a/sys/boot/fscrypt.nix b/sys/boot/fscrypt.nix index e6a745c..459e02b 100644 --- a/sys/boot/fscrypt.nix +++ b/sys/boot/fscrypt.nix @@ -1,18 +1,25 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.boot.fscrypt; -in -{ +in { options.local.boot.fscrypt = { enable = mkEnableOption "fscrypt support"; }; config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.fscrypt-experimental ]; + environment.systemPackages = [pkgs.fscrypt-experimental]; local.boot.impermanence = { directories = [ - { directory = "/.fscrypt"; mode = "u=rwx,g=rx,o=rx"; } + { + directory = "/.fscrypt"; + mode = "u=rwx,g=rx,o=rx"; + } ]; files = [ diff --git a/sys/boot/impermanence.nix b/sys/boot/impermanence.nix index 4902239..632094b 100644 --- a/sys/boot/impermanence.nix +++ b/sys/boot/impermanence.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.boot.impermanence; -in -{ +in { options.local.boot.impermanence = { enable = mkEnableOption "root fs impermanence"; @@ -10,12 +13,12 @@ in directories = mkOption { type = with lib.types; listOf (either str attrs); - default = [ ]; + default = []; }; files = mkOption { type = with lib.types; listOf (either str attrs); - default = [ ]; + default = []; }; }; diff --git a/sys/boot/namespaced.nix b/sys/boot/namespaced.nix index db01d55..3f95960 100644 --- a/sys/boot/namespaced.nix +++ b/sys/boot/namespaced.nix @@ -1,8 +1,12 @@ -{ config, lib, options, ... }: +{ + config, + lib, + options, + ... +}: with lib; let cfg = config.local.boot.namespaced; -in -{ +in { options.local.boot.namespaced = { enable = mkEnableOption "system containerization"; }; @@ -11,15 +15,16 @@ in boot.isContainer = true; local.boot = mkMerge ([ - { - loader = mkForce "none"; + { + loader = mkForce "none"; - efi.enable = mkForce false; - firmware.mode = mkForce "none"; - secureBoot.enable = mkForce false; - impermanence.enable = mkForce false; - } - ] ++ map + efi.enable = mkForce false; + firmware.mode = mkForce "none"; + secureBoot.enable = mkForce false; + impermanence.enable = mkForce false; + } + ] + ++ map (name: { stack.${name}.enable = mkForce false; }) diff --git a/sys/boot/secure-boot.nix b/sys/boot/secure-boot.nix index 3e874c3..b13ab7c 100644 --- a/sys/boot/secure-boot.nix +++ b/sys/boot/secure-boot.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.boot.secureBoot; @@ -6,8 +11,7 @@ with lib; let if cfg.legacyPath then "/etc/secureboot" else "/var/lib/sbctl"; -in -{ +in { options.local.boot.secureBoot = { enable = mkEnableOption "secure boot"; @@ -42,6 +46,6 @@ in pkgs.sbctl ]; - local.boot.impermanence.directories = [ pkiBundle ]; + local.boot.impermanence.directories = [pkiBundle]; }; } diff --git a/sys/boot/stack/btrfs-toplevel-multidrive.nix b/sys/boot/stack/btrfs-toplevel-multidrive.nix index 1dbfa14..52db865 100644 --- a/sys/boot/stack/btrfs-toplevel-multidrive.nix +++ b/sys/boot/stack/btrfs-toplevel-multidrive.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.boot.stack.btrfsToplevelMultidrive; -in -{ +in { options.local.boot.stack.btrfsToplevelMultidrive = { enable = mkEnableOption "filesystem stack: persistent btrfs toplevel with optional hdd drive"; @@ -80,15 +83,17 @@ in }; }; - snapper = optionalAttrs cfg.toplevel.snapshot + snapper = + optionalAttrs cfg.toplevel.snapshot { root = "/"; - } // optionalAttrs cfg.secondary.snapshot { - home = "/home"; - }; + } + // optionalAttrs cfg.secondary.snapshot { + home = "/home"; + }; }; # Asegura que /hdd sea descifrado antes de intentar montar /home - fileSystems."/home".depends = [ "/hdd" ]; + fileSystems."/home".depends = ["/hdd"]; }; } diff --git a/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix index 72336d6..81feb60 100644 --- a/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix +++ b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix @@ -1,8 +1,12 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.boot.stack.luksExt4FscryptImpermanence; -in -{ +in { options.local.boot.stack.luksExt4FscryptImpermanence = { enable = mkEnableOption "filesystem stack: whatever LUKS approach+ext4+impermanence with per-boot keys"; @@ -30,60 +34,58 @@ in # - /toplevel/boots/... # - /toplevel/boots/last -> 2000-01-01T00:00:00-06:00 (mounted as /) config = mkIf cfg.enable { - boot.initrd.luks.devices.${cfg.target}.postOpenCommands = - let - fscryptctl = "${pkgs.fscryptctl}/bin/fscryptctl"; - in - '' - # FIXME: posiblemente algunos --make-* son innecesarios a partir de aquĆ - mkdir -p /mnt-root /mnt-toplevel - mount -o noatime /dev/mapper/${cfg.target} /mnt-toplevel - mount --make-private /mnt-toplevel + boot.initrd.luks.devices.${cfg.target}.postOpenCommands = let + fscryptctl = "${pkgs.fscryptctl}/bin/fscryptctl"; + in '' + # FIXME: posiblemente algunos --make-* son innecesarios a partir de aquĆ + mkdir -p /mnt-root /mnt-toplevel + mount -o noatime /dev/mapper/${cfg.target} /mnt-toplevel + mount --make-private /mnt-toplevel - boot_stamp="$(date -Is)" - root_from_toplevel="/mnt-toplevel/boots/$boot_stamp" + boot_stamp="$(date -Is)" + root_from_toplevel="/mnt-toplevel/boots/$boot_stamp" - mkdir -p "$root_from_toplevel" /mnt-toplevel/boot-keys - chmod 700 /mnt-toplevel/boot-keys + mkdir -p "$root_from_toplevel" /mnt-toplevel/boot-keys + chmod 700 /mnt-toplevel/boot-keys - head -c64 /dev/urandom >/boot-key - key_id=$(${fscryptctl} add_key /mnt-toplevel </boot-key) - ${fscryptctl} set_policy "$key_id" "$root_from_toplevel" - (umask 077; test -f /mnt-toplevel/boot-archive.pub && \ - ${pkgs.openssl}/bin/openssl pkeyutl -encrypt \ - -in /boot-key -pubin -inkey /mnt-toplevel/boot-archive.pub \ - -out "/mnt-toplevel/boot-keys/$boot_stamp.key.crypt") - rm -f /boot-key + head -c64 /dev/urandom >/boot-key + key_id=$(${fscryptctl} add_key /mnt-toplevel </boot-key) + ${fscryptctl} set_policy "$key_id" "$root_from_toplevel" + (umask 077; test -f /mnt-toplevel/boot-archive.pub && \ + ${pkgs.openssl}/bin/openssl pkeyutl -encrypt \ + -in /boot-key -pubin -inkey /mnt-toplevel/boot-archive.pub \ + -out "/mnt-toplevel/boot-keys/$boot_stamp.key.crypt") + rm -f /boot-key - ln -Tsf "$boot_stamp" /mnt-toplevel/boots/last - ln -Tsf "$boot_stamp.key.crypt" /mnt-toplevel/boot-keys/last.key.crypt + ln -Tsf "$boot_stamp" /mnt-toplevel/boots/last + ln -Tsf "$boot_stamp.key.crypt" /mnt-toplevel/boot-keys/last.key.crypt - mount --bind "$root_from_toplevel" /mnt-root - mount --make-shared /mnt-root + mount --bind "$root_from_toplevel" /mnt-root + mount --make-shared /mnt-root - # mount --move es mala idea, ya que "moving a mount residing under a - # shared mount is unsupported" - mkdir -p /mnt-root/toplevel - mount --bind /mnt-toplevel /mnt-root/toplevel - mount --make-private /mnt-root/toplevel - umount /mnt-toplevel - ''; + # mount --move es mala idea, ya que "moving a mount residing under a + # shared mount is unsupported" + mkdir -p /mnt-root/toplevel + mount --bind /mnt-toplevel /mnt-root/toplevel + mount --make-private /mnt-root/toplevel + umount /mnt-toplevel + ''; fileSystems = { "/" = { device = "none"; fsType = "ext4"; - options = [ "remount" ]; + options = ["remount"]; }; "/nix" = { device = "/persist/nix"; - options = [ "bind" ]; + options = ["bind"]; }; "/persist" = { device = "/toplevel/persist"; - options = [ "bind" ]; + options = ["bind"]; neededForBoot = true; }; }; diff --git a/sys/boot/tpm.nix b/sys/boot/tpm.nix index 0e29066..ecc115b 100644 --- a/sys/boot/tpm.nix +++ b/sys/boot/tpm.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.boot.tpm; @@ -52,13 +57,12 @@ with lib; let openssl dgst -sha256 -sign /dev/stdin -out auth.sig auth.policy ''; }; -in -{ +in { options.local.boot.tpm = { enable = mkEnableOption "Trusted Platform Module 2.0"; driver = mkOption { - type = types.enum [ "tis" "crb" ]; + type = types.enum ["tis" "crb"]; }; initrd = { @@ -81,7 +85,6 @@ in 12 # kernel-config 13 # sysexts 14 # shim-policy - ]; }; }; diff --git a/sys/btrfs/mounts.nix b/sys/btrfs/mounts.nix index 133f08f..3863356 100644 --- a/sys/btrfs/mounts.nix +++ b/sys/btrfs/mounts.nix @@ -1,39 +1,47 @@ -{ lib, config, pkgs, ... }: +{ + lib, + config, + pkgs, + ... +}: with lib; let cfg = config.local.btrfs; -in -{ +in { options.local.btrfs = { mounts = mkOption { - default = { }; + default = {}; - type = with lib.types; attrsOf (submodule { - options = { - ssd = mkOption { - type = bool; - }; + type = with lib.types; + attrsOf (submodule { + options = { + ssd = mkOption { + type = bool; + }; - device = mkOption { - type = str; - }; + device = mkOption { + type = str; + }; - subvol = mkOption { - type = str; + subvol = mkOption { + type = str; + }; }; - }; - }); + }); }; }; - config = mkIf (cfg.mounts != { }) { - fileSystems = - let - btrfsMount = { device, subvol, ssd }: { - inherit device; - fsType = "btrfs"; - options = [ "noatime" "compress=zstd" "subvol=${subvol}" ] ++ optional ssd "ssd"; - }; - in + config = mkIf (cfg.mounts != {}) { + fileSystems = let + btrfsMount = { + device, + subvol, + ssd, + }: { + inherit device; + fsType = "btrfs"; + options = ["noatime" "compress=zstd" "subvol=${subvol}"] ++ optional ssd "ssd"; + }; + in mapAttrs (_: btrfsMount) cfg.mounts; }; } diff --git a/sys/btrfs/snapper.nix b/sys/btrfs/snapper.nix index 27d2779..2d29aa4 100644 --- a/sys/btrfs/snapper.nix +++ b/sys/btrfs/snapper.nix @@ -1,73 +1,76 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.btrfs; -in -{ +in { options.local.btrfs = { snapper = mkOption { type = with lib.types; attrsOf str; - default = { }; + default = {}; }; }; - config = mkIf (cfg.snapper != { }) { - environment.systemPackages = [ pkgs.local.btclone ]; + config = mkIf (cfg.snapper != {}) { + environment.systemPackages = [pkgs.local.btclone]; - services.snapper.configs = - let - snapperConfig = _: subvolume: { - SUBVOLUME = subvolume; + services.snapper.configs = let + snapperConfig = _: subvolume: { + SUBVOLUME = subvolume; - # btrfs qgroup for space aware cleanup algorithms - QGROUP = ""; + # btrfs qgroup for space aware cleanup algorithms + QGROUP = ""; - # fraction of the filesystems space the snapshots may use - SPACE_LIMIT = "0.5"; + # fraction of the filesystems space the snapshots may use + SPACE_LIMIT = "0.5"; - # fraction of the filesystems space that should be free - FREE_LIMIT = "0.2"; + # fraction of the filesystems space that should be free + FREE_LIMIT = "0.2"; - # users and groups allowed to work with config - ALLOW_USERS = [ ]; - ALLOW_GROUPS = [ ]; + # users and groups allowed to work with config + ALLOW_USERS = []; + ALLOW_GROUPS = []; - # sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots - # directory - SYNC_ACL = "no"; + # sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots + # directory + SYNC_ACL = "no"; - # start comparing pre- and post-snapshot in background after creating - # post-snapshot - BACKGROUND_COMPARISON = "yes"; + # start comparing pre- and post-snapshot in background after creating + # post-snapshot + BACKGROUND_COMPARISON = "yes"; - # run daily number cleanup - NUMBER_CLEANUP = "yes"; + # run daily number cleanup + NUMBER_CLEANUP = "yes"; - # limit for number cleanup - NUMBER_MIN_AGE = "1800"; - NUMBER_LIMIT = "100"; - NUMBER_LIMIT_IMPORTANT = "10"; + # limit for number cleanup + NUMBER_MIN_AGE = "1800"; + NUMBER_LIMIT = "100"; + NUMBER_LIMIT_IMPORTANT = "10"; - # create hourly snapshots - TIMELINE_CREATE = true; + # create hourly snapshots + TIMELINE_CREATE = true; - # cleanup hourly snapshots after some time - TIMELINE_CLEANUP = true; + # cleanup hourly snapshots after some time + TIMELINE_CLEANUP = true; - # limits for timeline cleanup - TIMELINE_MIN_AGE = "1800"; - TIMELINE_LIMIT_HOURLY = "24"; - TIMELINE_LIMIT_DAILY = "7"; - TIMELINE_LIMIT_WEEKLY = "4"; - TIMELINE_LIMIT_MONTHLY = "12"; - TIMELINE_LIMIT_YEARLY = "10"; + # limits for timeline cleanup + TIMELINE_MIN_AGE = "1800"; + TIMELINE_LIMIT_HOURLY = "24"; + TIMELINE_LIMIT_DAILY = "7"; + TIMELINE_LIMIT_WEEKLY = "4"; + TIMELINE_LIMIT_MONTHLY = "12"; + TIMELINE_LIMIT_YEARLY = "10"; - # cleanup empty pre-post-pairs - EMPTY_PRE_POST_CLEANUP = "yes"; + # cleanup empty pre-post-pairs + EMPTY_PRE_POST_CLEANUP = "yes"; - # limits for empty pre-post-pair cleanup - EMPTY_PRE_POST_MIN_AGE = "1800"; - }; - in + # limits for empty pre-post-pair cleanup + EMPTY_PRE_POST_MIN_AGE = "1800"; + }; + in mapAttrs snapperConfig cfg.snapper; }; } diff --git a/sys/default.nix b/sys/default.nix index 0ce00a1..131ddeb 100644 --- a/sys/default.nix +++ b/sys/default.nix @@ -1,4 +1,10 @@ -{ lib, config, flakes, pkgs, ... }: +{ + lib, + config, + flakes, + pkgs, + ... +}: with lib; { imports = [ flakes.nixpkgs.nixosModules.notDetected diff --git a/sys/gitea/default.nix b/sys/gitea/default.nix index 69dfbc2..212b9f1 100644 --- a/sys/gitea/default.nix +++ b/sys/gitea/default.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.gitea; -in -{ +in { options.local.gitea = { enable = mkEnableOption "gitea"; }; diff --git a/sys/hardware/altera.nix b/sys/hardware/altera.nix index 2fc1bb6..fddd722 100644 --- a/sys/hardware/altera.nix +++ b/sys/hardware/altera.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.hardware.altera; -in -{ +in { options.local.hardware.altera = { enable = mkEnableOption "Altera USB Blaster"; }; diff --git a/sys/hardware/apc.nix b/sys/hardware/apc.nix index 9614c48..97a5bb0 100644 --- a/sys/hardware/apc.nix +++ b/sys/hardware/apc.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.hardware.apc; -in -{ +in { options.local.hardware.apc = { enable = mkEnableOption "APC UPS support"; }; diff --git a/sys/hardware/athena.nix b/sys/hardware/athena.nix index 06d10b3..755c184 100644 --- a/sys/hardware/athena.nix +++ b/sys/hardware/athena.nix @@ -1,10 +1,14 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.hardware.athena; athena = pkgs.local.athena-bccr.${cfg.release}; -in -{ +in { options.local.hardware.athena = { enable = mkEnableOption "Athena ASEDrive III smartcard reader"; @@ -25,11 +29,11 @@ in ''; }; - systemPackages = [ athena.ase-pkcs11 ]; + systemPackages = [athena.ase-pkcs11]; }; #FIXME: Extremadamente peligroso si BCCR o MICITT caen, investigar polĆtica nacional de root CA - security.pki.certificateFiles = [ "${athena.bccr-cacerts}/root-ca.pem" ]; + security.pki.certificateFiles = ["${athena.bccr-cacerts}/root-ca.pem"]; services = { pcscd.enable = true; diff --git a/sys/hardware/bluetooth.nix b/sys/hardware/bluetooth.nix index 0d53750..63e3f0c 100644 --- a/sys/hardware/bluetooth.nix +++ b/sys/hardware/bluetooth.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.hardware.bluetooth; -in -{ +in { options.local.hardware.bluetooth = { enable = mkEnableOption "bluetooth services"; }; diff --git a/sys/hardware/epson.nix b/sys/hardware/epson.nix index 66304f9..30b1303 100644 --- a/sys/hardware/epson.nix +++ b/sys/hardware/epson.nix @@ -1,8 +1,12 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.hardware.epson; -in -{ +in { options.local.hardware.epson = { enable = mkEnableOption "Epson printers and scanners"; }; diff --git a/sys/hardware/laptop.nix b/sys/hardware/laptop.nix index d9ba753..3b5b772 100644 --- a/sys/hardware/laptop.nix +++ b/sys/hardware/laptop.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.hardware.laptop; -in -{ +in { options.local.hardware.laptop = { enable = mkEnableOption "laptop stuff"; }; diff --git a/sys/hardware/printing.nix b/sys/hardware/printing.nix index 30c6962..e11a016 100644 --- a/sys/hardware/printing.nix +++ b/sys/hardware/printing.nix @@ -1,15 +1,18 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.hardware.printing; inherit (config.local.net) dhcpInterface; -in -{ +in { options.local.hardware.printing = { enable = mkEnableOption "print and scan services"; users = mkOption { type = with types; listOf str; - default = [ ]; + default = []; }; }; @@ -32,7 +35,7 @@ in hardware.sane.enable = true; networking.firewall.interfaces = mkIf (dhcpInterface != null) { - ${dhcpInterface}.allowedUDPPorts = [ 5353 ]; + ${dhcpInterface}.allowedUDPPorts = [5353]; }; services.printing.enable = true; @@ -40,7 +43,7 @@ in users.users = listToAttrs (map (user: { name = user; - value.extraGroups = [ "scanner" "lp" ]; + value.extraGroups = ["scanner" "lp"]; }) cfg.users); }; diff --git a/sys/hardware/thinkpad.nix b/sys/hardware/thinkpad.nix index 7341e68..ab18694 100644 --- a/sys/hardware/thinkpad.nix +++ b/sys/hardware/thinkpad.nix @@ -1,8 +1,12 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.hardware.thinkpad; -in -{ +in { options.local.hardware.thinkpad = { enable = mkEnableOption "Thinkpad hardware support"; }; @@ -13,18 +17,18 @@ in # Fingerprint sensor requires a firmware-update to work. boot = { - extraModulePackages = with config.boot.kernelPackages; [ acpi_call ]; + extraModulePackages = with config.boot.kernelPackages; [acpi_call]; extraModprobeConfig = "options iwlwifi 11n_disable=1 wd_disable=1"; # acpi_call makes tlp work for newer thinkpads - kernelModules = [ "acpi_call" ]; + kernelModules = ["acpi_call"]; # Force use of the thinkpad_acpi driver for backlight control. # This allows the backlight save/load systemd service to work. - kernelParams = [ "acpi_backlight=native" ]; + kernelParams = ["acpi_backlight=native"]; }; - hardware.firmware = [ pkgs.sof-firmware ]; + hardware.firmware = [pkgs.sof-firmware]; local.hardware.laptop.enable = true; diff --git a/sys/hardware/yubico.nix b/sys/hardware/yubico.nix index 0078210..0c8478c 100644 --- a/sys/hardware/yubico.nix +++ b/sys/hardware/yubico.nix @@ -1,8 +1,12 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.hardware.yubico; -in -{ +in { options.local.hardware.yubico = { enable = mkEnableOption "Yubico hardware support"; }; @@ -14,7 +18,7 @@ in services = { pcscd.enable = true; - udev.packages = [ pkgs.yubikey-personalization ]; + udev.packages = [pkgs.yubikey-personalization]; }; }; } diff --git a/sys/home-assistant/hass.nix b/sys/home-assistant/hass.nix index 4a3ba31..7fd3251 100644 --- a/sys/home-assistant/hass.nix +++ b/sys/home-assistant/hass.nix @@ -1,8 +1,12 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.home-assistant; -in -{ +in { options.local.home-assistant = { enable = mkEnableOption "home-assistant"; }; @@ -12,20 +16,25 @@ in environment.etc."fail2ban/filter.d/home-assistant.local".text = '' [Definition] failregex = ^.* \[homeassistant\.components\.http\.ban\] Login attempt or request with invalid authentication from <HOST>.*$ - + ignoreregex = - + journalmatch = _SYSTEMD_UNIT=home-assistant.service + _COMM=home-assistant - + datepattern = {^LN-BEG} ''; local.boot.impermanence.directories = [ - { directory = "/var/lib/hass"; user = "hass"; group = "hass"; mode = "u=rwx,g=,o="; } + { + directory = "/var/lib/hass"; + user = "hass"; + group = "hass"; + mode = "u=rwx,g=,o="; + } ]; services = { - fail2ban.jails.home-assistant = { }; + fail2ban.jails.home-assistant = {}; home-assistant = { enable = true; @@ -43,7 +52,7 @@ in config = { # Includes dependencies for a basic setup # https://www.home-assistant.io/integrations/default_config/ - default_config = { }; + default_config = {}; switch = [ # Televisor 192.168.42.205 diff --git a/sys/home-assistant/yaml-extra.nix b/sys/home-assistant/yaml-extra.nix index 6275e12..77d1ed2 100644 --- a/sys/home-assistant/yaml-extra.nix +++ b/sys/home-assistant/yaml-extra.nix @@ -1,22 +1,23 @@ -{ lib, ... }: +{lib, ...}: with lib; { options.services.home-assistant = { config = mkOption { - type = with lib.types; nullOr (submodule { - options = { - http = { - use_x_forwarded_for = mkOption { - type = nullOr bool; - default = null; - }; + type = with lib.types; + nullOr (submodule { + options = { + http = { + use_x_forwarded_for = mkOption { + type = nullOr bool; + default = null; + }; - trusted_proxies = mkOption { - type = nullOr (either str (listOf str)); - default = null; + trusted_proxies = mkOption { + type = nullOr (either str (listOf str)); + default = null; + }; }; }; - }; - }); + }); }; }; } diff --git a/sys/jobs/pki-expiry/default.nix b/sys/jobs/pki-expiry/default.nix index b61d6f5..553cdc8 100644 --- a/sys/jobs/pki-expiry/default.nix +++ b/sys/jobs/pki-expiry/default.nix @@ -1,9 +1,13 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.jobs.pkiExpiry; inherit (config.local) pki; -in -{ +in { options.local.jobs.pkiExpiry = { enable = mkEnableOption "PKI expiration reminder"; }; @@ -11,43 +15,40 @@ in config = mkIf cfg.enable { systemd = { services.pki-expiry = { - after = [ "postfix.service" ]; - path = [ "/run/wrappers" ]; + after = ["postfix.service"]; + path = ["/run/wrappers"]; - environment.PKI_PUBLIC = - let - mkdir = "mkdir -p $out/{ca,cert,crl}"; + environment.PKI_PUBLIC = let + mkdir = "mkdir -p $out/{ca,cert,crl}"; - cas = mapAttrsToList (_: ca: "ln -s ${ca.cert} $out/ca/${ca.path}") pki.ca; - crls = mapAttrsToList (_: ca: "ln -s ${ca.crl} $out/crl/${ca.path}") pki.ca; + cas = mapAttrsToList (_: ca: "ln -s ${ca.cert} $out/ca/${ca.path}") pki.ca; + crls = mapAttrsToList (_: ca: "ln -s ${ca.crl} $out/crl/${ca.path}") pki.ca; - certs = mapAttrsToList - (path: leaf: "ln -s ${leaf.cert} $out/cert/${path}") - (filterAttrs (_: object: ! object ? leaves) pki.byPath); + certs = + mapAttrsToList + (path: leaf: "ln -s ${leaf.cert} $out/cert/${path}") + (filterAttrs (_: object: ! object ? leaves) pki.byPath); - pkiPublic = pkgs.runCommandNoCCLocal "pki-public" { } (concatLines ([ mkdir ] ++ cas ++ crls ++ certs)); - in - "${pkiPublic}"; + pkiPublic = pkgs.runCommandNoCCLocal "pki-public" {} (concatLines ([mkdir] ++ cas ++ crls ++ certs)); + in "${pkiPublic}"; serviceConfig = { Type = "oneshot"; StateDirectory = "pki-expiry"; WorkingDirectory = "/var/lib/pki-expiry"; - ExecStart = - let - script = pkgs.writeShellApplication { - name = "pki-expiry"; - text = readFile ./pki-expiry.sh; - runtimeInputs = with pkgs; [ diffutils openssl ]; - }; - in - "${getExe script}"; + ExecStart = let + script = pkgs.writeShellApplication { + name = "pki-expiry"; + text = readFile ./pki-expiry.sh; + runtimeInputs = with pkgs; [diffutils openssl]; + }; + in "${getExe script}"; }; }; timers.pki-expiry = { - wantedBy = [ "timers.target" ]; + wantedBy = ["timers.target"]; timerConfig = { OnStartupSec = "10m"; diff --git a/sys/kiosk/default.nix b/sys/kiosk/default.nix index b450733..be20829 100644 --- a/sys/kiosk/default.nix +++ b/sys/kiosk/default.nix @@ -1,8 +1,11 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.kiosk; -in -{ +in { options.local.kiosk = { enable = mkEnableOption "kiosk mode"; @@ -26,7 +29,13 @@ in enable = true; inherit (cfg) program user; - extraArguments = [ (if cfg.allowVTSwitch then "-sd" else "-d") ]; + extraArguments = [ + ( + if cfg.allowVTSwitch + then "-sd" + else "-d" + ) + ]; }; physlock = { diff --git a/sys/mail/default.nix b/sys/mail/default.nix index 6eba9cd..f87b6fe 100644 --- a/sys/mail/default.nix +++ b/sys/mail/default.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.mailHost; imapHostname = config.local.domains.imap.main; @@ -6,8 +11,7 @@ with lib; let cert = config.security.acme.certs.${imapHostname}.directory; inherit (config.local) users virtual; -in -{ +in { options.local.mailHost = { enable = mkEnableOption "mailbox host service"; @@ -45,7 +49,7 @@ in mailUser = "vmail"; mailGroup = "vmail"; mailLocation = "maildir:~/mail"; - mailPlugins.perProtocol.lmtp.enable = [ "sieve" ]; + mailPlugins.perProtocol.lmtp.enable = ["sieve"]; # https://github.com/NixOS/nixpkgs/issues/286859 sieve.extensions = [ @@ -53,168 +57,164 @@ in "mailbox" ]; - extraConfig = - let - inherit (config.networking) domain; - - # https://dovecot.org/list/dovecot/2019-March/115250.html - # Otra solución posible (https://serverfault.com/a/1062274/980378): - # auth_username_format = %{if;%d;eq;${domain};%Ln;%Lu} - localEntry = canonical: username: '' - ${username}:::::::user=${canonical} nopassword userdb_user=${canonical} - ''; - - localMailboxes = - pkgs.writeText "local-mailboxes" - (concatStrings - (flatten (mapAttrsToList - (canonical: user: - map (localEntry canonical) ([ canonical ] ++ user.hardAliases)) - users))); - - localCerts = - flatten (mapAttrsToList - (canonical: user: - let - certNames = { - inherit canonical; - logins = [ canonical ] ++ user.hardAliases; - }; - in - map (flip nameValuePair certNames) user.mail.certs) - users); - - vmailCerts = - flatten (flatten (mapAttrsToList - (domain: virtual: mapAttrsToList - (username: user: - let - address = "${username}@${domain}"; - - certNames = { - canonical = address; - logins = [ address ]; - }; - in - map (flip nameValuePair certNames) user.mail.certs) - virtual.users) - virtual)); - - certLogins = - pkgs.writeText "cert-logins" - (concatLines (flatten (mapAttrsToList - (certPath: names: map - (addr: "${config.local.pki.byPath.${certPath}.commonName}@nodomain,${addr}:::::::user=${names.canonical}") - names.logins) - (listToAttrs (localCerts ++ vmailCerts))))); - - vmailPath = "/var/lib/vmail/%{if;%d;ne;;%Ld;${domain}}"; - in - '' - auth_mechanisms = plain login external - - ssl_ca = <${config.local.pki.ca.mail.fullchain} - ssl_require_crl = yes - ssl_verify_client_cert = yes - - # Esto descarta @domain.tld de locales explĆcitos, pero lo exige para los demĆ”s. - # Implicación: locales implĆcitos sin dominio fallan en autenticar - auth_username_format = %{if;%Ld;eq;${domain};%Ln;%{if;%d;ne;;%Lu;%Ln@nodomain}} - auth_ssl_username_from_cert = yes - - # TODO: los defaults de nixpkgs dejan los sockets bajo - # /run/dovecot2 con demasiados permisos rwx, arreglar - - service auth { - inet_listener mta-sasl { - port = ${toString cfg.saslPort} - address = ${cfg.mdaListen} - } - } - - service lmtp { - inet_listener mta-lmtp { - port = ${toString cfg.lmtpPort} - address = ${cfg.mdaListen} - } - } - - # FIXME: Esta cadena de passdbs hace que 'doveadm user lookup' - # falle para usuarios locales, pero todo lo demĆ”s sirve. Parece - # ser debido a que pam no puede enumerar. - - passdb { - driver = static - args = nopassword - - master = yes - mechanisms = external - - result_success = continue-fail - result_failure = return-fail - result_internalfail = return-fail - } - - passdb { - driver = passwd-file - args = scheme=PLAIN username_format=%{master_user},%Lu ${certLogins} - - mechanisms = external - override_fields = nopassword - - result_failure = return-fail - result_internalfail = return-fail - } - - passdb { - driver = passwd-file - args = username_format=%Ln ${vmailPath}/passwd - } - - passdb { - driver = passwd-file - args = scheme=PLAIN ${localMailboxes} - - # Esta es una forma de determinar si se encontró el usuario en - # el passwd-file por medio de nopassword sin realmente - # autenticarlo. Cuidado con result_success, porque si eso se - # configura mal se permite inicio de sesión con cualquier - # contraseƱa (!!!). - result_success = continue - result_failure = return-fail - result_internalfail = return-fail - - username_filter = !*@* - } + extraConfig = let + inherit (config.networking) domain; - passdb { - driver = pam - args = dovecot2 - username_filter = !*@* - #TODO: algo como 'override_fields = allow_nets=...' - } - - userdb { - driver = passwd-file - args = username_format=%Ln ${vmailPath}/passwd - override_fields = uid=vmail gid=vmail home=${vmailPath}/home/%Ln - } - - userdb { - driver = passwd-file - args = ${localMailboxes} + # https://dovecot.org/list/dovecot/2019-March/115250.html + # Otra solución posible (https://serverfault.com/a/1062274/980378): + # auth_username_format = %{if;%d;eq;${domain};%Ln;%Lu} + localEntry = canonical: username: '' + ${username}:::::::user=${canonical} nopassword userdb_user=${canonical} + ''; - result_success = continue-ok - result_internalfail = return-fail - skip = found + localMailboxes = + pkgs.writeText "local-mailboxes" + (concatStrings + (flatten (mapAttrsToList + (canonical: user: + map (localEntry canonical) ([canonical] ++ user.hardAliases)) + users))); + + localCerts = flatten (mapAttrsToList + (canonical: user: let + certNames = { + inherit canonical; + logins = [canonical] ++ user.hardAliases; + }; + in + map (flip nameValuePair certNames) user.mail.certs) + users); + + vmailCerts = flatten (flatten (mapAttrsToList + (domain: virtual: + mapAttrsToList + (username: user: let + address = "${username}@${domain}"; + + certNames = { + canonical = address; + logins = [address]; + }; + in + map (flip nameValuePair certNames) user.mail.certs) + virtual.users) + virtual)); + + certLogins = + pkgs.writeText "cert-logins" + (concatLines (flatten (mapAttrsToList + (certPath: names: + map + (addr: "${config.local.pki.byPath.${certPath}.commonName}@nodomain,${addr}:::::::user=${names.canonical}") + names.logins) + (listToAttrs (localCerts ++ vmailCerts))))); + + vmailPath = "/var/lib/vmail/%{if;%d;ne;;%Ld;${domain}}"; + in '' + auth_mechanisms = plain login external + + ssl_ca = <${config.local.pki.ca.mail.fullchain} + ssl_require_crl = yes + ssl_verify_client_cert = yes + + # Esto descarta @domain.tld de locales explĆcitos, pero lo exige para los demĆ”s. + # Implicación: locales implĆcitos sin dominio fallan en autenticar + auth_username_format = %{if;%Ld;eq;${domain};%Ln;%{if;%d;ne;;%Lu;%Ln@nodomain}} + auth_ssl_username_from_cert = yes + + # TODO: los defaults de nixpkgs dejan los sockets bajo + # /run/dovecot2 con demasiados permisos rwx, arreglar + + service auth { + inet_listener mta-sasl { + port = ${toString cfg.saslPort} + address = ${cfg.mdaListen} } + } - userdb { - driver = passwd - args = blocking=no - skip = notfound + service lmtp { + inet_listener mta-lmtp { + port = ${toString cfg.lmtpPort} + address = ${cfg.mdaListen} } - ''; + } + + # FIXME: Esta cadena de passdbs hace que 'doveadm user lookup' + # falle para usuarios locales, pero todo lo demĆ”s sirve. Parece + # ser debido a que pam no puede enumerar. + + passdb { + driver = static + args = nopassword + + master = yes + mechanisms = external + + result_success = continue-fail + result_failure = return-fail + result_internalfail = return-fail + } + + passdb { + driver = passwd-file + args = scheme=PLAIN username_format=%{master_user},%Lu ${certLogins} + + mechanisms = external + override_fields = nopassword + + result_failure = return-fail + result_internalfail = return-fail + } + + passdb { + driver = passwd-file + args = username_format=%Ln ${vmailPath}/passwd + } + + passdb { + driver = passwd-file + args = scheme=PLAIN ${localMailboxes} + + # Esta es una forma de determinar si se encontró el usuario en + # el passwd-file por medio de nopassword sin realmente + # autenticarlo. Cuidado con result_success, porque si eso se + # configura mal se permite inicio de sesión con cualquier + # contraseƱa (!!!). + result_success = continue + result_failure = return-fail + result_internalfail = return-fail + + username_filter = !*@* + } + + passdb { + driver = pam + args = dovecot2 + username_filter = !*@* + #TODO: algo como 'override_fields = allow_nets=...' + } + + userdb { + driver = passwd-file + args = username_format=%Ln ${vmailPath}/passwd + override_fields = uid=vmail gid=vmail home=${vmailPath}/home/%Ln + } + + userdb { + driver = passwd-file + args = ${localMailboxes} + + result_success = continue-ok + result_internalfail = return-fail + skip = found + } + + userdb { + driver = passwd + args = blocking=no + skip = notfound + } + ''; }; fail2ban.jails.dovecot.settings = { @@ -225,12 +225,12 @@ in security = { # Necesario debido a 'enablePAM = false' - pam.services.dovecot2 = { }; + pam.services.dovecot2 = {}; acme.certs.${imapHostname} = { inherit (config.services.dovecot2) group; - reloadServices = [ "dovecot2.service" ]; + reloadServices = ["dovecot2.service"]; }; }; @@ -239,7 +239,7 @@ in groups.${config.services.dovecot2.mailGroup}.gid = 993; }; - networking.firewall.allowedTCPPorts = [ 143 993 ]; + networking.firewall.allowedTCPPorts = [143 993]; local.certs.imap.enable = true; }; diff --git a/sys/mta/default.nix b/sys/mta/default.nix index 4305f70..57c1c27 100644 --- a/sys/mta/default.nix +++ b/sys/mta/default.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.mta; @@ -22,13 +27,12 @@ with lib; let if isPrimary then "lmtp:inet:${cfg.mdaAddr}:${toString cfg.lmtpPort}" else "error:bad transport"; -in -{ +in { options.local.mta = { enable = mkEnableOption "mail transfer agent"; mode = mkOption { - type = types.enum [ "primary" "backup" ]; + type = types.enum ["primary" "backup"]; }; mdaAddr = mkOption { @@ -58,7 +62,7 @@ in enable = true; group = "postfix"; - domains = "csl:" + concatStringsSep "," ([ domain ] ++ attrNames virtualDomains); + domains = "csl:" + concatStringsSep "," ([domain] ++ attrNames virtualDomains); selector = "202408"; configFile = pkgs.writeText "opendkim.conf" '' @@ -76,7 +80,7 @@ in hostname = mtaDomain.main; #TODO: check_recipient_access para rechazar localhost desde afuera - destination = optionals isPrimary [ "localhost" "$mydomain" ]; + destination = optionals isPrimary ["localhost" "$mydomain"]; origin = "$mydomain"; networksStyle = "host"; @@ -95,20 +99,25 @@ in # TambiĆ©n es postmaster rootAlias = config.local.sysadmin; - extraAliases = optionalString isPrimary + extraAliases = + optionalString isPrimary (concatLines (flatten (mapAttrsToList - (name: user: map + (name: user: + map (alias: "${alias}: ${name}") user.hardAliases) users))); - localRecipients = optionals isPrimary + localRecipients = + optionals isPrimary (map (user: "${user}@${domain}") (attrNames (users // virtual.${domain}.users))); - virtual = optionalString isPrimary + virtual = + optionalString isPrimary (concatLines (flatten (mapAttrsToList - (name: virtual: mapAttrsToList + (name: virtual: + mapAttrsToList (alias: targets: "${alias}@${name} ${concatStringsSep ", " targets}") virtual.aliases) virtual))); @@ -116,101 +125,108 @@ in mapFiles = optionalAttrs isPrimary { sender_ccerts = pkgs.writeText "postfix-sender_ccerts" - (concatLines (flatten (mapAttrsToList - (username: user: map - (alias: "${alias}@${domain} CCERTS ${concatStringsSep "," - (map (certPath: config.local.pki.byPath.${certPath}.fingerprint.sha256-bytes-upper) - user.mail.certs)}") - ([ username ] ++ user.hardAliases)) - (filterAttrs (_: user: user.mail.certs != [ ]) users)))); + (concatLines (flatten (mapAttrsToList + (username: user: + map + (alias: "${alias}@${domain} CCERTS ${concatStringsSep "," + (map (certPath: config.local.pki.byPath.${certPath}.fingerprint.sha256-bytes-upper) + user.mail.certs)}") + ([username] ++ user.hardAliases)) + (filterAttrs (_: user: user.mail.certs != []) users)))); sender_login = pkgs.writeText "postfix-sender_login" - (concatLines (flatten (mapAttrsToList - (username: user: map - (alias: "${alias}@${domain} ${username}") - ([ username ] ++ user.hardAliases)) - users))); + (concatLines (flatten (mapAttrsToList + (username: user: + map + (alias: "${alias}@${domain} ${username}") + ([username] ++ user.hardAliases)) + users))); virtual_recipients = pkgs.writeText "postfix-virtual_recipients" - (concatLines (flatten (mapAttrsToList - (virtualDomain: virtual: mapAttrsToList - # El lado derecho de esta tabla debe existir pero nunca se usa - (username: _: "${username}@${virtualDomain} foo") - virtual.users) - virtualDomains))); + (concatLines (flatten (mapAttrsToList + (virtualDomain: virtual: + mapAttrsToList + # El lado derecho de esta tabla debe existir pero nunca se usa + (username: _: "${username}@${virtualDomain} foo") + virtual.users) + virtualDomains))); virtual_rules = pkgs.writeText "postfix-virtual_rules" - (concatLines (flatten (mapAttrsToList - (name: virtual: map - (rule: "/^${rule.pattern}@${name}$/ ${concatStringsSep ", " rule.targets}") - virtual.rules) - virtual))); + (concatLines (flatten (mapAttrsToList + (name: virtual: + map + (rule: "/^${rule.pattern}@${name}$/ ${concatStringsSep ", " rule.targets}") + virtual.rules) + virtual))); }; - config = { - # user+extension@domain.tld - recipient_delimiter = optionalString isPrimary "+"; - - message_size_limit = toString (50 * 1048576); - - local_transport = mdaTransport; - virtual_transport = mdaTransport; - - smtpd_tls_auth_only = true; - # Nota: smtpd_tls_dh1024_param_file fue deprecado en 3.9 - - tls_append_default_CA = false; # CrĆtico - - # https://linux-audit.com/postfix-hardening-guide-for-security-and-privacy/ - smtpd_helo_required = true; - disable_vrfy_command = true; - } // optionalAttrs isPrimary { - virtual_alias_maps = mkAfter [ "pcre:/etc/postfix/virtual_rules" ]; - virtual_mailbox_domains = attrNames virtualDomains; - virtual_mailbox_maps = [ "hash:/etc/postfix/virtual_recipients" ]; - - smtpd_sasl_type = "dovecot"; - smtpd_sasl_path = "inet:${cfg.mdaAddr}:${toString cfg.saslPort}"; - smtpd_sasl_local_domain = "$mydomain"; - smtpd_sasl_security_options = [ "noanonymous" ]; - - smtpd_tls_CAfile = "${config.local.pki.ca.mail.fullchain}"; - smtpd_tls_ccert_verifydepth = "1"; - - # Inventado, no es parĆ”metro de postfix - local_submission_client_restrictions = [ - "permit_tls_all_clientcerts" - "permit_sasl_authenticated" - "reject" - ]; - - smtpd_sender_login_maps = [ "hash:/etc/postfix/sender_login" ]; - - smtpd_relay_restrictions = [ - "permit_mynetworks" - "permit_tls_all_clientcerts" - "permit_sasl_authenticated" - "reject_unauth_destination" - ]; - - smtpd_sender_restrictions = [ - "check_sender_access hash:/etc/postfix/sender_ccerts" - "reject_sender_login_mismatch" - ]; - - smtpd_milters = "unix:/run/opendkim/opendkim.sock"; - non_smtpd_milters = "$smtpd_milters"; - milter_default_action = "accept"; - } // optionalAttrs isBackup { - inet_interfaces = [ cfg.relayListen ]; - - smtpd_relay_restrictions = [ - "reject_unauth_destination" - ]; - }; + config = + { + # user+extension@domain.tld + recipient_delimiter = optionalString isPrimary "+"; + + message_size_limit = toString (50 * 1048576); + + local_transport = mdaTransport; + virtual_transport = mdaTransport; + + smtpd_tls_auth_only = true; + # Nota: smtpd_tls_dh1024_param_file fue deprecado en 3.9 + + tls_append_default_CA = false; # CrĆtico + + # https://linux-audit.com/postfix-hardening-guide-for-security-and-privacy/ + smtpd_helo_required = true; + disable_vrfy_command = true; + } + // optionalAttrs isPrimary { + virtual_alias_maps = mkAfter ["pcre:/etc/postfix/virtual_rules"]; + virtual_mailbox_domains = attrNames virtualDomains; + virtual_mailbox_maps = ["hash:/etc/postfix/virtual_recipients"]; + + smtpd_sasl_type = "dovecot"; + smtpd_sasl_path = "inet:${cfg.mdaAddr}:${toString cfg.saslPort}"; + smtpd_sasl_local_domain = "$mydomain"; + smtpd_sasl_security_options = ["noanonymous"]; + + smtpd_tls_CAfile = "${config.local.pki.ca.mail.fullchain}"; + smtpd_tls_ccert_verifydepth = "1"; + + # Inventado, no es parĆ”metro de postfix + local_submission_client_restrictions = [ + "permit_tls_all_clientcerts" + "permit_sasl_authenticated" + "reject" + ]; + + smtpd_sender_login_maps = ["hash:/etc/postfix/sender_login"]; + + smtpd_relay_restrictions = [ + "permit_mynetworks" + "permit_tls_all_clientcerts" + "permit_sasl_authenticated" + "reject_unauth_destination" + ]; + + smtpd_sender_restrictions = [ + "check_sender_access hash:/etc/postfix/sender_ccerts" + "reject_sender_login_mismatch" + ]; + + smtpd_milters = "unix:/run/opendkim/opendkim.sock"; + non_smtpd_milters = "$smtpd_milters"; + milter_default_action = "accept"; + } + // optionalAttrs isBackup { + inet_interfaces = [cfg.relayListen]; + + smtpd_relay_restrictions = [ + "reject_unauth_destination" + ]; + }; # Importante: existe submissionOptions por aparte, no son iguales submissionsOptions = optionalAttrs isPrimary { @@ -223,19 +239,31 @@ in }; #TODO: solo para las destination addresses necesarias - networking.firewall.allowedTCPPorts = optionals isPrimary [ 25 465 ]; + networking.firewall.allowedTCPPorts = optionals isPrimary [25 465]; local = { - boot.impermanence.directories = [ - { directory = "/var/lib/postfix"; user = "root"; group = "root"; mode = "u=rwx,g=rx,o=rx"; } - ] ++ optionals isPrimary [ - { directory = "/var/lib/opendkim"; user = "opendkim"; group = "postfix"; mode = "u=rwx,g=,o="; } - ]; + boot.impermanence.directories = + [ + { + directory = "/var/lib/postfix"; + user = "root"; + group = "root"; + mode = "u=rwx,g=rx,o=rx"; + } + ] + ++ optionals isPrimary [ + { + directory = "/var/lib/opendkim"; + user = "opendkim"; + group = "postfix"; + mode = "u=rwx,g=,o="; + } + ]; certs.smtp.enable = isPrimary; certs.smtp-backup.enable = isBackup; }; - security.acme.certs.${mtaDomain.main}.reloadServices = [ "postfix.service" ]; + security.acme.certs.${mtaDomain.main}.reloadServices = ["postfix.service"]; }; } diff --git a/sys/net/fail2ban.nix b/sys/net/fail2ban.nix index 8d3aa3d..32197b6 100644 --- a/sys/net/fail2ban.nix +++ b/sys/net/fail2ban.nix @@ -1,9 +1,13 @@ -{ lib, config, pkgs, ... }: +{ + lib, + config, + pkgs, + ... +}: with lib; let cfg = config.local.net.fail2ban; inherit (config.local) nets; -in -{ +in { options.local.net.fail2ban = { enable = mkEnableOption "fail2ban"; }; diff --git a/sys/net/interfaces.nix b/sys/net/interfaces.nix index 281f5ca..3b0abcd 100644 --- a/sys/net/interfaces.nix +++ b/sys/net/interfaces.nix @@ -1,8 +1,12 @@ -{ lib, config, pkgs, ... }: +{ + lib, + config, + pkgs, + ... +}: with lib; let cfg = config.local.net; -in -{ +in { options.local.net = with lib.types; { enable = mkEnableOption "networking stack"; diff --git a/sys/net/options.nix b/sys/net/options.nix index 11b913c..0608fb9 100644 --- a/sys/net/options.nix +++ b/sys/net/options.nix @@ -1,65 +1,71 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let v4PtrHierarchy = address: bits: reverseList (sublist 0 (bits / 8) (splitString "." address)); - v6PtrHierarchy = address: bits: - let - separator = lists.findFirstIndex (hextet: hextet == "") null colonSplit; - colonSplit = splitString ":" address; + v6PtrHierarchy = address: bits: let + separator = lists.findFirstIndex (hextet: hextet == "") null colonSplit; + colonSplit = splitString ":" address; - zeroFill = replicate (8 - length colonSplit + 1) "0000"; - leftSplit = sublist 0 separator colonSplit; - rightSplit = sublist (separator + 1) (length colonSplit - separator - 1) colonSplit; + zeroFill = replicate (8 - length colonSplit + 1) "0000"; + leftSplit = sublist 0 separator colonSplit; + rightSplit = sublist (separator + 1) (length colonSplit - separator - 1) colonSplit; - fullSplit = - if separator != null - then leftSplit ++ zeroFill ++ rightSplit - else colonSplit; + fullSplit = + if separator != null + then leftSplit ++ zeroFill ++ rightSplit + else colonSplit; - padded = map (hextet: strings.replicate (4 - stringLength hextet) "0" + hextet) fullSplit; - in + padded = map (hextet: strings.replicate (4 - stringLength hextet) "0" + hextet) fullSplit; + in reverseList (sublist 0 (bits / 4) (flatten (map stringToCharacters padded))); - matchPtrRecordName = { splitter, netAddress, netBits, targetAddress, targetBits }: - let - netSplit = splitter netAddress netBits; - targetSplit = splitter targetAddress targetBits; - - netLength = length netSplit; - lengthDelta = length targetSplit - netLength; - - withinNet = lengthDelta >= 0 && sublist lengthDelta netLength targetSplit == netSplit; - throwMessage = "${targetAddress}/${toString targetBits} is not a subset of ${netAddress}/${toString netBits}"; - - recordHierarchy = sublist 0 lengthDelta targetSplit; - - recordName = - if recordHierarchy != [ ] - then concatStringsSep "." recordHierarchy - else "@"; - in + matchPtrRecordName = { + splitter, + netAddress, + netBits, + targetAddress, + targetBits, + }: let + netSplit = splitter netAddress netBits; + targetSplit = splitter targetAddress targetBits; + + netLength = length netSplit; + lengthDelta = length targetSplit - netLength; + + withinNet = lengthDelta >= 0 && sublist lengthDelta netLength targetSplit == netSplit; + throwMessage = "${targetAddress}/${toString targetBits} is not a subset of ${netAddress}/${toString netBits}"; + + recordHierarchy = sublist 0 lengthDelta targetSplit; + + recordName = + if recordHierarchy != [] + then concatStringsSep "." recordHierarchy + else "@"; + in throwIfNot withinNet throwMessage recordName; -in -{ - options.local.nets = with lib.types; mkOption { - readOnly = true; +in { + options.local.nets = with lib.types; + mkOption { + readOnly = true; - type = attrsOf (submodule ({ config, ... }: { - options = - let + type = attrsOf (submodule ({config, ...}: { + options = let v4config = config.v4; v6config = config.v6; - in - { + in { hosts = mkOption { - default = { }; + default = {}; type = attrsOf (submodule { options = { v4 = mkOption { default = null; - type = nullOr (submodule ({ config, ... }: { + type = nullOr (submodule ({config, ...}: { options = { suffix = mkOption { type = str; @@ -98,7 +104,7 @@ in v6 = mkOption { default = null; - type = nullOr (submodule ({ config, ... }: { + type = nullOr (submodule ({config, ...}: { options = { suffix = mkOption { type = str; @@ -121,19 +127,21 @@ in }; config = { - address = - let - hextets = fragment: length (splitString ":" fragment); - separator = if doubleColon then "::" else ":"; - doubleColon = hextets v6config.prefix + hextets config.suffix < 8; - - joined = - if v6config.bits == 128 - then v6config.prefix - else if v6config.bits == 0 - then config.suffix - else "${v6config.prefix}${separator}${config.suffix}"; - in + address = let + hextets = fragment: length (splitString ":" fragment); + separator = + if doubleColon + then "::" + else ":"; + doubleColon = hextets v6config.prefix + hextets config.suffix < 8; + + joined = + if v6config.bits == 128 + then v6config.prefix + else if v6config.bits == 0 + then config.suffix + else "${v6config.prefix}${separator}${config.suffix}"; + in joined; cidr = "${config.address}/${toString v6config.bits}"; @@ -148,10 +156,10 @@ in v4 = mkOption { default = null; - type = nullOr (submodule ({ config, ... }: { + type = nullOr (submodule ({config, ...}: { options = { bits = mkOption { - type = enum [ 0 8 16 24 32 ]; + type = enum [0 8 16 24 32]; }; prefix = mkOption { @@ -189,15 +197,16 @@ in ptrDomain = concatStrings (map (x: x + ".") (v4PtrHierarchy config.subnet config.bits)) + "in-addr.arpa"; - ptrRecordName = address: bits: matchPtrRecordName { - splitter = v4PtrHierarchy; + ptrRecordName = address: bits: + matchPtrRecordName { + splitter = v4PtrHierarchy; - netBits = config.bits; - netAddress = config.subnet; + netBits = config.bits; + netAddress = config.subnet; - targetBits = bits; - targetAddress = address; - }; + targetBits = bits; + targetAddress = address; + }; }; })); }; @@ -205,12 +214,14 @@ in v6 = mkOption { default = null; - type = nullOr (submodule ({ config, ... }: { + type = nullOr (submodule ({config, ...}: { options = { bits = mkOption { - type = addCheck (ints.between 0 128) (b: mod b 4 == 0) // { - description = "IPv6 subnet bits at nibble boundary"; - }; + type = + addCheck (ints.between 0 128) (b: mod b 4 == 0) + // { + description = "IPv6 subnet bits at nibble boundary"; + }; }; prefix = mkOption { @@ -248,19 +259,20 @@ in ptrDomain = concatStrings (map (x: x + ".") (v6PtrHierarchy config.subnet config.bits)) + "ip6.arpa"; - ptrRecordName = address: bits: matchPtrRecordName { - splitter = v6PtrHierarchy; + ptrRecordName = address: bits: + matchPtrRecordName { + splitter = v6PtrHierarchy; - netBits = config.bits; - netAddress = config.subnet; + netBits = config.bits; + netAddress = config.subnet; - targetBits = bits; - targetAddress = address; - }; + targetBits = bits; + targetAddress = address; + }; }; })); }; }; - })); - }; + })); + }; } diff --git a/sys/net/vsock.nix b/sys/net/vsock.nix index d1bd250..c6b0ad6 100644 --- a/sys/net/vsock.nix +++ b/sys/net/vsock.nix @@ -1,59 +1,63 @@ -{ lib, config, pkgs, ... }: +{ + lib, + config, + pkgs, + ... +}: with lib; let cfg = config.local.net.vsock; -in -{ +in { options.local.net.vsock = { connect = mkOption { - default = { }; - type = with lib.types; attrsOf (submodule ({ name, ... }: { - options = { - enable = mkEnableOption "vsock connect '${name}'"; + default = {}; + type = with lib.types; + attrsOf (submodule ({name, ...}: { + options = { + enable = mkEnableOption "vsock connect '${name}'"; - cid = mkOption { - type = ints.u32; - default = 2; - }; + cid = mkOption { + type = ints.u32; + default = 2; + }; - localPort = mkOption { - type = port; - }; + localPort = mkOption { + type = port; + }; - vsockPort = mkOption { - type = port; + vsockPort = mkOption { + type = port; + }; }; - }; - })); + })); }; }; config = { - systemd = - let - connects = mapAttrs - (_: connect: { - service.serviceConfig = { - Type = "simple"; - ExecStart = "${getExe pkgs.socat} - VSOCK:${toString connect.cid}:${toString connect.vsockPort}"; - StandardInput = "socket"; - }; - - socket = { - wantedBy = [ "sockets.target" ]; + systemd = let + connects = + mapAttrs + (_: connect: { + service.serviceConfig = { + Type = "simple"; + ExecStart = "${getExe pkgs.socat} - VSOCK:${toString connect.cid}:${toString connect.vsockPort}"; + StandardInput = "socket"; + }; - socketConfig = { - Accept = true; - ListenStream = "[::1]:${toString connect.localPort}"; - }; + socket = { + wantedBy = ["sockets.target"]; - unitConfig.ConditionVirtualization = "kvm"; + socketConfig = { + Accept = true; + ListenStream = "[::1]:${toString connect.localPort}"; }; - }) - cfg.connect; - in - { - sockets = mapAttrs' (name: connect: nameValuePair "vsock-${name}" connect.socket) connects; - services = mapAttrs' (name: connect: nameValuePair "vsock-${name}@" connect.service) connects; - }; + + unitConfig.ConditionVirtualization = "kvm"; + }; + }) + cfg.connect; + in { + sockets = mapAttrs' (name: connect: nameValuePair "vsock-${name}" connect.socket) connects; + services = mapAttrs' (name: connect: nameValuePair "vsock-${name}@" connect.service) connects; + }; }; } diff --git a/sys/ns/mx.nix b/sys/ns/mx.nix index 40a5574..892b684 100644 --- a/sys/ns/mx.nix +++ b/sys/ns/mx.nix @@ -1,33 +1,60 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let inherit (config.local) domains; -in -{ +in { options.local.ns.zones = mkOption { - type = with lib.types; attrsOf (submodule ({ config, name, ... }: { - options.localMX = { - enable = mkEnableOption "local MX settings"; - }; + type = with lib.types; + attrsOf (submodule ({ + config, + name, + ... + }: { + options.localMX = { + enable = mkEnableOption "local MX settings"; + }; - config = mkIf config.localMX.enable { - mx = [ - { name = "@"; priority = 10; host = "${domains.smtp.gated}."; } - { name = "@"; priority = 20; host = "${domains.smtp-backup.main}."; } - # Many thanks to junkemailfilter.com for all their years of service. RIP. - #{ name = "@"; priority = 30; host = "mxbackup1.junkemailfilter.com."; } - #{ name = "@"; priority = 40; host = "mxbackup2.junkemailfilter.com."; } - ]; + config = mkIf config.localMX.enable { + mx = [ + { + name = "@"; + priority = 10; + host = "${domains.smtp.gated}."; + } + { + name = "@"; + priority = 20; + host = "${domains.smtp-backup.main}."; + } + # Many thanks to junkemailfilter.com for all their years of service. RIP. + #{ name = "@"; priority = 30; host = "mxbackup1.junkemailfilter.com."; } + #{ name = "@"; priority = 40; host = "mxbackup2.junkemailfilter.com."; } + ]; - txt = [ - { name = "@"; text = "v=spf1 mx a -all"; } - { name = "_dmarc"; text = "v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;fo=1;rf=afrf;rua=mailto:postmaster@${name}"; } - { name = "_adsp._domainkey"; text = "dkim=all"; } - ] ++ map - (selector: { - name = "${toString selector}._domainkey"; - text = readFile (./dkim + "/${toString selector}.txt"); - }) [ 202001 202102 202402 202408 ]; - }; - })); + txt = + [ + { + name = "@"; + text = "v=spf1 mx a -all"; + } + { + name = "_dmarc"; + text = "v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;fo=1;rf=afrf;rua=mailto:postmaster@${name}"; + } + { + name = "_adsp._domainkey"; + text = "dkim=all"; + } + ] + ++ map + (selector: { + name = "${toString selector}._domainkey"; + text = readFile (./dkim + "/${toString selector}.txt"); + }) [202001 202102 202402 202408]; + }; + })); }; } diff --git a/sys/ns/ns.nix b/sys/ns/ns.nix index 1e74502..e5b30e8 100644 --- a/sys/ns/ns.nix +++ b/sys/ns/ns.nix @@ -1,130 +1,153 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let inherit (config.networking) domain; inherit (config.local.nets) gate-public; inherit (config.local.ns.server) tsigName; ptrNets = config.local.ns.ptr; -in -{ +in { options.local.ns.zones = mkOption { - type = with lib.types; attrsOf + type = with lib.types; + attrsOf (submodule - ({ config, name, ... }: - let - inherit (config.soa) primary; - - cfg = config.localNS; - ptrDomain = cfg.ptrNet.v4 != null || cfg.ptrNet.v6 != null; - in - { - options.localNS = { - enable = mkEnableOption "local NS settings"; - - acme = mkOption { - default = { }; - type = attrsOf str; - }; + ({ + config, + name, + ... + }: let + inherit (config.soa) primary; + + cfg = config.localNS; + ptrDomain = cfg.ptrNet.v4 != null || cfg.ptrNet.v6 != null; + in { + options.localNS = { + enable = mkEnableOption "local NS settings"; + + acme = mkOption { + default = {}; + type = attrsOf str; + }; - ptrNet = { - v4 = mkOption { - type = nullOr str; - default = null; - }; + ptrNet = { + v4 = mkOption { + type = nullOr str; + default = null; + }; - v6 = mkOption { - type = nullOr str; - default = null; - }; + v6 = mkOption { + type = nullOr str; + default = null; }; }; - - config = mkIf cfg.enable - { - ptrName = - let - name = - if cfg.ptrNet.v6 != null - then "${cfg.ptrNet.v6}-v6" - else "${cfg.ptrNet.v4}-v4"; - in - mkIf ptrDomain name; - - # https://docs.gandi.net/en/domain_names/advanced_users/secondary_nameserver.html - nsdConfig = - let - providerSecondary = [ - "37.205.15.45 ${tsigName}" # ns3.vpsfree.cz - "37.205.11.85 ${tsigName}" # ns4.vpsfree.cz - "2a03:3b40:fe:2be::1 ${tsigName}" # ns3.vpsfree.cz - "2a03:3b40:101:4::1 ${tsigName}" # ns4.vpsfree.cz - ]; - in - { - notify = providerSecondary; - provideXFR = providerSecondary; - }; - - ns = [ - { name = "@"; host = primary; } - { name = "@"; host = "ns3.vpsfree.cz."; } - { name = "@"; host = "ns4.vpsfree.cz."; } + }; + + config = + mkIf cfg.enable + { + ptrName = let + name = + if cfg.ptrNet.v6 != null + then "${cfg.ptrNet.v6}-v6" + else "${cfg.ptrNet.v4}-v4"; + in + mkIf ptrDomain name; + + # https://docs.gandi.net/en/domain_names/advanced_users/secondary_nameserver.html + nsdConfig = let + providerSecondary = [ + "37.205.15.45 ${tsigName}" # ns3.vpsfree.cz + "37.205.11.85 ${tsigName}" # ns4.vpsfree.cz + "2a03:3b40:fe:2be::1 ${tsigName}" # ns3.vpsfree.cz + "2a03:3b40:101:4::1 ${tsigName}" # ns4.vpsfree.cz ]; + in { + notify = providerSecondary; + provideXFR = providerSecondary; + }; - a = optional (!ptrDomain) - { name = primary; ipv4 = gate-public.hosts.gate.v4.address; ptr = null; }; - - aaaa = optional (!ptrDomain) - { name = primary; ipv6 = gate-public.hosts.gate.v6.address; ptr = null; }; + ns = [ + { + name = "@"; + host = primary; + } + { + name = "@"; + host = "ns3.vpsfree.cz."; + } + { + name = "@"; + host = "ns4.vpsfree.cz."; + } + ]; + + a = + optional (!ptrDomain) + { + name = primary; + ipv4 = gate-public.hosts.gate.v4.address; + ptr = null; + }; - ptr = - let - ptrsToRecords = mapAttrsToList (suffix: target: { - name = suffix; - inherit target; - }); + aaaa = + optional (!ptrDomain) + { + name = primary; + ipv6 = gate-public.hosts.gate.v6.address; + ptr = null; + }; - v4Net = cfg.ptrNet.v4; - v6Net = cfg.ptrNet.v6; + ptr = let + ptrsToRecords = mapAttrsToList (suffix: target: { + name = suffix; + inherit target; + }); - v4Records = optionals (v4Net != null) (ptrsToRecords ptrNets.${v4Net}.v4.targets); - v6Records = optionals (v6Net != null) (ptrsToRecords ptrNets.${v6Net}.v6.targets); - in - v4Records ++ v6Records; + v4Net = cfg.ptrNet.v4; + v6Net = cfg.ptrNet.v6; - soa = mkIf ptrDomain { - authorityZone = mkDefault "${domain}."; - }; + v4Records = optionals (v4Net != null) (ptrsToRecords ptrNets.${v4Net}.v4.targets); + v6Records = optionals (v6Net != null) (ptrsToRecords ptrNets.${v6Net}.v6.targets); + in + v4Records ++ v6Records; - cname = mapAttrsToList - (name: id: { - name = "_acme-challenge" + optionalString (name != "@") ".${name}"; - target = "${id}.acme-challenge.${domain}."; - }) - cfg.acme; + soa = mkIf ptrDomain { + authorityZone = mkDefault "${domain}."; }; - })); - }; - config = - { - assertions = mapAttrsToList - (name: zone: { - assertion = zone.localNS.ptrNet.v4 != null -> zone.localNS.ptrNet.v6 == null; - message = "zone '${name}' defined as both a v4 and v6 PTR zone"; - }) - config.local.ns.zones; - - local.ns.ptr = - let - zonePtrNets = name: zone: - optionalAttrs (zone.localNS.ptrNet.v4 != null) - { - ${zone.localNS.ptrNet.v4}.v4.zone = name; - } // optionalAttrs (zone.localNS.ptrNet.v6 != null) { - ${zone.localNS.ptrNet.v6}.v6.zone = name; + cname = + mapAttrsToList + (name: id: { + name = "_acme-challenge" + optionalString (name != "@") ".${name}"; + target = "${id}.acme-challenge.${domain}."; + }) + cfg.acme; }; - in - mkMerge (flatten (mapAttrsToList zonePtrNets (filterAttrs (_: zone: zone.localNS.enable) config.local.ns.zones))); - }; + })); + }; + + config = { + assertions = + mapAttrsToList + (name: zone: { + assertion = zone.localNS.ptrNet.v4 != null -> zone.localNS.ptrNet.v6 == null; + message = "zone '${name}' defined as both a v4 and v6 PTR zone"; + }) + config.local.ns.zones; + + local.ns.ptr = let + zonePtrNets = name: zone: + optionalAttrs (zone.localNS.ptrNet.v4 != null) + { + ${zone.localNS.ptrNet.v4}.v4.zone = name; + } + // optionalAttrs (zone.localNS.ptrNet.v6 != null) { + ${zone.localNS.ptrNet.v6}.v6.zone = name; + }; + in + mkMerge (flatten (mapAttrsToList zonePtrNets (filterAttrs (_: zone: zone.localNS.enable) config.local.ns.zones))); + }; } diff --git a/sys/ns/nsd.nix b/sys/ns/nsd.nix index 1dfa16b..d49e464 100644 --- a/sys/ns/nsd.nix +++ b/sys/ns/nsd.nix @@ -1,12 +1,15 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let inherit (config.networking) domain; cfg = config.local.ns.server; acmeChallengeDomain = "acme-challenge.${domain}"; -in -{ +in { options. local. ns. server = { enable = mkEnableOption "nsd authoritative server"; @@ -34,14 +37,12 @@ in } ]; - networking.firewall = - let - inherit (config.services.nsd) port; - in - { - allowedTCPPorts = [ port ]; - allowedUDPPorts = [ port ]; - }; + networking.firewall = let + inherit (config.services.nsd) port; + in { + allowedTCPPorts = [port]; + allowedUDPPorts = [port]; + }; services = { acme-dns = { diff --git a/sys/ns/ptr/default.nix b/sys/ns/ptr/default.nix index d583dd7..b4fba7e 100644 --- a/sys/ns/ptr/default.nix +++ b/sys/ns/ptr/default.nix @@ -1,8 +1,6 @@ -{ config, ... }: -let +{config, ...}: let inherit (config.local) nets; -in -{ +in { config.local.ns.zones = { ${nets.gate-public.v4.ptrDomain} = import ./gate-public-v4; ${nets.gate-public.v6.ptrDomain} = import ./gate-public-v6; diff --git a/sys/ns/ptr/gate-public-v4/default.nix b/sys/ns/ptr/gate-public-v4/default.nix index a2595d9..44c7f2e 100644 --- a/sys/ns/ptr/gate-public-v4/default.nix +++ b/sys/ns/ptr/gate-public-v4/default.nix @@ -1,8 +1,6 @@ -{ config, ... }: -let +{config, ...}: let inherit (config.local) nets; -in -{ +in { imports = [ ./serial.nix ]; diff --git a/sys/ns/ptr/gate-public-v4/serial.nix b/sys/ns/ptr/gate-public-v4/serial.nix index c3a41e9..008e5d8 100644 --- a/sys/ns/ptr/gate-public-v4/serial.nix +++ b/sys/ns/ptr/gate-public-v4/serial.nix @@ -4,4 +4,3 @@ nullSerialHash = "sha256-afaedee02017aabd45b944a657ce91515866982c7cb900927edcee6d2b39c731"; }; } - diff --git a/sys/ns/ptr/gate-public-v6/default.nix b/sys/ns/ptr/gate-public-v6/default.nix index 15a4095..674421f 100644 --- a/sys/ns/ptr/gate-public-v6/default.nix +++ b/sys/ns/ptr/gate-public-v6/default.nix @@ -1,8 +1,6 @@ -{ config, ... }: -let +{config, ...}: let inherit (config.local) nets; -in -{ +in { imports = [ ./serial.nix ]; diff --git a/sys/ns/ptr/gate-public-v6/serial.nix b/sys/ns/ptr/gate-public-v6/serial.nix index 2f1b4a9..126a17e 100644 --- a/sys/ns/ptr/gate-public-v6/serial.nix +++ b/sys/ns/ptr/gate-public-v6/serial.nix @@ -4,4 +4,3 @@ nullSerialHash = "sha256-9a8ac8849ea6c8993e44feefe439b96c643e2ccf3a03d0d700558e9a188f57d7"; }; } - diff --git a/sys/ns/ptr/static-prefix-v6/default.nix b/sys/ns/ptr/static-prefix-v6/default.nix index f02222c..7688b97 100644 --- a/sys/ns/ptr/static-prefix-v6/default.nix +++ b/sys/ns/ptr/static-prefix-v6/default.nix @@ -1,8 +1,6 @@ -{ config, ... }: -let +{config, ...}: let inherit (config.local) nets; -in -{ +in { imports = [ ./serial.nix ]; diff --git a/sys/ns/ptr/static-prefix-v6/serial.nix b/sys/ns/ptr/static-prefix-v6/serial.nix index 454b3dd..a7c214a 100644 --- a/sys/ns/ptr/static-prefix-v6/serial.nix +++ b/sys/ns/ptr/static-prefix-v6/serial.nix @@ -4,4 +4,3 @@ nullSerialHash = "sha256-a5ce7781b014aa816998410db440dd40278d8b566d1de76e06776a83c9839b35"; }; } - diff --git a/sys/ns/rr.nix b/sys/ns/rr.nix index e4fbe12..8b4d119 100644 --- a/sys/ns/rr.nix +++ b/sys/ns/rr.nix @@ -1,4 +1,10 @@ -{ config, lib, options, pkgs, ... }: +{ + config, + lib, + options, + pkgs, + ... +}: with lib; let inherit (config.local) nets; @@ -11,15 +17,13 @@ with lib; let domainRefType = lib.types.strMatching "@|${segmentRegex}\\.?"; domainNameType = lib.types.strMatching "${segmentRegex}\\."; - zoneHashCheck = name: zone: - let - zoneHash = algorithm: "${algorithm}-${builtins.hashString algorithm cfg.nullSerialZones.${name}.content}"; - expected = zoneHash "sha256"; - in - { - inherit expected zone; - needsUpdate = zone.soa.serial == null || zone.nullSerialHash != expected; - }; + zoneHashCheck = name: zone: let + zoneHash = algorithm: "${algorithm}-${builtins.hashString algorithm cfg.nullSerialZones.${name}.content}"; + expected = zoneHash "sha256"; + in { + inherit expected zone; + needsUpdate = zone.soa.serial == null || zone.nullSerialHash != expected; + }; rrTypes = [ "A" @@ -32,8 +36,7 @@ with lib; let "SRV" "TXT" ]; -in -{ +in { options.local.ns = { nullSerialZones = mkOption { type = options.local.ns.zones.type; @@ -41,54 +44,62 @@ in }; ptr = mkOption { - default = { }; + default = {}; - type = with lib.types; attrsOf (submodule { - options = { - v4 = { - zone = mkOption { - type = nullOr str; - default = null; - }; + type = with lib.types; + attrsOf (submodule { + options = { + v4 = { + zone = mkOption { + type = nullOr str; + default = null; + }; - targets = mkOption { - type = attrsOf str; - default = { }; + targets = mkOption { + type = attrsOf str; + default = {}; + }; }; - }; - v6 = { - zone = mkOption { - type = nullOr str; - default = null; - }; + v6 = { + zone = mkOption { + type = nullOr str; + default = null; + }; - targets = mkOption { - type = attrsOf str; - default = { }; + targets = mkOption { + type = attrsOf str; + default = {}; + }; }; }; - }; - }); + }); }; zones = mkOption { - default = { }; - - type = with lib.types; attrsOf (submodule ({ config, name, ... }: - let - nameOption = args@{ defaultZone ? "${name}.", permitRelative ? true, ... }: - mkOption (removeAttrs args [ "defaultZone" "permitRelative" ] // { + default = {}; + + type = with lib.types; attrsOf (submodule ({ + config, + name, + ... + }: let + nameOption = args @ { + defaultZone ? "${name}.", + permitRelative ? true, + ... + }: + mkOption (removeAttrs args ["defaultZone" "permitRelative"] + // { type = domainRefType; - apply = value: - let - zone = - throwIfNot - (hasSuffix "." defaultZone) - "zone expression '${defaultZone}' must be absolute, not relative" - defaultZone; - in + apply = value: let + zone = + throwIfNot + (hasSuffix "." defaultZone) + "zone expression '${defaultZone}' must be absolute, not relative" + defaultZone; + in if value == "@" then zone else if hasSuffix "." value @@ -98,401 +109,410 @@ in else throw "zone expression '${value}' in zone '${zone}' must be absolute, not relative"; }); - rrType = options: mkOption { - default = [ ]; + rrType = options: + mkOption { + default = []; type = listOf (submodule { - options = options // { - name = nameOption { }; - - ttl = mkOption { - type = int; - default = config.defaultTTL; + options = + options + // { + name = nameOption {}; + + ttl = mkOption { + type = int; + default = config.defaultTTL; + }; }; - }; }); }; - rrConfig = { rrs, type, format, applyName ? (rr: rr.name) }: (map - (rr: { - inherit type; - inherit (rr) ttl; + rrConfig = { + rrs, + type, + format, + applyName ? (rr: rr.name), + }: (map + (rr: { + inherit type; + inherit (rr) ttl; + + data = format rr; + name = applyName rr; + }) + rrs); + in { + options = { + local = mkOption { + type = unspecified; + default = globalConfig.local; + readOnly = true; + }; - data = format rr; - name = applyName rr; - }) - rrs); - in - { - options = { - local = mkOption { - type = unspecified; - default = globalConfig.local; - readOnly = true; - }; + defaultTTL = mkOption { + type = int; + default = 3600; + }; - defaultTTL = mkOption { - type = int; - default = 3600; + ptrName = mkOption { + type = nullOr str; + default = null; + }; + + defaultPtr = { + v4 = mkOption { + type = nullOr str; + default = null; }; - ptrName = mkOption { + v6 = mkOption { type = nullOr str; default = null; }; + }; - defaultPtr = { - v4 = mkOption { - type = nullOr str; - default = null; - }; + nsdConfig = mkOption { + type = attrsOf unspecified; + default = {}; + }; - v6 = mkOption { - type = nullOr str; - default = null; - }; + content = mkOption { + type = lines; + readOnly = true; + }; + + nullSerialHash = mkOption { + type = nullOr str; + default = null; + }; + + rr = mkOption { + default = []; + type = + listOf + (submodule { + options = { + name = nameOption {}; + + ttl = mkOption { + type = int; + }; + + class = mkOption { + type = enum ["IN"]; + default = "IN"; + }; + + type = mkOption { + type = enum rrTypes; + }; + + data = mkOption { + type = listOf (either int str); + default = []; + }; + }; + }); + }; + + soa = { + authorityZone = nameOption { + default = "@"; + permitRelative = false; + }; + + ttl = mkOption { + type = int; + default = config.defaultTTL; }; - nsdConfig = mkOption { - type = attrsOf unspecified; - default = { }; + primary = nameOption { + default = "ns1"; + defaultZone = config.soa.authorityZone; }; - content = mkOption { - type = lines; - readOnly = true; + hostmaster = mkOption { + type = emailType; + default = "hostmaster"; + + apply = address: let + split = splitString "@" address; + + user = head split; + domain = + if length split == 2 + then head (tail split) + else removeSuffix "." config.soa.authorityZone; + in + if hasSuffix "." address + then address + else "${replaceStrings ["."] ["\\."] user}.${domain}."; }; - nullSerialHash = mkOption { - type = nullOr str; + serial = mkOption { + type = nullOr int; default = null; }; - rr = mkOption { - default = [ ]; - type = listOf - (submodule { - options = { - name = nameOption { }; - - ttl = mkOption { - type = int; - }; - - class = mkOption { - type = enum [ "IN" ]; - default = "IN"; - }; - - type = mkOption { - type = enum rrTypes; - }; - - data = mkOption { - type = listOf (either int str); - default = [ ]; - }; - }; - }); + refresh = mkOption { + type = int; + default = 3 * 3600; }; - soa = { - authorityZone = nameOption { default = "@"; permitRelative = false; }; - - ttl = mkOption { - type = int; - default = config.defaultTTL; - }; + retry = mkOption { + type = int; + default = 3600; + }; - primary = nameOption { - default = "ns1"; - defaultZone = config.soa.authorityZone; - }; + expire = mkOption { + type = int; + default = 7 * 24 * 3600; + }; - hostmaster = mkOption { - type = emailType; - default = "hostmaster"; + negativeTTL = mkOption { + type = int; + default = 3600; + }; + }; - apply = address: - let - split = splitString "@" address; + a = rrType { + ipv4 = mkOption { + type = str; + }; - user = head split; - domain = if length split == 2 then head (tail split) else removeSuffix "." config.soa.authorityZone; - in - if hasSuffix "." address - then address - else "${replaceStrings [ "." ] [ "\\." ] user}.${domain}."; - }; + ptr = mkOption { + type = nullOr str; + default = config.defaultPtr.v4; + }; + }; - serial = mkOption { - type = nullOr int; - default = null; - }; + aaaa = rrType { + ipv6 = mkOption { + type = str; + }; - refresh = mkOption { - type = int; - default = 3 * 3600; - }; + ptr = mkOption { + type = nullOr str; + default = config.defaultPtr.v6; + }; + }; - retry = mkOption { - type = int; - default = 3600; - }; + cname = rrType { + target = nameOption {}; + }; - expire = mkOption { - type = int; - default = 7 * 24 * 3600; - }; + mx = rrType { + host = nameOption {}; - negativeTTL = mkOption { - type = int; - default = 3600; - }; + priority = mkOption { + type = int; }; + }; - a = rrType { - ipv4 = mkOption { - type = str; - }; + ns = rrType { + host = nameOption {}; + }; - ptr = mkOption { - type = nullOr str; - default = config.defaultPtr.v4; - }; - }; + ptr = rrType { + target = nameOption {}; + }; - aaaa = rrType { - ipv6 = mkOption { - type = str; - }; + srv = rrType { + host = nameOption {}; - ptr = mkOption { - type = nullOr str; - default = config.defaultPtr.v6; - }; + port = mkOption { + type = port; }; - cname = rrType { - target = nameOption { }; + priority = mkOption { + type = int; }; - mx = rrType { - host = nameOption { }; + proto = mkOption { + type = enum ["tcp" "udp"]; + }; - priority = mkOption { - type = int; - }; + service = mkOption { + type = str; }; - ns = rrType { - host = nameOption { }; + weight = mkOption { + type = int; }; + }; - ptr = rrType { - target = nameOption { }; + txt = rrType { + text = mkOption { + type = strMatching "[^\"\n\\]*\n?"; + apply = removeSuffix "\n"; }; + }; + }; - srv = rrType { - host = nameOption { }; + config = { + nsdConfig.data = config.content; + + content = let + rrLine = rr: concatMapStringsSep " " toString ([rr.name rr.ttl rr.class rr.type] ++ rr.data); + in + '' + $ORIGIN ${name}. + $TTL ${toString config.defaultTTL} + '' + + concatLines (map rrLine config.rr); + + rr = mkMerge [ + (mkOrder 0 (singleton { + inherit (config.soa) ttl; + + name = "${name}."; + type = "SOA"; + + data = with config.soa; [ + primary + hostmaster + (throwIf (serial == null) "No serial defined for zone ${name}" serial) + refresh + retry + expire + negativeTTL + ]; + })) + + (mkOrder 1 (rrConfig { + rrs = config.ns; + type = "NS"; + format = rr: [rr.host]; + })) + + (rrConfig { + rrs = config.a; + type = "A"; + format = rr: [rr.ipv4]; + }) - port = mkOption { - type = port; - }; + (rrConfig { + rrs = config.aaaa; + type = "AAAA"; + format = rr: [rr.ipv6]; + }) - priority = mkOption { - type = int; - }; + (rrConfig { + rrs = config.cname; + type = "CNAME"; + format = rr: [rr.target]; + }) - proto = mkOption { - type = enum [ "tcp" "udp" ]; - }; + (rrConfig { + rrs = config.mx; + type = "MX"; + format = rr: [rr.priority rr.host]; + }) - service = mkOption { - type = str; - }; + (rrConfig { + rrs = config.ptr; + type = "PTR"; + format = rr: [rr.target]; + }) - weight = mkOption { - type = int; - }; - }; + (rrConfig { + rrs = config.srv; + type = "SRV"; - txt = rrType { - text = mkOption { - type = strMatching "[^\"\n\\]*\n?"; - apply = removeSuffix "\n"; - }; - }; - }; + format = rr: [rr.priority rr.weight rr.port rr.host]; + applyName = rr: "_${rr.service}._${rr.proto}.${rr.name}"; + }) - config = { - nsdConfig.data = config.content; + (rrConfig { + rrs = config.txt; + type = "TXT"; - content = - let - rrLine = rr: concatMapStringsSep " " toString ([ rr.name rr.ttl rr.class rr.type ] ++ rr.data); + format = rr: let + # nsd-zonecheck: text string is longer than 255 characters, try splitting it into multiple parts + txtFragments = text: let + max = 255; + length = stringLength text; + in + singleton (substring 0 max text) ++ optionals (length > max) (txtFragments (substring max length text)); in - '' - $ORIGIN ${name}. - $TTL ${toString config.defaultTTL} - '' + concatLines (map rrLine config.rr); - - rr = mkMerge [ - (mkOrder 0 (singleton { - inherit (config.soa) ttl; - - name = "${name}."; - type = "SOA"; - - data = with config.soa; [ - primary - hostmaster - (throwIf (serial == null) "No serial defined for zone ${name}" serial) - refresh - retry - expire - negativeTTL - ]; - })) - - (mkOrder 1 (rrConfig { - rrs = config.ns; - type = "NS"; - format = rr: [ rr.host ]; - })) - - (rrConfig { - rrs = config.a; - type = "A"; - format = rr: [ rr.ipv4 ]; - }) - - (rrConfig { - rrs = config.aaaa; - type = "AAAA"; - format = rr: [ rr.ipv6 ]; - }) - - (rrConfig { - rrs = config.cname; - type = "CNAME"; - format = rr: [ rr.target ]; - }) - - (rrConfig { - rrs = config.mx; - type = "MX"; - format = rr: [ rr.priority rr.host ]; - }) - - (rrConfig { - rrs = config.ptr; - type = "PTR"; - format = rr: [ rr.target ]; - }) - - (rrConfig { - rrs = config.srv; - type = "SRV"; - - format = rr: [ rr.priority rr.weight rr.port rr.host ]; - applyName = rr: "_${rr.service}._${rr.proto}.${rr.name}"; - }) - - (rrConfig { - rrs = config.txt; - type = "TXT"; - - format = rr: - let - # nsd-zonecheck: text string is longer than 255 characters, try splitting it into multiple parts - txtFragments = text: - let - max = 255; - length = stringLength text; - in - singleton (substring 0 max text) ++ optionals (length > max) (txtFragments (substring max length text)); - in - map (fragment: "\"${fragment}\"") (txtFragments rr.text); - }) - ]; - }; - })); + map (fragment: "\"${fragment}\"") (txtFragments rr.text); + }) + ]; + }; + })); }; }; config = { - assertions = [ - ( - let - badZones = attrNames (filterAttrs (name: zone: (zoneHashCheck name zone).needsUpdate) cfg.zones); - in - { - assertion = badZones == [ ]; - message = "Update serials for these zones (null-serial hash mismatch): ${concatStringsSep ", " badZones}"; - } - ) - ] ++ flatten (mapAttrsToList - (name: ptr: [ - { - assertion = ptr.v4.targets != { } -> ptr.v4.zone != null; - message = "undefined v4 PTR net '${name}': ${concatStringsSep ", " (attrValues ptr.v4.targets)}"; - } - { - assertion = ptr.v6.targets != { } -> ptr.v6.zone != null; - message = "undefined v6 PTR net '${name}': ${concatStringsSep ", " (attrValues ptr.v6.targets)}"; - } - ]) - cfg.ptr); - - lib.local.zoneSerialUpdates = - let - ptrChecks = filterAttrs (_: check: check.zone.ptrName != null) allZoneChecks; - zoneChecks = filterAttrs (_: check: check.zone.ptrName == null) allZoneChecks; - allZoneChecks = filterAttrs (_: check: check.needsUpdate) (mapAttrs zoneHashCheck cfg.zones); - - updateInfo = name: check: { - inherit name; - inherit (check) expected; - inherit (check.zone.soa) serial; - }; - in - { - ptr = mapAttrs (_: check: updateInfo check.zone.ptrName check) ptrChecks; - zones = mapAttrs updateInfo zoneChecks; + assertions = + [ + ( + let + badZones = attrNames (filterAttrs (name: zone: (zoneHashCheck name zone).needsUpdate) cfg.zones); + in { + assertion = badZones == []; + message = "Update serials for these zones (null-serial hash mismatch): ${concatStringsSep ", " badZones}"; + } + ) + ] + ++ flatten (mapAttrsToList + (name: ptr: [ + { + assertion = ptr.v4.targets != {} -> ptr.v4.zone != null; + message = "undefined v4 PTR net '${name}': ${concatStringsSep ", " (attrValues ptr.v4.targets)}"; + } + { + assertion = ptr.v6.targets != {} -> ptr.v6.zone != null; + message = "undefined v6 PTR net '${name}': ${concatStringsSep ", " (attrValues ptr.v6.targets)}"; + } + ]) + cfg.ptr); + + lib.local.zoneSerialUpdates = let + ptrChecks = filterAttrs (_: check: check.zone.ptrName != null) allZoneChecks; + zoneChecks = filterAttrs (_: check: check.zone.ptrName == null) allZoneChecks; + allZoneChecks = filterAttrs (_: check: check.needsUpdate) (mapAttrs zoneHashCheck cfg.zones); + + updateInfo = name: check: { + inherit name; + inherit (check) expected; + inherit (check.zone.soa) serial; }; + in { + ptr = mapAttrs (_: check: updateInfo check.zone.ptrName check) ptrChecks; + zones = mapAttrs updateInfo zoneChecks; + }; local.ns = { - nullSerialZones = - let - defaultAttrs = [ "defaultTTL" "defaultPtr" "ptrName" ]; - filteredAttrs = defaultAttrs ++ map toLower rrTypes; - in + nullSerialZones = let + defaultAttrs = ["defaultTTL" "defaultPtr" "ptrName"]; + filteredAttrs = defaultAttrs ++ map toLower rrTypes; + in mapAttrs - (_: zone: mkMerge [ + (_: zone: + mkMerge [ (filterAttrs (name: _: elem name filteredAttrs) zone) - { soa.serial = mkOverride 0 0; } + {soa.serial = mkOverride 0 0;} ]) - cfg.zones; - - ptr = - let - zonePtrs = zone: - let - v4Ptrs = map - (a: { - ${a.ptr}.v4.targets.${nets.${a.ptr}.v4.ptrRecordName a.ipv4 32} = a.name; - }) - (filter (a: a.ptr != null) zone.a); - - v6Ptrs = map - (aaaa: { - ${aaaa.ptr}.v6.targets.${nets.${aaaa.ptr}.v6.ptrRecordName aaaa.ipv6 128} = aaaa.name; - }) - (filter (aaaa: aaaa.ptr != null) zone.aaaa); - in - v4Ptrs ++ v6Ptrs; + cfg.zones; + + ptr = let + zonePtrs = zone: let + v4Ptrs = + map + (a: { + ${a.ptr}.v4.targets.${nets.${a.ptr}.v4.ptrRecordName a.ipv4 32} = a.name; + }) + (filter (a: a.ptr != null) zone.a); + v6Ptrs = + map + (aaaa: { + ${aaaa.ptr}.v6.targets.${nets.${aaaa.ptr}.v6.ptrRecordName aaaa.ipv6 128} = aaaa.name; + }) + (filter (aaaa: aaaa.ptr != null) zone.aaaa); in + v4Ptrs ++ v6Ptrs; + in mkMerge (flatten (mapAttrsToList (_: zonePtrs) cfg.zones)); }; }; diff --git a/sys/nspawn/dmz.nix b/sys/nspawn/dmz.nix index fb3acea..805ca72 100644 --- a/sys/nspawn/dmz.nix +++ b/sys/nspawn/dmz.nix @@ -1,4 +1,10 @@ -{ lib, config, flakes, pkgs, ... }: +{ + lib, + config, + flakes, + pkgs, + ... +}: with lib; let cfg = config.local.nspawn.dmz; inherit (config.local) mailHost; @@ -7,8 +13,7 @@ with lib; let hassPort = config.services.home-assistant.config.http.server_port; hassEnable = config.local.home-assistant.enable; -in -{ +in { options.local.nspawn.dmz = { enable = mkEnableOption "DMZ services in a container"; @@ -60,71 +65,70 @@ in nspawn.dmz = { hostAddr6 = dmzNet.hosts.gateway.v6.address; - system = - let - containerModule = { ... }: { - #TODO: urgente: bloquear puertos de dovecot a non-postfix con iptables - config = { - local = { - preset.dmz = { - enable = true; - container = true; - }; + system = let + containerModule = {...}: { + #TODO: urgente: bloquear puertos de dovecot a non-postfix con iptables + config = { + local = { + preset.dmz = { + enable = true; + container = true; + }; - mta = { - mdaAddr = "[${mailHost.mdaListen}]"; - inherit (mailHost) saslPort lmtpPort; - }; + mta = { + mdaAddr = "[${mailHost.mdaListen}]"; + inherit (mailHost) saslPort lmtpPort; + }; - web.sites = { - home = { - enable = hassEnable; - proxyUrl = "http://[${cfg.hostAddr6}]:${toString hassPort}"; - }; + web.sites = { + home = { + enable = hassEnable; + proxyUrl = "http://[${cfg.hostAddr6}]:${toString hassPort}"; }; }; + }; - nixpkgs = { - pkgs = mkDefault pkgs; - localSystem = mkDefault pkgs.stdenv.hostPlatform; - }; + nixpkgs = { + pkgs = mkDefault pkgs; + localSystem = mkDefault pkgs.stdenv.hostPlatform; + }; - services.nginx.virtualHosts = { - "${config.local.domains.imap.main}".locations."^~ /.well-known/acme-challenge/" = { - root = "/var/lib/acme/acme-challenge"; + services.nginx.virtualHosts = { + "${config.local.domains.imap.main}".locations."^~ /.well-known/acme-challenge/" = { + root = "/var/lib/acme/acme-challenge"; - extraConfig = '' - auth_basic off; - auth_request off; - ''; - }; + extraConfig = '' + auth_basic off; + auth_request off; + ''; }; + }; - systemd.network.networks."40-host0" = { - name = "host0"; + systemd.network.networks."40-host0" = { + name = "host0"; - networkConfig = { - DNS = [ cfg.dns64 ]; + networkConfig = { + DNS = [cfg.dns64]; - DHCP = "no"; - IPv6AcceptRA = "yes"; - LinkLocalAddressing = "ipv6"; - }; + DHCP = "no"; + IPv6AcceptRA = "yes"; + LinkLocalAddressing = "ipv6"; + }; - ipv6AcceptRAConfig = { - Token = [ - "static:::${dmzNet.hosts.dmz.v6.suffix}" - "eui64" - "static:::${dmzNet.hosts.mta.v6.suffix}" - "static:::${dmzNet.hosts.web.v6.suffix}" - ]; + ipv6AcceptRAConfig = { + Token = [ + "static:::${dmzNet.hosts.dmz.v6.suffix}" + "eui64" + "static:::${dmzNet.hosts.mta.v6.suffix}" + "static:::${dmzNet.hosts.web.v6.suffix}" + ]; - UseDNS = false; - }; + UseDNS = false; }; }; }; - in + }; + in # Tomado de la definición de pkgs.nixos junto con definición de nixpkgs.{pkgs,localSystem} arriba import "${flakes.nixpkgs}/nixos/lib/eval-config.nix" { modules = [ @@ -133,15 +137,15 @@ in ]; system = null; - specialArgs = { inherit flakes; }; + specialArgs = {inherit flakes;}; }; }; }; services = { home-assistant.config.http = mkIf hassEnable { - server_host = [ cfg.hostAddr6 ]; - trusted_proxies = [ dmzNet.hosts.web.v6.address ]; + server_host = [cfg.hostAddr6]; + trusted_proxies = [dmzNet.hosts.web.v6.address]; use_x_forwarded_for = true; }; }; @@ -199,26 +203,27 @@ in }; services = { - dovecot2.after = [ "systemd-nspawn@dmz.service" ]; + dovecot2.after = ["systemd-nspawn@dmz.service"]; "systemd-nspawn@dmz" = { overrideStrategy = "asDropin"; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - wantedBy = [ "machines.target" ]; + after = ["network-online.target"]; + wants = ["network-online.target"]; + wantedBy = ["machines.target"]; }; }; }; networking.firewall = { - allowedTCPPorts = [ 25 80 443 ]; + allowedTCPPorts = [25 80 443]; interfaces.ve-dmz = { - allowedTCPPorts = [ mailHost.saslPort mailHost.lmtpPort ] + allowedTCPPorts = + [mailHost.saslPort mailHost.lmtpPort] ++ optional hassEnable hassPort; - allowedUDPPorts = [ 67 ]; # DHCP + allowedUDPPorts = [67]; # DHCP }; }; }; diff --git a/sys/preset/dmz.nix b/sys/preset/dmz.nix index d740d14..5a04c1e 100644 --- a/sys/preset/dmz.nix +++ b/sys/preset/dmz.nix @@ -1,8 +1,12 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.preset.dmz; -in -{ +in { options.local.preset.dmz = { enable = mkEnableOption "dmz preset"; @@ -48,7 +52,7 @@ in services = { resolved = { llmnr = "false"; - fallbackDns = [ ]; # Disable the default systemd-resolved server list + fallbackDns = []; # Disable the default systemd-resolved server list }; }; diff --git a/sys/preset/user.nix b/sys/preset/user.nix index 886adae..fd9c5ff 100644 --- a/sys/preset/user.nix +++ b/sys/preset/user.nix @@ -1,9 +1,12 @@ -{ config, lib, pkgs, ... }: -let +{ + config, + lib, + pkgs, + ... +}: let inherit (lib) mkDefault; cfg = config.local.preset.user; -in -{ +in { options.local.preset.user = { enable = lib.mkEnableOption "user-like preset"; }; diff --git a/sys/seat/default.nix b/sys/seat/default.nix index be545e8..402047f 100644 --- a/sys/seat/default.nix +++ b/sys/seat/default.nix @@ -1,10 +1,14 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.seat; users = filterAttrs (_: user: user.install) config.local.users; -in -{ +in { options.local.seat = { enable = mkEnableOption "user seat"; @@ -23,9 +27,10 @@ in }; }; - config = mkIf cfg.enable + config = + mkIf cfg.enable (mkMerge [ - ({ + { hardware = { acpilight.enable = true; }; @@ -50,11 +55,14 @@ in }; users = { - groups = mapAttrs (_: user: { inherit (user) gid; }) users // { - adbusers.gid = 1008; - }; + groups = + mapAttrs (_: user: {inherit (user) gid;}) users + // { + adbusers.gid = 1008; + }; - users = mapAttrs + users = + mapAttrs (username: user: { isNormalUser = true; @@ -62,13 +70,16 @@ in description = user.gecos; group = username; - extraGroups = [ "users" ] ++ user.groups; + extraGroups = ["users"] ++ user.groups; - shell = if user.allowLogin then pkgs.zsh else null; + shell = + if user.allowLogin + then pkgs.zsh + else null; }) users; }; - }) + } (mkIf cfg.graphical { environment = { sessionVariables.NIXOS_OZONE_WL = "1"; @@ -87,8 +98,8 @@ in gtklock = { enable = true; - config = { }; - modules = [ ]; + config = {}; + modules = []; }; }; @@ -101,7 +112,7 @@ in xserver = mkIf (!cfg.wayland) { enable = true; - videoDrivers = cfg.videoDrivers ++ [ "modesetting" "fbdev" ]; + videoDrivers = cfg.videoDrivers ++ ["modesetting" "fbdev"]; displayManager.startx.enable = mkDefault true; }; }; @@ -109,18 +120,18 @@ in xdg.portal = { enable = true; wlr.enable = true; - extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; + extraPortals = [pkgs.xdg-desktop-portal-gtk]; xdgOpenUsePortal = true; # warning: xdg-desktop-portal 1.17 reworked how portal implementations are loaded, you # should either set `xdg.portal.config` or `xdg.portal.configPackages` # to specify which portal backend to use for the requested interface. - # + # # https://github.com/flatpak/xdg-desktop-portal/blob/1.18.1/doc/portals.conf.rst.in - # + # # If you simply want to keep the behaviour in < 1.17, which uses the first # portal implementation found in lexicographical order, use the following: - # + # # xdg.portal.config.common.default = "*"; config.common.default = "*"; }; diff --git a/sys/syncthing/default.nix b/sys/syncthing/default.nix index b87e683..951ad30 100644 --- a/sys/syncthing/default.nix +++ b/sys/syncthing/default.nix @@ -1,20 +1,23 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.syncthing; -in -{ +in { options.local.syncthing = { enable = mkEnableOption "syncthing server"; openFirewall = mkEnableOption "syncthing firewall rules"; }; config = mkMerge [ - ({ + { networking.firewall = { allowedTCPPorts = optional cfg.openFirewall 22000; allowedUDPPorts = optional cfg.openFirewall 22000; }; - }) + } (mkIf cfg.enable { local.syncthing.openFirewall = true; @@ -33,7 +36,7 @@ in relay = { enable = true; - pools = [ ]; + pools = []; providedBy = "${config.networking.hostName}.${config.networking.domain}"; }; }; diff --git a/sys/virt/libvirt.nix b/sys/virt/libvirt.nix index 1cc42a9..ebbfbcd 100644 --- a/sys/virt/libvirt.nix +++ b/sys/virt/libvirt.nix @@ -1,16 +1,22 @@ -{ config, flakes, lib, pkgs, ... }: +{ + config, + flakes, + lib, + pkgs, + ... +}: with lib; let cfg = config.local.virt; inherit (config.lib.local) importAll; - doms = mapAttrs (_: dom: dom { inherit config lib pkgs; }) (importAll { root = ./dom; }); -in -{ + doms = mapAttrs (_: dom: dom {inherit config lib pkgs;}) (importAll {root = ./dom;}); +in { options.local.virt = { enable = mkEnableOption "hypervisor support"; - dom = mapAttrs + dom = + mapAttrs (name: _: { enable = mkEnableOption "domain ${name}"; }) @@ -19,21 +25,25 @@ in config = mkIf cfg.enable { local.boot.impermanence.directories = [ - { directory = "/var/dom"; user = "root"; group = "qemu-libvirtd"; mode = "u=rwx,g=rx,o="; } + { + directory = "/var/dom"; + user = "root"; + group = "qemu-libvirtd"; + mode = "u=rwx,g=rx,o="; + } ]; virtualisation = { libvirt = { enable = any (dom: dom.enable) (attrValues cfg.dom); - connections."qemu:///system".domains = - let - makeDomain = def: { - active = true; - restart = false; - definition = flakes.nixvirt.lib.domain.writeXML def; - }; - in + connections."qemu:///system".domains = let + makeDomain = def: { + active = true; + restart = false; + definition = flakes.nixvirt.lib.domain.writeXML def; + }; + in map makeDomain (attrValues (filterAttrs (name: _: cfg.dom.${name}.enable) doms)); swtpm.enable = true; diff --git a/sys/web/nginx.nix b/sys/web/nginx.nix index b6e7414..a054289 100644 --- a/sys/web/nginx.nix +++ b/sys/web/nginx.nix @@ -1,9 +1,12 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.web; inherit (config.local) domains; -in -{ +in { options.local.web = { enable = mkEnableOption "web server"; @@ -13,7 +16,7 @@ in ownedCerts = mkOption { type = with lib.types; listOf str; - default = [ ]; + default = []; }; }; @@ -72,7 +75,7 @@ in }) cfg.ownedCerts); - networking.firewall.allowedTCPPorts = [ 80 443 ]; + networking.firewall.allowedTCPPorts = [80 443]; security = { acme.certs = listToAttrs (map @@ -80,12 +83,12 @@ in name = domains.${name}.main; value = { group = mkDefault config.services.nginx.group; - reloadServices = [ "nginx.service" ]; + reloadServices = ["nginx.service"]; }; }) cfg.ownedCerts); - dhparams.params.nginx = { }; + dhparams.params.nginx = {}; }; }; } diff --git a/sys/web/php-fpm.nix b/sys/web/php-fpm.nix index 65276ba..33efe1a 100644 --- a/sys/web/php-fpm.nix +++ b/sys/web/php-fpm.nix @@ -2,151 +2,153 @@ # See also: # - <https://albert.cx/20181125-use-separate-systemd-units-for-php-fpm-pools> # - <https://freedesktop.org/wiki/Software/systemd/DaemonSocketActivation/> - -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.services.php-fpm-isolated; - configFile = { pool, poolOpts, runtimeDir, sockFile, pidFile }: - let - config = { - global = { - daemonize = false; - error_log = "syslog"; - pid = pidFile; - }; + configFile = { + pool, + poolOpts, + runtimeDir, + sockFile, + pidFile, + }: let + config = { + global = { + daemonize = false; + error_log = "syslog"; + pid = pidFile; + }; - "${pool}" = - let - enforced = { - inherit (poolOpts) user group; - listen = sockFile; - }; + "${pool}" = let + enforced = { + inherit (poolOpts) user group; + listen = sockFile; + }; - defaults = { - "pm" = "dynamic"; - "pm.max_children" = 16; - "pm.min_spare_servers" = 1; - "pm.max_spare_servers" = 4; - "pm.start_servers" = 1; - "catch_workers_output" = true; - "php_admin_flag[log_errors]" = true; - "env[PATH]" = makeBinPath [ pkgs.php ]; - }; + defaults = { + "pm" = "dynamic"; + "pm.max_children" = 16; + "pm.min_spare_servers" = 1; + "pm.max_spare_servers" = 4; + "pm.start_servers" = 1; + "catch_workers_output" = true; + "php_admin_flag[log_errors]" = true; + "env[PATH]" = makeBinPath [pkgs.php]; + }; - env = mapAttrs' - (name: value: { - name = "env[${name}]"; - value = "\"${escape [ "\"" ] value}\""; - }) - poolOpts.env; - in - defaults // poolOpts.config // env // enforced; - }; - in - (pkgs.formats.ini { }).generate "php-fpm-pool-${pool}.conf" config; -in -{ + env = + mapAttrs' + (name: value: { + name = "env[${name}]"; + value = "\"${escape ["\""] value}\""; + }) + poolOpts.env; + in + defaults // poolOpts.config // env // enforced; + }; + in + (pkgs.formats.ini {}).generate "php-fpm-pool-${pool}.conf" config; +in { options.services.php-fpm-isolated.pools = mkOption { - default = { }; + default = {}; - type = with types; attrsOf (submodule { - options = { - enable = mkEnableOption "PHP-FPM pool"; + type = with types; + attrsOf (submodule { + options = { + enable = mkEnableOption "PHP-FPM pool"; - user = mkOption { - type = str; - }; + user = mkOption { + type = str; + }; - group = mkOption { - type = str; - }; + group = mkOption { + type = str; + }; - unveil = mkOption { - type = listOf (either package str); - }; + unveil = mkOption { + type = listOf (either package str); + }; - env = mkOption { - type = attrsOf str; - default = { }; - }; + env = mkOption { + type = attrsOf str; + default = {}; + }; - config = mkOption { - type = attrsOf (oneOf [ int str bool ]); - default = { }; + config = mkOption { + type = attrsOf (oneOf [int str bool]); + default = {}; + }; }; - }; - }); + }); }; - config.systemd = - let - php-fpm = "${pkgs.php}/bin/php-fpm"; - - unitsFor = pool: poolOpts: - let - runtimeBase = "php-fpm-isolated/${pool}"; - runtimeDir = "/run/${runtimeBase}"; - pidFile = "${runtimeDir}/${pool}.pid"; - sockFile = "${runtimeDir}/${pool}.sock"; - in - { - name = "php-fpm-pool-${pool}"; - - value.service = { - description = "PHP-FPM process manager for pool '${pool}'"; - after = [ "network.target" ]; - - confinement.enable = true; - - serviceConfig = { - Type = "notify"; - ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"; - PIDFile = pidFile; - - Environment = "FPM_SOCKETS=${sockFile}=3"; - - ExecStart = - let - fpmConfig = configFile { - inherit pool poolOpts runtimeDir sockFile pidFile; - }; - in - "${php-fpm} --nodaemonize --fpm-config ${fpmConfig} --pid ${pidFile}"; - - PrivateTmp = true; - PrivateNetwork = true; - PrivateDevices = true; - # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work - RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; - - User = poolOpts.user; - Group = poolOpts.group; - RuntimeDirectory = runtimeBase; - - BindReadOnlyPaths = - let - unveiled = map builtins.toString poolOpts.unveil; - in - [ "/run/systemd/journal/socket" ] ++ unveiled; - }; - }; + config.systemd = let + php-fpm = "${pkgs.php}/bin/php-fpm"; + + unitsFor = pool: poolOpts: let + runtimeBase = "php-fpm-isolated/${pool}"; + runtimeDir = "/run/${runtimeBase}"; + pidFile = "${runtimeDir}/${pool}.pid"; + sockFile = "${runtimeDir}/${pool}.sock"; + in { + name = "php-fpm-pool-${pool}"; - value.socket = { - description = "PHP-FPM socket for pool '${pool}'"; - listenStreams = [ sockFile ]; + value.service = { + description = "PHP-FPM process manager for pool '${pool}'"; + after = ["network.target"]; - socketConfig = { - User = poolOpts.user; - Group = poolOpts.group; + confinement.enable = true; + + serviceConfig = { + Type = "notify"; + ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"; + PIDFile = pidFile; + + Environment = "FPM_SOCKETS=${sockFile}=3"; + + ExecStart = let + fpmConfig = configFile { + inherit pool poolOpts runtimeDir sockFile pidFile; }; - }; + in "${php-fpm} --nodaemonize --fpm-config ${fpmConfig} --pid ${pidFile}"; + + PrivateTmp = true; + PrivateNetwork = true; + PrivateDevices = true; + # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + + User = poolOpts.user; + Group = poolOpts.group; + RuntimeDirectory = runtimeBase; + + BindReadOnlyPaths = let + unveiled = map builtins.toString poolOpts.unveil; + in + ["/run/systemd/journal/socket"] ++ unveiled; }; + }; + + value.socket = { + description = "PHP-FPM socket for pool '${pool}'"; + listenStreams = [sockFile]; - units = mapAttrs' unitsFor (filterAttrs (_: pool: pool.enable) cfg.pools); - in - { - sockets = mapAttrs (_: unit: unit.socket) units; - services = mapAttrs (_: unit: unit.service) units; + socketConfig = { + User = poolOpts.user; + Group = poolOpts.group; + }; + }; }; + + units = mapAttrs' unitsFor (filterAttrs (_: pool: pool.enable) cfg.pools); + in { + sockets = mapAttrs (_: unit: unit.socket) units; + services = mapAttrs (_: unit: unit.service) units; + }; } diff --git a/sys/web/sites/home.nix b/sys/web/sites/home.nix index 616bf94..fed9b84 100644 --- a/sys/web/sites/home.nix +++ b/sys/web/sites/home.nix @@ -1,9 +1,12 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.web.sites.home; inherit (config.local) domains; -in -{ +in { options.local.web.sites.home = { enable = mkEnableOption "home site"; @@ -15,7 +18,7 @@ in config = mkIf cfg.enable { local.web = { enable = mkDefault true; - ownedCerts = [ "home" ]; + ownedCerts = ["home"]; }; services.nginx.virtualHosts.${domains.home.main} = { diff --git a/sys/web/sites/host.nix b/sys/web/sites/host.nix index 32ef1a6..ea6cc23 100644 --- a/sys/web/sites/host.nix +++ b/sys/web/sites/host.nix @@ -1,4 +1,8 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.web.sites.host; @@ -10,15 +14,15 @@ with lib; let hostDomainName = "host-${hostname}"; userCerts = flatten (flatten (mapAttrsToList - (name: user: map + (name: user: + map (cert: { fprint = config.local.pki.byPath.${cert}.fingerprint.sha1-lower; inherit name; }) user.mail.certs) users)); -in -{ +in { options.local.web.sites.host = { enable = mkEnableOption "host site, restricted to per-user client certs"; }; @@ -26,7 +30,7 @@ in config = mkIf cfg.enable { local.web = { enable = mkDefault true; - ownedCerts = [ hostDomainName ]; + ownedCerts = [hostDomainName]; }; services = { @@ -53,31 +57,36 @@ in #} ''; - locations = { - "/".return = 403; - } // concatMapAttrs - (name: user: - let - userLocation = config: { - extraConfig = '' + locations = + { + "/".return = 403; + } + // concatMapAttrs + (name: user: let + userLocation = config: { + extraConfig = + '' if ($host_user_from_fprint != "${name}") { return 403; } - '' + config; - }; + '' + + config; + }; - userLocations = { + userLocations = + { "/${name}" = '' return 404; ''; - } // optionalAttrs user.mail.dav { + } + // optionalAttrs user.mail.dav { "/${name}/dav" = '' proxy_pass http://unix:/run/host-www/${name}/dav.sock; ''; }; - in + in mapAttrs (_: userLocation) userLocations) - (filterAttrs (_: user: user.mail.certs != [ ]) users); + (filterAttrs (_: user: user.mail.certs != []) users); }; }; }; @@ -85,13 +94,13 @@ in systemd.tmpfiles.settings."10-run-host-www" = concatMapAttrs - (name: _: { - "/run/host-www/${name}".d = { - mode = "0750"; - user = name; - group = "nginx"; - }; - }) - users; + (name: _: { + "/run/host-www/${name}".d = { + mode = "0750"; + user = name; + group = "nginx"; + }; + }) + users; }; } diff --git a/sys/web/sites/portal.nix b/sys/web/sites/portal.nix index 2365ba1..fe96cfb 100644 --- a/sys/web/sites/portal.nix +++ b/sys/web/sites/portal.nix @@ -1,9 +1,12 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: with lib; let cfg = config.local.web.sites.portal; inherit (config.local) domains; -in -{ +in { options.local.web.sites.portal = { enable = mkEnableOption "public non-fqdn portal"; }; @@ -11,7 +14,7 @@ in config = mkIf cfg.enable { local.web = { enable = mkDefault true; - ownedCerts = [ "host" "exdev" ]; + ownedCerts = ["host" "exdev"]; defaultACMEHost = domains.host.main; }; @@ -19,13 +22,13 @@ in ${domains.host.www} = { forceSSL = true; useACMEHost = domains.host.main; - serverAliases = [ domains.host.main ]; + serverAliases = [domains.host.main]; }; ${domains.exdev.main} = { forceSSL = true; useACMEHost = domains.exdev.main; - serverAliases = [ domains.exdev.www ]; + serverAliases = [domains.exdev.www]; locations."/fsociety".return = "301 https://meet.posixlycorrect.com/%C6%92%C6%A8%C5%8F%C4%8B%D3%80%C9%99%CF%AE%D0%A3"; }; |
