The config's custom-commands run as root during install, so a config poisoned by a compromised mirror was root RCE — TLS covers the transport, not the upstream. Verify it before the @@BASE@@ substitution: - default: SHA-256 pinned in the script (kept in lockstep with the config); ARCH_TURNKEY_CONFIG_SHA256 overrides it, =skip opts out. Catches drift, truncation, and a mirror poisoning only the config. - ARCH_TURNKEY_PUBKEY: require + verify a detached GPG signature (configs/archinstall.json.sig) against an out-of-band key — the only mode that resists a fully compromised upstream. Document both knobs in the README and CHANGELOG.
125 lines
6.1 KiB
Bash
Executable File
125 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# One-line ISO entrypoint. From the Arch live ISO:
|
|
# curl -fsSL https://raw.githubusercontent.com/sudolulo/arch-turnkey/master/bootstrap.sh | bash
|
|
# Installs archinstall, pulls the config, launches the guided install, then reboots.
|
|
set -euo pipefail
|
|
|
|
# Where the rest of the tree is fetched from. Defaults to the public GitHub mirror so a
|
|
# plain `curl | bash` works for anyone. Point it at any fork or your own forge with:
|
|
# ARCH_TURNKEY_BASE=https://git.example.com/you/arch-turnkey/raw/branch/master
|
|
BASE="${ARCH_TURNKEY_BASE:-https://raw.githubusercontent.com/sudolulo/arch-turnkey/master}"
|
|
|
|
# Optional site config: personal provisioning (dotfiles repo, vault, forge). Absent by
|
|
# default, so a stranger gets a clean generic install. Bake one into a custom ISO with
|
|
# iso/build.sh to keep your own install fully unattended — see docs/SITE.md.
|
|
SITE="${ARCH_TURNKEY_SITE:-/root/site.env}"
|
|
|
|
# interactive I/O must come from the console, since this runs via `curl | bash`
|
|
TTY=/dev/tty
|
|
|
|
# Integrity of the fetched config. TLS authenticates the transport, not the upstream:
|
|
# a compromised mirror or branch could serve a poisoned archinstall.json whose
|
|
# custom-commands run as root during install. Verify it against a trust anchor first.
|
|
# ARCH_TURNKEY_PUBKEY=/path/to/key -> require + verify a detached GPG signature
|
|
# (configs/archinstall.json.sig). Strongest: the key is yours, supplied out of
|
|
# band (e.g. baked into a custom ISO), so a mirror cannot forge it.
|
|
# otherwise the pinned SHA-256 below is checked automatically. That catches drift,
|
|
# truncation, and a mirror poisoning *only* the config -- but NOT a fully
|
|
# compromised BASE, which would also rewrite this script. Point BASE at a fork?
|
|
# set ARCH_TURNKEY_CONFIG_SHA256=<sha> (the mismatch message prints it), or
|
|
# ARCH_TURNKEY_CONFIG_SHA256=skip to opt out. Keep this pin in lockstep with
|
|
# configs/archinstall.json -- change one, change the other in the same commit.
|
|
CONFIG_SHA256_PINNED="2f556dea0b98e1b07feed85ba6481c36421c41b88bbc0308e9d1f2beb62e9f30"
|
|
|
|
verify_config() {
|
|
local f="$1"
|
|
if [ -n "${ARCH_TURNKEY_PUBKEY:-}" ]; then
|
|
echo ">> Verifying config signature (key: $ARCH_TURNKEY_PUBKEY)..."
|
|
command -v gpg >/dev/null || { echo "!! gpg unavailable to verify the signature."; exit 1; }
|
|
curl -fsSL "$BASE/configs/archinstall.json.sig" -o "$f.sig" \
|
|
|| { echo "!! No signature at $BASE/configs/archinstall.json.sig."; exit 1; }
|
|
local gnupghome; gnupghome="$(mktemp -d)"
|
|
if ! gpg --homedir "$gnupghome" --quiet --import "$ARCH_TURNKEY_PUBKEY" 2>/dev/null \
|
|
|| ! gpg --homedir "$gnupghome" --trust-model always --verify "$f.sig" "$f" 2>/dev/null; then
|
|
rm -rf "$gnupghome"
|
|
echo "!! Config signature verification FAILED. Aborting."; exit 1
|
|
fi
|
|
rm -rf "$gnupghome"
|
|
echo " signature OK."
|
|
return
|
|
fi
|
|
local want="${ARCH_TURNKEY_CONFIG_SHA256:-$CONFIG_SHA256_PINNED}"
|
|
if [ "$want" = skip ]; then
|
|
echo "!! Config integrity check SKIPPED — trusting $BASE over TLS only."
|
|
return
|
|
fi
|
|
local got; got="$(sha256sum "$f" | awk '{print $1}')"
|
|
if [ "$got" != "$want" ]; then
|
|
echo "!! archinstall.json checksum mismatch. Aborting."
|
|
echo " expected: $want"
|
|
echo " got: $got"
|
|
echo " If you changed the config or point BASE at a fork, set"
|
|
echo " ARCH_TURNKEY_CONFIG_SHA256=$got (or =skip to bypass)."
|
|
exit 1
|
|
fi
|
|
echo " config checksum OK."
|
|
}
|
|
|
|
net_up() { ping -c1 -W3 archlinux.org >/dev/null 2>&1; }
|
|
|
|
# --- preflight ---
|
|
[ -d /sys/firmware/efi ] || { echo "!! Not booted in UEFI mode. Reboot the ISO in UEFI."; exit 1; }
|
|
lsblk -dno NAME,TYPE 2>/dev/null | awk '$2=="disk" && $1 !~ /^zram/ {f=1} END{exit !f}' \
|
|
|| { echo "!! No target disk found."; exit 1; }
|
|
|
|
# --- network: prompt + help connect if needed ---
|
|
if ! net_up; then
|
|
echo "!! No network connection."
|
|
echo " Wired usually auto-connects. For wifi, use iwctl:"
|
|
echo " device list; station <dev> scan; station <dev> get-networks; station <dev> connect <SSID>"
|
|
while ! net_up; do
|
|
read -rp ">> Type 'wifi' to open iwctl, or connect another way then press Enter: " ans <"$TTY" || true
|
|
[ "$ans" = wifi ] && iwctl <"$TTY" >"$TTY" 2>&1 || true
|
|
done
|
|
fi
|
|
echo ">> Network is up."
|
|
|
|
# archinstall + curl are normally already on the ISO; only sync the db if not
|
|
# (avoids a needless -Sy partial-sync on every run).
|
|
if ! command -v archinstall >/dev/null || ! command -v curl >/dev/null; then
|
|
echo ">> Installing archinstall..."
|
|
pacman -Sy --noconfirm --needed archinstall curl
|
|
fi
|
|
|
|
echo ">> Fetching config..."
|
|
curl -fsSL "$BASE/configs/archinstall.json" -o /tmp/archinstall.json
|
|
# Verify the fetched config BEFORE the @@BASE@@ substitution below, so the hash is
|
|
# taken over the file exactly as published.
|
|
verify_config /tmp/archinstall.json
|
|
# archinstall's custom-commands run in the installed system and cannot see our env, so
|
|
# bake the resolved BASE into them. Keeps a single source of truth for the fetch root.
|
|
sed -i "s|@@BASE@@|$BASE|g" /tmp/archinstall.json
|
|
|
|
echo ">> Launching archinstall."
|
|
echo " In the menu: set hostname, pick Disk -> btrfs + subvolumes + LUKS encryption,"
|
|
echo " create your user. Everything else is preset."
|
|
echo
|
|
archinstall --config /tmp/archinstall.json <"$TTY"
|
|
|
|
# Hand the site config (if any) to the installed system so firstboot can provision
|
|
# dotfiles/secrets unattended. No site.env => firstboot skips that phase entirely.
|
|
if [ -f "$SITE" ]; then
|
|
echo ">> Installing site config for first-boot provisioning..."
|
|
install -Dm600 "$SITE" /mnt/etc/arch-turnkey/site.env
|
|
else
|
|
echo ">> No site config — first boot will skip dotfiles/secret provisioning."
|
|
echo " (That's the generic path. See docs/SITE.md to provision your own.)"
|
|
fi
|
|
|
|
# --- auto-reboot into the installed system (which auto-runs firstboot) ---
|
|
echo
|
|
echo ">> Base install done. Rebooting in 10s into first-boot setup..."
|
|
echo " (Ctrl-C to stay in the live environment.)"
|
|
for i in $(seq 10 -1 1); do printf "\r %2ds " "$i"; sleep 1; done; echo
|
|
systemctl reboot
|