Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 254 additions & 0 deletions bin/omarchy-mac-migrate-bootstrap
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#!/bin/bash -p

# omarchy:summary=Migrate this Mac with the engine in a verified download of the official omarchy-mac-boot
# omarchy:args=[--channel stable|rc|edge] [--prime]
# omarchy:requires-sudo=true
# omarchy:hidden=true

# A Mac that does not run the official omarchy-mac-boot yet (an omarchy-mx-mac
# fork install, a quattro-upstream tester, a legacy omarchy-mac install) has no
# engine it can trust to move it onto Omarchy's packages. This fetches the
# official omarchy-mac-boot from pkgs.omarchy.org, accepts it only with a
# signature by the Omarchy packaging key pinned below, unpacks it into a
# directory only root can write and runs its omarchy-mac-migrate from there.
# The engine's one package transaction then installs omarchy-mac-boot with
# omarchy and omarchy-settings, and omarchy-dev leaves in that transaction
# through their conflict, so nothing is overwritten.
#
# The package's own migration target is the switch that activates this: until
# the published omarchy-mac-boot ships one, nothing runs. The Mac moves to the
# channel that target names, or to --channel's, which
# /etc/omarchy-mac/migration-target then keeps for later runs.
#
# --prime fetches, checks and unpacks the package, and stops there.
#
# Exit 0: migrated, waiting for its reboot, or nothing to migrate. Exit 75
# (deferred): nothing changed and a later run tries again, e.g. no network, no
# package published or activated yet, or a preflight refusal. Any other status:
# the migration failed part way, and running this again resumes it.
#
# Root starts over in an empty environment. Unprivileged tests name a fixture
# root in OMARCHY_MAC_MIGRATE_ROOT, a server in OMARCHY_MAC_BOOTSTRAP_SERVER
# ($channel is replaced) and a signing key in OMARCHY_MAC_BOOTSTRAP_KEY.

if (( EUID == 0 )) && [[ ${1:-} != "--clean-environment" ]]; then
exec /usr/bin/env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/bin HOME=/root /bin/bash -p -- "${BASH_SOURCE[0]}" --clean-environment "$@"
fi
[[ ${1:-} != "--clean-environment" ]] || shift

set -euo pipefail
umask 022

official_key=40DFB630FF42BCFFB047046CF0134EE680CAC571
server='https://pkgs.omarchy.org/$channel/aarch64'
keyserver=https://keys.openpgp.org/vks/v1/by-fingerprint
protocols==https
if (( EUID == 0 )); then
R=""
else
R=${OMARCHY_MAC_MIGRATE_ROOT:-}
if [[ $R != /?* ]]; then
echo "omarchy-mac-migrate-bootstrap: run it as root: sudo omarchy-mac-migrate-bootstrap $*" >&2
exit 1
fi
R=${R%/}
official_key=${OMARCHY_MAC_BOOTSTRAP_KEY:-$official_key}
server=${OMARCHY_MAC_BOOTSTRAP_SERVER:-$server}
keyserver=${OMARCHY_MAC_BOOTSTRAP_KEYSERVER:-$keyserver}
protocols==https,file
fi

state=$R/var/lib/omarchy-mac/migration
bootstrap=$R/var/lib/omarchy-mac/bootstrap
payload=$bootstrap/payload
admin_target=$R/etc/omarchy-mac/migration-target
lock_file=$R/run/lock/omarchy-mac-migrate-bootstrap.lock

channel="" prime=0
while (( $# )); do
case $1 in
--channel) channel=${2:-}; shift 2 || true ;;
--prime) prime=1; shift ;;
*) echo "Usage: omarchy-mac-migrate-bootstrap [--channel stable|rc|edge] [--prime]" >&2; exit 2 ;;
esac
done
[[ -z $channel || $channel =~ ^(stable|rc|edge)$ ]] || { echo "omarchy-mac-migrate-bootstrap: unknown channel $channel" >&2; exit 2; }

say() {
printf '%s\n' "$*"
}

# Nothing changed; a later run tries again.
defer() {
echo "omarchy-mac-migrate-bootstrap: $*; the migration stays pending" >&2
exit 75
}

apple_silicon() {
local platform
platform=$(omarchy-hw-platform 2>/dev/null) || platform=""
[[ $platform == "apple-silicon" ]] || { [[ -z $platform ]] && omarchy-hw-apple-silicon 2>/dev/null; }
}

