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