nixos/self-deploy: init (#120940)

Add `self-deploy` service to facilitate continuous deployment of NixOS
configuration from a git repository.

authored by

Ashlynn Anderson and committed by
GitHub
903665f3 6ca9ae4e

+171
+1
nixos/modules/module-list.nix
··· 895 895 ./services/system/kerberos/default.nix 896 896 ./services/system/nscd.nix 897 897 ./services/system/saslauthd.nix 898 + ./services/system/self-deploy.nix 898 899 ./services/system/uptimed.nix 899 900 ./services/torrent/deluge.nix 900 901 ./services/torrent/flexget.nix
+170
nixos/modules/services/system/self-deploy.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + let 4 + cfg = config.services.self-deploy; 5 + 6 + workingDirectory = "/var/lib/nixos-self-deploy"; 7 + repositoryDirectory = "${workingDirectory}/repo"; 8 + outPath = "${workingDirectory}/system"; 9 + 10 + gitWithRepo = "git -C ${repositoryDirectory}"; 11 + 12 + renderNixArgs = args: 13 + let 14 + toArg = key: value: 15 + if builtins.isString value 16 + then " --argstr ${lib.escapeShellArg key} ${lib.escapeShellArg value}" 17 + else " --arg ${lib.escapeShellArg key} ${lib.escapeShellArg (toString value)}"; 18 + in 19 + lib.concatStrings (lib.mapAttrsToList toArg args); 20 + 21 + isPathType = x: lib.strings.isCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; 22 + 23 + in 24 + { 25 + options.services.self-deploy = { 26 + enable = lib.mkEnableOption "self-deploy"; 27 + 28 + nixFile = lib.mkOption { 29 + type = lib.types.path; 30 + 31 + default = "/default.nix"; 32 + 33 + description = '' 34 + Path to nix file in repository. Leading '/' refers to root of 35 + git repository. 36 + ''; 37 + }; 38 + 39 + nixAttribute = lib.mkOption { 40 + type = lib.types.str; 41 + 42 + description = '' 43 + Attribute of `nixFile` that builds the current system. 44 + ''; 45 + }; 46 + 47 + nixArgs = lib.mkOption { 48 + type = lib.types.attrs; 49 + 50 + default = { }; 51 + 52 + description = '' 53 + Arguments to `nix-build` passed as `--argstr` or `--arg` depending on 54 + the type. 55 + ''; 56 + }; 57 + 58 + switchCommand = lib.mkOption { 59 + type = lib.types.enum [ "boot" "switch" "dry-activate" "test" ]; 60 + 61 + default = "switch"; 62 + 63 + description = '' 64 + The `switch-to-configuration` subcommand used. 65 + ''; 66 + }; 67 + 68 + repository = lib.mkOption { 69 + type = with lib.types; oneOf [ path str ]; 70 + 71 + description = '' 72 + The repository to fetch from. Must be properly formatted for git. 73 + 74 + If this value is set to a path (must begin with `/`) then it's 75 + assumed that the repository is local and the resulting service 76 + won't wait for the network to be up. 77 + 78 + If the repository will be fetched over SSH, you must add an 79 + entry to `programs.ssh.knownHosts` for the SSH host for the fetch 80 + to be successful. 81 + ''; 82 + }; 83 + 84 + sshKeyFile = lib.mkOption { 85 + type = with lib.types; nullOr path; 86 + 87 + default = null; 88 + 89 + description = '' 90 + Path to SSH private key used to fetch private repositories over 91 + SSH. 92 + ''; 93 + }; 94 + 95 + branch = lib.mkOption { 96 + type = lib.types.str; 97 + 98 + default = "master"; 99 + 100 + description = '' 101 + Branch to track 102 + 103 + Technically speaking any ref can be specified here, as this is 104 + passed directly to a `git fetch`, but for the use-case of 105 + continuous deployment you're likely to want to specify a branch. 106 + ''; 107 + }; 108 + 109 + startAt = lib.mkOption { 110 + type = with lib.types; either str (listOf str); 111 + 112 + default = "hourly"; 113 + 114 + description = '' 115 + The schedule on which to run the `self-deploy` service. Format 116 + specified by `systemd.time 7`. 117 + 118 + This value can also be a list of `systemd.time 7` formatted 119 + strings, in which case the service will be started on multiple 120 + schedules. 121 + ''; 122 + }; 123 + }; 124 + 125 + config = lib.mkIf cfg.enable { 126 + systemd.services.self-deploy = { 127 + wantedBy = [ "multi-user.target" ]; 128 + 129 + requires = lib.mkIf (!(isPathType cfg.repository)) [ "network-online.target" ]; 130 + 131 + environment.GIT_SSH_COMMAND = lib.mkIf (!(isNull cfg.sshKeyFile)) 132 + "${pkgs.openssh}/bin/ssh -i ${lib.escapeShellArg cfg.sshKeyFile}"; 133 + 134 + restartIfChanged = false; 135 + 136 + path = with pkgs; [ 137 + git 138 + nix 139 + systemd 140 + ]; 141 + 142 + script = '' 143 + if [ ! -e ${repositoryDirectory} ]; then 144 + mkdir --parents ${repositoryDirectory} 145 + git init ${repositoryDirectory} 146 + fi 147 + 148 + ${gitWithRepo} fetch ${lib.escapeShellArg cfg.repository} ${lib.escapeShellArg cfg.branch} 149 + 150 + ${gitWithRepo} checkout FETCH_HEAD 151 + 152 + nix-build${renderNixArgs cfg.nixArgs} ${lib.cli.toGNUCommandLineShell { } { 153 + attr = cfg.nixAttribute; 154 + out-link = outPath; 155 + }} ${lib.escapeShellArg "${repositoryDirectory}${cfg.nixFile}"} 156 + 157 + ${lib.optionalString (cfg.switchCommand != "test") 158 + "nix-env --profile /nix/var/nix/profiles/system --set ${outPath}"} 159 + 160 + ${outPath}/bin/switch-to-configuration ${cfg.switchCommand} 161 + 162 + rm ${outPath} 163 + 164 + ${gitWithRepo} gc --prune=all 165 + 166 + ${lib.optionalString (cfg.switchCommand == "boot") "systemctl reboot"} 167 + ''; 168 + }; 169 + }; 170 + }