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