target_value() {
sed -n "s/^$2=//p" "$1" | tail -n 1
}

fetch() {
curl -fsSL --proto "$protocols" --proto-redir "$protocols" --retry 2 --connect-timeout 20 --max-time 600 -o "$2" "$1"
}

# The key that made SIGNATURE over FILE, as gpgv reads KEYRING, or nothing. A
# revoked or expired key or signature does not count.
signer_of() {
local keyring=$1 file=$2 signature=$3 status
status=$(gpgv --homedir "$bootstrap/gnupg" --keyring "$keyring" --status-fd 1 "$signature" "$file" 2>/dev/null) || return 1
awk '$1 != "[GNUPG:]" { next }
$2 ~ /^(BADSIG|ERRSIG|EXPSIG|EXPKEYSIG|REVKEYSIG|KEYEXPIRED|KEYREVOKED)$/ { bad = 1 }
$2 == "GOODSIG" { good = 1 }
$2 == "VALIDSIG" { primary = $NF; valid++ }
END { if (!good || valid != 1 || bad) exit 1; print primary }' <<<"$status"
}

# A keyring gpgv reads: the file itself, or its dearmored copy.
binary_keyring() {
local source=$1 copy=$2
if head -c 64 "$source" | grep -q -- '-----BEGIN PGP'; then
gpg --batch --homedir "$bootstrap/gnupg" --dearmor <"$source" >"$copy" 2>/dev/null || return 1
else
cp "$source" "$copy" || return 1
fi
}

# The package's signature must come from the pinned key, found in the keyring
# omarchy-keyring installs, pacman's own, or else the keyserver.
verify_signature() {
local file=$1 signature=$2 source index=0 keyring
install -d -m 700 "$bootstrap/gnupg"
for source in "$R/usr/share/pacman/keyrings/omarchy.gpg" "$R/etc/pacman.d/gnupg/pubring.gpg" "$R/etc/pacman.d/gnupg/pubring.kbx" keyserver; do
keyring=$bootstrap/gnupg/keyring-$(( index++ )).gpg
if [[ $source == "keyserver" ]]; then
fetch "$keyserver/$official_key" "$keyring.asc" 2>/dev/null && binary_keyring "$keyring.asc" "$keyring" || continue
else
[[ -f $source ]] && binary_keyring "$source" "$keyring" || continue
fi
[[ $(signer_of "$keyring" "$file" "$signature") == "$official_key" ]] && return 0
done
return 1
}

