diff options
| author | Alejandro Soto <alejandro@34project.org> | 2024-07-14 17:53:13 -0600 |
|---|---|---|
| committer | Alejandro Soto <alejandro@34project.org> | 2024-07-15 09:34:00 -0600 |
| commit | 02abf4ed0131237c25e0a10db50fa4c41a902a50 (patch) | |
| tree | 20904894fc0952806e341cdaff5941e81b3ce51c /sys | |
| parent | 08e746700341dda3e3bdf704332fc3c07053d3e7 (diff) | |
sys: final merge of dmz, hv into sys
Diffstat (limited to 'sys')
58 files changed, 2022 insertions, 592 deletions
diff --git a/sys/auth.nix b/sys/auth.nix deleted file mode 100644 index 835f836..0000000 --- a/sys/auth.nix +++ /dev/null @@ -1,82 +0,0 @@ -{ config, lib, pkgs, ... }: -with lib; let - cfg = config.local; -in -{ - config = { - security.pam = { - oath = { - usersFile = "/var/trust/auth/users.oath"; - digits = 6; - window = 30; - }; - - services.sshd.oathAuth = true; - }; - - services.openssh = { - enable = true; - openFirewall = false; - ports = [ 2234 ]; - startWhenNeeded = true; - - hostKeys = [ - { - bits = 4096; - path = "/etc/ssh/ssh_host_rsa_key"; - type = "rsa"; - } - { - path = "/etc/ssh/ssh_host_ed25519_key"; - type = "ed25519"; - } - #TODO: Desfasar, inseguro - { - path = "/etc/ssh/ssh_host_ecdsa_key"; - type = "ecdsa"; - } - ]; - - settings = { - X11Forwarding = true; - PermitRootLogin = "no"; - PasswordAuthentication = true; # Necesario para oath, no reemplaza a oath - }; - - extraConfig = '' - # 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 - ''} - ''; - }; - - services.pcscd.enable = true; - services.udev.packages = [ pkgs.yubikey-personalization ]; - - networking.firewall.allowedTCPPorts = [ 2234 ]; - - users.users.tunnel = { - uid = 1100; - group = "nogroup"; - isSystemUser = true; - - # Requiere oath - password = "tunnel"; - - home = "/var/empty"; - shell = "${pkgs.coreutils}/bin/true"; - }; - }; -} diff --git a/sys/auth/default.nix b/sys/auth/default.nix new file mode 100644 index 0000000..4678da9 --- /dev/null +++ b/sys/auth/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./oath.nix + ./openssh.nix + ]; +} diff --git a/sys/auth/oath.nix b/sys/auth/oath.nix new file mode 100644 index 0000000..7030bab --- /dev/null +++ b/sys/auth/oath.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.auth.oath; +in +{ + options.local.auth.oath = { + enable = lib.mkEnableOption "pam-oath"; + }; + + config = lib.mkIf cfg.enable { + security.pam = { + oath = { + digits = 6; + window = 30; + + usersFile = "/var/trust/auth/users.oath"; + }; + + services.sshd.oathAuth = true; + }; + + users.users.tunnel = { + uid = 1100; + group = "nogroup"; + isSystemUser = true; + + # Requiere oath + password = "tunnel"; + + home = "/var/empty"; + shell = "${pkgs.coreutils}/bin/true"; + }; + }; +} diff --git a/sys/auth/openssh.nix b/sys/auth/openssh.nix new file mode 100644 index 0000000..2030682 --- /dev/null +++ b/sys/auth/openssh.nix @@ -0,0 +1,86 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.auth.openssh; + withOath = config.local.auth.oath.enable; +in +{ + options.local.auth.openssh = { + enable = mkEnableOption "openssh"; + tunnel.enable = mkEnableOption "ssh tunnel user"; + + #TODO: Desfasar ecdsa, inseguro + hostKeys = listToAttrs (map + (name: { + inherit name; + + value = mkOption { + type = types.bool; + default = false; + }; + }) [ "ecdsa" "ed25519" "rsa" ]); + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.tunnel.enable -> withOath; + message = "SSH tunnel requires oath"; + } + ]; + + local.boot.impermanence.files = + flatten (map (key: [ key.path "${key.path}.pub" ]) config.services.openssh.hostKeys); + + services.openssh = { + enable = true; + openFirewall = true; + ports = [ 2234 ]; + startWhenNeeded = true; + + 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 = { + X11Forwarding = true; + PermitRootLogin = "prohibit-password"; + PasswordAuthentication = withOath; # Necesario para oath, no reemplaza a oath + }; + + extraConfig = 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 + ''} + ''; + }; + + users.users.tunnel = mkIf cfg.tunnel.enable { + uid = 1100; + group = "nogroup"; + isSystemUser = true; + + # Requiere oath + password = "tunnel"; + + home = "/var/empty"; + shell = "${pkgs.coreutils}/bin/true"; + }; + }; +} diff --git a/sys/baseline/default.nix b/sys/baseline/default.nix new file mode 100644 index 0000000..49b9b43 --- /dev/null +++ b/sys/baseline/default.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: +with lib; { + config = { + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "21.11"; # Did you read the comment? + + environment = { + pathsToLink = [ "/share/zsh" ]; + + systemPackages = with pkgs; [ + git + ] ++ optionals (!config.boot.isContainer) [ + lm_sensors + pciutils + smartmontools + usbutils + ]; + }; + + local.boot.impermanence.directories = [ "/var/lib/dhparams" ]; + + nix = { + package = pkgs.nixFlakes; + + extraOptions = '' + experimental-features = nix-command flakes repl-flake + ''; + + # No me interesa el global registry + settings.flake-registry = ""; + }; + + programs = { + fuse.userAllowOther = true; + zsh.enable = true; + }; + + security.dhparams = { + enable = true; + defaultBitSize = 4096; + }; + + services.earlyoom = { + enable = mkDefault true; + enableNotifications = true; + }; + + # Coredumps son un riesgo de seguridad y puden usar mucho disco + systemd.coredump.extraConfig = '' + Storage=none + ProcessSizeMax=0 + ''; + + time.timeZone = mkDefault "America/Costa_Rica"; + }; +} diff --git a/sys/boot.nix b/sys/boot.nix deleted file mode 100644 index 1e8685a..0000000 --- a/sys/boot.nix +++ /dev/null @@ -1,116 +0,0 @@ -{ lib, config, pkgs, ... }: -with lib; let - cfg = config.local; -in -{ - options.local = with lib.types; { - loader = mkOption { - type = enum [ "grub" "systemd-boot" ]; - }; - - cpuVendor = mkOption { - type = enum [ "amd" "intel" ]; - }; - - canTouchEfiVariables = mkOption { - type = bool; - }; - - videoDrivers = mkOption { - type = listOf str; - }; - - initrdModules = mkOption { - type = listOf str; - }; - }; - - config = { - boot = { - kernelPackages = pkgs.linuxPackages_latest; - - loader = (if cfg.loader == "grub" then { - grub = { - enable = true; - device = "nodev"; - efiSupport = true; - }; - } else { - systemd-boot.enable = true; - }) // { - efi = { - inherit (cfg) canTouchEfiVariables; - }; - }; - - initrd = - let - crypt = cfg.crypt.toplevel; - headerPathEscaped = escapeShellArg "/initrd-boot/${crypt.headerFromBoot}"; - in - { - availableKernelModules = cfg.initrdModules; - supportedFilesystems = [ "vfat" ]; - - preDeviceCommands = optionalString (crypt != null) '' - mkdir -p `dirname ${headerPathEscaped}` - touch ${headerPathEscaped} - ''; - - preLVMCommands = optionalString cfg.portable '' - sleep 2 #TODO - ''; - - postMountCommands = - let - fromRoot = path: escapeShellArg "/mnt-root/${path}"; - auxOpen = aux: '' - cryptsetup -v open \ - --header ${fromRoot aux.header} \ - --key-file ${fromRoot aux.keyfile} \ - ${aux.device} ${aux.target} - ''; - in - concatStringsSep "\n" (map auxOpen cfg.crypt.aux); - - luks.devices = mkIf (crypt != null) { - "${crypt.target}" = { - inherit (crypt) device; - header = "/initrd-boot/${crypt.headerFromBoot}"; - preLVM = false; - - preOpenCommands = '' - mount -o ro -t vfat ${escapeShellArg cfg.fs.boot.device} /initrd-boot - ''; - - postOpenCommands = '' - umount /initrd-boot - ''; - }; - }; - - #network = { - # enable = true; - - # ssh = { - # enable = true; - # port = 2234; - # }; - #}; - }; - }; - - hardware = { - cpu = - let - ucode.updateMicrocode = true; - in - { - amd = mkIf (cfg.cpuVendor == "amd") ucode; - intel = mkIf (cfg.cpuVendor == "intel") ucode; - }; - - enableRedistributableFirmware = true; - }; - }; -} diff --git a/sys/boot/chain.nix b/sys/boot/chain.nix new file mode 100644 index 0000000..c726cf8 --- /dev/null +++ b/sys/boot/chain.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot; +in +{ + options.local.boot = { + enable = mkEnableOption "system boot"; + + loader = mkOption { + type = types.enum [ "grub" "systemd-boot" ]; + }; + + kernel = mkOption { + type = types.raw; + }; + }; + + config = mkIf cfg.enable { + boot = { + kernelPackages = cfg.kernel; + + loader = + if cfg.loader == "grub" then { + grub = { + enable = true; + device = "nodev"; + efiSupport = true; + }; + } else { + systemd-boot = { + enable = true; + editor = true; + }; + }; + }; + }; +} diff --git a/sys/boot/default.nix b/sys/boot/default.nix new file mode 100644 index 0000000..157ba0e --- /dev/null +++ b/sys/boot/default.nix @@ -0,0 +1,13 @@ +{ + imports = [ + ./chain.nix + ./detached-luks.nix + ./efi.nix + ./firmware.nix + ./fscrypt.nix + ./impermanence.nix + ./namespaced.nix + ./sb.nix + ./stack + ]; +} diff --git a/sys/boot/detached-luks.nix b/sys/boot/detached-luks.nix new file mode 100644 index 0000000..a7b1bc9 --- /dev/null +++ b/sys/boot/detached-luks.nix @@ -0,0 +1,73 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.detachedLuks; +in +{ + options.local.boot.detachedLuks = { + enable = mkEnableOption "detached LUKS header in initrd"; + + headerFromBoot = mkOption { + type = types.str; + }; + + crypt = mkOption { + type = types.str; + }; + + target = mkOption { + type = types.str; + }; + }; + + config = mkIf cfg.enable { + boot.initrd = + let + headerPath = "/initrd-boot/${cfg.headerFromBoot}"; + in + { + preDeviceCommands = + let + headerPathEscaped = escapeShellArg headerPath; + in + '' + 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; + + preOpenCommands = + let + boot = config.fileSystems."/boot"; + in + '' + mount -o ro -t ${boot.fsType} ${boot.device} /initrd-boot + ''; + + postOpenCommands = mkBefore '' + umount /initrd-boot + ''; + }; + }; + + local.boot.stack = { + btrfsToplevelMultidrive.toplevel.device = "/dev/mapper/${cfg.target}"; + luksExt4FscryptImpermanence = { inherit (cfg) target; }; + }; + }; +} diff --git a/sys/boot/efi.nix b/sys/boot/efi.nix new file mode 100644 index 0000000..35cf687 --- /dev/null +++ b/sys/boot/efi.nix @@ -0,0 +1,35 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.boot.efi; +in +{ + options.local.boot.efi = { + enable = mkEnableOption "EFI with FAT32 system partition"; + + esp.uuid = mkOption { + type = types.strMatching "[0-9A-F]{4}-[0-9A-F]{4}"; + }; + + removable = mkOption { + type = types.bool; + }; + }; + + config = mkIf cfg.enable { + boot = { + initrd.supportedFilesystems = [ "vfat" ]; + + loader = { + efi.canTouchEfiVariables = !cfg.removable; + grub.efiInstallAsRemovable = cfg.removable; + }; + }; + + fileSystems."/boot" = { + device = "/dev/disk/by-uuid/${cfg.esp.uuid}"; + fsType = "vfat"; + options = [ "noatime" "umask=027" "sync" ]; + neededForBoot = true; + }; + }; +} diff --git a/sys/boot/firmware.nix b/sys/boot/firmware.nix new file mode 100644 index 0000000..70a3c4b --- /dev/null +++ b/sys/boot/firmware.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.firmware; +in +{ + options.local.boot.firmware = { + mode = mkOption { + type = types.enum [ "none" "redistributable" "all" ]; + }; + + cpuVendor = mkOption { + type = types.enum [ "amd" "intel" ]; + }; + }; + + config = mkIf (cfg.mode != "none") { + hardware = { + cpu = { + amd.updateMicrocode = cfg.cpuVendor == "amd"; + intel.updateMicrocode = cfg.cpuVendor == "intel"; + }; + + enableAllFirmware = cfg.mode == "all"; + enableRedistributableFirmware = true; + }; + + services.fwupd.enable = true; + }; +} diff --git a/sys/boot/fscrypt.nix b/sys/boot/fscrypt.nix new file mode 100644 index 0000000..e6a745c --- /dev/null +++ b/sys/boot/fscrypt.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.fscrypt; +in +{ + options.local.boot.fscrypt = { + enable = mkEnableOption "fscrypt support"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.fscrypt-experimental ]; + + local.boot.impermanence = { + directories = [ + { directory = "/.fscrypt"; mode = "u=rwx,g=rx,o=rx"; } + ]; + + files = [ + "/etc/fscrypt.conf" + ]; + }; + }; +} diff --git a/sys/boot/impermanence.nix b/sys/boot/impermanence.nix new file mode 100644 index 0000000..4902239 --- /dev/null +++ b/sys/boot/impermanence.nix @@ -0,0 +1,53 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.boot.impermanence; +in +{ + options.local.boot.impermanence = { + enable = mkEnableOption "root fs impermanence"; + + #TODO: type correcto de files, directories? + + directories = mkOption { + type = with lib.types; listOf (either str attrs); + default = [ ]; + }; + + files = mkOption { + type = with lib.types; listOf (either str attrs); + default = [ ]; + }; + }; + + config = mkMerge [ + { + local.boot.impermanence = { + directories = [ + "/etc/lvm" + "/var/lib/nixos" + "/var/log" + ]; + + files = [ + "/etc/machine-id" + "/var/lib/logrotate.status" + ]; + }; + } + (mkIf cfg.enable { + assertions = [ + { + assertion = (config.fileSystems ? "/persist") && config.fileSystems."/persist".neededForBoot; + message = "Impermanence requires /persist to be a neededForBoot mountpoint"; + } + ]; + + environment.persistence."/persist" = { + hideMounts = true; + + files = cfg.files; + directories = cfg.directories; + }; + }) + ]; +} diff --git a/sys/boot/namespaced.nix b/sys/boot/namespaced.nix new file mode 100644 index 0000000..9927ae2 --- /dev/null +++ b/sys/boot/namespaced.nix @@ -0,0 +1,27 @@ +{ config, lib, options, ... }: +with lib; let + cfg = config.local.boot.namespaced; +in +{ + options.local.boot.namespaced = { + enable = mkEnableOption "system containerization"; + }; + + config = mkIf cfg.enable { + boot.isContainer = true; + + local.boot = mkMerge ([ + { + enable = mkForce false; + + efi.enable = mkForce false; + secureBoot.enable = mkForce false; + impermanence.enable = mkForce false; + } + ] ++ map + (name: { + stack.${name}.enable = mkForce false; + }) + (attrNames options.local.boot.stack)); + }; +} diff --git a/sys/boot/sb.nix b/sys/boot/sb.nix new file mode 100644 index 0000000..bdf7f0f --- /dev/null +++ b/sys/boot/sb.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.secureBoot; +in +{ + options.local.boot.secureBoot = { + enable = mkEnableOption "secure boot"; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = config.local.boot.efi.enable; + message = "secure boot requires EFI"; + } + { + assertion = config.local.boot.loader == "systemd-boot"; + message = "lanzaboote requires systemd-boot"; + } + ]; + + boot = { + loader.systemd-boot.enable = mkForce false; + + lanzaboote = { + enable = true; + pkiBundle = "/etc/secureboot"; + }; + }; + + environment.systemPackages = [ + pkgs.sbctl + ]; + + local.boot.impermanence.directories = [ "/etc/secureboot" ]; + }; +} diff --git a/sys/boot/stack/btrfs-toplevel-multidrive.nix b/sys/boot/stack/btrfs-toplevel-multidrive.nix new file mode 100644 index 0000000..1dbfa14 --- /dev/null +++ b/sys/boot/stack/btrfs-toplevel-multidrive.nix @@ -0,0 +1,94 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.boot.stack.btrfsToplevelMultidrive; +in +{ + options.local.boot.stack.btrfsToplevelMultidrive = { + enable = mkEnableOption "filesystem stack: persistent btrfs toplevel with optional hdd drive"; + + toplevel = { + device = mkOption { + type = types.str; + }; + + ssd = mkOption { + type = types.bool; + }; + + snapshot = mkOption { + type = types.bool; + default = false; + }; + + root = mkOption { + type = types.str; + }; + + pivot = mkOption { + type = types.str; + default = "/"; + }; + }; + + secondary = { + device = mkOption { + type = types.str; + }; + + ssd = mkOption { + type = types.bool; + }; + + snapshot = mkOption { + type = types.bool; + default = false; + }; + + home = mkOption { + type = types.str; + }; + + pivot = mkOption { + type = types.str; + default = "/"; + }; + }; + }; + + config = mkIf cfg.enable { + local.btrfs = { + mounts = { + "/" = { + inherit (cfg.toplevel) device ssd; + subvol = cfg.toplevel.root; + }; + + "/toplevel" = { + inherit (cfg.toplevel) device ssd; + subvol = cfg.toplevel.pivot; + }; + + #FIXME: Este nombre es legacy + "/hdd" = { + inherit (cfg.secondary) device ssd; + subvol = cfg.secondary.pivot; + }; + + "/home" = { + inherit (cfg.secondary) device ssd; + subvol = cfg.secondary.home; + }; + }; + + snapper = optionalAttrs cfg.toplevel.snapshot + { + root = "/"; + } // optionalAttrs cfg.secondary.snapshot { + home = "/home"; + }; + }; + + # Asegura que /hdd sea descifrado antes de intentar montar /home + fileSystems."/home".depends = [ "/hdd" ]; + }; +} diff --git a/sys/boot/stack/default.nix b/sys/boot/stack/default.nix new file mode 100644 index 0000000..ff211e6 --- /dev/null +++ b/sys/boot/stack/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./btrfs-toplevel-multidrive.nix + ./luks-ext4-fscrypt-impermanence.nix + ]; +} diff --git a/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix new file mode 100644 index 0000000..72336d6 --- /dev/null +++ b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix @@ -0,0 +1,96 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.boot.stack.luksExt4FscryptImpermanence; +in +{ + options.local.boot.stack.luksExt4FscryptImpermanence = { + enable = mkEnableOption "filesystem stack: whatever LUKS approach+ext4+impermanence with per-boot keys"; + + target = mkOption { + type = types.str; + }; + }; + + # - boot device + # - some unknown fs, probably vfat + # - detached luks header file + # + # - toplevel device + # - headerless luks + # - /toplevel (ext4) + # - /toplevel/nix + # - /toplevel/persist + # - /toplevel/boot-archive.pub + # - /toplevel/boot-keys + # - /toplevel/boot-keys/2000-01-01T00:00:00-06:00.key.crypt (encrypted for /toplevel/boot-archive.pub) + # - /toplevel/boot-keys/... + # - /toplevel/boot-keys/last.key.crypt -> 2000-01-01T00:00:00-06:00.key.crypt + # - /toplevel/boots + # - /toplevel/boots/2000-01-01T00:00:00-06:00 (raw protector in last.key.crypt) + # - /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_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 + + 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 + + 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 + ''; + + fileSystems = { + "/" = { + device = "none"; + fsType = "ext4"; + options = [ "remount" ]; + }; + + "/nix" = { + device = "/persist/nix"; + options = [ "bind" ]; + }; + + "/persist" = { + device = "/toplevel/persist"; + options = [ "bind" ]; + neededForBoot = true; + }; + }; + + local.boot = { + fscrypt.enable = true; + impermanence.enable = true; + }; + }; +} diff --git a/sys/btrfs/default.nix b/sys/btrfs/default.nix new file mode 100644 index 0000000..ee76537 --- /dev/null +++ b/sys/btrfs/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./mounts.nix + ./snapper.nix + ]; +} diff --git a/sys/btrfs/mounts.nix b/sys/btrfs/mounts.nix new file mode 100644 index 0000000..133f08f --- /dev/null +++ b/sys/btrfs/mounts.nix @@ -0,0 +1,39 @@ +{ lib, config, pkgs, ... }: +with lib; let + cfg = config.local.btrfs; +in +{ + options.local.btrfs = { + mounts = mkOption { + default = { }; + + type = with lib.types; attrsOf (submodule { + options = { + ssd = mkOption { + type = bool; + }; + + device = 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 + mapAttrs (_: btrfsMount) cfg.mounts; + }; +} diff --git a/sys/fs/btrfs.nix b/sys/btrfs/snapper.nix index f240b0d..27d2779 100644 --- a/sys/fs/btrfs.nix +++ b/sys/btrfs/snapper.nix @@ -1,60 +1,17 @@ -{ lib, config, pkgs, ... }: +{ config, lib, pkgs, ... }: with lib; let - cfg = config.local; + cfg = config.local.btrfs; in { - options.local = with lib.types; { - snapperSubvols = mkOption { - type = attrsOf str; + options.local.btrfs = { + snapper = mkOption { + type = with lib.types; attrsOf str; default = { }; }; - - fs.btrfs = mkOption { - default = [ ]; - - type = attrsOf (submodule { - options = { - device = mkOption { - type = str; - }; - - subvol = mkOption { - type = str; - }; - - ssd = mkOption { - type = bool; - }; - - snapper = mkOption { - type = nullOr str; - default = null; - }; - }; - }); - }; }; - config = { - environment.systemPackages = optional (cfg.snapperSubvols != { }) pkgs.local.btclone; - - fileSystems = - let - inherit (cfg) fs; - btrfs = { device, subvol, ssd, ... }: { - inherit device; - fsType = "btrfs"; - options = [ "noatime" "compress=zstd" "subvol=${subvol}" ] ++ optional ssd "ssd"; - }; - in - mapAttrs (_: btrfs) cfg.fs.btrfs; - - local.snapperSubvols = - let - snapperEntry = path: opts: { name = opts.snapper; value = path; }; - validEntry = _: opts: opts.snapper != null; - in - mapAttrs' snapperEntry (filterAttrs validEntry cfg.fs.btrfs); + config = mkIf (cfg.snapper != { }) { + environment.systemPackages = [ pkgs.local.btclone ]; services.snapper.configs = let @@ -111,6 +68,6 @@ in EMPTY_PRE_POST_MIN_AGE = "1800"; }; in - mapAttrs snapperConfig cfg.snapperSubvols; + mapAttrs snapperConfig cfg.snapper; }; } diff --git a/sys/default.nix b/sys/default.nix index 565598c..969739a 100644 --- a/sys/default.nix +++ b/sys/default.nix @@ -1,55 +1,26 @@ { lib, config, flakes, pkgs, ... }: with lib; { imports = [ - (flakes.impermanence.nixosModule) - ../env - ./auth.nix - ./boot.nix - ./fs - ./net.nix - ./nspawn.nix - ./options.nix - ./users.nix + flakes.nixpkgs.nixosModules.notDetected + flakes.nixvirt.nixosModules.default + flakes.lanzaboote.nixosModules.lanzaboote + flakes.impermanence.nixosModule + flakes.home-manager.nixosModules.home-manager + ./auth + ./baseline + ./boot + ./btrfs + ./env + ./gitea + ./hardware + ./kiosk + ./mail + ./mta + ./net + ./nspawn + ./preset + ./seat + ./virt + ./web ]; - - config = { - # This value determines the NixOS release from which the default - # settings for stateful data, like file locations and database versions - # on your system were taken. It‘s perfectly fine and recommended to leave - # this value at the release version of the first install of this system. - # Before changing this value read the documentation for this option - # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). - system.stateVersion = "21.11"; # Did you read the comment? - - nix = { - package = pkgs.nixFlakes; - extraOptions = '' - experimental-features = nix-command flakes repl-flake - ''; - }; - - # Coredumps son un riesgo de seguridad y puden usar mucho disco - systemd.coredump.extraConfig = '' - Storage=none - ProcessSizeMax=0 - ''; - - time.timeZone = "America/Costa_Rica"; - - environment.systemPackages = with pkgs; [ - git - lm_sensors - pciutils - smartmontools - usbutils - ]; - - # No me interesa el global registry - nix.settings.flake-registry = ""; - - services.earlyoom = { - enable = true; - enableNotifications = true; - }; - }; } diff --git a/sys/env/default.nix b/sys/env/default.nix new file mode 100644 index 0000000..5e3cb98 --- /dev/null +++ b/sys/env/default.nix @@ -0,0 +1,8 @@ +{ + imports = [ + ./maps.nix + ./domains.nix + ./users.nix + ./virtual.nix + ]; +} diff --git a/sys/env/domains.nix b/sys/env/domains.nix new file mode 100644 index 0000000..1bb3788 --- /dev/null +++ b/sys/env/domains.nix @@ -0,0 +1 @@ +# This file has been lustrated. diff --git a/sys/env/maps.nix b/sys/env/maps.nix new file mode 100644 index 0000000..1bb3788 --- /dev/null +++ b/sys/env/maps.nix @@ -0,0 +1 @@ +# This file has been lustrated. diff --git a/sys/env/users.nix b/sys/env/users.nix new file mode 100644 index 0000000..1bb3788 --- /dev/null +++ b/sys/env/users.nix @@ -0,0 +1 @@ +# This file has been lustrated. diff --git a/sys/env/virtual.nix b/sys/env/virtual.nix new file mode 100644 index 0000000..1bb3788 --- /dev/null +++ b/sys/env/virtual.nix @@ -0,0 +1 @@ +# This file has been lustrated. diff --git a/sys/fs/default.nix b/sys/fs/default.nix deleted file mode 100644 index 41871df..0000000 --- a/sys/fs/default.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ lib, config, ... }: -with lib; let - cfg = config.local.fs; -in -{ - imports = [ ./btrfs.nix ./layout.nix ]; - - options.local.fs = with lib.types; { - impermanence = mkOption { - type = bool; - default = false; - }; - - boot.device = mkOption { - type = str; - }; - }; - - config = { - # !!! - boot.tmp.useTmpfs = true; - - fileSystems."/" = mkIf cfg.impermanence { - device = "tmpfs"; - fsType = "tmpfs"; - options = [ "size=1G" "mode=755" ]; - }; - - fileSystems."/boot" = { - inherit (cfg.boot) device; - fsType = "vfat"; - options = [ "noatime" "umask=027" ]; - }; - }; -} diff --git a/sys/fs/layout.nix b/sys/fs/layout.nix deleted file mode 100644 index 7e1ac2e..0000000 --- a/sys/fs/layout.nix +++ /dev/null @@ -1,87 +0,0 @@ -{ lib, config, ... }: -with lib; let - cfg = config.local; -in -{ - options.local.fs.layout = with lib.types; { - sysHddBtrfs = mkOption { - default = null; - - type = nullOr (submodule { - options = { - sys = { - device = mkOption { - type = str; - }; - - ssd = mkOption { - type = bool; - }; - - root = mkOption { - type = str; - }; - - toplevel = mkOption { - type = str; - }; - }; - - hdd = { - device = mkOption { - type = str; - }; - - home = mkOption { - type = str; - }; - }; - }; - }); - }; - }; - - config = { - local.fs.btrfs = - let - sysHddBtrfs = layout: { - "/" = { - inherit (layout.sys) device ssd; - subvol = layout.sys.root; - }; - - "/toplevel" = { - inherit (layout.sys) device ssd; - subvol = layout.sys.toplevel; - }; - - "/hdd" = { - inherit (layout.hdd) device; - subvol = "/"; - ssd = false; - }; - - "/home" = { - inherit (layout.hdd) device; - subvol = layout.hdd.home; - ssd = false; - snapper = "home"; - }; - }; - - inherit (cfg.fs) layout; - - layoutMaps = [ sysHddBtrfs ]; - layoutOpts = [ layout.sysHddBtrfs ]; - valid = filter ({ snd, ... }: snd != null) (zipLists layoutMaps layoutOpts); - in - optionalAttrs (valid != [ ]) ((head valid).fst (head valid).snd); - - assertions = [ - { - assertion = length (filter (layout: layout != null) (attrValues cfg.fs.layout)) <= 1; - message = "Cannot enable more than one filesystem layout"; - } - ]; - }; -} diff --git a/sys/gitea/default.nix b/sys/gitea/default.nix new file mode 100644 index 0000000..6096950 --- /dev/null +++ b/sys/gitea/default.nix @@ -0,0 +1,21 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.gitea; +in +{ + options.local.gitea = { + enable = mkEnableOption "gitea"; + }; + + config = mkIf cfg.enable { + services.gitea = { + enable = true; + useWizard = true; + }; + + users = { + users.gitea.uid = 962; + groups.gitea.gid = 962; + }; + }; +} diff --git a/sys/hardware/altera.nix b/sys/hardware/altera.nix new file mode 100644 index 0000000..2fc1bb6 --- /dev/null +++ b/sys/hardware/altera.nix @@ -0,0 +1,22 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.hardware.altera; +in +{ + options.local.hardware.altera = { + enable = mkEnableOption "Altera USB Blaster"; + }; + + config = mkIf cfg.enable { + services.udev.extraRules = '' + # USB-Blaster + ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6001", MODE="660", GROUP="users", TAG+="uaccess" + ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6002", MODE="660", GROUP="users", TAG+="uaccess" + ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6003", MODE="660", GROUP="users", TAG+="uaccess" + + # USB-Blaster II + ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6010", MODE="660", GROUP="users", TAG+="uaccess" + ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6810", MODE="660", GROUP="users", TAG+="uaccess" + ''; + }; +} diff --git a/sys/hardware/apc.nix b/sys/hardware/apc.nix new file mode 100644 index 0000000..2a7adaa --- /dev/null +++ b/sys/hardware/apc.nix @@ -0,0 +1,30 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.hardware.apc; +in +{ + options.local.hardware.apc = { + enable = mkEnableOption "APC UPS support"; + }; + + config = mkIf cfg.enable { + services.apcupsd = { + enable = true; + + configText = concatStrings (mapAttrsToList (k: v: "${k} ${v}\n") { + UPSMODE = "disable"; + UPSTYPE = "usb"; + UPSCABLE = "usb"; + UPSCLASS = "standalone"; + + NISIP = "127.0.0.1"; + NETSERVER = "on"; + + MINUTES = "10"; + BATTERYLEVEL = "15"; + + NOLOGON = "disable"; + }); + }; + }; +} diff --git a/sys/hardware/bluetooth.nix b/sys/hardware/bluetooth.nix new file mode 100644 index 0000000..0d53750 --- /dev/null +++ b/sys/hardware/bluetooth.nix @@ -0,0 +1,16 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.hardware.bluetooth; +in +{ + options.local.hardware.bluetooth = { + enable = mkEnableOption "bluetooth services"; + }; + + config = mkIf cfg.enable { + hardware.bluetooth = { + enable = true; + powerOnBoot = mkDefault false; + }; + }; +} diff --git a/sys/hardware/default.nix b/sys/hardware/default.nix new file mode 100644 index 0000000..10bdece --- /dev/null +++ b/sys/hardware/default.nix @@ -0,0 +1,12 @@ +{ + imports = [ + ./altera.nix + ./apc.nix + ./bluetooth.nix + ./epson.nix + ./laptop.nix + ./printing.nix + ./thinkpad.nix + ./yubico.nix + ]; +} diff --git a/sys/hardware/epson.nix b/sys/hardware/epson.nix new file mode 100644 index 0000000..3900a2c --- /dev/null +++ b/sys/hardware/epson.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.hardware.epson; +in +{ + options.local.hardware.epson = { + enable = mkEnableOption "Epson printers and scanners"; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = config.local.hardware.printing.enable; + message = "epson requires printing"; + } + ]; + + services.printing.enable = true; + hardware.sane.enable = true; + + hardware.sane.extraBackends = [ + pkgs.epkowa + ]; + + services.printing.drivers = [ + pkgs.epson_201207w + ]; + }; +} diff --git a/sys/hardware/laptop.nix b/sys/hardware/laptop.nix new file mode 100644 index 0000000..d9ba753 --- /dev/null +++ b/sys/hardware/laptop.nix @@ -0,0 +1,16 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.hardware.laptop; +in +{ + options.local.hardware.laptop = { + enable = mkEnableOption "laptop stuff"; + }; + + config = mkIf cfg.enable { + services = { + tlp.enable = true; + upower.enable = true; + }; + }; +} diff --git a/sys/hardware/printing.nix b/sys/hardware/printing.nix new file mode 100644 index 0000000..8280da9 --- /dev/null +++ b/sys/hardware/printing.nix @@ -0,0 +1,32 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.hardware.printing; +in +{ + options.local.hardware.printing = { + enable = mkEnableOption "print and scan services"; + + users = mkOption { + type = with types; listOf str; + default = [ ]; + }; + }; + + config = mkIf cfg.enable { + services.avahi = { + enable = true; + nssmdns4 = true; + openFirewall = true; + }; + + hardware.sane.enable = true; + services.printing.enable = true; + + users.users = listToAttrs (map + (user: { + name = user; + value.extraGroups = [ "scanner" "lp" ]; + }) + cfg.users); + }; +} diff --git a/sys/hardware/thinkpad.nix b/sys/hardware/thinkpad.nix new file mode 100644 index 0000000..a65ffb2 --- /dev/null +++ b/sys/hardware/thinkpad.nix @@ -0,0 +1,38 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.hardware.thinkpad; +in +{ + options.local.hardware.thinkpad = { + enable = mkEnableOption "Thinkpad hardware support"; + }; + + config = mkIf cfg.enable { + # For suspending to RAM to work, set Config -> Power -> Sleep State to "Linux" in EFI. + # See https://wiki.archlinux.org/index.php/Lenovo_ThinkPad_X1_Carbon_(Gen_6)#Suspend_issues + # Fingerprint sensor requires a firmware-update to work. + + boot = { + extraModulePackages = with config.boot.kernelPackages; [ acpi_call digimend ]; + extraModprobeConfig = "options iwlwifi 11n_disable=1 wd_disable=1"; + + # acpi_call makes tlp work for newer thinkpads + kernelModules = [ "acpi_call" "digimend" ]; + + # Force use of the thinkpad_acpi driver for backlight control. + # This allows the backlight save/load systemd service to work. + kernelParams = [ "acpi_backlight=native" ]; + }; + + hardware.firmware = [ pkgs.sof-firmware ]; + + local.hardware.laptop.enable = true; + + services = { + fprintd.enable = true; + thinkfan.enable = true; + tlp.enable = true; + tp-auto-kbbl.enable = true; + }; + }; +} diff --git a/sys/hardware/yubico.nix b/sys/hardware/yubico.nix new file mode 100644 index 0000000..a3440c3 --- /dev/null +++ b/sys/hardware/yubico.nix @@ -0,0 +1,14 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.hardware.yubico; +in +{ + options.local.hardware.yubico = { + enable = mkEnableOption "Yubico hardware support"; + }; + + config = mkIf cfg.enable { + services.pcscd.enable = true; + services.udev.packages = [ pkgs.yubikey-personalization ]; + }; +} diff --git a/sys/kiosk/default.nix b/sys/kiosk/default.nix new file mode 100644 index 0000000..b450733 --- /dev/null +++ b/sys/kiosk/default.nix @@ -0,0 +1,39 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.kiosk; +in +{ + options.local.kiosk = { + enable = mkEnableOption "kiosk mode"; + + program = mkOption { + type = types.str; + }; + + user = mkOption { + type = types.str; + }; + + allowVTSwitch = mkOption { + type = types.bool; + default = false; + }; + }; + + config = mkIf cfg.enable { + services = { + cage = { + enable = true; + inherit (cfg) program user; + + extraArguments = [ (if cfg.allowVTSwitch then "-sd" else "-d") ]; + }; + + physlock = { + enable = true; + disableSysRq = true; + muteKernelMessages = true; + }; + }; + }; +} diff --git a/sys/mail/default.nix b/sys/mail/default.nix new file mode 100644 index 0000000..5b7e4b5 --- /dev/null +++ b/sys/mail/default.nix @@ -0,0 +1,238 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.mailHost; + imapHostname = config.local.domains.imap.main; + + inherit (config.local) users virtual; +in +{ + options.local.mailHost = { + enable = mkEnableOption "mailbox host service"; + + mdaListen = mkOption { + type = types.str; + }; + + saslPort = mkOption { + type = types.port; + }; + + lmtpPort = mkOption { + type = types.port; + }; + }; + + config = mkIf cfg.enable { + services.dovecot2 = + let + cert = config.security.acme.certs.${imapHostname}.directory; + in + { + enable = true; + enablePAM = false; + enableLmtp = true; + + sslServerKey = "${cert}/key.pem"; + sslServerCert = "${cert}/fullchain.pem"; + + modules = [ pkgs.dovecot_pigeonhole ]; + + mailUser = "vmail"; + mailGroup = "vmail"; + mailLocation = "maildir:~/mail"; + mailPlugins.perProtocol.lmtp.enable = [ "sieve" ]; + + 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" + (concatStrings (flatten (mapAttrsToList + (uuid: names: map + (addr: '' + ${uuid}.mail-client@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 + + #TODO: automatizar implantación de archivo de CA + + # Orden de concatenación de mail-fullchain-crl.crt: + # - Issuing CA cert + # - Issuing CA CRL + # - Intermediate CA cert + # - Intermediate CA CRL + # - Root CA cert + # - Root CA CRL + ssl_ca = </var/trust/ca/mail-fullchain-crl.crt + 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 = !*@* + } + + 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 + } + ''; + }; + + security = { + # Necesario debido a 'enablePAM = false' + pam.services.dovecot2 = { }; + + acme.certs.${imapHostname} = { + inherit (config.services.dovecot2) group; + }; + }; + + users = { + users.${config.services.dovecot2.mailUser}.uid = 995; + groups.${config.services.dovecot2.mailGroup}.gid = 993; + }; + + networking.firewall.allowedTCPPorts = [ 143 993 ]; + + local.certs.imap.enable = true; + }; +} diff --git a/sys/mta/default.nix b/sys/mta/default.nix new file mode 100644 index 0000000..4d0ec91 --- /dev/null +++ b/sys/mta/default.nix @@ -0,0 +1,171 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.mta; + + inherit (config.local) domains virtual users; + inherit (config.networking) domain; +in +{ + options.local.mta = { + enable = mkEnableOption "mail transfer agent"; + + mdaAddr = mkOption { + type = types.str; + }; + + saslPort = mkOption { + type = types.port; + }; + + lmtpPort = mkOption { + type = types.port; + }; + }; + + config = mkIf cfg.enable { + services.postfix = + let + cert = config.security.acme.certs.${domains.smtp.main}.directory; + virtualDomains = filterAttrs (name: _: name != domain) virtual; + in + { + enable = true; + enableSmtp = true; + enableSubmission = true; + enableSubmissions = true; + + inherit domain; + hostname = domains.smtp.main; + #TODO: check_recipient_access para rechazar localhost desde afuera + destination = [ "localhost" "$mydomain" ]; + origin = "$mydomain"; + + networksStyle = "host"; + + sslKey = "${cert}/key.pem"; + sslCert = "${cert}/fullchain.pem"; + + # También es postmaster + rootAlias = config.local.sysadmin; + + extraAliases = concatStrings + (flatten (mapAttrsToList + (name: user: map + (alias: '' + ${alias}: ${name} + '') + user.hardAliases) + users)); + + localRecipients = map + (user: "${user}@${domain}") + (attrNames (users // virtual.${domain}.users)); + + virtual = concatStrings (flatten (mapAttrsToList + (name: virtual: mapAttrsToList + (alias: targets: '' + ${alias}@${name} ${concatStringsSep ", " targets} + '') + virtual.aliases) + virtual)); + + mapFiles = { + sender_login = + pkgs.writeText "postfix-sender_login" + (concatStrings (flatten (mapAttrsToList + (username: user: map + (alias: '' + ${alias}@${domain} ${username} + '') + ([ username ] ++ user.hardAliases)) + users))); + + virtual_recipients = + pkgs.writeText "postfix-virtual-recipients" + (concatStrings (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" + (concatStrings (flatten (mapAttrsToList + (name: virtual: map + (rule: '' + /^${rule.pattern}@${name}$/ ${concatStringsSep ", " rule.targets} + '') + virtual.rules) + virtual))); + }; + + config = + let + mdaTransport = "lmtp:inet:${cfg.mdaAddr}:${toString cfg.lmtpPort}"; + in + { + message_size_limit = toString (50 * 1048576); + + virtual_alias_maps = mkAfter [ "pcre:/etc/postfix/virtual_rules" ]; + virtual_mailbox_domains = attrNames virtualDomains; + virtual_mailbox_maps = [ "hash:/etc/postfix/virtual_recipients" ]; + + local_transport = mdaTransport; + virtual_transport = mdaTransport; + + smtpd_sasl_auth_enable = true; + smtpd_sasl_type = "dovecot"; + smtpd_sasl_path = "inet:${cfg.mdaAddr}:${toString cfg.saslPort}"; + smtpd_sasl_tls_security_options = [ "noanonymous" ]; + + smtpd_tls_auth_only = true; + smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix.path; + + smtpd_relay_restrictions = [ + "permit_mynetworks" + "permit_sasl_authenticated" + "reject_unauth_destination" + ]; + + smtpd_sender_login_maps = [ "hash:/etc/postfix/sender_login" ]; + + smtpd_sender_restrictions = [ + "permit_mynetworks" + "reject_sender_login_mismatch" + "permit_sasl_authenticated" + ]; + + smtpd_milters = "unix:/run/opendkim/opendkim.sock"; + non_smtpd_milters = "$smtpd_milters"; + milter_default_action = "accept"; + }; + }; + + services.opendkim = { + enable = true; + + group = "postfix"; + domains = "csl:${domain}"; + selector = "202402"; + + configFile = pkgs.writeText "opendkim.conf" '' + UMask 007 + ''; + }; + + security.dhparams.params.postfix = { }; + networking.firewall.allowedTCPPorts = [ 25 465 ]; + + local = { + boot.impermanence.directories = [ + { directory = "/var/lib/opendkim"; user = "opendkim"; group = "postfix"; mode = "u=rwx,g=,o="; } + { directory = "/var/lib/postfix"; user = "root"; group = "root"; mode = "u=rwx,g=rx,o=rx"; } + ]; + + certs.smtp.enable = true; + }; + }; +} diff --git a/sys/net.nix b/sys/net.nix deleted file mode 100644 index b441350..0000000 --- a/sys/net.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ lib, config, pkgs, ... }: -with lib; let - cfg = config.local; -in -{ - options.local = with lib.types; { - hostname = mkOption { - type = str; - }; - - dhcpInterface = mkOption { - type = nullOr str; - default = null; - }; - }; - - config = { - environment.systemPackages = [ pkgs.dhcpcd ]; - - networking = { - hostName = cfg.hostname; - - useDHCP = false; - useNetworkd = true; - - wireguard.enable = true; - }; - - systemd.network.networks."40-${cfg.dhcpInterface}" = mkIf (cfg.dhcpInterface != null) { - matchConfig.Name = cfg.dhcpInterface; - - networkConfig = { - DHCP = "ipv4"; - IPv6AcceptRA = true; - IPv6PrivacyExtensions = "kernel"; - }; - - # make routing on this interface a dependency for network-online.target - linkConfig.RequiredForOnline = "routable"; - }; - }; -} diff --git a/sys/net/default.nix b/sys/net/default.nix new file mode 100644 index 0000000..0341440 --- /dev/null +++ b/sys/net/default.nix @@ -0,0 +1,49 @@ +{ lib, config, pkgs, ... }: +with lib; let + cfg = config.local.net; +in +{ + options.local.net = with lib.types; { + enable = mkEnableOption "networking stack"; + + hostname = mkOption { + type = str; + }; + + dhcpInterface = mkOption { + type = nullOr str; + default = null; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.dhcpcd ]; + + networking = { + domain = mkDefault config.local.domains.host.main; + hostName = cfg.hostname; + + useDHCP = false; + enableIPv6 = true; + useNetworkd = true; + useHostResolvConf = false; + + wireguard.enable = true; + }; + + systemd.network.networks = mkIf (cfg.dhcpInterface != null) { + "40-${cfg.dhcpInterface}" = { + matchConfig.Name = cfg.dhcpInterface; + + networkConfig = { + DHCP = "ipv4"; + IPv6AcceptRA = true; + IPv6PrivacyExtensions = "kernel"; + }; + + # make routing on this interface a dependency for network-online.target + linkConfig.RequiredForOnline = "routable"; + }; + }; + }; +} diff --git a/sys/nspawn/default.nix b/sys/nspawn/default.nix new file mode 100644 index 0000000..15e60de --- /dev/null +++ b/sys/nspawn/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./dmz.nix + ]; +} diff --git a/sys/nspawn.nix b/sys/nspawn/dmz.nix index cd6dbb0..080b32d 100644 --- a/sys/nspawn.nix +++ b/sys/nspawn/dmz.nix @@ -1,6 +1,7 @@ { lib, config, flakes, pkgs, ... }: with lib; let - cfg = config.local; + cfg = config.local.nspawn.dmz; + inherit (config.local) mailHost; in { options.local.nspawn.dmz = with types; { @@ -41,10 +42,10 @@ in # NixOS evidentemente no usa la segunda ruta por ser FHS, así que la duct tape # final es 'mkdir rootfs/usr/lib && touch rootfs/usr/lib/os-release'. - config = mkIf cfg.nspawn.dmz.enable { + config = mkIf cfg.enable { local = { mailHost = { - mdaListen = cfg.nspawn.dmz.hostAddr; + mdaListen = cfg.hostAddr; saslPort = 11000; lmtpPort = 11001; }; @@ -55,21 +56,35 @@ in containerModule = { ... }: { #TODO: urgente: bloquear puertos de dovecot a non-postfix con iptables config = { - boot.isContainer = true; + local = { + preset.dmz = { + enable = true; + container = true; + }; + + mta = { + mdaAddr = mailHost.mdaListen; + inherit (mailHost) saslPort lmtpPort; + }; + }; - local.mta = { - mdaAddr = cfg.mailHost.mdaListen; - inherit (cfg.mailHost) saslPort lmtpPort; + nixpkgs = { + pkgs = mkDefault pkgs; + localSystem = mkDefault pkgs.stdenv.hostPlatform; }; }; }; in - pkgs.nixos [ - ../dmz - containerModule - flakes.nixpkgs.nixosModules.notDetected - flakes.impermanence.nixosModule - ]; + # 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 = [ + ../. + containerModule + ]; + + system = null; + specialArgs = { inherit flakes; }; + }; net = "10.34.3.0"; netBits = 28; @@ -85,7 +100,7 @@ in # idmap porque algunos hacks en nixpkgs (postfix-setup.service) # asumen que la store es de root "/nix/store:/nix/store:idmap" - "${cfg.nspawn.dmz.system.toplevel}/init:/sbin/init" + "${cfg.system.config.system.build.toplevel}/init:/sbin/init" ]; networkConfig.Port = [ @@ -104,7 +119,7 @@ in }; networkConfig = { - Address = "${cfg.nspawn.dmz.hostAddr}/${toString cfg.nspawn.dmz.netBits}"; + Address = "${cfg.hostAddr}/${toString cfg.netBits}"; LinkLocalAddressing = "yes"; DHCPServer = "yes"; IPMasquerade = "both"; @@ -137,7 +152,7 @@ in allowedTCPPorts = [ 25 80 443 ]; interfaces.ve-dmz = { - allowedTCPPorts = [ cfg.mailHost.saslPort cfg.mailHost.lmtpPort ]; + allowedTCPPorts = [ mailHost.saslPort mailHost.lmtpPort ]; allowedUDPPorts = [ 67 ]; # DHCP }; }; diff --git a/sys/options.nix b/sys/options.nix deleted file mode 100644 index cfb2827..0000000 --- a/sys/options.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ lib, ... }: -with lib.types; let - inherit (lib) mkOption; -in -{ - options.local = { - portable = mkOption { - type = bool; - }; - - crypt = { - toplevel = mkOption { - default = null; - - type = nullOr (submodule { - options = { - device = mkOption { - type = str; - }; - - target = mkOption { - type = str; - }; - - headerFromBoot = mkOption { - type = str; - }; - }; - }); - }; - - aux = mkOption { - default = [ ]; - type = listOf (submodule { - options = { - device = mkOption { - type = str; - }; - - target = mkOption { - type = str; - }; - - header = mkOption { - type = str; - }; - - keyfile = mkOption { - type = str; - }; - }; - }); - }; - }; - }; -} diff --git a/sys/preset/default.nix b/sys/preset/default.nix new file mode 100644 index 0000000..45ae529 --- /dev/null +++ b/sys/preset/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./dmz.nix + ./user.nix + ]; +} diff --git a/sys/preset/dmz.nix b/sys/preset/dmz.nix new file mode 100644 index 0000000..16b125f --- /dev/null +++ b/sys/preset/dmz.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.preset.dmz; +in +{ + options.local.preset.dmz = { + enable = mkEnableOption "dmz preset"; + + container = mkOption { + type = types.bool; + default = false; + }; + }; + + config = lib.mkIf cfg.enable { + local = { + boot = { + enable = mkDefault true; + + kernel = mkDefault pkgs.linuxPackages_hardened; + loader = mkDefault "grub"; + + efi.enable = mkDefault (!cfg.container); + firmware.mode = mkDefault "none"; + namespaced.enable = cfg.container; + + stack.luksExt4FscryptImpermanence = { + enable = mkDefault (!cfg.container); + }; + }; + + mta.enable = mkDefault true; + + net = { + enable = true; + hostname = "dmz"; + }; + + web.enable = true; + }; + + users = { + allowNoPasswordLogin = cfg.container; + mutableUsers = false; + }; + }; +} diff --git a/sys/preset/user.nix b/sys/preset/user.nix new file mode 100644 index 0000000..5f06f15 --- /dev/null +++ b/sys/preset/user.nix @@ -0,0 +1,63 @@ +{ config, lib, pkgs, ... }: +let + inherit (lib) mkDefault; + cfg = config.local.preset.user; +in +{ + options.local.preset.user = { + enable = lib.mkEnableOption "user-like preset"; + }; + + config = lib.mkIf cfg.enable { + local = { + auth = { + oath.enable = mkDefault true; + + openssh = { + enable = mkDefault true; + + hostKeys = { + rsa = mkDefault true; + ecdsa = mkDefault true; + ed25519 = mkDefault true; + }; + }; + }; + + boot = { + enable = mkDefault true; + + kernel = mkDefault pkgs.linuxPackages_latest; + loader = mkDefault "grub"; + + efi = { + enable = mkDefault true; + removable = mkDefault false; + }; + + firmware.mode = mkDefault "redistributable"; + detachedLuks.enable = mkDefault true; + + stack.btrfsToplevelMultidrive = { + enable = mkDefault true; + + toplevel.root = mkDefault "/root"; + secondary.home = mkDefault "/home"; + }; + }; + + hardware = { + yubico.enable = mkDefault true; + bluetooth.enable = mkDefault true; + }; + + net.enable = true; + + seat = { + enable = true; + graphical = mkDefault true; + installUsers = mkDefault "single"; + }; + }; + }; +} diff --git a/sys/seat/default.nix b/sys/seat/default.nix new file mode 100644 index 0000000..d5c5f3d --- /dev/null +++ b/sys/seat/default.nix @@ -0,0 +1,85 @@ +{ config, lib, pkgs, ... }: +with lib; let + cfg = config.local.seat; + + users = filterAttrs; +in +{ + options.local.seat = { + enable = mkEnableOption "user seat"; + + installUsers = mkOption { + type = types.enum [ "none" "single" "all" ]; + }; + + graphical = mkOption { + type = types.bool; + default = false; + }; + + videoDrivers = mkOption { + type = with types; listOf str; + }; + }; + + config = mkIf cfg.enable + (mkMerge [ + ( + let + users = + if cfg.installUsers == "all" + then config.local.users + else if cfg.installUsers == "single" + then filterAttrs (_: user: user.sysadmin) config.local.users + else { }; + in + { + hardware.acpilight.enable = true; + + users = { + groups = mapAttrs (_: user: { inherit (user) gid; }) users // { + adbusers.gid = 1008; + }; + + users = mapAttrs + (username: user: { + isNormalUser = true; + + inherit (user) uid; + description = user.gecos; + + group = username; + extraGroups = [ "users" ] ++ user.groups; + + shell = if user.allowLogin then pkgs.zsh else null; + }) + users; + }; + } + ) + + (mkIf cfg.graphical { + hardware.pulseaudio.enable = true; + + programs.dconf.enable = true; + + services = { + libinput.enable = true; + + udev.packages = [ + pkgs.android-udev-rules + ]; + + xserver = { + enable = true; + videoDrivers = cfg.videoDrivers ++ [ "modesetting" "fbdev" ]; + displayManager.startx.enable = true; + }; + }; + + sound.enable = true; + + users.groups.adbusers.gid = 1008; + }) + ]); +} diff --git a/sys/users.nix b/sys/users.nix deleted file mode 100644 index 1fda5fc..0000000 --- a/sys/users.nix +++ /dev/null @@ -1,57 +0,0 @@ -{ lib, config, pkgs, ... }: -with lib; let - cfg = config.local; -in -{ - config = { - sound.enable = true; - - hardware = { - bluetooth = { - enable = true; - powerOnBoot = false; - }; - - pulseaudio.enable = true; - }; - - services = { - libinput.enable = true; - - udev.packages = [ - pkgs.android-udev-rules - ]; - - xserver = { - enable = true; - videoDrivers = cfg.videoDrivers ++ [ "modesetting" "fbdev" ]; - displayManager.startx.enable = true; - }; - }; - - programs = { - dconf.enable = true; - zsh.enable = true; - }; - - environment.pathsToLink = [ "/share/zsh" ]; - - users.users = mapAttrs - (username: user: { - isNormalUser = true; - - inherit (user) uid; - description = user.gecos; - - group = username; - extraGroups = [ "users" ] ++ user.groups; - - shell = if user.allowLogin then pkgs.zsh else null; - }) - cfg.users; - - users.groups = mapAttrs (_: user: { inherit (user) gid; }) cfg.users // { - adbusers.gid = 1008; - }; - }; -} diff --git a/sys/virt/default.nix b/sys/virt/default.nix new file mode 100644 index 0000000..1434bad --- /dev/null +++ b/sys/virt/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./libvirt.nix + ]; +} diff --git a/sys/virt/dom/README.md b/sys/virt/dom/README.md new file mode 100644 index 0000000..37073ba --- /dev/null +++ b/sys/virt/dom/README.md @@ -0,0 +1 @@ +# This directory has been lustrated. diff --git a/sys/virt/libvirt.nix b/sys/virt/libvirt.nix new file mode 100644 index 0000000..29679ae --- /dev/null +++ b/sys/virt/libvirt.nix @@ -0,0 +1,53 @@ +{ config, flakes, lib, pkgs, ... }: +with lib; let + cfg = config.local.virt; + + inherit (import ../../util lib) importAll; + + doms = mapAttrs (_: dom: dom { inherit config lib pkgs; }) (importAll { root = ./dom; }); +in +{ + options.local.virt = { + enable = mkEnableOption "hypervisor support"; + + dom = mapAttrs + (name: _: { + enable = mkEnableOption "domain ${name}"; + }) + doms; + }; + + config = mkIf cfg.enable { + local.boot.impermanence.directories = [ + { directory = "/var/dom"; user = "root"; group = "qemu-libvirtd"; mode = "u=rwx,g=rx,o="; } + ]; + + virtualisation = { + libvirt = { + enable = true; + + connections."qemu:///system".domains = + let + makeDomain = def: { + active = true; + definition = flakes.nixvirt.lib.domain.writeXML def; + }; + in + map makeDomain (attrValues (filterAttrs (name: _: cfg.dom.${name}.enable) doms)); + + swtpm.enable = true; + }; + + libvirtd = { + enable = true; + + qemu = { + runAsRoot = false; + + ovmf.enable = true; + swtpm.enable = true; + }; + }; + }; + }; +} diff --git a/sys/web/default.nix b/sys/web/default.nix new file mode 100644 index 0000000..6c73506 --- /dev/null +++ b/sys/web/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./nginx.nix + ./php-fpm.nix + ]; +} diff --git a/sys/web/nginx.nix b/sys/web/nginx.nix new file mode 100644 index 0000000..db2d27d --- /dev/null +++ b/sys/web/nginx.nix @@ -0,0 +1,45 @@ +{ config, lib, ... }: +with lib; let + cfg = config.local.web; + inherit (config.local) domains; +in +{ + options.local.web = { + enable = mkEnableOption "web server"; + }; + + config = mkIf cfg.enable { + services.nginx = { + enable = true; + + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + + sslDhparam = config.security.dhparams.params.nginx.path; + + clientMaxBodySize = "42M"; + + virtualHosts = { + ${domains.host.www} = { + serverAliases = [ domains.host.main ]; + useACMEHost = domains.host.main; + forceSSL = true; + }; + }; + }; + + security = { + acme.certs.${domains.host.main} = { + inherit (config.services.nginx) group; + }; + + dhparams.params.nginx = { }; + }; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + local.certs.host.enable = true; + }; +} diff --git a/sys/web/php-fpm.nix b/sys/web/php-fpm.nix new file mode 100644 index 0000000..65276ba --- /dev/null +++ b/sys/web/php-fpm.nix @@ -0,0 +1,152 @@ +# Based on <https://gist.github.com/aanderse/3344baef2c3b86c8a1e98e63bd9256ea> +# See also: +# - <https://albert.cx/20181125-use-separate-systemd-units-for-php-fpm-pools> +# - <https://freedesktop.org/wiki/Software/systemd/DaemonSocketActivation/> + +{ 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; + }; + + "${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 ]; + }; + + 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 = { }; + + type = with types; attrsOf (submodule { + options = { + enable = mkEnableOption "PHP-FPM pool"; + + user = mkOption { + type = str; + }; + + group = mkOption { + type = str; + }; + + unveil = mkOption { + type = listOf (either package str); + }; + + env = mkOption { + type = attrsOf str; + 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; + }; + }; + + value.socket = { + description = "PHP-FPM socket for pool '${pool}'"; + listenStreams = [ sockFile ]; + + 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; + }; +} |
