Fix: Verify the initial Panama revision
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
|
||||
# Panama's front door: the one command a fresh Fedora machine needs.
|
||||
#
|
||||
# bash <(curl -fsSL https://git.gbrown.org/gib/Panama/raw/branch/main/boot)
|
||||
# bash <(curl -fsSL https://git.gbrown.org/gib/Panama/raw/branch/main/boot) --server
|
||||
# Download this file from the documented commit URL, verify its documented
|
||||
# SHA-256, then pass both immutable values as PANAMA_BOOT_REVISION and
|
||||
# PANAMA_BOOT_SHA256 when invoking it.
|
||||
#
|
||||
# Deliberately dumb, because a copy of this script leaves the repository the
|
||||
# moment somebody curls it -- nothing here can be fixed by re-running
|
||||
@@ -17,9 +18,85 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! "${PANAMA_BOOT_REVISION:-}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "boot: PANAMA_BOOT_REVISION must be a full lowercase commit" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "${PANAMA_BOOT_SHA256:-}" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "boot: PANAMA_BOOT_SHA256 must be a lowercase SHA-256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_boot_sha="$(sha256sum "${BASH_SOURCE[0]}" | cut -d' ' -f1)"
|
||||
if [[ "$actual_boot_sha" != "$PANAMA_BOOT_SHA256" ]]; then
|
||||
echo "boot: downloaded boot file does not match PANAMA_BOOT_SHA256" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_URL="https://git.gbrown.org/gib/Panama.git"
|
||||
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
|
||||
export PANAMA_PATH
|
||||
BOOTSTRAP_USER=""
|
||||
|
||||
checkout_command() {
|
||||
if [[ -n "$BOOTSTRAP_USER" ]]; then
|
||||
runuser -u "$BOOTSTRAP_USER" -- "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_panama_checkout() {
|
||||
local checkout="$1" actual_head checkout_status
|
||||
|
||||
if [[ -e "$checkout" && ! -d "$checkout/.git" ]]; then
|
||||
printf 'boot: %s exists but is not a Panama Git checkout\n' "$checkout" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -d "$checkout/.git" ]]; then
|
||||
checkout_status="$(checkout_command git -C "$checkout" status --porcelain)" || {
|
||||
printf 'boot: could not inspect the existing checkout at %s\n' "$checkout" >&2
|
||||
return 1
|
||||
}
|
||||
if [[ -n "$checkout_status" ]]; then
|
||||
printf 'boot: existing checkout at %s has local changes\n' "$checkout" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Panama is already cloned at $checkout; fetching the verified revision"
|
||||
checkout_command git -C "$checkout" fetch origin "$PANAMA_BOOT_REVISION" || return 1
|
||||
if ! checkout_command git -C "$checkout" merge-base --is-ancestor \
|
||||
HEAD "$PANAMA_BOOT_REVISION"; then
|
||||
echo "boot: existing checkout cannot fast-forward to the verified revision" >&2
|
||||
return 1
|
||||
fi
|
||||
checkout_command git -C "$checkout" merge --ff-only "$PANAMA_BOOT_REVISION" || return 1
|
||||
else
|
||||
checkout_command mkdir -p "$(dirname "$checkout")" || return 1
|
||||
checkout_command git init "$checkout" || return 1
|
||||
checkout_command git -C "$checkout" remote add origin "$REPO_URL" || return 1
|
||||
checkout_command git -C "$checkout" fetch --depth=1 origin \
|
||||
"$PANAMA_BOOT_REVISION" || return 1
|
||||
checkout_command git -C "$checkout" checkout --detach \
|
||||
"$PANAMA_BOOT_REVISION" || return 1
|
||||
|
||||
actual_head="$(checkout_command git -C "$checkout" rev-parse 'HEAD^{commit}')" || return 1
|
||||
if [[ "$actual_head" != "$PANAMA_BOOT_REVISION" ]]; then
|
||||
echo "boot: fetched checkout does not match PANAMA_BOOT_REVISION" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
checkout_command git -C "$checkout" checkout -b main || return 1
|
||||
checkout_command git -C "$checkout" config branch.main.remote origin || return 1
|
||||
checkout_command git -C "$checkout" config branch.main.merge refs/heads/main || return 1
|
||||
fi
|
||||
|
||||
actual_head="$(checkout_command git -C "$checkout" rev-parse 'HEAD^{commit}')" || return 1
|
||||
if [[ "$actual_head" != "$PANAMA_BOOT_REVISION" ]]; then
|
||||
echo "boot: checkout HEAD does not match PANAMA_BOOT_REVISION" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
SERVER=0
|
||||
INSTALL_ARGS=()
|
||||
@@ -397,18 +474,11 @@ if [[ "$(id -u)" -eq 0 ]]; then
|
||||
dnf install -y git
|
||||
fi
|
||||
|
||||
# Cloned straight into the user's home and owned by them: this is the
|
||||
# checkout `panama update` will pull from for the life of the machine, and
|
||||
# a root-owned .git in a user's home is a wound that never heals.
|
||||
# Create or advance the checkout as the target user. A root-owned .git in a
|
||||
# user's home would break every later update.
|
||||
PANAMA_PATH="$bootstrap_home/.local/share/Panama"
|
||||
if [[ -d "$PANAMA_PATH/.git" ]]; then
|
||||
echo "Panama is already cloned at $PANAMA_PATH; updating"
|
||||
runuser -u "$username" -- git -C "$PANAMA_PATH" pull --ff-only \
|
||||
|| echo "Could not fast-forward; installing from the clone as it is" >&2
|
||||
else
|
||||
runuser -u "$username" -- mkdir -p "$bootstrap_home/.local/share"
|
||||
runuser -u "$username" -- git clone "$REPO_URL" "$PANAMA_PATH"
|
||||
fi
|
||||
BOOTSTRAP_USER="$username"
|
||||
prepare_panama_checkout "$PANAMA_PATH"
|
||||
|
||||
echo "Handing off to install as $username"
|
||||
exec runuser -u "$username" -- env PANAMA_PATH="$PANAMA_PATH" \
|
||||
@@ -422,19 +492,10 @@ if ! command -v git >/dev/null 2>&1; then
|
||||
sudo dnf install -y git
|
||||
fi
|
||||
|
||||
if [[ -d "$PANAMA_PATH/.git" ]]; then
|
||||
# An existing clone makes this the recovery command too. Only a fast-forward:
|
||||
# local work is never rewritten, and a diverged clone still installs from
|
||||
# what it has rather than stopping someone mid-repair.
|
||||
echo "Panama is already cloned at $PANAMA_PATH; updating"
|
||||
git -C "$PANAMA_PATH" pull --ff-only \
|
||||
|| echo "Could not fast-forward; installing from the clone as it is" >&2
|
||||
else
|
||||
git clone "$REPO_URL" "$PANAMA_PATH"
|
||||
fi
|
||||
prepare_panama_checkout "$PANAMA_PATH"
|
||||
|
||||
# `curl | bash` and `bash <(curl ...)` can leave stdin as the pipe, and the
|
||||
# first thing install runs is the interview, which has to be able to ask.
|
||||
# A shell invoked from automation can have a pipe as stdin, while the first
|
||||
# thing install runs is the interview, which has to be able to ask.
|
||||
# Reattach the terminal when there is one; without one the interview will say
|
||||
# so itself.
|
||||
# The probe actually opens /dev/tty rather than testing -r: a process with no
|
||||
|
||||
Reference in New Issue
Block a user