X-Git-Url: http://git.scottworley.com/auto-upgrade-with-pinch/blobdiff_plain/d8537205b9696e3b76bc8cad98966e52f1ab626f..318cb8e22fa7dcdf691149b520b5d24428ee3cc9:/modules/auto-upgrade.nix diff --git a/modules/auto-upgrade.nix b/modules/auto-upgrade.nix index 1facaba..e52720d 100644 --- a/modules/auto-upgrade.nix +++ b/modules/auto-upgrade.nix @@ -1,6 +1,52 @@ { config, lib, pkgs, ... }: with lib; -let cfg = config.system.autoUpgradeWithPinch; +let + cfg = config.system.autoUpgradeWithPinch; + auto-upgrade-script = pkgs.writeShellScript "auto-upgrade" '' + ${pkgs.utillinux}/bin/flock /run/auto-upgrade-with-pinch ${ + pkgs.writeShellScript "auto-upgrade-with-lock-held" '' + set -e + + in_tmpdir() { + d=$(mktemp -d) + pushd "$d" + "$@" + popd + rm -r "$d" + } + + as_user() { + ${ + if cfg.userEnvironment.enable then '' + /run/wrappers/bin/sudo -u ${escapeShellArg cfg.userEnvironment.user} "$@" + '' else '' + : + '' + } + } + + # Update channels + ( + cd /etc/nixos + ${pkgs.git}/bin/git fetch + PATH="${pkgs.keyedgit cfg.keys}/bin:$PATH" ${pkgs.polite-merge}/bin/polite-merge --ff-only --verify-signatures + ${pkgs.pinch}/bin/pinch update channels + ) + + # Build + in_tmpdir ${config.system.build.nixos-rebuild}/bin/nixos-rebuild build + as_user nix-build --no-out-link '' -A ${ + escapeShellArg cfg.userEnvironment.package + } + + # Install + ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch + as_user nix-env -f '' -riA ${ + escapeShellArg cfg.userEnvironment.package + } + '' + } + ''; in { options = { system.autoUpgradeWithPinch = { @@ -26,21 +72,81 @@ in { ''; }; - key = mkOption { + keys = mkOption { type = types.path; description = '' - GPG key that signs updates. Updates are only merged if the commit - at the tip of the remote branch is signed with this key. + File containing GPG keys that sign updates. Updates are only merged + if the commit at the tip of the remote branch is signed with one of + these keys. ''; }; + + userEnvironment = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to update a user-environment as well. This update is done + with nix-env -riA. Note the -r! I.e., ALL OTHER PACKAGES INSTALLED + WITH nix-env WILL BE DELETED! + + This presumes that you have configured an "entire user environment" + package as shown in + https://nixos.wiki/wiki/FAQ#How_can_I_manage_software_with_nix-env_like_with_configuration.nix.3F + + To check if you're set up for this, run "nix-env --query". If it + only lists one package, you're good to go. + ''; + }; + + user = mkOption { + type = types.str; + description = '' + The username of the user whose environment should be updated. + ''; + }; + + package = mkOption { + type = types.str; + example = "nixos.userPackages"; + description = '' + The name of the single package that is the user's entire environment. + ''; + }; + + }; }; }; config = lib.mkIf cfg.enable { + + security.sudo.extraRules = lib.mkAfter [{ + groups = [ "users" ]; + commands = [{ + command = "${auto-upgrade-script}"; + options = [ "NOPASSWD" "NOSETENV" ]; + }]; + }]; + # NOSETENV above still allows through ~17 vars, including PATH. Block those + # as well: + security.sudo.extraConfig = '' + Defaults!${auto-upgrade-script} !env_check + Defaults!${auto-upgrade-script} !env_keep + ''; + nixpkgs.overlays = [ (import ../overlays/keyedgit.nix) (import ../overlays/pinch.nix) + (import ../overlays/polite-merge.nix) + (self: super: { + auto-upgrade = super.writeShellScriptBin "auto-upgrade" '' + /run/wrappers/bin/sudo ${auto-upgrade-script} + ''; + }) ]; + + environment.systemPackages = [ pkgs.auto-upgrade ]; + systemd.services.nixos-upgrade = { description = "NixOS Upgrade"; restartIfChanged = false; @@ -58,22 +164,38 @@ in { gitMinimal gnutar gzip - pinch xz.bin ]; script = '' set -e - ( - cd /etc/nixos - ${pkgs.keyedgit cfg.key}/bin/git pull --ff-only --verify-signatures - pinch update channels - ) - ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch --no-build-output + # Chill for awhile before applying updates. If applying an update + # badly breaks things, we want a window in which an operator can + # intervene either to fix the problem or disable automatic updates. + sleep 2h + + # Wait until outside business hours + now=$(date +%s) + day_of_week=$(date +%u) + business_start=$(date -d 8:00 +%s) + business_end=$( date -d 17:00 +%s) + if (( day_of_week <= 5 && now > business_start && now < business_end ));then + delay=$((business_end - now)) + echo "Waiting $delay seconds so we don't upgrade during business hours" >&2 + sleep "$delay" + fi + + ${auto-upgrade-script} ''; startAt = cfg.dates; }; + + assertions = [{ + assertion = cfg.userEnvironment.enable -> cfg.enable; + message = + "User environment upgrades cannot yet be enabled separately from system upgrades."; + }]; }; }