Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9dd3de5
Deliver missing zram repair to previously migrated users
malik-na Sep 13, 2026
1c3dc3f
Seed the Hermes launcher during fresh user setup
malik-na Sep 13, 2026
bfb9c12
Share local Wi-Fi when route lookup fails
malik-na Sep 13, 2026
6b5e311
Defer reboot prompts during unattended updates
malik-na Sep 13, 2026
ad531ed
Stage isolated ARM channel transactions before selecting a lane
malik-na Sep 13, 2026
232b336
Pin Mac package inputs and preserve local boot configuration
malik-na Sep 13, 2026
eda3a6d
Configure zram during fresh ARM system setup
malik-na Sep 13, 2026
63c9c07
Install preflighted ARM channel packages with isolated trust
malik-na Sep 13, 2026
86ec160
Deliver ARM defaults from their available package sources
malik-na Sep 13, 2026
ce15680
Prepare version 4.0.3rc1
malik-na Sep 13, 2026
14faf42
Remove trailing whitespace from channel installer test
malik-na Sep 13, 2026
198262c
Align installer and update fixtures with channel orchestration
malik-na Sep 13, 2026
af4d69a
Describe ARM defaults as pending qualification exclusions
malik-na Sep 13, 2026
6e463e9
Preserve nested Snapper history across Mac root restores
malik-na Sep 13, 2026
23e1e7c
Require a retained root subvolume before repairing snapshot history
malik-na Sep 13, 2026
7585a5f
Fix factory reset boot and retained-root handoff
malik-na Sep 13, 2026
cdc341a
Keep channel downloads outside private home directories
malik-na Sep 13, 2026
30f530c
Prepare version 4.0.3rc2
malik-na Sep 13, 2026
7e091f3
Capture complete factory reset history under maintenance
malik-na Sep 13, 2026
3107742
Use a private read-only view of the mounted ESP
malik-na Sep 13, 2026
525e430
Expose staged reset roots to GRUB probes
malik-na Sep 13, 2026
784bcb7
Hand factory resets the Btrfs default root
malik-na Sep 13, 2026
f6845e0
Install pacman-conf for ARM channel tests
malik-na Sep 13, 2026
1052c83
Fail closed on residual provisioning unlock keys
malik-na Sep 13, 2026
fea4693
Bootstrap fork-owned package signing trust
malik-na Sep 13, 2026
e639d8e
Validate prepared package recipes
malik-na Sep 13, 2026
46f3294
Honor configured package recipe checkout in tests
malik-na Sep 13, 2026
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
7 changes: 5 additions & 2 deletions .github/workflows/install-vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ jobs:
# The default pull_request checkout tests GitHub's merge ref.
persist-credentials: false

- name: Read package recipe pin
id: recipes
run: echo "revision=$(cat build-inputs/omarchy-pkgs-revision)" >> "$GITHUB_OUTPUT"

- name: Checkout omarchy-pkgs
uses: actions/checkout@v4
with:
repository: omacom/omarchy-pkgs
# Companion first-run dependencies and units (omacom/omarchy-pkgs#341).
ref: 6d27290193109c07b0134360d382e64790ae5dda
ref: ${{ steps.recipes.outputs.revision }}
path: omarchy-pkgs
persist-credentials: false

Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,22 @@ jobs:
with:
node-version: "20"

- name: Read package recipe pin
id: recipes
run: echo "revision=$(cat build-inputs/omarchy-pkgs-revision)" >> "$GITHUB_OUTPUT"

- name: Checkout omarchy-pkgs
uses: actions/checkout@v4
with:
repository: omacom/omarchy-pkgs
# Companion first-run dependencies and units (omacom/omarchy-pkgs#341).
ref: 6d27290193109c07b0134360d382e64790ae5dda
ref: ${{ steps.recipes.outputs.revision }}
path: omarchy-pkgs
persist-credentials: false

