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