at 25.11-pre 9.1 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.system.autoUpgrade; 9 10in 11{ 12 13 options = { 14 15 system.autoUpgrade = { 16 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 }; 27 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 }; 41 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 }; 51 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 }; 63 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`. 76 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 }; 82 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. 90 91 The format is described in 92 {manpage}`systemd.time(7)`. 93 ''; 94 }; 95 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 }; 106 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 }; 118 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 }; 129 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 }; 151 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 }; 160 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 }; 176 177 }; 178 179 }; 180 181 config = lib.mkIf cfg.enable { 182 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 ]; 191 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 ); 205 206 systemd.services.nixos-upgrade = { 207 description = "NixOS Upgrade"; 208 209 restartIfChanged = false; 210 unitConfig.X-StopOnRemoval = false; 211 212 serviceConfig.Type = "oneshot"; 213 214 environment = 215 config.nix.envVars 216 // { 217 inherit (config.environment.sessionVariables) NIX_PATH; 218 HOME = "/root"; 219 } 220 // config.networking.proxy.envVars; 221 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 ]; 231 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})" 245 246 ${lib.optionalString (cfg.rebootWindow != null) '' 247 current_time="$(${date} +%H:%M)" 248 249 lower="${cfg.rebootWindow.lower}" 250 upper="${cfg.rebootWindow.upper}" 251 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 ''} 270 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 ''; 285 286 startAt = cfg.dates; 287 288 after = [ "network-online.target" ]; 289 wants = [ "network-online.target" ]; 290 }; 291 292 systemd.timers.nixos-upgrade = { 293 timerConfig = { 294 RandomizedDelaySec = cfg.randomizedDelaySec; 295 FixedRandomDelay = cfg.fixedRandomDelay; 296 Persistent = cfg.persistent; 297 }; 298 }; 299 }; 300 301}