base16-schemes: unstable-2024-06-21 -> unstable-2024-11-12
[NixPkgs.git] / nixos / modules / tasks / auto-upgrade.nix
blob0d3756a772f4a233b40a7b7aecdbbedb9b84553a
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   cfg = config.system.autoUpgrade;
13   options = {
15     system.autoUpgrade = {
17       enable = lib.mkOption {
18         type = lib.types.bool;
19         default = false;
20         description = ''
21           Whether to periodically upgrade NixOS to the latest
22           version. If enabled, a systemd timer will run
23           `nixos-rebuild switch --upgrade` once a
24           day.
25         '';
26       };
28       operation = lib.mkOption {
29         type = lib.types.enum [
30           "switch"
31           "boot"
32         ];
33         default = "switch";
34         example = "boot";
35         description = ''
36           Whether to run
37           `nixos-rebuild switch --upgrade` or run
38           `nixos-rebuild boot --upgrade`
39         '';
40       };
42       flake = lib.mkOption {
43         type = lib.types.nullOr lib.types.str;
44         default = null;
45         example = "github:kloenk/nix";
46         description = ''
47           The Flake URI of the NixOS configuration to build.
48           Disables the option {option}`system.autoUpgrade.channel`.
49         '';
50       };
52       channel = lib.mkOption {
53         type = lib.types.nullOr lib.types.str;
54         default = null;
55         example = "https://nixos.org/channels/nixos-14.12-small";
56         description = ''
57           The URI of the NixOS channel to use for automatic
58           upgrades. By default, this is the channel set using
59           {command}`nix-channel` (run `nix-channel --list`
60           to see the current value).
61         '';
62       };
64       flags = lib.mkOption {
65         type = lib.types.listOf lib.types.str;
66         default = [ ];
67         example = [
68           "-I"
69           "stuff=/home/alice/nixos-stuff"
70           "--option"
71           "extra-binary-caches"
72           "http://my-cache.example.org/"
73         ];
74         description = ''
75           Any additional flags passed to {command}`nixos-rebuild`.
77           If you are using flakes and use a local repo you can add
78           {command}`[ "--update-input" "nixpkgs" "--commit-lock-file" ]`
79           to update nixpkgs.
80         '';
81       };
83       dates = lib.mkOption {
84         type = lib.types.str;
85         default = "04:40";
86         example = "daily";
87         description = ''
88           How often or when upgrade occurs. For most desktop and server systems
89           a sufficient upgrade frequency is once a day.
91           The format is described in
92           {manpage}`systemd.time(7)`.
93         '';
94       };
96       allowReboot = lib.mkOption {
97         default = false;
98         type = lib.types.bool;
99         description = ''
100           Reboot the system into the new generation instead of a switch
101           if the new generation uses a different kernel, kernel modules
102           or initrd than the booted system.
103           See {option}`rebootWindow` for configuring the times at which a reboot is allowed.
104         '';
105       };
107       randomizedDelaySec = lib.mkOption {
108         default = "0";
109         type = lib.types.str;
110         example = "45min";
111         description = ''
112           Add a randomized delay before each automatic upgrade.
113           The delay will be chosen between zero and this value.
114           This value must be a time span in the format specified by
115           {manpage}`systemd.time(7)`
116         '';
117       };
119       fixedRandomDelay = lib.mkOption {
120         default = false;
121         type = lib.types.bool;
122         example = true;
123         description = ''
124           Make the randomized delay consistent between runs.
125           This reduces the jitter between automatic upgrades.
126           See {option}`randomizedDelaySec` for configuring the randomized delay.
127         '';
128       };
130       rebootWindow = lib.mkOption {
131         description = ''
132           Define a lower and upper time value (in HH:MM format) which
133           constitute a time window during which reboots are allowed after an upgrade.
134           This option only has an effect when {option}`allowReboot` is enabled.
135           The default value of `null` means that reboots are allowed at any time.
136         '';
137         default = null;
138         example = {
139           lower = "01:00";
140           upper = "05:00";
141         };
142         type =
143           with lib.types;
144           nullOr (submodule {
145             options = {
146               lower = lib.mkOption {
147                 description = "Lower limit of the reboot window";
148                 type = lib.types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
149                 example = "01:00";
150               };
152               upper = lib.mkOption {
153                 description = "Upper limit of the reboot window";
154                 type = lib.types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
155                 example = "05:00";
156               };
157             };
158           });
159       };
161       persistent = lib.mkOption {
162         default = true;
163         type = lib.types.bool;
164         example = false;
165         description = ''
166           Takes a boolean argument. If true, the time when the service
167           unit was last triggered is stored on disk. When the timer is
168           activated, the service unit is triggered immediately if it
169           would have been triggered at least once during the time when
170           the timer was inactive. Such triggering is nonetheless
171           subject to the delay imposed by RandomizedDelaySec=. This is
172           useful to catch up on missed runs of the service when the
173           system was powered down.
174         '';
175       };
177     };
179   };
181   config = lib.mkIf cfg.enable {
183     assertions = [
184       {
185         assertion = !((cfg.channel != null) && (cfg.flake != null));
186         message = ''
187           The options 'system.autoUpgrade.channel' and 'system.autoUpgrade.flake' cannot both be set.
188         '';
189       }
190     ];
192     system.autoUpgrade.flags = (
193       if cfg.flake == null then
194         [ "--no-build-output" ]
195         ++ lib.optionals (cfg.channel != null) [
196           "-I"
197           "nixpkgs=${cfg.channel}/nixexprs.tar.xz"
198         ]
199       else
200         [
201           "--refresh"
202           "--flake ${cfg.flake}"
203         ]
204     );
206     systemd.services.nixos-upgrade = {
207       description = "NixOS Upgrade";
209       restartIfChanged = false;
210       unitConfig.X-StopOnRemoval = false;
212       serviceConfig.Type = "oneshot";
214       environment =
215         config.nix.envVars
216         // {
217           inherit (config.environment.sessionVariables) NIX_PATH;
218           HOME = "/root";
219         }
220         // config.networking.proxy.envVars;
222       path = with pkgs; [
223         coreutils
224         gnutar
225         xz.bin
226         gzip
227         gitMinimal
228         config.nix.package.out
229         config.programs.ssh.package
230       ];
232       script =
233         let
234           nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
235           date = "${pkgs.coreutils}/bin/date";
236           readlink = "${pkgs.coreutils}/bin/readlink";
237           shutdown = "${config.systemd.package}/bin/shutdown";
238           upgradeFlag = lib.optional (cfg.channel == null) "--upgrade";
239         in
240         if cfg.allowReboot then
241           ''
242             ${nixos-rebuild} boot ${toString (cfg.flags ++ upgradeFlag)}
243             booted="$(${readlink} /run/booted-system/{initrd,kernel,kernel-modules})"
244             built="$(${readlink} /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
246             ${lib.optionalString (cfg.rebootWindow != null) ''
247               current_time="$(${date} +%H:%M)"
249               lower="${cfg.rebootWindow.lower}"
250               upper="${cfg.rebootWindow.upper}"
252               if [[ "''${lower}" < "''${upper}" ]]; then
253                 if [[ "''${current_time}" > "''${lower}" ]] && \
254                    [[ "''${current_time}" < "''${upper}" ]]; then
255                   do_reboot="true"
256                 else
257                   do_reboot="false"
258                 fi
259               else
260                 # lower > upper, so we are crossing midnight (e.g. lower=23h, upper=6h)
261                 # we want to reboot if cur > 23h or cur < 6h
262                 if [[ "''${current_time}" < "''${upper}" ]] || \
263                    [[ "''${current_time}" > "''${lower}" ]]; then
264                   do_reboot="true"
265                 else
266                   do_reboot="false"
267                 fi
268               fi
269             ''}
271             if [ "''${booted}" = "''${built}" ]; then
272               ${nixos-rebuild} ${cfg.operation} ${toString cfg.flags}
273             ${lib.optionalString (cfg.rebootWindow != null) ''
274               elif [ "''${do_reboot}" != true ]; then
275                 echo "Outside of configured reboot window, skipping."
276             ''}
277             else
278               ${shutdown} -r +1
279             fi
280           ''
281         else
282           ''
283             ${nixos-rebuild} ${cfg.operation} ${toString (cfg.flags ++ upgradeFlag)}
284           '';
286       startAt = cfg.dates;
288       after = [ "network-online.target" ];
289       wants = [ "network-online.target" ];
290     };
292     systemd.timers.nixos-upgrade = {
293       timerConfig = {
294         RandomizedDelaySec = cfg.randomizedDelaySec;
295         FixedRandomDelay = cfg.fixedRandomDelay;
296         Persistent = cfg.persistent;
297       };
298     };
299   };