X-Git-Url: http://git.scottworley.com/auto-upgrade-with-pinch/blobdiff_plain/edaaa0c0ab2761711c3b9217a2c7396a56d6a54e..a084161200e7edcdb818651e8b23a0757507579e:/modules/auto-upgrade.nix?ds=sidebyside diff --git a/modules/auto-upgrade.nix b/modules/auto-upgrade.nix index 54a3083..c52f0bc 100644 --- a/modules/auto-upgrade.nix +++ b/modules/auto-upgrade.nix @@ -1,4 +1,15 @@ -{ config, lib, pkgs, ... }: +# auto-upgrade-with-pinch: Secure managed NixOS updates +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, version 3. + +{ + config, + lib, + pkgs, + ... +}: with lib; let local-pkgs = import ../. { inherit pkgs; }; @@ -59,39 +70,41 @@ let ''; auto-upgrade-script = pkgs.writeShellScript "auto-upgrade" '' - ${pkgs.utillinux}/bin/flock /run/auto-upgrade-with-pinch ${ - pkgs.writeShellScript "auto-upgrade-with-lock-held" '' - set -eo pipefail - - dry_run=false - pinch_args=() - if [[ "$1" == --dry-run ]];then - dry_run=true - pinch_args=( --dry-run ) - fi - - hydrate() { - if "$dry_run";then - echo "Would run: $*" - else - "$@" - fi - } - - die() { - echo "$*" >&2 - exit 1 - } - - in_tmpdir() { - d=$(${pkgs.coreutils}/bin/mktemp -d) - pushd "$d" + ${pkgs.coreutils}/bin/nice -n 17 \ + ${pkgs.util-linux}/bin/ionice -c 3 \ + ${pkgs.util-linux}/bin/flock /run/auto-upgrade-with-pinch ${pkgs.writeShellScript "auto-upgrade-with-lock-held" '' + set -eo pipefail + + dry_run=false + pinch_args=() + if [[ "$1" == --dry-run ]];then + dry_run=true + pinch_args=( --dry-run ) + fi + + hydrate() { + if "$dry_run";then + echo "Would run: $*" + else "$@" - popd - ${pkgs.coreutils}/bin/rm -r "$d" - } - - ${optionalString (cfg.upgradeConfigOwnershipPolicy != "any") ('' + fi + } + + die() { + echo "$*" >&2 + exit 1 + } + + in_tmpdir() { + d=$(${pkgs.coreutils}/bin/mktemp -d) + pushd "$d" + "$@" + popd + ${pkgs.coreutils}/bin/rm -r "$d" + } + + ${optionalString (cfg.upgradeConfigOwnershipPolicy != "any") ( + '' verify_ownership() { if [[ "$1" != /* ]];then die "Unexpected relative path: $1" @@ -137,63 +150,69 @@ let fi fi ''; - }."${cfg.upgradeConfigOwnershipPolicy}" + } + ."${cfg.upgradeConfigOwnershipPolicy}" } } '' - + concatMapStringsSep "\n" (f: "verify_ownership ${escapeShellArg f}") - cfg.upgradeConfig)} - - config=$(${pkgs.nix}/bin/nix eval --json -f ${../upgrade-config.nix} \ - --arg upgradeConfig ${ - escapeShellArg ("[" - + lib.concatMapStringsSep " " lib.strings.escapeNixString - cfg.upgradeConfig + "]") - } config) - - config_query() { - ${pkgs.jq}/bin/jq -r "$@" <<< "$config" - } - - repo_query() { - config_query --arg path "$1" ".repos[\$ARGS.named.path]$2" - } - - userenv_query() { - config_query --arg user "$1" ".userEnvironments[\$ARGS.named.user]$2" - } - - # Pull updates - while read path;do - hydrate /run/wrappers/bin/sudo -u "$(repo_query "$path" .user)" \ - ${pull-repo-script} "$path" "$(repo_query "$path" "")" - done < <( config_query '.repos | keys []' ) - - # Update channels - config_query '.pinchFiles[]' | ${pkgs.findutils}/bin/xargs --no-run-if-empty --delimiter=\\n ${pkgs.pinch}/bin/pinch update "''${pinch_args[@]}" - - # Build - in_tmpdir hydrate ${config.system.build.nixos-rebuild}/bin/nixos-rebuild build - while read user;do - hydrate /run/wrappers/bin/sudo -u "$user" \ - ${pkgs.nix}/bin/nix-build --no-out-link '' -A "$(userenv_query "$user" .package)" - done < <( config_query '.userEnvironments | keys []' ) - - # Install - hydrate ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch - while read user;do - remove_arg=-r - if [[ "$(userenv_query "$user" .otherPackagesAction)" == keep ]];then - remove_arg= - fi - hydrate /run/wrappers/bin/sudo -u "$user" \ - ${pkgs.nix}/bin/nix-env -f '' $remove_arg -iA "$(userenv_query "$user" .package)" - done < <( config_query '.userEnvironments | keys []' ) - '' - } + + concatMapStringsSep "\n" (f: "verify_ownership ${escapeShellArg f}") cfg.upgradeConfig + )} + + config=$(${pkgs.nix}/bin/nix-instantiate --eval --strict --json -A config \ + --arg upgradeConfig ${ + escapeShellArg ( + "[" + lib.concatMapStringsSep " " lib.strings.escapeNixString cfg.upgradeConfig + "]" + ) + } ${../upgrade-config.nix}) + + config_query() { + ${pkgs.jq}/bin/jq -r "$@" <<< "$config" + } + + repo_query() { + config_query --arg path "$1" ".repos[\$ARGS.named.path]$2" + } + + userenv_query() { + config_query --arg user "$1" ".userEnvironments[\$ARGS.named.user]$2" + } + + # Pull updates + while read path;do + hydrate /run/wrappers/bin/sudo -u "$(repo_query "$path" .user)" \ + ${pull-repo-script} "$path" "$(repo_query "$path" "")" + done < <( config_query '.repos | keys []' ) + + # Update channels + config_query '.pinchFiles[]' | ${pkgs.findutils}/bin/xargs --no-run-if-empty --delimiter=\\n ${pkgs.pinch}/bin/pinch update "''${pinch_args[@]}" + + # Build + in_tmpdir hydrate ${config.system.build.nixos-rebuild}/bin/nixos-rebuild build + while read user;do + pushd / + hydrate /run/wrappers/bin/sudo -u "$user" \ + ${pkgs.nix}/bin/nix-build --no-out-link '' -A "$(userenv_query "$user" .package)" + popd + done < <( config_query '.userEnvironments | keys []' ) + sync + + # Install + hydrate ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch + sync + while read user;do + remove_arg=-r + if [[ "$(userenv_query "$user" .otherPackagesAction)" == keep ]];then + remove_arg= + fi + hydrate /run/wrappers/bin/sudo -u "$user" \ + ${pkgs.nix}/bin/nix-env -f '' $remove_arg -iA "$(userenv_query "$user" .package)" + sync + done < <( config_query '.userEnvironments | keys []' ) + ''} ''; -in { +in +{ options = { system.autoUpgradeWithPinch = { @@ -239,7 +258,11 @@ in { }; upgradeConfigOwnershipPolicy = mkOption { - type = types.enum [ "root" "wheel" "any" ]; + type = types.enum [ + "root" + "wheel" + "any" + ]; default = "root"; description = '' Verify ownership of upgrade config files before using them for @@ -255,13 +278,20 @@ in { config = lib.mkIf cfg.enable { - security.sudo.extraRules = lib.mkAfter [{ - groups = [ "users" ]; - commands = [{ - command = "${auto-upgrade-script}"; - options = [ "NOPASSWD" "NOSETENV" ]; - }]; - }]; + 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 = '' @@ -286,10 +316,13 @@ in { restartIfChanged = false; unitConfig.X-StopOnRemoval = false; serviceConfig.Type = "oneshot"; - environment = config.nix.envVars // { - inherit (config.environment.sessionVariables) NIX_PATH; - HOME = "/root"; - } // config.networking.proxy.envVars; + environment = + config.nix.envVars + // { + inherit (config.environment.sessionVariables) NIX_PATH; + HOME = "/root"; + } + // config.networking.proxy.envVars; path = with pkgs; [ config.nix.package.out