summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2024-07-14 17:53:13 -0600
committerAlejandro Soto <alejandro@34project.org>2024-07-15 09:34:00 -0600
commit02abf4ed0131237c25e0a10db50fa4c41a902a50 (patch)
tree20904894fc0952806e341cdaff5941e81b3ce51c
parent08e746700341dda3e3bdf704332fc3c07053d3e7 (diff)
sys: final merge of dmz, hv into sys
-rw-r--r--env/acme/default.nix48
-rw-r--r--env/acme/domains.nix12
-rw-r--r--env/default.nix8
-rw-r--r--env/dhe.nix6
-rw-r--r--env/users/default.nix143
-rw-r--r--env/users/mailbox.nix241
-rw-r--r--flake.nix17
-rw-r--r--sys/auth.nix82
-rw-r--r--sys/auth/default.nix6
-rw-r--r--sys/auth/oath.nix34
-rw-r--r--sys/auth/openssh.nix86
-rw-r--r--sys/baseline/default.nix61
-rw-r--r--sys/boot.nix116
-rw-r--r--sys/boot/chain.nix37
-rw-r--r--sys/boot/default.nix13
-rw-r--r--sys/boot/detached-luks.nix73
-rw-r--r--sys/boot/efi.nix35
-rw-r--r--sys/boot/firmware.nix29
-rw-r--r--sys/boot/fscrypt.nix23
-rw-r--r--sys/boot/impermanence.nix53
-rw-r--r--sys/boot/namespaced.nix27
-rw-r--r--sys/boot/sb.nix37
-rw-r--r--sys/boot/stack/btrfs-toplevel-multidrive.nix94
-rw-r--r--sys/boot/stack/default.nix6
-rw-r--r--sys/boot/stack/luks-ext4-fscrypt-impermanence.nix96
-rw-r--r--sys/btrfs/default.nix6
-rw-r--r--sys/btrfs/mounts.nix39
-rw-r--r--sys/btrfs/snapper.nix (renamed from sys/fs/btrfs.nix)59
-rw-r--r--sys/default.nix71
-rw-r--r--sys/env/default.nix8
-rw-r--r--sys/env/domains.nix (renamed from env/users/users.nix)0
-rw-r--r--sys/env/maps.nix (renamed from env/users/virtual.nix)0
-rw-r--r--sys/env/users.nix1
-rw-r--r--sys/env/virtual.nix1
-rw-r--r--sys/fs/default.nix35
-rw-r--r--sys/fs/layout.nix87
-rw-r--r--sys/gitea/default.nix21
-rw-r--r--sys/hardware/altera.nix22
-rw-r--r--sys/hardware/apc.nix30
-rw-r--r--sys/hardware/bluetooth.nix16
-rw-r--r--sys/hardware/default.nix12
-rw-r--r--sys/hardware/epson.nix29
-rw-r--r--sys/hardware/laptop.nix16
-rw-r--r--sys/hardware/printing.nix32
-rw-r--r--sys/hardware/thinkpad.nix38
-rw-r--r--sys/hardware/yubico.nix14
-rw-r--r--sys/kiosk/default.nix39
-rw-r--r--sys/mail/default.nix238
-rw-r--r--sys/mta/default.nix171
-rw-r--r--sys/net.nix42
-rw-r--r--sys/net/default.nix49
-rw-r--r--sys/nspawn/default.nix5
-rw-r--r--sys/nspawn/dmz.nix (renamed from sys/nspawn.nix)47
-rw-r--r--sys/options.nix56
-rw-r--r--sys/preset/default.nix6
-rw-r--r--sys/preset/dmz.nix47
-rw-r--r--sys/preset/user.nix63
-rw-r--r--sys/seat/default.nix85
-rw-r--r--sys/users.nix57
-rw-r--r--sys/virt/default.nix5
-rw-r--r--sys/virt/dom/README.md1
-rw-r--r--sys/virt/libvirt.nix53
-rw-r--r--sys/web/default.nix6
-rw-r--r--sys/web/nginx.nix45
-rw-r--r--sys/web/php-fpm.nix152
65 files changed, 2022 insertions, 1065 deletions
diff --git a/env/acme/default.nix b/env/acme/default.nix
deleted file mode 100644
index 9b3db80..0000000
--- a/env/acme/default.nix
+++ /dev/null
@@ -1,48 +0,0 @@
-{ config, lib, ... }:
-with lib; let
- cfg = config.local;
-in
-{
- options.local = with types; {
- domains = mkOption {
- type = attrsOf (attrsOf str);
- };
-
- certs = mapAttrs
- (_: _: {
- enable = mkEnableOption "TLS cert for ${name}";
- })
- cfg.domains;
- };
-
- config = {
- security.acme = {
- acceptTerms = true;
-
- defaults = {
- email = "security@${config.networking.domain}";
- renewInterval = "weekly";
-
- webroot =
- if config.security.acme.defaults.dnsProvider == null
- then "/var/lib/acme/acme-challenge"
- else null;
- };
-
- certs =
- let
- domainSort = sort (a: b: splitString "." a < splitString "." b);
-
- certConfig = domains: {
- domain = domains.main;
- extraDomainNames = domainSort (attrValues (filterAttrs (k: _: k != "main") domains));
- };
- in
- mapAttrs'
- (_: value: nameValuePair value.main (certConfig value))
- (filterAttrs (name: _: cfg.certs.${name}.enable) cfg.domains);
- };
-
- local.domains = import ./domains.nix;
- };
-}
diff --git a/env/acme/domains.nix b/env/acme/domains.nix
deleted file mode 100644
index 0412391..0000000
--- a/env/acme/domains.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- host = {
- main = "34project.org";
- www = "www.34project.org";
- mail = "mail.34project.org";
- };
-
- smtp.main = "smtp.34project.org";
- imap.main = "imap.34project.org";
-
- git.main = "git.cluster451.org";
-}
diff --git a/env/default.nix b/env/default.nix
deleted file mode 100644
index d9b85ab..0000000
--- a/env/default.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{ lib, ... }:
-with lib; {
- imports = [
- ./acme
- ./dhe.nix
- ./users
- ];
-}
diff --git a/env/dhe.nix b/env/dhe.nix
deleted file mode 100644
index 7a95f2d..0000000
--- a/env/dhe.nix
+++ /dev/null
@@ -1,6 +0,0 @@
-{ ... }: {
- config.security.dhparams = {
- enable = true;
- defaultBitSize = 2048;
- };
-}
diff --git a/env/users/default.nix b/env/users/default.nix
deleted file mode 100644
index 0e77e0d..0000000
--- a/env/users/default.nix
+++ /dev/null
@@ -1,143 +0,0 @@
-{ config, lib, ... }:
-with lib; let
- cfg = config.local;
- inherit (config.networking) domain;
-in
-{
- imports = [
- ./mailbox.nix
- ];
-
- options.local = with types; let
- mailOption = mkOption {
- default = { };
-
- type = submodule {
- options.certs = mkOption {
- type = listOf str;
- default = [ ];
- };
- };
- };
- in
- {
- sysadmin = mkOption {
- type = str;
- };
-
- users = mkOption {
- default = { };
-
- type = attrsOf (submodule ({ config, ... }: {
- options = {
- uid = mkOption {
- type = int;
- };
-
- gid = mkOption {
- type = int;
- };
-
- gecos = mkOption {
- type = str;
- default = "";
- };
-
- sysadmin = mkOption {
- type = bool;
- default = false;
- };
-
- groups = mkOption {
- type = listOf str;
- default = [ ];
- };
-
- allowLogin = mkOption {
- type = bool;
- default = true;
- };
-
- hardAliases = mkOption {
- type = listOf str;
- default = [ ];
- };
-
- mail = mailOption;
- };
-
- config.groups = mkBefore (optional config.sysadmin "wheel");
- }));
- };
-
- virtual = mkOption {
- default = { };
-
- type = attrsOf (submodule ({ name, ... }: {
- options = {
- aliases = mkOption {
- type = attrsOf (listOf str);
- default = { };
- };
-
- rules = mkOption {
- default = [ ];
-
- type = listOf (submodule {
- options = {
- pattern = mkOption {
- type = str;
- };
-
- targets = mkOption {
- type = listOf str;
- };
- };
- });
- };
-
- users = mkOption {
- default = { };
-
- type = attrsOf (submodule {
- options.mail = mailOption;
- });
- };
- };
-
- config.aliases =
- let
- sysadmin = mkDefault [ "sysadmin@${name}" ];
- in
- {
- abuse = sysadmin;
- security = sysadmin;
- webmaster = sysadmin;
- hostmaster = sysadmin;
- postmaster = sysadmin;
-
- sysadmin = mkDefault [ "sysadmin@${domain}" ];
- };
- }));
- };
- };
-
- config.local = mkMerge [
- {
- users = import ./users.nix;
- virtual = import ./virtual.nix;
-
- sysadmin =
- (findSingle
- (user: user.value.sysadmin)
- (throw "no user is declared as sysadmin")
- (throw "more than one user is declared as sysadmin")
- (mapAttrsToList nameValuePair cfg.users)
- ).name;
- }
-
- {
- virtual.${domain}.aliases.sysadmin = [ cfg.sysadmin ];
- }
- ];
-}
diff --git a/env/users/mailbox.nix b/env/users/mailbox.nix
deleted file mode 100644
index eaec5fc..0000000
--- a/env/users/mailbox.nix
+++ /dev/null
@@ -1,241 +0,0 @@
-{ config, lib, pkgs, ... }:
-with lib; let
- cfg = config.local;
-in
-{
- options.local.mailHost = with types; {
- enable = mkEnableOption "mailbox host service";
-
- security.acme.defaults.dnsProvider = "gandiv5";
-
- mdaListen = mkOption {
- type = str;
- };
-
- saslPort = mkOption {
- type = port;
- };
-
- lmtpPort = mkOption {
- type = port;
- };
- };
-
- config =
- let
- imapHostname = cfg.domains.imap.main;
- in
- mkIf cfg.mailHost.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))
- cfg.users)));
-
- localCerts =
- flatten (mapAttrsToList
- (canonical: user:
- let
- certNames = {
- inherit canonical;
- logins = [ canonical ] ++ user.hardAliases;
- };
- in
- map (flip nameValuePair certNames) user.mail.certs)
- cfg.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)
- cfg.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.mailHost.saslPort}
- address = ${cfg.mailHost.mdaListen}
- }
- }
-
- service lmtp {
- inet_listener mta-lmtp {
- port = ${toString cfg.mailHost.lmtpPort}
- address = ${cfg.mailHost.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/flake.nix b/flake.nix
index 8a25fb0..2c87557 100644
--- a/flake.nix
+++ b/flake.nix
@@ -81,10 +81,7 @@
nixosConfigurations =
let
nixosSystem = { modules }: makeOverridable nixpkgs.lib.nixosSystem {
- inherit pkgs system;
- modules = modules ++ [
- nixpkgs.nixosModules.notDetected
- ];
+ inherit modules pkgs system;
specialArgs = {
inherit flakes;
@@ -95,20 +92,10 @@
modules = [
./sys
host
- nixvirt.nixosModules.default
];
};
in
- mapAttrs (_: hostConfig) (importAll { root = ./sys/platform; })
- // {
- dmz = nixosSystem {
- modules = [ ./dmz ];
- };
-
- hv = nixosSystem {
- modules = [ ./hv ];
- };
- };
+ mapAttrs (_: hostConfig) (importAll { root = ./sys/platform; });
homeConfigurations =
let
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/env/users/users.nix b/sys/env/domains.nix
index 1bb3788..1bb3788 100644
--- a/env/users/users.nix
+++ b/sys/env/domains.nix
diff --git a/env/users/virtual.nix b/sys/env/maps.nix
index 1bb3788..1bb3788 100644
--- a/env/users/virtual.nix
+++ b/sys/env/maps.nix
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;
+ };
+}