{ 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; };
defaultText = literalExpression ''{ flake = { type = "git"; url = "ssh://''${config.system.rebuild-machine.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;
};
};
};
}