diff --git a/config/support/server.nix b/config/support/server.nix new file mode 100644 index 0000000..4d3e51d --- /dev/null +++ b/config/support/server.nix @@ -0,0 +1,55 @@ +{ + environment = { + # Print the URL instead on servers + variables.BROWSER = "echo"; + # Don't install the /lib/ld-linux.so.2 and /lib64/ld-linux-x86-64.so.2 + # stubs. Server users should know what they are doing. + stub-ld.enable = false; + }; + # Given that our systems are headless, emergency mode is useless. + # We prefer the system to attempt to continue booting so + # that we can hopefully still access it remotely. + boot.initrd.systemd.suppressedUnits = [ + "emergency.service" + "emergency.target" + ]; + # Given that our systems are headless, emergency mode is useless. + # We prefer the system to attempt to continue booting so + # that we can hopefully still access it remotely. + systemd.enableEmergencyMode = false; + + documentation.nixos.enable = false; + + # No need for fonts on a server + fonts.fontconfig.enable = false; + + programs.command-not-found.enable = false; + + # freedesktop xdg files + xdg.autostart.enable = false; + xdg.icons.enable = false; + xdg.menus.enable = false; + xdg.mime.enable = false; + xdg.sounds.enable = false; + + systemd = { + + # For more detail, see: + # https://0pointer.de/blog/projects/watchdog.html + watchdog = { + # systemd will send a signal to the hardware watchdog at half + # the interval defined here, so every 7.5s. + # If the hardware watchdog does not get a signal for 15s, + # it will forcefully reboot the system. + runtimeTime = "15s"; + # Forcefully reboot if the final stage of the reboot + # hangs without progress for more than 30s. + # For more info, see: + # https://utcc.utoronto.ca/~cks/space/blog/linux/SystemdShutdownWatchdog + rebootTime = "30s"; + # Forcefully reboot when a host hangs after kexec. + # This may be the case when the firmware does not support kexec. + kexecTime = "1m"; + }; + }; +} diff --git a/flake.lock b/flake.lock index c0d0c06..f817007 100644 --- a/flake.lock +++ b/flake.lock @@ -1642,11 +1642,11 @@ "treefmt-nix": "treefmt-nix_3" }, "locked": { - "lastModified": 1733348844, - "narHash": "sha256-glufwHZDCoXjPrfvYSw8PrwQLyFVsg933gt/Gg4hlLE=", + "lastModified": 1734202825, + "narHash": "sha256-/9r2lRpVLG81uF7zxuk4LDnPZN0kk93tTclMA5KQK0E=", "ref": "refs/heads/main", - "rev": "3052ba7b255b8e3c333fcb318e79ce15c88dd2a7", - "revCount": 22, + "rev": "09fb938cb462681aaf6d7016e35a90d4995aad8c", + "revCount": 23, "type": "git", "url": "https://forge.lel.lol/patrick/nixp-meta.git" }, diff --git a/hosts/elisabeth/default.nix b/hosts/elisabeth/default.nix index 84fd7c7..457b5e6 100644 --- a/hosts/elisabeth/default.nix +++ b/hosts/elisabeth/default.nix @@ -16,6 +16,7 @@ ../../config/support/initrd-ssh.nix ../../config/support/physical.nix ../../config/support/secureboot.nix + ../../config/support/server.nix ../../config/support/zfs.nix ./net.nix @@ -28,58 +29,5 @@ }; nixpkgs.hostPlatform = "x86_64-linux"; - # Given that our systems are headless, emergency mode is useless. - # We prefer the system to attempt to continue booting so - # that we can hopefully still access it remotely. - boot.initrd.systemd.suppressedUnits = [ - "emergency.service" - "emergency.target" - ]; - environment = { - # Print the URL instead on servers - variables.BROWSER = "echo"; - # Don't install the /lib/ld-linux.so.2 and /lib64/ld-linux-x86-64.so.2 - # stubs. Server users should know what they are doing. - stub-ld.enable = false; - }; - # Given that our systems are headless, emergency mode is useless. - # We prefer the system to attempt to continue booting so - # that we can hopefully still access it remotely. - systemd.enableEmergencyMode = false; - - documentation.nixos.enable = false; - - # No need for fonts on a server - fonts.fontconfig.enable = false; - - programs.command-not-found.enable = false; - - # freedesktop xdg files - xdg.autostart.enable = false; - xdg.icons.enable = false; - xdg.menus.enable = false; - xdg.mime.enable = false; - xdg.sounds.enable = false; - - systemd = { - - # For more detail, see: - # https://0pointer.de/blog/projects/watchdog.html - watchdog = { - # systemd will send a signal to the hardware watchdog at half - # the interval defined here, so every 7.5s. - # If the hardware watchdog does not get a signal for 15s, - # it will forcefully reboot the system. - runtimeTime = "15s"; - # Forcefully reboot if the final stage of the reboot - # hangs without progress for more than 30s. - # For more info, see: - # https://utcc.utoronto.ca/~cks/space/blog/linux/SystemdShutdownWatchdog - rebootTime = "30s"; - # Forcefully reboot when a host hangs after kexec. - # This may be the case when the firmware does not support kexec. - kexecTime = "1m"; - }; - }; topology.self.interfaces.lan.network = "home"; } diff --git a/hosts/elisabeth/net.nix b/hosts/elisabeth/net.nix index 0197c49..ab03195 100644 --- a/hosts/elisabeth/net.nix +++ b/hosts/elisabeth/net.nix @@ -20,14 +20,6 @@ MulticastDNS = true; }; }; - "40-lan01" = { - dhcpV6Config.UseDNS = false; - dhcpV4Config.UseDNS = false; - ipv6AcceptRAConfig.UseDNS = false; - networkConfig = { - MulticastDNS = true; - }; - }; }; boot.initrd.systemd.network = { enable = true; diff --git a/hosts/nucnix/default.nix b/hosts/nucnix/default.nix new file mode 100644 index 0000000..c36938f --- /dev/null +++ b/hosts/nucnix/default.nix @@ -0,0 +1,31 @@ +{ + inputs, + minimal, + lib, + ... +}: +{ + imports = [ + inputs.nixos-hardware.nixosModules.common-pc + inputs.nixos-hardware.nixosModules.common-pc-ssd + inputs.nixos-hardware.nixosModules.common-cpu-intel + + ../../config/basic + + ../../config/support/initrd-ssh.nix + ../../config/support/physical.nix + ../../config/support/zfs.nix + ../../config/support/server.nix + + ./net.nix + ./fs.nix + ] ++ lib.lists.optionals (!minimal) [ ./guests.nix ]; + services.xserver = { + xkb = { + layout = "de"; + }; + }; + nixpkgs.hostPlatform = "x86_64-linux"; + + topology.self.interfaces.lan.network = "home"; +} diff --git a/hosts/nucnix/fs.nix b/hosts/nucnix/fs.nix new file mode 100644 index 0000000..670bf3a --- /dev/null +++ b/hosts/nucnix/fs.nix @@ -0,0 +1,91 @@ +{ + config, + lib, + ... +}: +{ + disko.devices = { + disk = { + ssd = rec { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko.nvme}"; + content = with lib.disko.gpt; { + type = "gpt"; + partitions = { + boot = (partEfi "1G") // { + device = "${device}-part1"; + }; + rpool = (partLuksZfs "ssd" "rpool" "100%") // { + device = "${device}-part2"; + }; + }; + }; + }; + }; + + zpool = with lib.disko.zfs; { + rpool = mkZpool { datasets = impermanenceZfsDatasets; }; + }; + }; + + boot.kernel.sysctl."fs.inotify.max_user_instances" = 1024; + + services.zrepl = { + enable = true; + settings = { + global = { + logging = [ + { + type = "syslog"; + level = "info"; + format = "human"; + } + ]; + # TODO Monitoring + }; + jobs = [ + #{ + # type = "push"; + # name = "push-to-remote"; + #} + { + type = "snap"; + name = "mach-schnipp-schusss"; + filesystems = { + "rpool/local/state<" = true; + "rpool/local/guests<" = true; + "rpool/safe<" = true; + }; + snapshotting = { + type = "periodic"; + prefix = "zrepl-"; + interval = "10m"; + timestamp_format = "iso-8601"; + }; + pruning = { + keep = [ + { + type = "regex"; + regex = "^zrepl-.*$"; + negate = true; + } + { + type = "grid"; + grid = lib.concatStringsSep " | " [ + "1x1d(keep=all)" + "142x1h(keep=2)" + "90x1d(keep=2)" + "500x7d" + ]; + regex = "^zrepl-.*$"; + } + ]; + }; + } + ]; + }; + }; + + fileSystems."/state".neededForBoot = true; + fileSystems."/persist".neededForBoot = true; +} diff --git a/hosts/nucnix/guests.nix b/hosts/nucnix/guests.nix new file mode 100644 index 0000000..51cec1f --- /dev/null +++ b/hosts/nucnix/guests.nix @@ -0,0 +1,182 @@ +{ + config, + stateVersion, + inputs, + lib, + minimal, + nodes, + ... +}: +let + domainOf = + hostName: + let + domains = + { + }; + in + "${domains.${hostName}}.${config.secrets.secrets.global.domains.web}"; + # TODO hard coded elisabeth nicht so schön + ipOf = hostName: nodes."elisabeth-${hostName}".config.wireguard.elisabeth.ipv4; +in +{ + services.nginx = + let + blockOf = + hostName: + { + virtualHostExtraConfig ? "", + maxBodySize ? "500M", + port ? 3000, + upstream ? hostName, + protocol ? "http", + }: + { + upstreams.${hostName} = { + servers."${ipOf upstream}:${toString port}" = { }; + extraConfig = '' + zone ${hostName} 64k ; + keepalive 5 ; + ''; + }; + virtualHosts.${domainOf hostName} = { + forceSSL = true; + useACMEHost = "web"; + locations."/" = { + proxyPass = "${protocol}://${hostName}"; + proxyWebsockets = true; + X-Frame-Options = "SAMEORIGIN"; + }; + extraConfig = + '' + client_max_body_size ${maxBodySize} ; + '' + + virtualHostExtraConfig; + }; + }; + proxyProtect = + hostName: cfg: allowedGroup: + lib.mkMerge [ + (blockOf hostName cfg) + { + virtualHosts.${domainOf hostName} = { + locations."/".extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = /oauth2/sign_in; + + # pass information via X-User and X-Email headers to backend, + # requires running with --set-xauthrequest flag + auth_request_set $user $upstream_http_x_auth_request_preferred_username; + # Set the email to our own domain in case user change their mail + auth_request_set $email "''${upstream_http_x_auth_request_preferred_username}@${config.secrets.secrets.global.domains.web}"; + proxy_set_header X-User $user; + proxy_set_header X-Email $email; + + # if you enabled --cookie-refresh, this is needed for it to work with auth_request + auth_request_set $auth_cookie $upstream_http_set_cookie; + add_header Set-Cookie $auth_cookie; + ''; + locations."/oauth2/" = { + proxyPass = "http://oauth2-proxy"; + extraConfig = '' + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + ''; + }; + + locations."= /oauth2/auth" = { + proxyPass = + "http://oauth2-proxy/oauth2/auth" + + lib.optionalString allowedGroup "?allowed_groups=${hostName}_access"; + extraConfig = '' + internal; + + proxy_set_header X-Scheme $scheme; + # nginx auth_request includes headers but not body + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; + }; + }; + } + ]; + in + lib.mkMerge [ + { + enable = false; + recommendedSetup = true; + } + ]; + + guests = + let + mkGuest = guestName: { + autostart = true; + zfs."/state" = { + pool = "rpool"; + dataset = "local/guests/${guestName}"; + }; + zfs."/persist" = { + pool = "rpool"; + dataset = "safe/guests/${guestName}"; + }; + modules = [ + ../../config/basic + ../../config/services/${guestName}.nix + { + node.secretsDir = config.node.secretsDir + "/${guestName}"; + networking.nftables.firewall.zones.untrusted.interfaces = [ + config.guests.${guestName}.networking.mainLinkName + ]; + systemd.network.networks."10-${config.guests.${guestName}.networking.mainLinkName}" = { + DHCP = lib.mkForce "no"; + address = [ + (lib.net.cidr.hostCidr + config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName}" + config.secrets.secrets.global.net.privateSubnetv4 + ) + (lib.net.cidr.hostCidr + config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName}" + config.secrets.secrets.global.net.privateSubnetv6 + ) + ]; + gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ]; + }; + } + ]; + }; + + mkMicrovm = guestName: cfg: { + ${guestName} = mkGuest guestName cfg // { + backend = "microvm"; + microvm = { + system = "x86_64-linux"; + macvtap = "lan"; + baseMac = config.secrets.secrets.local.networking.interfaces.lan01.mac; + }; + extraSpecialArgs = { + inherit (inputs.self) nodes; + inherit (inputs.self.pkgs.x86_64-linux) lib; + inherit inputs minimal stateVersion; + }; + }; + }; + + mkContainer = guestName: cfg: { + ${guestName} = mkGuest guestName cfg // { + backend = "container"; + container.macvlan = "lan"; + extraSpecialArgs = { + inherit + lib + nodes + inputs + minimal + stateVersion + ; + }; + }; + }; + in + { }; +} diff --git a/hosts/nucnix/net.nix b/hosts/nucnix/net.nix new file mode 100644 index 0000000..a8b7206 --- /dev/null +++ b/hosts/nucnix/net.nix @@ -0,0 +1,54 @@ +{ config, lib, ... }: +{ + networking = { + inherit (config.secrets.secrets.local.networking) hostId; + }; + systemd.network.networks = { + "10-lan01" = { + address = [ + (lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name} + config.secrets.secrets.global.net.privateSubnetv4 + ) + ]; + gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ]; + #matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac; + matchConfig.Name = "lan"; + dhcpV6Config.UseDNS = false; + dhcpV4Config.UseDNS = false; + ipv6AcceptRAConfig.UseDNS = false; + networkConfig = { + MulticastDNS = true; + }; + }; + }; + boot.initrd.systemd.network = { + enable = true; + networks = { + # redo the network cause the livesystem has macvlans + "10-lan01" = { + address = [ + (lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name} + config.secrets.secrets.global.net.privateSubnetv4 + ) + ]; + gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ]; + matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac; + dhcpV6Config.UseDNS = false; + dhcpV4Config.UseDNS = false; + ipv6AcceptRAConfig.UseDNS = false; + networkConfig = { + IPv6PrivacyExtensions = "yes"; + MulticastDNS = true; + }; + }; + }; + }; + networking.nftables.firewall.zones.untrusted.interfaces = [ "lan" ]; + + # To be able to ping containers from the host, it is necessary + # to create a macvlan on the host on the VLAN 1 network. + networking.macvlans.lan = { + interface = "lan01"; + mode = "bridge"; + }; +} diff --git a/hosts/nucnix/secrets/generated/initrd_host_ed25519_key.age b/hosts/nucnix/secrets/generated/initrd_host_ed25519_key.age new file mode 100644 index 0000000..fe8fd27 Binary files /dev/null and b/hosts/nucnix/secrets/generated/initrd_host_ed25519_key.age differ diff --git a/hosts/nucnix/secrets/host.pub b/hosts/nucnix/secrets/host.pub new file mode 100644 index 0000000..30e8790 --- /dev/null +++ b/hosts/nucnix/secrets/host.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDS0gxZD8aIAAKBtt7gyMHZ2KloQPlHxS+LsQY/62SzE diff --git a/hosts/nucnix/secrets/secrets.nix.age b/hosts/nucnix/secrets/secrets.nix.age new file mode 100644 index 0000000..be0161d Binary files /dev/null and b/hosts/nucnix/secrets/secrets.nix.age differ diff --git a/modules-hm/hm-all.nix b/modules-hm/hm-all.nix index 898d36f..b3d5ffa 100644 --- a/modules-hm/hm-all.nix +++ b/modules-hm/hm-all.nix @@ -21,6 +21,7 @@ in name = "Home-manager options for the main user"; merge = _loc: defs: (map (x: x.value) defs); }; + default = { }; }; hm-all = mkOption { description = "Home-manager options for the primary User and root."; @@ -28,6 +29,7 @@ in name = "Home-manager options for the all users"; merge = _loc: defs: (map (x: x.value) defs); }; + default = { }; }; }; config.home-manager.users = mkMerge [ diff --git a/modules/actual.nix b/modules/actual.nix deleted file mode 100644 index 6421f60..0000000 --- a/modules/actual.nix +++ /dev/null @@ -1,154 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: -let - inherit (lib) - getExe - mkEnableOption - mkIf - mkOption - mkPackageOption - types - ; - - cfg = config.services.actual; - configFile = formatType.generate "config.json" cfg.settings; - dataDir = "/var/lib/actual"; - - formatType = pkgs.formats.json { }; -in -{ - options.services.actual = { - enable = mkEnableOption "actual, a privacy focused app for managing your finances"; - package = mkPackageOption pkgs "actual-server" { }; - - user = mkOption { - type = types.str; - default = "actual"; - description = '' - User to run actual as. - - ::: {.note} - If left as the default value this user will automatically be created - on system activation, otherwise the sysadmin is responsible for - ensuring the user exists. - ::: - ''; - }; - - group = mkOption { - type = types.str; - default = "actual"; - description = '' - Group under which to run. - - ::: {.note} - If left as the default value this group will automatically be created - on system activation, otherwise the sysadmin is responsible for - ensuring the user exists. - ::: - ''; - }; - - openFirewall = mkOption { - default = false; - type = types.bool; - description = "Whether to open the firewall for the specified port."; - }; - - settings = mkOption { - default = { }; - description = "Server settings, refer to (the documentation)[https://actualbudget.org/docs/config/] for available options."; - type = types.submodule { - freeformType = formatType.type; - - options = { - hostname = mkOption { - type = types.str; - description = "The address to listen on"; - default = "::"; - }; - - port = mkOption { - type = types.port; - description = "The port to listen on"; - default = 3000; - }; - }; - - config = { - serverFiles = "${dataDir}/server-files"; - userFiles = "${dataDir}/user-files"; - inherit dataDir; - }; - }; - }; - }; - - config = mkIf cfg.enable { - networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.port ]; - - users.groups = mkIf (cfg.group == "actual") { - ${cfg.group} = { }; - }; - - users.users = mkIf (cfg.user == "actual") { - ${cfg.user} = { - isSystemUser = true; - inherit (cfg) group; - home = dataDir; - }; - }; - - systemd.services.actual = { - description = "Actual server, a local-first personal finance app"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - environment.ACTUAL_CONFIG_PATH = configFile; - serviceConfig = { - ExecStart = getExe cfg.package; - User = cfg.user; - Group = cfg.group; - StateDirectory = "actual"; - WorkingDirectory = dataDir; - LimitNOFILE = "1048576"; - PrivateTmp = true; - PrivateDevices = true; - StateDirectoryMode = "0700"; - Restart = "always"; - - # Hardening - CapabilityBoundingSet = ""; - LockPersonality = true; - #MemoryDenyWriteExecute = true; # Leads to coredump because V8 does JIT - PrivateUsers = true; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - ProcSubset = "pid"; - ProtectSystem = "strict"; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - "AF_NETLINK" - ]; - RestrictNamespaces = true; - RestrictRealtime = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - "@pkey" - ]; - UMask = "0077"; - }; - }; - }; -} diff --git a/secrets/secrets.nix.age b/secrets/secrets.nix.age index b6f93bf..d7a3181 100644 Binary files a/secrets/secrets.nix.age and b/secrets/secrets.nix.age differ