at 23.05-pre 8.3 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 rebootWindow = mkOption { 113 description = lib.mdDoc '' 114 Define a lower and upper time value (in HH:MM format) which 115 constitute a time window during which reboots are allowed after an upgrade. 116 This option only has an effect when {option}`allowReboot` is enabled. 117 The default value of `null` means that reboots are allowed at any time. 118 ''; 119 default = null; 120 example = { lower = "01:00"; upper = "05:00"; }; 121 type = with types; nullOr (submodule { 122 options = { 123 lower = mkOption { 124 description = lib.mdDoc "Lower limit of the reboot window"; 125 type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}"; 126 example = "01:00"; 127 }; 128 129 upper = mkOption { 130 description = lib.mdDoc "Upper limit of the reboot window"; 131 type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}"; 132 example = "05:00"; 133 }; 134 }; 135 }); 136 }; 137 138 persistent = mkOption { 139 default = true; 140 type = types.bool; 141 example = false; 142 description = lib.mdDoc '' 143 Takes a boolean argument. If true, the time when the service 144 unit was last triggered is stored on disk. When the timer is 145 activated, the service unit is triggered immediately if it 146 would have been triggered at least once during the time when 147 the timer was inactive. Such triggering is nonetheless 148 subject to the delay imposed by RandomizedDelaySec=. This is 149 useful to catch up on missed runs of the service when the 150 system was powered down. 151 ''; 152 }; 153 154 }; 155 156 }; 157 158 config = lib.mkIf cfg.enable { 159 160 assertions = [{ 161 assertion = !((cfg.channel != null) && (cfg.flake != null)); 162 message = '' 163 The options 'system.autoUpgrade.channels' and 'system.autoUpgrade.flake' cannot both be set. 164 ''; 165 }]; 166 167 system.autoUpgrade.flags = (if cfg.flake == null then 168 [ "--no-build-output" ] ++ optionals (cfg.channel != null) [ 169 "-I" 170 "nixpkgs=${cfg.channel}/nixexprs.tar.xz" 171 ] 172 else 173 [ "--flake ${cfg.flake}" ]); 174 175 systemd.services.nixos-upgrade = { 176 description = "NixOS Upgrade"; 177 178 restartIfChanged = false; 179 unitConfig.X-StopOnRemoval = false; 180 181 serviceConfig.Type = "oneshot"; 182 183 environment = config.nix.envVars // { 184 inherit (config.environment.sessionVariables) NIX_PATH; 185 HOME = "/root"; 186 } // config.networking.proxy.envVars; 187 188 path = with pkgs; [ 189 coreutils 190 gnutar 191 xz.bin 192 gzip 193 gitMinimal 194 config.nix.package.out 195 config.programs.ssh.package 196 ]; 197 198 script = let 199 nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild"; 200 date = "${pkgs.coreutils}/bin/date"; 201 readlink = "${pkgs.coreutils}/bin/readlink"; 202 shutdown = "${config.systemd.package}/bin/shutdown"; 203 upgradeFlag = optional (cfg.channel == null) "--upgrade"; 204 in if cfg.allowReboot then '' 205 ${nixos-rebuild} boot ${toString (cfg.flags ++ upgradeFlag)} 206 booted="$(${readlink} /run/booted-system/{initrd,kernel,kernel-modules})" 207 built="$(${readlink} /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})" 208 209 ${optionalString (cfg.rebootWindow != null) '' 210 current_time="$(${date} +%H:%M)" 211 212 lower="${cfg.rebootWindow.lower}" 213 upper="${cfg.rebootWindow.upper}" 214 215 if [[ "''${lower}" < "''${upper}" ]]; then 216 if [[ "''${current_time}" > "''${lower}" ]] && \ 217 [[ "''${current_time}" < "''${upper}" ]]; then 218 do_reboot="true" 219 else 220 do_reboot="false" 221 fi 222 else 223 # lower > upper, so we are crossing midnight (e.g. lower=23h, upper=6h) 224 # we want to reboot if cur > 23h or cur < 6h 225 if [[ "''${current_time}" < "''${upper}" ]] || \ 226 [[ "''${current_time}" > "''${lower}" ]]; then 227 do_reboot="true" 228 else 229 do_reboot="false" 230 fi 231 fi 232 ''} 233 234 if [ "''${booted}" = "''${built}" ]; then 235 ${nixos-rebuild} ${cfg.operation} ${toString cfg.flags} 236 ${optionalString (cfg.rebootWindow != null) '' 237 elif [ "''${do_reboot}" != true ]; then 238 echo "Outside of configured reboot window, skipping." 239 ''} 240 else 241 ${shutdown} -r +1 242 fi 243 '' else '' 244 ${nixos-rebuild} ${cfg.operation} ${toString (cfg.flags ++ upgradeFlag)} 245 ''; 246 247 startAt = cfg.dates; 248 249 after = [ "network-online.target" ]; 250 wants = [ "network-online.target" ]; 251 }; 252 253 systemd.timers.nixos-upgrade = { 254 timerConfig = { 255 RandomizedDelaySec = cfg.randomizedDelaySec; 256 Persistent = cfg.persistent; 257 }; 258 }; 259 }; 260 261} 262