- name: Install test dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3-yaml jq lua5.4 imagemagick libxkbcommon-tools ripgrep desktop-file-utils
sudo apt-get install -y python3-yaml jq lua5.4 imagemagick libxkbcommon-tools ripgrep desktop-file-utils pacman-package-manager
sudo ln -sf /usr/bin/lua5.4 /usr/local/bin/lua
if ! command -v magick >/dev/null; then
sudo ln -sf "$(command -v convert)" /usr/local/bin/magick
Expand Down
3 changes: 3 additions & 0 deletions bin/omarchy-channel-current
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ elif pacman -Q omarchy omarchy-settings >/dev/null 2>&1; then
case "$channel" in
stable) echo stable ;;
rc) echo rc ;;
edge)
if [[ $(uname -m) == "aarch64" ]]; then echo edge; else echo unknown; fi
;;
*) echo unknown ;;
esac
else
Expand Down
15 changes: 14 additions & 1 deletion bin/omarchy-channel-set
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ validate_dev_checkout() {

link_dev_checkout() {
local checkout="$1"
[[ -d $checkout/.git ]] || git clone https://github.com/basecamp/omarchy.git "$checkout"
local repository="${2:-https://github.com/basecamp/omarchy.git}"
[[ -d $checkout/.git ]] || git clone "$repository" "$checkout"

omarchy-dev-link "$checkout" --no-reboot
}
Expand Down Expand Up @@ -76,6 +77,18 @@ if [[ -z $dev_checkout && $OMARCHY_PATH != "/usr/share/omarchy" ]]; then
leaving_dev=1
fi

if [[ $(uname -m) == "aarch64" ]]; then
# ARM lanes all ship omarchy + omarchy-settings. The normal update owns the
# lock, snapshot, staged transaction and post-package migrations. A missing
# lane fails before either pacman.conf or a development link is changed.
OMARCHY_UPDATE_CHANNEL="$pacman_channel" omarchy-update -y
if [[ -n $dev_checkout ]]; then
link_dev_checkout "$dev_checkout" https://github.com/omacom/omarchy-mac.git
omarchy-state set reboot-required
fi
exit 0
fi

if [[ -n $dev_checkout ]]; then
link_dev_checkout "$dev_checkout"
export OMARCHY_PATH="$dev_checkout"
Expand Down
372 changes: 372 additions & 0 deletions bin/omarchy-mac-snapper-backend

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions bin/omarchy-mac-snapshot-restore
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,10 @@ main() {
read -rp "Type 'restore' to continue: " typed
[[ $typed == "restore" ]] || fail "not confirmed"

btrfs subvolume snapshot "$TOP/$source_path" "$TOP/@new" >/dev/null
mv "$TOP/@" "$TOP/@old-$stamp"
mv "$TOP/@new" "$TOP/@"
warn "Do not start other Snapper or Btrfs writers during recovery."
local backend_helper
backend_helper="$(dirname -- "$(realpath -- "${BASH_SOURCE[0]}")")/omarchy-mac-snapper-backend"
bash "$backend_helper" restore "$source_path" "$stamp"

# /boot is the ESP and outside every snapshot, so the restored root is now
# paired with whatever kernel is on it. If the install updated linux-asahi
Expand All @@ -202,7 +203,10 @@ main() {
log "Restored. Reboot to run from it."
log "If it is not what you wanted, from a shell on the restored system:"
log " sudo mount -o subvolid=5 $root_source /mnt"
log " sudo mv /mnt/@ /mnt/@discard && sudo mv /mnt/@old-$stamp /mnt/@ && sudo reboot"
log " sudo bash /mnt/@old-$stamp/usr/share/omarchy/bin/omarchy-mac-snapper-backend restore @old-$stamp \$(date +%s)"
log " sudo umount /mnt && sudo reboot"
warn "The undo uses the retained recovery helper; it also preserves snapshot history."
warn "After restoring older software, update before another recovery operation."
}

# Sourcing the script exposes its functions to the tests without running
Expand Down
2 changes: 1 addition & 1 deletion bin/omarchy-network-qr
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ if [[ -z $interface ]]; then
# menu's visibility gate describe. Fall back to the first connected Wi-Fi
# device. nmcli localizes state names, so pin the locale, and the prefix
# match accepts states like "connected (externally)".
route_device=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == "dev") { print $(i + 1); exit } }')
route_device=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == "dev") { print $(i + 1); exit } }') || route_device=""
if [[ -n $route_device && -d /sys/class/net/$route_device/wireless ]]; then
interface=$route_device
else
Expand Down
130 changes: 71 additions & 59 deletions bin/omarchy-provision-owner
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,34 @@ NOW=0
FINALIZE_BASE=-1
FINALIZE_TOTAL=$(grep -c '^run_logged' "$OMARCHY_PATH/install/user/all.sh" 2>/dev/null || echo 0)

# Any surviving auto-unlock artifact means retirement is still pending. Test
# symlinks explicitly: a dangling link must fail closed instead of making owner
# provisioning look complete.
owner_auto_unlock_pending() {
local provisioning_dir="${1:-$PROVISIONING_DIR}" system_root="${2:-/}"
local path
for path in \
"$provisioning_dir/luks-key" \
"$system_root/etc/omarchy/provisioning.key" \
"$system_root/etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf" \
"$system_root/etc/default/grub.d/99-omarchy-provisioning-unlock.cfg" \
"$system_root/etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf"; do
[[ -e $path || -L $path ]] && return 0
done
return 1
}

