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