+# 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; };
cfg = config.system.autoUpgradeWithPinch;
- pull-repo-script =
- pkgs.writeShellScript "pull-repo" ''
- set -eo pipefail
-
- path=$1
- config=$2
-
- prop() {
- ${pkgs.jq}/bin/jq -r ".$1" <<< "$config"
- }
-
- echo Pulling in "$path" >&2
-
- if [[ ! -e "$path" ]];then
- d=$(mktemp -d)
- ${pkgs.git}/bin/git init "$d"
- ${pkgs.git}/bin/git -C "$d" checkout -b "$(prop localBranch)"
- ${pkgs.git}/bin/git -C "$d" remote add "$(prop remoteName)" "$(prop url)"
- ${pkgs.git}/bin/git -C "$d" branch -u "$(prop remoteBranch)"
- mkdir -p "$(${pkgs.coreutils}/bin/dirname "$path")"
- mv "$d" "$path"
- fi
-
- cd "$path"
-
- if [[ "$(${pkgs.git}/bin/git remote get-url "$(prop remoteName)")" != "$(prop url)" ]]; then
- echo Expected git remote "$(prop remoteName)" to point at "$(prop url)" \
- but it points at "$(${pkgs.git}/bin/git remote get-url "$(prop remoteName)")" >&2
- case "$(prop onRemoteURLMismatch)" in
- abort) exit 1;;
- update) echo Updating it >&2
- ${pkgs.git}/bin/git -C "$d" remote set-url "$(prop remoteName)" "$(prop url)";;
- esac
- fi
-
- ${pkgs.git}/bin/git fetch "$(prop remoteName)" "$(prop remoteBranch)"
-
- if [[ "$(${pkgs.git}/bin/git rev-parse --abbrev-ref HEAD)" != "$(prop localBranch)" ]];then
- echo Could not merge because currently-checked-out \
- \""$(${pkgs.git}/bin/git rev-parse --abbrev-ref HEAD)"\" is not \
- \""$(prop localBranch)"\"
- case "$(prop onBranchMismatch)" in
- abort) exit 1;;
- continue) exit 0;;
- esac
- fi
-
- if [[ "$(prop requireSignature)" == true ]]; then
- ${pkgs.polite-merge}/bin/polite-merge \
- -c gpg.program='${pkgs.keyedgpg} '"$(prop 'signingKeys[]' | tr \\n ' ')"' --' \
- merge --ff-only --verify-signatures
- else
- ${pkgs.polite-merge}/bin/polite-merge merge --ff-only
- fi
- '';
+ pull-repo-script = pkgs.writeShellScript "pull-repo" ''
+ set -eo pipefail
+
+ path=$1
+ config=$2
+
+ prop() {
+ ${pkgs.jq}/bin/jq -r ".$1" <<< "$config"
+ }
+
+ echo Pulling in "$path" >&2
+
+ if [[ ! -e "$path" ]];then
+ d=$(mktemp -d)
+ ${pkgs.git}/bin/git init "$d"
+ ${pkgs.git}/bin/git -C "$d" checkout -b "$(prop localBranch)"
+ ${pkgs.git}/bin/git -C "$d" remote add "$(prop remoteName)" "$(prop url)"
+ ${pkgs.git}/bin/git -C "$d" branch -u "$(prop remoteBranch)"
+ mkdir -p "$(${pkgs.coreutils}/bin/dirname "$path")"
+ mv "$d" "$path"
+ fi
+
+ cd "$path"
+
+ if [[ "$(${pkgs.git}/bin/git remote get-url "$(prop remoteName)")" != "$(prop url)" ]]; then
+ echo Expected git remote "$(prop remoteName)" to point at "$(prop url)" \
+ but it points at "$(${pkgs.git}/bin/git remote get-url "$(prop remoteName)")" >&2
+ case "$(prop onRemoteURLMismatch)" in
+ abort) exit 1;;
+ update) echo Updating it >&2
+ ${pkgs.git}/bin/git -C "$d" remote set-url "$(prop remoteName)" "$(prop url)";;
+ esac
+ fi
+
+ ${pkgs.git}/bin/git fetch "$(prop remoteName)" "$(prop remoteBranch)"
+
+ if [[ "$(${pkgs.git}/bin/git rev-parse --abbrev-ref HEAD)" != "$(prop localBranch)" ]];then
+ echo Could not merge because currently-checked-out \
+ \""$(${pkgs.git}/bin/git rev-parse --abbrev-ref HEAD)"\" is not \
+ \""$(prop localBranch)"\"
+ case "$(prop onBranchMismatch)" in
+ abort) exit 1;;
+ continue) exit 0;;
+ esac
+ fi
+
+ if [[ "$(prop requireSignature)" == true ]]; then
+ ${pkgs.polite-merge}/bin/polite-merge \
+ -c gpg.program=${escapeShellArg (local-pkgs.keyed-gpg cfg.signingKeys)} \
+ merge --ff-only --verify-signatures
+ else
+ ${pkgs.polite-merge}/bin/polite-merge merge --ff-only
+ fi
+ '';
auto-upgrade-script = pkgs.writeShellScript "auto-upgrade" ''
- ${pkgs.utillinux}/bin/flock /run/auto-upgrade-with-pinch ${
+ ${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
+ concatMapStringsSep "\n" (f: "verify_ownership ${escapeShellArg f}")
cfg.upgradeConfig)}
- config=$(${pkgs.nix}/bin/nix eval --json -f ${../upgrade-config.nix} \
+ config=$(${pkgs.nix}/bin/nix-instantiate --eval --strict --json -A config \
--arg upgradeConfig ${
escapeShellArg ("["
+ lib.concatMapStringsSep " " lib.strings.escapeNixString
cfg.upgradeConfig + "]")
- } config)
+ } ${../upgrade-config.nix})
config_query() {
${pkgs.jq}/bin/jq -r "$@" <<< "$config"
'';
};
+ signingKeys = mkOption {
+ type = types.listOf types.path;
+ description = ''
+ Files containing GPG keys that are authorized to sign updates.
+ Updates are only merged if the commit at the tip of the remote
+ ref is signed with one of these keys.
+ '';
+ };
+
upgradeConfig = mkOption {
type = types.listOf types.path;
description = ''
'';
nixpkgs.overlays = [
- (import ../overlays/keyedgpg.nix)
(import ../overlays/pinch.nix)
(import ../overlays/polite-merge.nix)
(self: super: {