owner_rekey_pending() {
local provisioning_dir="${1:-$PROVISIONING_DIR}" system_root="${2:-/}"
owner_auto_unlock_pending "$provisioning_dir" "$system_root" ||
[[ -e $provisioning_dir/owner-rekey || -L $provisioning_dir/owner-rekey ]]
}

# Whether this boot will re-key LUKS (encrypted installs stage a throwaway key).
# The re-key rebuilds the UKI and is the slowest single step, so it needs a wide
# band of its own; unencrypted installs skip it and let finalize take the room.
REKEY_PENDING=false
[[ -f $PROVISIONING_DIR/luks-key ]] && REKEY_PENDING=true
owner_rekey_pending && REKEY_PENDING=true

# Per-mille bands per phase: "lo hi tau". tau shapes the asymptotic time floor;
# it is not a duration prediction. A wide band moves visibly; a narrow one looks
Expand Down Expand Up @@ -888,70 +911,59 @@ finalize_user() {
# the staged auto-unlock keyfile would leave the disk effectively unencrypted
# forever.
rekey_luks() {
[[ -f $PROVISIONING_DIR/luks-key ]] || return 0

local device
if ! device=$(luks_device) || [[ ! -e $device ]]; then
log_step "cannot locate the LUKS device from /proc/cmdline: $(cat /proc/cmdline)"
say --foreground 1 "Could not locate the LUKS device to re-key."
return 1
local state="$PROVISIONING_DIR/owner-rekey" staged_key="$PROVISIONING_DIR/luks-key" device
# A valid in-progress receipt survives removal of the throwaway slot/key.
# If only the installed auto-unlock copy survived, it is still the staged
# credential and can complete retirement. Unsafe links fail reset_private_file
# inside owner_rekey_run. Config remnants without a credential or receipt are
# not enough to prove a safe boot, so leave provisioning pending.
if [[ ! -e $staged_key && ! -L $staged_key ]]; then
if [[ -e /etc/omarchy/provisioning.key || -L /etc/omarchy/provisioning.key ]]; then
staged_key=/etc/omarchy/provisioning.key
elif [[ ! -e $state && ! -L $state ]]; then
return 1
fi
fi

if ! cryptsetup open --test-passphrase --key-file "$PROVISIONING_DIR/luks-key" "$device" 2>>"$LOG_FILE"; then
log_step "staged LUKS key does not unlock $device"
say --foreground 1 "The staged LUKS key no longer unlocks $device."
source "$OMARCHY_PATH/install/helpers/owner-rekey.sh"
device=$(luks_device) || return $?
if ! owner_rekey_run "$device" "$staged_key" <(printf '%s' "$password") "$state" >>"$LOG_FILE" 2>&1; then
say --foreground 1 "Disk re-key remains pending. Retry with the same confirmed owner disk password; do not discard its receipt."
return 1
fi
# A completed receipt proves the disk and published boot, but must not make a
# reintroduced key or drop-in invisible to the outer provisioning flow.
! owner_auto_unlock_pending || return 1
}

# Add the user's key (a retry with a different password just adds another
# slot; all but the current one are killed once the rebuild succeeds).
cryptsetup luksAddKey --key-file "$PROVISIONING_DIR/luks-key" "$device" <(printf '%s' "$password")

# Rebuild the no-auto-unlock UKI FIRST, keeping the throwaway key and slot as
# a fallback. Only once that succeeds do we kill the other slots and destroy
# the staged key — so a limine-update failure leaves a recoverable,
# still-auto-unlocking state to retry, never a disk locked to a password the
# user may have just changed.
rm -f /etc/omarchy/provisioning.key \
/etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf \
/etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf
owner_rekey_limine_boot() {
local state=$1 esp path line hash
reset_limine_config
if ! limine-update >>"$LOG_FILE" 2>&1; then
log_step "limine-update failed during re-key; restoring auto-unlock for retry"
install -Dm600 "$PROVISIONING_DIR/luks-key" /etc/omarchy/provisioning.key
echo 'KERNEL_CMDLINE[default]+=" cryptkey=rootfs:/etc/omarchy/provisioning.key"' \
>/etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf
echo 'FILES+=(/etc/omarchy/provisioning.key)' >/etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf
limine-update >>"$LOG_FILE" 2>&1 || true
return 1
fi

local new_slot slot other_slots
new_slot=$(cryptsetup open --test-passphrase --verbose --key-file <(printf '%s' "$password") "$device" 2>&1 |
grep -o 'Key slot [0-9]* unlocked' | grep -o '[0-9]*' | head -1)
# Retiring the throwaway/seller slots must be all-or-nothing: if we can't
# identify the user's slot or a kill fails, keep the staged key and retry —
# never shred it while a slot the seller knows still unlocks the disk.
if [[ -z $new_slot ]]; then
log_step "could not identify the user's LUKS slot after re-key; keeping the staged key for retry"
say --foreground 1 "Could not confirm the LUKS re-key; will retry."
return 1
fi
if ! other_slots=$(cryptsetup luksDump "$device" | awk '/^ +[0-9]+: luks2/ { sub(":", "", $1); print $1 }'); then
log_step "luksDump failed while retiring slots; keeping the staged key for retry"
say --foreground 1 "Could not enumerate LUKS slots; will retry."
# Preserve the established Limine retry behavior; the owner slot has
# already been tested and no previous slots have been retired yet.
if [[ -f $PROVISIONING_DIR/luks-key ]]; then
install -Dm600 "$PROVISIONING_DIR/luks-key" /etc/omarchy/provisioning.key || return $?
install -d /etc/limine-entry-tool.d /etc/mkinitcpio.conf.d || return $?
echo 'KERNEL_CMDLINE[default]+=" cryptkey=rootfs:/etc/omarchy/provisioning.key"' > /etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf
echo 'FILES+=(/etc/omarchy/provisioning.key)' > /etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf
limine-update >>"$LOG_FILE" 2>&1 || true
fi
return 1
fi
for slot in $other_slots; do
[[ $slot == "$new_slot" ]] && continue
if ! cryptsetup luksKillSlot -q --key-file <(printf '%s' "$password") "$device" "$slot"; then
log_step "failed to kill LUKS slot $slot; keeping the staged key for retry"
say --foreground 1 "Could not remove the throwaway LUKS key; will retry."
return 1
fi
done

shred -u "$PROVISIONING_DIR/luks-key" 2>/dev/null || rm -f "$PROVISIONING_DIR/luks-key"
esp=$(esp_path)
[[ $esp == /boot || $esp == /efi ]] || return 1
sha256sum "$esp/limine.conf" >"$state/boot-manifest" || return $?
local entries
entries=$(grep -o 'boot():/EFI/Linux/[^#]*#[0-9a-f]*' "$esp/limine.conf") || return $?
[[ -n $entries ]] || return 1
while IFS= read -r line; do
path=${line#*boot():}; path=${path%%#*}; hash=${line##*#}
[[ $path == /EFI/Linux/* && $path != *'/../'* && $hash =~ ^[0-9a-f]{128}$ && -f $esp$path && ! -L $esp$path ]] || return 1
[[ $(b2sum "$esp$path" | cut -d' ' -f1) == "$hash" ]] || return 1
sha256sum "$esp$path" >>"$state/boot-manifest" || return $?
done <<<"$entries"
# Limine may reference the same UKI in several entries.
LC_ALL=C sort -u "$state/boot-manifest" -o "$state/boot-manifest"
}

# Start the ESP's limine.conf over from the shipped template and drop foreign
Expand Down Expand Up @@ -1045,10 +1057,10 @@ run_provisioning() {
touch "$FINALIZE_WARNING_FLAG"
fi

if [[ -f $PROVISIONING_DIR/luks-key ]]; then
if owner_rekey_pending; then
log_step "re-keying LUKS to the user's password"
echo rekey >"$STATE_FILE"
rekey_luks
rekey_luks || return $?
log_step "LUKS re-key complete"
fi

Expand Down
6 changes: 6 additions & 0 deletions bin/omarchy-refresh-pacman
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
# omarchy:summary=Overwrite the package configuration for /etc/pacman with the Omarchy default of using its dedicated mirrors and repositories, then update all packages.
# omarchy:requires-sudo=true

if [[ $(uname -m) == "aarch64" ]]; then
# ARM refresh preserves custom repositories and mirror ordering. Explicit
# refresh uses the same validated lane switch as omarchy channel set.
exec env OMARCHY_UPDATE_CHANNEL="${1:-stable}" omarchy-update -y
fi

sudo cp -f /etc/pacman.conf /etc/pacman.conf.bak
sudo cp -f /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak
if [[ -f /etc/pacman.d/mirrorlist.asahi-alarm ]]; then
Expand Down
Loading
Loading