]> git.scottworley.com Git - auto-upgrade-with-pinch/blob - modules/auto-upgrade.nix
66d857f74a1d1f9ce7e21750a7ba3f84abceafe2
[auto-upgrade-with-pinch] / modules / auto-upgrade.nix
1 { config, lib, pkgs, ... }:
2 with lib;
3 let
4 cfg = config.system.autoUpgradeWithPinch;
5 auto-upgrade-script = pkgs.writeShellScript "auto-upgrade" ''
6 ${pkgs.utillinux}/bin/flock /run/auto-upgrade-with-pinch ${
7 pkgs.writeShellScript "auto-upgrade-with-lock-held" ''
8 set -e
9
10 in_tmpdir() {
11 d=$(mktemp -d)
12 pushd "$d"
13 "$@"
14 popd
15 rm -r "$d"
16 }
17
18 as_user() {
19 ${
20 if cfg.userEnvironment.enable then ''
21 /run/wrappers/bin/sudo -u ${escapeShellArg cfg.userEnvironment.user} "$@"
22 '' else ''
23 :
24 ''
25 }
26 }
27
28 # Update channels
29 (
30 cd /etc/nixos
31 ${pkgs.keyedgit cfg.keys}/bin/git pull --ff-only --verify-signatures
32 ${pkgs.pinch}/bin/pinch update channels
33 )
34
35 # Build
36 in_tmpdir ${config.system.build.nixos-rebuild}/bin/nixos-rebuild build
37 as_user nix-build --no-out-link '<nixpkgs>' -A ${
38 escapeShellArg cfg.userEnvironment.package
39 }
40
41 # Install
42 ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch
43 as_user nix-env -f '<nixpkgs>' -riA ${
44 escapeShellArg cfg.userEnvironment.package
45 }
46 ''
47 }
48 '';
49 in {
50 options = {
51 system.autoUpgradeWithPinch = {
52
53 enable = mkOption {
54 type = types.bool;
55 default = false;
56 description = ''
57 Whether to periodically upgrade NixOS to the latest version.
58 Presumes that /etc/nixos is a git repo with a remote and
59 contains a pinch file called "channels".
60 '';
61 };
62
63 dates = mkOption {
64 default = "04:40";
65 type = types.str;
66 description = ''
67 Specification (in the format described by
68 <citerefentry><refentrytitle>systemd.time</refentrytitle>
69 <manvolnum>7</manvolnum></citerefentry>) of the time at
70 which the update will occur.
71 '';
72 };
73
74 keys = mkOption {
75 type = types.path;
76 description = ''
77 File containing GPG keys that sign updates. Updates are only merged
78 if the commit at the tip of the remote branch is signed with one of
79 these keys.
80 '';
81 };
82
83 userEnvironment = {
84 enable = mkOption {
85 type = types.bool;
86 default = false;
87 description = ''
88 Whether to update a user-environment as well. This update is done
89 with nix-env -riA. Note the -r! I.e., ALL OTHER PACKAGES INSTALLED
90 WITH nix-env WILL BE DELETED!
91
92 This presumes that you have configured an "entire user environment"
93 package as shown in
94 https://nixos.wiki/wiki/FAQ#How_can_I_manage_software_with_nix-env_like_with_configuration.nix.3F
95
96 To check if you're set up for this, run "nix-env --query". If it
97 only lists one package, you're good to go.
98 '';
99 };
100
101 user = mkOption {
102 type = types.str;
103 description = ''
104 The username of the user whose environment should be updated.
105 '';
106 };
107
108 package = mkOption {
109 type = types.str;
110 example = "nixos.userPackages";
111 description = ''
112 The name of the single package that is the user's entire environment.
113 '';
114 };
115
116 };
117 };
118 };
119
120 config = lib.mkIf cfg.enable {
121
122 security.sudo.extraRules = lib.mkAfter [{
123 groups = [ "users" ];
124 commands = [{
125 command = "${auto-upgrade-script}";
126 options = [ "NOPASSWD" "NOSETENV" ];
127 }];
128 }];
129 # NOSETENV above still allows through ~17 vars, including PATH. Block those
130 # as well:
131 security.sudo.extraConfig = ''
132 Defaults!${auto-upgrade-script} !env_check
133 Defaults!${auto-upgrade-script} !env_keep
134 '';
135
136 nixpkgs.overlays = [
137 (import ../overlays/keyedgit.nix)
138 (import ../overlays/pinch.nix)
139 (self: super: {
140 auto-upgrade = super.writeShellScriptBin "auto-upgrade" ''
141 /run/wrappers/bin/sudo ${auto-upgrade-script}
142 '';
143 })
144 ];
145
146 environment.systemPackages = [ pkgs.auto-upgrade ];
147
148 systemd.services.nixos-upgrade = {
149 description = "NixOS Upgrade";
150 restartIfChanged = false;
151 unitConfig.X-StopOnRemoval = false;
152 serviceConfig.Type = "oneshot";
153 environment = config.nix.envVars // {
154 inherit (config.environment.sessionVariables) NIX_PATH;
155 HOME = "/root";
156 } // config.networking.proxy.envVars;
157
158 path = with pkgs; [
159 config.nix.package.out
160 coreutils
161 git
162 gitMinimal
163 gnutar
164 gzip
165 xz.bin
166 ];
167
168 script = ''
169 set -e
170
171 # Chill for awhile before applying updates. If applying an update
172 # badly breaks things, we want a window in which an operator can
173 # intervene either to fix the problem or disable automatic updates.
174 sleep 2h
175
176 # Wait until outside business hours
177 now=$(date +%s)
178 day_of_week=$(date +%u)
179 business_start=$(date -d 8:00 +%s)
180 business_end=$( date -d 17:00 +%s)
181 if (( day_of_week <= 5 && now > business_start && now < business_end ));then
182 delay=$((business_end - now))
183 echo "Waiting $delay seconds so we don't upgrade during business hours" >&2
184 sleep "$delay"
185 fi
186
187 ${auto-upgrade-script}
188 '';
189
190 startAt = cfg.dates;
191 };
192
193 assertions = [{
194 assertion = cfg.userEnvironment.enable -> cfg.enable;
195 message =
196 "User environment upgrades cannot yet be enabled separately from system upgrades.";
197 }];
198 };
199 }