{ config, pkgs, hostName, lib, ... }: with lib; let cfg = config.system.rebuild-machine; sshConfig = pkgs.writeText "config" '' UserKnownHostsFile ${knownHostsFile} Host ${cfg.repoHost} User ${cfg.repoUser} IdentityFile ${if isNull cfg.sopsConfig then cfg.repoPrivkey else config.sops.secrets."${cfg.sopsName}".path} IdentitiesOnly yes ''; knownHostsFile = pkgs.writeText "known_hosts" (concatMapStringsSep "\n" (kPath: cfg.repoHost + " " + readFile kPath) (attrValues cfg.repoPubkeys)); rebuildScript = pkgs.stdenv.mkDerivation { name = "rebuild-${hostName}"; src = ./rebuild-machine.zsh; buildInputs = with pkgs; [ makeWrapper ]; phases = [ "buildPhase" "installPhase" ]; inherit (pkgs) zsh coreutils openssh; inherit (cfg) scriptName; inherit (cfg.flake) flakeOutput; flake = cfg.flake.name; nixosRebuild = config.system.build.nixos-rebuild; inherit (config.security) wrapperDir; inherit sshConfig; buildPhase = '' substituteAll $src rebuild-machine.zsh ''; installPhase = '' mkdir -p $out/bin install -m 0755 rebuild-machine.zsh $out/bin/${cfg.scriptName} ''; }; in { options = { system.rebuild-machine = { scriptName = mkOption { type = types.str; default = "rebuild-${hostName}"; description = '' Name of the script wrapping nixos-rebuild ''; }; flake = mkOption { type = types.submodule { options = { flake = mkOption { type = types.attrs; }; flakeOutput = mkOption { type = types.str; }; name = mkOption { type = types.str; default = "machines"; }; }; }; default = { flake = { type = "git"; url = "ssh://${cfg.repoHost}/nixos"; ref = "flakes"; }; flakeOutput = hostName; }; description = '' The Flake URI of the NixOS configuration to build. ''; }; repoHost = mkOption { type = types.str; default = "git.yggdrasil.li"; }; repoUser = mkOption { type = types.str; default = "gitolite"; }; repoPubkeys = mkOption { type = types.attrsOf types.path; default = genAttrs ["rsa" "ed25519"] (kType: ./ssh-pub + "/${cfg.repoHost}-${kType}.pub"); }; repoPrivkey = mkOption { type = types.path; default = ./ssh + "/${hostName}/private"; }; sopsName = mkOption { type = types.nullOr types.str; default = "rebuild-machines"; }; sopsConfig = mkOption { type = types.nullOr types.attrs; default = { format = "binary"; }; }; period = mkOption { type = types.nullOr types.str; description = "Call rebuild-${hostName} boot periodically"; default = null; example = "hourly"; }; }; }; config = { assertions = [ { assertion = isNull cfg.sopsConfig || (!(isNull cfg.sopsName)); message = "If option sopsConfig is not null option sopsName may not be null"; } ]; sops.secrets = mkIf (!(isNull cfg.sopsConfig)) { "${cfg.sopsName}" = { sopsFile = cfg.repoPrivkey; } // cfg.sopsConfig; }; environment.systemPackages = [rebuildScript]; systemd.services."rebuild-${hostName}" = mkIf (cfg.period != null) { description = "Upgrade System on Next Boot"; requisite = [ "network.target" ]; after = [ "network.target" ]; restartIfChanged = false; unitConfig.X-StopOnRemoval = false; serviceConfig = { Type = "oneshot"; ExecStart = "${rebuildScript}/bin/${cfg.scriptName} boot"; }; }; systemd.timers."rebuild-${hostName}" = mkIf (cfg.period != null) { wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = cfg.period; Persistent = true; }; }; nix.registry = mkIf (cfg.flake != null) { ${cfg.flake.name} = { exact = false; to = cfg.flake.flake; }; }; }; }