# Fetches CHANNEL's omarchy-mac-boot, when it is not the one already unpacked,
# into $payload. Prints its version.
fetch_payload() {
local channel=$1 base download entry version filename sha
base=${server//\$channel/$channel}
download=$bootstrap/download
rm -rf "$download"
install -d -m 700 "$download"
fetch "$base/omarchy.db" "$download/omarchy.db" || defer "cannot read the $channel [omarchy] repository at $base"
entry=$(bsdtar -tf "$download/omarchy.db" | grep -E '^omarchy-mac-boot-[^/-]+-[^/-]+/desc$' | head -n 1) ||
defer "the $channel [omarchy] repository has no omarchy-mac-boot yet"
bsdtar -xOf "$download/omarchy.db" "$entry" >"$download/desc" || defer "cannot read the $channel [omarchy] repository"
version=$(awk '/^%VERSION%$/ { getline; print; exit }' "$download/desc")
filename=$(awk '/^%FILENAME%$/ { getline; print; exit }' "$download/desc")
sha=$(awk '/^%SHA256SUM%$/ { getline; print; exit }' "$download/desc")
[[ -n $version && $filename =~ ^omarchy-mac-boot-[A-Za-z0-9._+:-]+\.pkg\.tar\.[a-z0-9]+$ ]] ||
defer "the $channel [omarchy] repository lists omarchy-mac-boot malformed"
if [[ -f $payload/.PKGINFO && $(sed -n 's/^pkgver = //p' "$payload/.PKGINFO") == "$version" ]]; then
printf '%s\n' "$version"
return 0
fi
fetch "$base/$filename" "$download/$filename" && fetch "$base/$filename.sig" "$download/$filename.sig" ||
defer "cannot download omarchy-mac-boot $version from $base"
[[ -z $sha || $(sha256sum "$download/$filename" | cut -d' ' -f1) == "$sha" ]] ||
defer "the downloaded omarchy-mac-boot $version does not match the repository's checksum"
verify_signature "$download/$filename" "$download/$filename.sig" ||
defer "omarchy-mac-boot $version is not signed by the Omarchy packaging key $official_key"
rm -rf "$payload.new"
install -d -m 755 "$payload.new"
bsdtar -xpf "$download/$filename" -C "$payload.new" || defer "cannot unpack omarchy-mac-boot $version"
[[ $(sed -n 's/^pkgname = //p' "$payload.new/.PKGINFO") == "omarchy-mac-boot" &&
$(sed -n 's/^pkgver = //p' "$payload.new/.PKGINFO") == "$version" ]] ||
defer "the download is not omarchy-mac-boot $version"
rm -rf "$payload.old"
[[ ! -e $payload ]] || mv "$payload" "$payload.old"
mv "$payload.new" "$payload"
rm -rf "$payload.old" "$download"
printf '%s\n' "$version"
}

# A migration past its preflight resumes with the engine it started with.
migration_in_progress() {
step_done preflight && [[ ! -f $state/complete ]]
}

step_done() {
[[ -f $state/journal ]] && awk -v step="$1" '$2 == step { event = $3 } END { exit event != "done" }' "$state/journal"
}

engine_from_payload() {
"$payload/usr/bin/omarchy-mac-migrate" --payload "$payload" "$@"
}

if ! apple_silicon; then
say "Not an Apple Silicon Mac: nothing to migrate."
exit 0
fi

install -d -m 755 "$(dirname "$lock_file")" "$R/var/lib/omarchy-mac"
install -d -m 700 "$bootstrap"
exec 9>"$lock_file"
flock -n 9 || defer "another run is in progress"

if migration_in_progress; then
(( ! prime )) || { say "A migration is in progress on this Mac."; exit 0; }
# Once the transaction ran, the package it installed is the engine.
if [[ -x $R/usr/bin/omarchy-mac-migrate ]] && { step_done transaction || [[ ! -x $payload/usr/bin/omarchy-mac-migrate ]]; }; then
exec "$R/usr/bin/omarchy-mac-migrate" run
fi
[[ -x $payload/usr/bin/omarchy-mac-migrate ]] || defer "a migration is in progress, but no engine is left to resume it"
engine_from_payload run
[[ ! -f $state/complete ]] || rm -rf "$bootstrap"
exit 0
fi

# An administrator's target names the channel this Mac keeps.
if [[ -f $admin_target ]]; then
[[ $(target_value "$admin_target" type) == "repository" ]] ||
defer "$admin_target is not a repository target; run omarchy-mac-migrate with it yourself"
channel=$(target_value "$admin_target" channel)
fi

version=$(fetch_payload "${channel:-stable}")
packaged=$payload/usr/lib/omarchy-mac/boot/migration-target
[[ -f $packaged ]] || defer "omarchy-mac-boot $version does not activate the migration yet"
grep -q -- '--payload' "$payload/usr/bin/omarchy-mac-migrate" || defer "omarchy-mac-boot $version cannot run from a download"
packaged_channel=$(target_value "$packaged" channel)
[[ $(target_value "$packaged" type) == "repository" && $packaged_channel =~ ^(stable|rc|edge)$ ]] ||
defer "omarchy-mac-boot $version ships a target that is not a repository channel"
if [[ -z $channel && $packaged_channel != "stable" ]]; then
channel=$packaged_channel
version=$(fetch_payload "$channel")
fi

target=$packaged
if [[ -n $channel && $channel != "$packaged_channel" ]]; then
install -d -m 755 "$(dirname "$admin_target")"
sed -e '/^server=/d' -e "s/^channel=.*/channel=$channel/" "$packaged" >"$admin_target.new"
chmod 644 "$admin_target.new"
if cmp -s "$admin_target.new" "$admin_target"; then
rm -f "$admin_target.new"
else
mv -f "$admin_target.new" "$admin_target"
fi
target=$admin_target
fi

if (( prime )); then
say "omarchy-mac-boot $version is ready to move this Mac onto the $(target_value "$target" channel) channel."
exit 0
fi

engine_from_payload run --target "$target"
# A migration that finished with the download no longer needs it.
[[ ! -f $state/complete ]] || rm -rf "$bootstrap"
Loading
Loading