]> git.scottworley.com Git - auto-upgrade-with-pinch/blob - modules/auto-upgrade.nix
216c4baf8fa624e4d2ed08680c21edfa0ccd27d4
[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 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 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.key}/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 '<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 key = mkOption {
75 type = types.path;
76 description = ''
77 GPG key that signs updates. Updates are only merged if the commit
78 at the tip of the remote branch is signed with this key.
79 '';
80 };
81
82 userEnvironment = {
83 enable = mkOption {
84 type = types.bool;
85 default = false;
86 description = ''
87 Whether to update a user-environment as well. This update is done
88 with nix-env -riA. Note the -r! I.e., ALL OTHER PACKAGES INSTALLED
89 WITH nix-env WILL BE DELETED!
90
91 This presumes that you have configured an "entire user environment"
92 package as shown in
93 https://nixos.wiki/wiki/FAQ#How_can_I_manage_software_with_nix-env_like_with_configuration.nix.3F
94
95 To check if you're set up for this, run "nix-env --query". If it
96 only lists one package, you're good to go.
97 '';
98 };
99
100 user = mkOption {
101 type = types.str;
102 description = ''
103 The username of the user whose environment should be updated.
104 '';
105 };
106
107 package = mkOption {
108 type = types.str;
109 example = "nixos.userPackages";
110 description = ''
111 The name of the single package that is the user's entire environment.
112 '';
113 };
114
115 };
116 };
117 };
118
119 config = lib.mkIf cfg.enable {
120
121 security.sudo.extraRules = lib.mkAfter [{
122 groups = [ "users" ];
123 commands = [{
124 command = "${auto-upgrade-script}";
125 options = [ "NOPASSWD" "NOSETENV" ];
126 }];
127 }];
128 # NOSETENV above still allows through ~17 vars, including PATH. Block those
129 # as well:
130 security.sudo.extraConfig = ''
131 Defaults!${auto-upgrade-script} !env_check
132 Defaults!${auto-upgrade-script} !env_keep
133 '';
134
135 nixpkgs.overlays = [
136 (import ../overlays/keyedgit.nix)
137 (import ../overlays/pinch.nix)
138 (self: super: {
139 auto-upgrade = super.writeShellScriptBin "auto-upgrade" ''
140 sudo ${auto-upgrade-script}
141 '';
142 })
143 ];
144
145 environment.systemPackages = [ pkgs.auto-upgrade ];
146
147 systemd.services.nixos-upgrade = {
148 description = "NixOS Upgrade";
149 restartIfChanged = false;
150 unitConfig.X-StopOnRemoval = false;
151 serviceConfig.Type = "oneshot";
152 environment = config.nix.envVars // {
153 inherit (config.environment.sessionVariables) NIX_PATH;
154 HOME = "/root";
155 } // config.networking.proxy.envVars;
156
157 path = with pkgs; [
158 config.nix.package.out
159 coreutils
160 git
161 gitMinimal
162 gnutar
163 gzip
164 xz.bin
165 ];
166
167 script = ''
168 set -e
169
170 # Chill for awhile before applying updates. If applying an update
171 # badly breaks things, we want a window in which an operator can
172 # intervene either to fix the problem or disable automatic updates.
173 sleep 2h
174
175 # Wait until outside business hours
176 now=$(date +%s)
177 day_of_week=$(date +%u)
178 business_start=$(date -d 8:00 +%s)
179 business_end=$( date -d 17:00 +%s)
180 if (( day_of_week <= 5 && now > business_start && now < business_end ));then
181 delay=$((business_end - now))
182 echo "Waiting $delay seconds so we don't upgrade during business hours" >&2
183 sleep "$delay"
184 fi
185
186 ${auto-upgrade-script}
187 '';
188
189 startAt = cfg.dates;
190 };
191
192 assertions = [{
193 assertion = cfg.userEnvironment.enable -> cfg.enable;
194 message =
195 "User environment upgrades cannot yet be enabled separately from system upgrades.";
196 }];
197 };
198 }