From f2e0f4c86f3318cc6df018b1948dd5bade77f082 Mon Sep 17 00:00:00 2001 From: "quentin@aristote.fr" Date: Sat, 22 Mar 2025 18:19:29 +0100 Subject: nixos: split nix and system; use own autoUpgrade script --- modules/nixos/personal/system.nix | 220 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 modules/nixos/personal/system.nix (limited to 'modules/nixos/personal/system.nix') diff --git a/modules/nixos/personal/system.nix b/modules/nixos/personal/system.nix new file mode 100644 index 0000000..3bd3716 --- /dev/null +++ b/modules/nixos/personal/system.nix @@ -0,0 +1,220 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.personal.system; + cfgRemote = cfg.autoUpgrade.remoteBuilding; + cfgNix = config.nix; + cfgLuks = config.boot.initrd.luks.devices; + + name = config.networking.hostName; +in { + options.personal.system = { + flake = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + }; + autoUpgrade = { + enable = lib.mkEnableOption "automatic system and nixpkgs upgrade"; + autoUpdateInputs = lib.mkOption { + type = with lib.types; listOf str; + default = ["nixpkgs" "my-nixpkgs/nur" "nixos-hardware"]; + }; + checkHosts = lib.mkOption { + type = with lib.types; listOf str; + default = with builtins; concatMap (match "https://([^/]*)/?") cfgNix.settings.substituters; + }; + remoteBuilding = { + enable = lib.mkEnableOption "remote building of the system configuration"; + builder = { + hostName = lib.mkOption { + type = lib.types.str; + default = "hephaistos"; + }; + domain = lib.mkOption {type = lib.types.str;}; + user = lib.mkOption { + type = lib.types.str; + default = name; + }; + protocol = lib.mkOption { + type = lib.types.str; + # Nix custom ssh-variant that avoids lots of "trusted-users" settings pain + default = "ssh-ng"; + }; + speedFactor = lib.mkOption { + type = + lib.types.int; + default = 8; + }; + }; + }; + }; + }; + + config = let + hasFlake = cfg.flake != null; + hasFlakeInputs = cfg.autoUpgrade.autoUpdateInputs != []; + + reboot = config.system.autoUpgrade.allowReboot; + nixosRebuild = "nixos-rebuild ${toString config.system.autoUpgrade.flags}"; + + remoteBuilder = with cfgRemote.builder; "${hostName}.${domain}"; + + checkNetwork = { + path = [pkgs.unixtools.ping]; + # Check network connectivity + preStart = "(${lib.concatMapStringsSep " && " (host: "ping -c 1 ${host}") cfg.autoUpgrade.checkHosts}) || kill -s SIGUSR1 $$"; + unitConfig = { + StartLimitIntervalSec = 300; + StartLimitBurst = 5; + }; + serviceConfig = { + Restart = "on-abort"; + RestartSec = 30; + }; + }; + in + lib.mkMerge [ + (lib.mkIf hasFlake { + system.autoUpgrade.flake = cfg.flake; + systemd.services.flake-update = lib.mkIf hasFlakeInputs (lib.mkMerge [ + checkNetwork + { + description = "Update flake inputs"; + serviceConfig.Type = "oneshot"; + script = "nix flake update --commit-lock-file --flake ${cfg.flake} " + lib.concatStringsSep " " cfg.autoUpgrade.autoUpdateInputs; + before = ["nixos-upgrade.service"]; + requiredBy = ["nixos-upgrade.service"]; + path = [pkgs.git cfgNix.package]; + personal.monitor = true; + } + ]); + + programs.git = lib.mkIf (lib.hasPrefix "git+file" cfg.flake) { + enable = true; + config.user = lib.mkDefault { + name = "Root user of ${name}"; + email = "root@${name}"; + }; + }; + }) + + ( + lib.mkIf (cfg.autoUpgrade.enable && cfgRemote.enable) { + assertions = [ + { + assertion = hasFlake && lib.hasPrefix "git+file://" cfg.flake; + message = "Auto remote upgrade is only supported when the system is specified by a flake with path of the shape 'git+file://...'"; + } + ]; + + personal.system.autoUpgrade.checkHosts = lib.mkOptionDefault [remoteBuilder]; + + programs.ssh = { + extraConfig = '' + Host ${remoteBuilder} + IdentitiesOnly yes + IdentityFile /etc/ssh/remoteBuilder + User ${cfgRemote.builder.user} + ''; + knownHosts."${remoteBuilder}".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHvtqi8tziBuviUV8LDK2ddQQUbHdJYB02dgWTK5Olxq"; + }; + } + ) + + (lib.mkIf cfg.autoUpgrade.enable { + personal.boot.unattendedReboot = lib.mkIf reboot true; + system.autoUpgrade = { + enable = true; + flags = lib.optional (!hasFlake) "--upgrade-all"; + }; + systemd.services.nixos-upgrade = lib.mkMerge [ + checkNetwork + { + path = + lib.optional reboot pkgs.coreutils + ++ [ + ( + if cfgRemote.enable + then cfgNix.package + else pkgs.nixos-rebuild + ) + ] + ++ lib.optional (reboot && cfgLuks ? crypt) pkgs.cryptsetup; + personal.monitor = true; + script = lib.mkForce (lib.concatStrings [ + '' + ## build configuration + '' + ( + let + in + if cfgRemote.enable + then '' + # update remote flake + pushd ${lib.removePrefix "git+file://" cfg.flake} + git push --force ${cfgRemote.builder.hostName} master + popd + # build remotely + config=$(ssh ${remoteBuilder} -- \ + 'nix build --print-out-paths \ + git+file://$(pwd)/nixos-configuration#nixosConfigurations.${name}.config.system.build.toplevel') + # copy result locally + nix-copy-closure --from ${remoteBuilder} "$config" + # create new generation + nix-env --profile /nix/var/nix/profiles/system \ + --set "$config" + + switch="$config/bin/switch-to-configuration" + '' + else '' + switch="${nixosRebuild}" + '' + ) + '' + ## check whether a reboot is necessary" + '' + ( + if reboot + then '' + $switch boot + booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})" + built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})" + reboot="$([ "$booted" = "$built" ] || echo true)" + '' + else '' + reboot="" + '' + ) + '' + ## switch to new configuration + '' + (let + ifcrypt = lib.optionalString (cfgLuks ? crypt); + crypt = cfgLuks.crypt.device; + luksKey = x: "/etc/luks/keys/" + x; + in '' + if [ "$reboot" ] + then + ${ifcrypt '' + cryptsetup luksAddKey ${crypt} ${luksKey "tmp"} \ + --key-file ${luksKey "master"} \ + --verbose + ''} + shutdown -r now ${ifcrypt '' + || cryptsetup luksRemoveKey ${crypt} \ + --key-file ${luksKey "tmp"} \ + --verbose + ''} + else + $switch switch + fi + '') + ]); + } + ]; + }) + ]; +} -- cgit v1.2.3