Compare commits

..
Author SHA1 Message Date
David Heinemeier HanssonandClaude Opus 5 0631c0530a Stop shipping a second kernel nobody boots
The live ISO boots one kernel for every machine, and after the preceding commit
that kernel is linux-t2. Stock linux is then a second kernel nobody boots, plus
its own archiso initramfs, copied into both the ISO tree and the
size-constrained FAT EFI image — about 147MB.

It could not simply be deleted: releng's broadcom-wl hard-depends on linux and
is the only releng package that does, so pacman would pull the kernel back in.

broadcom-wl is a prebuilt module for stock linux and cannot load on linux-t2.
It has kept working until now only because of the bug the preceding commit
fixes — the ISO has been booting stock all along — so it is booting T2 that
takes it away, not this commit. Dropping the package only stops shipping
something that can no longer load. Nothing in the offline install needs Wi-Fi
in the live environment.

Both packages stay in the offline mirror and this changes nothing about
installed systems: linux comes from builder/archinstall.packages and remains
what configurator's detect_kernel picks for every machine that is not a T2 Mac,
and broadcom-wl comes from omarchy-other.packages, so install/hardware/
fix-bcm43xx.sh can still install it offline on BCM4360/BCM4331 MacBooks.

The removal is anchored so linux-t2, linux-firmware, linux-firmware-marvell and
linux-atm are untouched; verified against releng's real list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 22:27:53 -07:00
David Heinemeier HanssonandClaude Opus 5 ed710a3b41 Boot the T2 kernel the ISO says it boots
Every boot entry has named vmlinuz-linux-t2 since 0638855, but the file has
contained stock Arch. Verified against the shipped ISO:

  arch/boot/x86_64/vmlinuz-linux-t2:
    bzImage, version 7.1.5-arch1-1 (linux@archlinux)

while the mirror carries linux-t2-7.1.4-2. T2 MacBooks have been installing
with no keyboard or trackpad in the live environment.

Kernel packages ship nothing in /boot — only usr/lib/modules/<kver>/vmlinuz.
install_kernel() in /usr/share/libalpm/scripts/mkinitcpio sources
/etc/mkinitcpio.d/<pkgbase>.preset and copies THAT package's kernel to whatever
ALL_kver names, whenever is_kernelcopy() sees an unowned absolute path. Our file
was named linux.preset, so it overrode releng's copy of the same name and
pointed the STOCK kernel at the T2 filenames. linux-t2's own generated preset
writes those same two paths, and stock won.

c23e0e7 introduced it a week after 0638855 added T2 support, so the feature
worked for exactly seven days: before the preset existed, releng's linux.preset
kept the stock kernel on its own filenames and linux-t2's generated preset
produced the real thing.

The fix is the filename. releng's linux.preset takes over the stock kernel
again, which now writes /boot/vmlinuz-linux and initramfs-linux.img instead of
squatting on the T2 names — so the ISO grows by one kernel and one archiso
initramfs. That cost is not free and is worth revisiting: the stock kernel is
in the live root only because releng's broadcom-wl hard-depends on it, and
broadcom-wl is a prebuilt module for that kernel which cannot load on the T2
one we now boot. Dropping both is ~147MB and old Broadcom Macs lose live Wi-Fi.

Boot the ISO on a T2 Mac before trusting this; the version string is only
evidence that the right kernel ships, not that it works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 22:20:15 -07:00
9 changed files with 96 additions and 392 deletions

No files matched your search

+6 -52
View File
@@ -2,89 +2,49 @@
# Preview the shipped Omarchy installer dashboard without building an ISO. # Preview the shipped Omarchy installer dashboard without building an ISO.
set -euo pipefail set -euo pipefail
# The dashboard drives the package phase from the target's pacman database, so
# fake one here; without it the preview falls back to its time curve and the bar
# barely moves, which reads as a bug.
PREVIEW_PACKAGES="${PREVIEW_PACKAGES:-925}"
write_simulation_state() { write_simulation_state() {
local state_file="$1" started_at="$2" phase_started_at="$3" current_phase="$4" local state_file="$1" started_at="$2" phase_started_at="$3" current_phase="$4"
local target="$5" index="$6" total="$7" local finished_at="${5:-null}" temporary="${state_file}.tmp"
local finished_at="${8:-null}" temporary="${state_file}.tmp"
jq -n \ jq -n \
--argjson started_at "$started_at" \ --argjson started_at "$started_at" \
--argjson phase_started_at "$phase_started_at" \ --argjson phase_started_at "$phase_started_at" \
--arg current_phase "$current_phase" \ --arg current_phase "$current_phase" \
--arg target "$target" \
--argjson current_index "$index" \
--argjson total_phases "$total" \
--argjson finished_at "$finished_at" \ --argjson finished_at "$finished_at" \
'{ '{
started_at: $started_at, started_at: $started_at,
phase_started_at: $phase_started_at, phase_started_at: $phase_started_at,
current_phase: $current_phase, current_phase: $current_phase
target: $target,
current_index: $current_index,
total_phases: $total_phases
} + if $finished_at == null then {} else {finished_at: $finished_at} end' \ } + if $finished_at == null then {} else {finished_at: $finished_at} end' \
>"$temporary" >"$temporary"
mv -f -- "$temporary" "$state_file" mv -f -- "$temporary" "$state_file"
} }
# Fill the fake database over the phase's duration, so the bar is driven by the
# same count it uses on a real install.
simulate_packages() {
local db="$1" duration="$2" total="$3" steps=25 i n
for ((i = 1; i <= steps; i++)); do
n=$(( total * i / steps ))
while (( $(ls -1 "$db" 2>/dev/null | wc -l) < n )); do
mkdir -p "$db/preview-$(( $(ls -1 "$db" 2>/dev/null | wc -l) ))-1.0-1"
done
sleep "$(awk -v d="$duration" -v s="$steps" 'BEGIN { printf "%.3f", d / s }')"
done
}
simulate_install() { simulate_install() {
local state_file="$1" local state_file="$1"
local started_at="$EPOCHREALTIME" local started_at="$EPOCHREALTIME"
local phase phase_started_at finished_at duration i index total local phase phase_started_at finished_at duration i
local target db
target="$(dirname -- "$state_file")/target"
db="$target/var/lib/pacman/local"
mkdir -p "$db"
local -a phases=( local -a phases=(
"Starting installation" 0.5 "Starting installation" 0.5
"Preparing live environment" 0.8 "Preparing live environment" 0.8
"Preparing install target" 0.3
"Installing Arch + Omarchy" 5.0 "Installing Arch + Omarchy" 5.0
"Configuring hibernation" 0.4
"Configuring system" 1.2 "Configuring system" 1.2
"Finalizing Limine boot" 1.0 "Finalizing Limine boot" 1.0
"Finalizing user" 1.0 "Finalizing user" 1.0
"Configuring login" 0.6 "Configuring login" 0.6
"Configuring DNS resolver" 0.3
"Validating boot setup" 0.8 "Validating boot setup" 0.8
) )
total=$(( ${#phases[@]} / 2 ))
for ((i = 0; i < ${#phases[@]}; i += 2)); do for ((i = 0; i < ${#phases[@]}; i += 2)); do
phase="${phases[i]}" phase="${phases[i]}"
duration="${phases[i + 1]}" duration="${phases[i + 1]}"
index=$(( i / 2 ))
phase_started_at="$EPOCHREALTIME" phase_started_at="$EPOCHREALTIME"
write_simulation_state "$state_file" "$started_at" "$phase_started_at" \ write_simulation_state "$state_file" "$started_at" "$phase_started_at" "$phase"
"$phase" "$target" "$index" "$total" sleep "$duration"
if [[ $phase == "Installing Arch + Omarchy" ]]; then
simulate_packages "$db" "$duration" "$PREVIEW_PACKAGES"
else
sleep "$duration"
fi
done done
finished_at="$EPOCHREALTIME" finished_at="$EPOCHREALTIME"
write_simulation_state "$state_file" "$started_at" "$finished_at" \ write_simulation_state "$state_file" "$started_at" "$finished_at" "Installation complete" "$finished_at"
"Installation complete" "$target" "$(( total - 1 ))" "$total" "$finished_at"
sleep 0.3 sleep 0.3
} }
@@ -131,12 +91,6 @@ log_file="$preview_dir/install.log"
state_file="$preview_dir/state.json" state_file="$preview_dir/state.json"
touch "$log_file" touch "$log_file"
# Match what the simulated database will reach, so the preview shows the
# package-driven bar rather than the missing-signal fallback.
printf '%s\n' "$PREVIEW_PACKAGES" >"$preview_dir/expected-packages"
OMARCHY_UI_AUTO_REBOOT=no \ OMARCHY_UI_AUTO_REBOOT=no \
OMARCHY_EXPECTED_PACKAGES_FILE="$preview_dir/expected-packages" \
PREVIEW_PACKAGES="$PREVIEW_PACKAGES" \
"$dashboard" "$log_file" "$state_file" -- \ "$dashboard" "$log_file" "$state_file" -- \
"$0" --simulate-install "$state_file" "$0" --simulate-install "$state_file"
+13 -64
View File
@@ -116,6 +116,19 @@ cp "/tmp/$NODE_FILENAME" "$build_cache_dir/airootfs/opt/packages/"
arch_packages=(linux-t2 git gum jq openssl plymouth python-terminaltexteffects tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted) arch_packages=(linux-t2 git gum jq openssl plymouth python-terminaltexteffects tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted)
printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.x86_64" printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.x86_64"
# The live ISO boots linux-t2 (see airootfs/etc/mkinitcpio.d/linux-t2.preset), so
# stock linux is a second kernel nobody boots: ~147MB of ISO, plus its own archiso
# initramfs, copied into both the ISO tree and the size-constrained FAT EFI image.
#
# It cannot just be deleted — releng's broadcom-wl hard-depends on it, and it is
# the only releng package that does, so pacman would drag the kernel straight back
# in. broadcom-wl is a prebuilt module for stock linux and cannot load on the
# kernel we boot, so it has done nothing since we started booting T2 anyway. The
# install is entirely offline and the live environment needs no Wi-Fi driver.
#
# Anchored so linux-t2 and linux-firmware are untouched.
sed -i -E '/^(linux|broadcom-wl)$/d' "$build_cache_dir/packages.x86_64"
# Build the offline mirror: everything pacstrap might want during the target # Build the offline mirror: everything pacstrap might want during the target
# install. With --local-source, the omarchy* packages we just built are # install. With --local-source, the omarchy* packages we just built are
# already in the mirror and we filter them out below. Without it, pacman -Syw # already in the mirror and we filter them out below. Without it, pacman -Syw
@@ -234,70 +247,6 @@ repo-add "$offline_mirror_dir/offline.db.tar.gz" "$offline_mirror_dir/"*.pkg.tar
mkdir -p /var/cache/omarchy/mirror mkdir -p /var/cache/omarchy/mirror
ln -sf "$offline_mirror_dir" /var/cache/omarchy/mirror/offline ln -sf "$offline_mirror_dir" /var/cache/omarchy/mirror/offline
# Denominator for the install dashboard's progress bar. Resolving the mirror's
# own package lists against the mirror we just indexed, with an empty local db,
# is the question pacstrap asks at install time — same resolver, same repo, same
# lists — so no hand-kept constant can drift.
#
# It over-counts by ~1 in 925: archinstall.packages lists both amd-ucode and
# intel-ucode because the mirror must contain either. phases.py records expected
# and actual in the timing JSON, so growing drift shows up in acceptance runs.
# The early-bootstrap set is already inside this closure, so restating it would
# only add a second list to drift.
resolve_expected_packages() {
local resolve_root=/tmp/omarchy-expected-packages
local resolved
local -a targets
rm -rf "$resolve_root"
mkdir -p "$resolve_root/var/lib/pacman"
mapfile -t targets < <(
{
grep -hv '^#\|^$' /builder/archinstall.packages
# Read the shipped copy, which is what _runtime_package_list reads at
# install time, not the build-time source it came from.
grep -hv '^#\|^$' \
"$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-base.packages"
printf '%s\n' "$OMARCHY_RUNTIME_PACKAGE" "$OMARCHY_SETTINGS_PACKAGE" \
"$OMARCHY_NVIM_PACKAGE"
} | sort -u
)
pacman --config "$build_cache_dir/pacman-offline.conf" \
--root "$resolve_root" --dbpath "$resolve_root/var/lib/pacman" \
--noconfirm -Sy >/dev/null || return 1
# Capture before counting: no pipefail here, so a pacman failure inside a
# pipeline would become a plausible partial count, which never trips the
# dashboard's fallback.
resolved="$(pacman --config "$build_cache_dir/pacman-offline.conf" \
--root "$resolve_root" --dbpath "$resolve_root/var/lib/pacman" \
--noconfirm -S --print --print-format '%n' "${targets[@]}")" || return 1
printf '%s\n' "$resolved" | sort -u | grep -c .
}
# Worth failing the build over: -S --print only aborts when a target is missing
# from the offline repo, which would fail pacstrap the same way. A count that
# merely looks wrong is not — the dashboard falls back without the file.
if ! expected_packages="$(resolve_expected_packages)"; then
echo "ERROR: could not resolve the target package count from the offline mirror." >&2
echo " pacman -S --print aborts the whole transaction if any single target" >&2
echo " is missing, so this almost certainly means pacstrap would fail the" >&2
echo " same way at install time." >&2
exit 1
fi
if (( expected_packages < 600 || expected_packages > 2000 )); then
echo "WARNING: resolved target package count $expected_packages is outside the" >&2
echo " expected 600-2000 range; shipping no denominator so the install" >&2
echo " dashboard falls back to its time-based curve." >&2
else
printf '%s\n' "$expected_packages" \
>"$build_cache_dir/airootfs/usr/share/omarchy-iso/expected-packages"
echo "Target install resolves to $expected_packages packages."
fi
# Live ISO uses the same offline pacman.conf. # Live ISO uses the same offline pacman.conf.
cp "$build_cache_dir/pacman-offline.conf" "$build_cache_dir/airootfs/etc/pacman.conf" cp "$build_cache_dir/pacman-offline.conf" "$build_cache_dir/airootfs/etc/pacman.conf"
@@ -0,0 +1,18 @@
# mkinitcpio preset file for the 'linux-t2' package on archiso
#
# The filename is the pkgbase, and that is load-bearing. Kernel packages ship
# nothing in /boot — they only install usr/lib/modules/<kver>/vmlinuz — so
# mkinitcpio's alpm hook sources /etc/mkinitcpio.d/<pkgbase>.preset and copies
# THAT package's kernel to whatever ALL_kver names (is_kernelcopy in
# /usr/share/libalpm/scripts/mkinitcpio). Named linux.preset, this pointed the
# stock kernel at the T2 filenames, so the ISO booted stock Arch while every
# boot entry said linux-t2 and T2 Macs got no keyboard or trackpad.
#
# releng ships its own linux.preset for the stock kernel; leave it alone.
PRESETS=('archiso')
ALL_kver='/boot/vmlinuz-linux-t2'
archiso_config='/etc/mkinitcpio.conf.d/archiso.conf'
archiso_image="/boot/initramfs-linux-t2.img"
@@ -1,9 +0,0 @@
# mkinitcpio preset file for the 'linux' package on archiso
PRESETS=('archiso')
ALL_kver='/boot/vmlinuz-linux-t2'
archiso_config='/etc/mkinitcpio.conf.d/archiso.conf'
archiso_image="/boot/initramfs-linux-t2.img"
@@ -137,63 +137,7 @@ LOGO_HEIGHT="$(logo_height)"
(( LOGO_HEIGHT > 0 )) || LOGO_HEIGHT=1 (( LOGO_HEIGHT > 0 )) || LOGO_HEIGHT=1
CONTENT_WIDTH="$LOGO_WIDTH" CONTENT_WIDTH="$LOGO_WIDTH"
DYNAMIC_ROW=$((LOGO_HEIGHT + 3)) DYNAMIC_ROW=$((LOGO_HEIGHT + 3))
LAST_PM=10 LAST_PCT=1
# ── Install progress ─────────────────────────────────────────────────────────
#
# Position = max(floor, work), both non-decreasing, in per-mille of the bar.
#
# floor lo + (span-1) * t / (t+tau), on time since this process saw the
# phase. Asymptotic, so it approaches the band end without reaching it.
# tau is a shape constant, not a duration prediction.
# work packages installed / PKG_TOTAL, for the package phase only (~85% of
# the install). libalpm makes one directory per package under
# <target>/var/lib/pacman/local; the denominator comes from
# build-iso.sh, so there is no second package list to drift.
#
# Per-mille because a whole-percent asymptote over a 2-point band has two
# reachable values and stops moving. No give-up predicate: two discontinuous
# curves behind a monotonic clamp freeze. With no signal the floor alone still
# finishes the band.
EXPECTED_PACKAGES_FILE="${OMARCHY_EXPECTED_PACKAGES_FILE:-/usr/share/omarchy-iso/expected-packages}"
PKG_TOTAL=0
if [[ -r $EXPECTED_PACKAGES_FILE ]]; then
read -r PKG_TOTAL _ <"$EXPECTED_PACKAGES_FILE" 2>/dev/null || PKG_TOTAL=0
fi
[[ $PKG_TOTAL =~ ^[0-9]+$ ]] || PKG_TOTAL=0
# A wrong denominator is worse than none: it never triggers the fallback.
(( PKG_TOTAL < 300 || PKG_TOTAL > 5000 )) && PKG_TOTAL=0
PKG_BONUS_MAX_PM=100 # cap on how far the bar may run ahead of the count
PKG_TAIL_PM=40 # band held back so a stalled bar has somewhere to go
PKG_FLOOR_TAU=900 # with a work signal, time is only a backstop until the
# first package, so slow it until it cannot outrun data
POS=10 # position, per-mille, monotonic
PROGRESS_PM=10
NOW=0 # monotonic seconds, refreshed once per frame
DASH_T0=0
PHASE_NAME="" # last phase this process observed
PHASE_T0=0 # NOW when it was first observed
PHASE_LO=10 # POS on entry, for an unknown phase with no index
PKG_DB_DIR=""
PKG_COUNT=0
PKG_BASE=-1 # package count when the phase started
PKG_SEEN=0 # running max of packages since PKG_BASE
PKG_LAST_CHANGE=0
PKG_BONUS=0
# Monotonic clock: tzupdate and systemd-timesyncd can step the wall clock
# mid-install, which would jump every time-driven band to its ceiling.
set_now() {
local up rest
if read -r up rest </proc/uptime 2>/dev/null && [[ $up == [0-9]* ]]; then
NOW=${up%%.*}
else
NOW=$EPOCHSECONDS
fi
return 0
}
left_padding() { left_padding() {
local width="${1:-$CONTENT_WIDTH}" cols pad local width="${1:-$CONTENT_WIDTH}" cols pad
@@ -239,146 +183,72 @@ render_logo() {
} }
progress_bar() { progress_bar() {
local pm="$1" width="${2:-34}" filled empty local pct="$1" width="${2:-34}" filled empty
(( pm < 0 )) && pm=0 (( pct < 0 )) && pct=0
(( pm > 1000 )) && pm=1000 (( pct > 100 )) && pct=100
filled=$(( pm * width / 1000 )) filled=$(( pct * width / 100 ))
empty=$(( width - filled )) empty=$(( width - filled ))
printf '%s%s%s%s%s' "$WHITE" "$(repeat █ "$filled")" "$DARK" "$(repeat ░ "$empty")" "$RESET" printf '%s%s%s%s%s' "$WHITE" "$(repeat █ "$filled")" "$DARK" "$(repeat ░ "$empty")" "$RESET"
} }
# Packages libalpm has finished installing. local/ holds one directory per
# package and nothing else in it is a directory, so */ is exact. One glob, no
# forks: this runs twice a second while the disk is saturated.
count_packages() {
local -a entries
PKG_COUNT=0
[[ -n $PKG_DB_DIR ]] || return 0
entries=("$PKG_DB_DIR"/*/)
# Unmatched globs expand to the pattern; nullglob stays off script-wide.
if (( ${#entries[@]} == 1 )) && [[ ! -d ${entries[0]} ]]; then
return 0
fi
PKG_COUNT=${#entries[@]}
return 0
}
# Sets PROGRESS_PM. Never call in a command substitution: POS, the phase timer
# and the package counters must survive between frames. Print nothing — output
# here would land on the TTY between the cursor escape and the bar.
install_progress() { install_progress() {
local phase finished index total target local phase finished phase_started elapsed span active pct
local lo hi tau span t pos work gap n pkg_signal=0 local start=1 end=99 expected=90
local -a state local -a state
PROGRESS_PM=$POS [[ -f $STATE_FILE ]] || { printf '1'; return; }
[[ -f $STATE_FILE ]] || return 0
mapfile -t state < <(jq -r ' mapfile -t state < <(jq -r '
(.current_phase // "Starting installation"), (.current_phase // "Starting installation"),
((.finished_at // 0) | tonumber? // 0 | floor), ((.finished_at // 0) | tonumber? // 0 | floor),
((.current_index // -1) | tonumber? // -1 | floor), ((.phase_started_at // .started_at // 0) | tonumber? // 0 | floor)
((.total_phases // 0) | tonumber? // 0 | floor),
(.target // "/mnt")
' "$STATE_FILE" 2>/dev/null) ' "$STATE_FILE" 2>/dev/null)
(( ${#state[@]} == 5 )) || return 0 (( ${#state[@]} == 3 )) || { printf '1'; return; }
phase="${state[0]}" finished="${state[1]}" phase="${state[0]}" finished="${state[1]}" phase_started="${state[2]}"
index="${state[2]}" total="${state[3]}" target="${state[4]}"
[[ -n $target ]] || target="/mnt"
if (( finished > 0 )) || [[ $phase == "Installation complete" ]]; then if (( finished > 0 )) || [[ $phase == "Installation complete" ]]; then
POS=1000 printf '100'
PROGRESS_PM=1000 return
return 0
fi fi
# Transitions are observed here rather than read from the state file, which # Phase weights: start%, end%, expected seconds in phase.
# keeps wall clock out of the dashboard entirely.
if [[ $phase != "$PHASE_NAME" ]]; then
PHASE_NAME="$phase"
PHASE_T0=$NOW
PHASE_LO=$POS
PKG_BASE=-1
PKG_SEEN=0
PKG_BONUS=0
PKG_LAST_CHANGE=$NOW
fi
# Bands in per-mille, plus tau. The tail bands are wide relative to what they
# cost on NVMe: run_system_finalizer can DKMS-build a driver and
# finalize_limine_boot rebuilds every UKI, so they vary most on real hardware.
# Over-allotting costs a step at a boundary; under-allotting costs a stall.
case "$phase" in case "$phase" in
"Starting installation") lo=10 hi=25 tau=5 ;; "Starting installation") start=1 end=2 expected=2 ;;
"Preparing live environment") lo=25 hi=50 tau=25 ;; "Preparing live environment") start=2 end=6 expected=5 ;;
"Preparing install target") lo=50 hi=65 tau=20 ;; "Verifying protected mounts") start=6 end=8 expected=3 ;;
"Installing Arch + Omarchy") lo=65 hi=700 tau=150 pkg_signal=1 ;; "Installing Arch + Omarchy") start=8 end=86 expected=95 ;;
"Configuring hibernation") lo=700 hi=720 tau=10 ;; "Installing base system") start=8 end=84 expected=85 ;;
"Configuring system") lo=720 hi=850 tau=120 ;; "Configuring protected boot") start=84 end=86 expected=8 ;;
"Finalizing Limine boot") lo=850 hi=950 tau=90 ;; "Configuring hibernation") start=86 end=88 expected=5 ;;
"Finalizing user") lo=950 hi=978 tau=30 ;; "Configuring system") start=88 end=93 expected=10 ;;
"Configuring login") lo=978 hi=988 tau=5 ;; "Finalizing Limine boot") start=93 end=96 expected=8 ;;
"Configuring DNS resolver") lo=988 hi=994 tau=5 ;; "Finalizing user") start=96 end=98 expected=10 ;;
"Validating boot setup") lo=994 hi=1000 tau=5 ;; "Finalizing in chroot") start=88 end=98 expected=10 ;;
*) "Configuring login") start=98 end=99 expected=3 ;;
# Unknown names used to inherit a 1->99 sweep. Derive an even band from "Configuring DNS resolver") start=99 end=100 expected=2 ;;
# the published index instead; with no index, a bounded slice, so a "Validating protected boot setup") start=99 end=100 expected=5 ;;
# renamed phase degrades to one cell rather than a runaway. "Validating boot setup") start=99 end=100 expected=5 ;;
if (( total >= 2 && index >= 0 && index < total )); then
lo=$(( 10 + 990 * index / total ))
hi=$(( 10 + 990 * (index + 1) / total ))
else
lo=$PHASE_LO
hi=$(( PHASE_LO + 30 ))
fi
tau=60
;;
esac esac
(( pkg_signal && PKG_TOTAL > 0 )) || pkg_signal=0 (( phase_started > 0 )) || phase_started=$EPOCHSECONDS
(( pkg_signal )) && tau=$PKG_FLOOR_TAU elapsed=$(( EPOCHSECONDS - phase_started ))
(( elapsed < 0 )) && elapsed=0
t=$(( NOW - PHASE_T0 )) span=$(( end - start ))
(( t < 0 )) && t=0 (( span < 0 )) && span=0
span=$(( hi - lo )) (( expected < 1 )) && expected=1
(( span < 2 )) && span=2 active=$(( elapsed * span / expected ))
(( tau < 1 )) && tau=1 (( active > span - 1 )) && active=$(( span - 1 ))
(( active < 0 )) && active=0
pos=$(( lo + (span - 1) * t / (t + tau) )) pct=$(( start + active ))
(( pct > 100 )) && pct=100
if (( pkg_signal )); then printf '%s' "$pct"
PKG_DB_DIR="$target/var/lib/pacman/local"
count_packages
# Baseline on first sight: a target that already holds packages would
# otherwise pin the bar at the band ceiling for the whole phase.
(( PKG_BASE < 0 )) && PKG_BASE=$PKG_COUNT
n=$(( PKG_COUNT - PKG_BASE ))
(( n < 0 )) && n=0
# Running max: a reinstalled package briefly drops its version-stamped dir.
if (( n > PKG_SEEN )); then
PKG_SEEN=$n
PKG_LAST_CHANGE=$NOW
fi
gap=$(( NOW - PKG_LAST_CHANGE ))
(( gap < 0 )) && gap=0
(( gap > PKG_BONUS_MAX_PM )) && gap=$PKG_BONUS_MAX_PM
(( gap > PKG_BONUS )) && PKG_BONUS=$gap
work=$(( lo + (span - 1 - PKG_TAIL_PM) * PKG_SEEN / PKG_TOTAL + PKG_BONUS ))
(( work > pos )) && pos=$work
fi
(( pos > hi - 1 )) && pos=$(( hi - 1 ))
(( pos > POS )) && POS=$pos
(( POS > 1000 )) && POS=1000
PROGRESS_PM=$POS
return 0
} }
# One tip per 8 seconds, in order. On elapsed time, not a frame counter: a frame # One tip per ~8 seconds (frames tick every 0.5s), in order, so each stays
# is 0.5s of sleep plus a render, which stretches under pacstrap I/O — on the # readable and none are skipped when progress jumps.
# machine where the tips, not the bar, are carrying liveness.
current_tip() { current_tip() {
printf '%s' "${tips[$(( (NOW - DASH_T0) / 8 % ${#tips[@]} ))]}" local frame="$1"
printf '%s' "${tips[$(( frame / 16 % ${#tips[@]} ))]}"
} }
render_static() { render_static() {
@@ -389,24 +259,21 @@ render_static() {
} }
render_dynamic() { render_dynamic() {
local pm local frame="$1" pct
set_now pct="$(install_progress)"
install_progress [[ $pct =~ ^[0-9]+$ ]] || pct=$LAST_PCT
pm=$PROGRESS_PM (( pct > 100 )) && pct=100
[[ $pm =~ ^[0-9]+$ ]] || pm=$LAST_PM (( pct < LAST_PCT )) && pct=$LAST_PCT
(( pm > 1000 )) && pm=1000 LAST_PCT=$pct
# POS is monotone by construction; this is a guard, not the mechanism.
(( pm < LAST_PM )) && pm=$LAST_PM
LAST_PM=$pm
printf '%s%d;1H' "$CSI" "$DYNAMIC_ROW" printf '%s%d;1H' "$CSI" "$DYNAMIC_ROW"
center "Installing Omarchy" "$CONTENT_WIDTH" center "Installing Omarchy" "$CONTENT_WIDTH"
blank_line blank_line
line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 34) / 2 )) "" line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 34) / 2 )) ""
progress_bar "$pm" 34 progress_bar "$pct" 34
printf '\n' printf '\n'
blank_line blank_line
center "${DIM}Tip:${RESET} ${GREEN}$(current_tip)${RESET}" "$CONTENT_WIDTH" center "${DIM}Tip:${RESET} ${GREEN}$(current_tip "$frame")${RESET}" "$CONTENT_WIDTH"
printf '%s' "$CLEAR_TO_END" printf '%s' "$CLEAR_TO_END"
} }
@@ -674,14 +541,12 @@ launch_child() {
[[ -e $TTY_PATH ]] || exit 2 [[ -e $TTY_PATH ]] || exit 2
printf '%s' "$HIDE_CURSOR" >"$TTY_PATH" printf '%s' "$HIDE_CURSOR" >"$TTY_PATH"
render_static >"$TTY_PATH" render_static >"$TTY_PATH"
set_now
DASH_T0=$NOW
PHASE_T0=$NOW
PKG_LAST_CHANGE=$NOW
launch_child launch_child
frame=0
while jobs -pr | grep -qx "$child_pid"; do while jobs -pr | grep -qx "$child_pid"; do
render_dynamic >"$TTY_PATH" 2>/dev/null || true render_dynamic "$frame" >"$TTY_PATH" 2>/dev/null || true
frame=$((frame + 1))
sleep 0.5 sleep 0.5
done done
@@ -694,13 +559,6 @@ child_pgid=""
dashboard_log "installer child exited with status $status" dashboard_log "installer child exited with status $status"
if (( status == 0 )); then
# finished_at lands microseconds before the child exits, so the 0.5s poll
# rarely renders a full bar before the finish screen replaces it.
render_dynamic >"$TTY_PATH" 2>/dev/null || true
sleep 0.4
fi
if (( status != 0 )); then if (( status != 0 )); then
failure_status="$status" failure_status="$status"
failure_summary="" failure_summary=""
@@ -65,15 +65,11 @@ def main() -> int:
info(f"Installing Omarchy for {ctx.username} → {ctx.target}") info(f"Installing Omarchy for {ctx.username} → {ctx.target}")
from .phases_impl import ( from .phases_impl import (
boost_cpu_governor,
cleanup_bind_mounts, cleanup_bind_mounts,
cleanup_protected_state, cleanup_protected_state,
cleanup_target_hook_masks, cleanup_target_hook_masks,
restore_cpu_governors,
) )
governors = boost_cpu_governor()
success = False success = False
try: try:
try: try:
@@ -89,7 +85,6 @@ def main() -> int:
info("Installation complete.") info("Installation complete.")
return 0 return 0
finally: finally:
restore_cpu_governors(governors)
cleanup_bind_mounts(ctx) cleanup_bind_mounts(ctx)
cleanup_target_hook_masks(ctx) cleanup_target_hook_masks(ctx)
if not success: if not success:
@@ -4,7 +4,6 @@ the InstallContext and either return cleanly or raise to abort the install."""
from __future__ import annotations from __future__ import annotations
import json import json
import os
import time import time
import traceback import traceback
from collections.abc import Callable from collections.abc import Callable
@@ -26,9 +25,6 @@ def run(ctx: InstallContext, phases: list[tuple[str, PhaseFn]]) -> None:
state_path = ctx.state_dir / "state.json" state_path = ctx.state_dir / "state.json"
state = { state = {
"started_at": time.time(), "started_at": time.time(),
# The dashboard counts packages under <target>/var/lib/pacman/local;
# publish the path rather than have the UI assume /mnt.
"target": str(ctx.target),
"total_phases": len(phases), "total_phases": len(phases),
"current_index": 0, "current_index": 0,
"current_phase": "Starting installation", "current_phase": "Starting installation",
@@ -67,10 +63,6 @@ def run(ctx: InstallContext, phases: list[tuple[str, PhaseFn]]) -> None:
state["current_index"] = max(len(phases) - 1, 0) state["current_index"] = max(len(phases) - 1, 0)
state["current_phase"] = "Installation complete" state["current_phase"] = "Installation complete"
state["finished_at"] = time.time() state["finished_at"] = time.time()
# Expected vs actual for the bar's denominator, so drift is visible in
# acceptance runs rather than only by watching a bar creep.
state["installed_packages"] = _installed_package_count(ctx.target)
state["expected_packages"] = _expected_package_count()
_write_state(state_path, state) _write_state(state_path, state)
timing_path = ctx.target / "var" / "log" / "omarchy-install-timing.json" timing_path = ctx.target / "var" / "log" / "omarchy-install-timing.json"
@@ -78,25 +70,6 @@ def run(ctx: InstallContext, phases: list[tuple[str, PhaseFn]]) -> None:
_write_state(timing_path, state) _write_state(timing_path, state)
def _installed_package_count(target: Path) -> int:
"""Packages libalpm installed into the target — one directory each under
local/, which is what the dashboard counts live."""
local_db = target / "var" / "lib" / "pacman" / "local"
try:
with os.scandir(local_db) as entries:
return sum(1 for entry in entries if entry.is_dir())
except OSError:
return 0
def _expected_package_count() -> int:
path = Path("/usr/share/omarchy-iso/expected-packages")
try:
return int(path.read_text().split()[0])
except (OSError, ValueError, IndexError):
return 0
def _write_state(path: Path, state: dict) -> None: def _write_state(path: Path, state: dict) -> None:
# Dashboard polls this file while phases update it. Write atomically so the # Dashboard polls this file while phases update it. Write atomically so the
# reader never observes a truncated/partial JSON document and resets UI. # reader never observes a truncated/partial JSON document and resets UI.
@@ -1396,40 +1396,6 @@ def _validate_pre_mounted_filesystems(ctx: InstallContext) -> None:
raise RuntimeError(f"{crypttab} missing LUKS UUID {storage['luks_uuid']}") raise RuntimeError(f"{crypttab} missing LUKS UUID {storage['luks_uuid']}")
CPU_SYSFS = Path("/sys/devices/system/cpu")
def boost_cpu_governor() -> dict[Path, str]:
"""Run the live CPUs flat out for the install.
Package extraction and the UKI build are both CPU-bound, and archiso boots
on whatever governor the kernel defaults to. Writing an unsupported
governor just fails, so nothing needs probing first, and hosts without
cpufreq (most VMs) have no paths at all. Returns the prior governors.
"""
saved: dict[Path, str] = {}
for path in sorted(CPU_SYSFS.glob("cpu*/cpufreq/scaling_governor")):
try:
saved[path] = path.read_text().strip()
path.write_text("performance\n")
except OSError:
saved.pop(path, None)
if saved:
info(f"› CPU governor set to performance ({len(saved)} CPUs)")
return saved
def restore_cpu_governors(saved: dict[Path, str]) -> None:
"""Only matters when an install fails and the user keeps using the live
environment — a successful one reboots out of it."""
for path, governor in saved.items():
try:
path.write_text(f"{governor}\n")
except OSError:
continue
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# cleanup_bind_mounts: invoked from main()'s finally so bind mounts get # cleanup_bind_mounts: invoked from main()'s finally so bind mounts get
# unwound on success, failure, or interrupt. Idempotent. # unwound on success, failure, or interrupt. Idempotent.
+3 -3
View File
@@ -76,9 +76,9 @@ aarch64 has no BIOS — only UEFI. Drop the syslinux path entirely on ARM.
The cleanest cut: keep one `grub.cfg` shared, strip the x86 shell/memtest fragments at build time when `OMARCHY_ARCH=aarch64`. Don't fork the file. The cleanest cut: keep one `grub.cfg` shared, strip the x86 shell/memtest fragments at build time when `OMARCHY_ARCH=aarch64`. Don't fork the file.
### 6. mkinitcpio preset — `configs/airootfs/etc/mkinitcpio.d/linux.preset` ### 6. mkinitcpio preset — `configs/airootfs/etc/mkinitcpio.d/linux-t2.preset`
References `vmlinuz-linux-t2` and `initramfs-linux-t2.img` (lines 5, 8). For aarch64, swap to `vmlinuz-linux` / `initramfs-linux.img`. Generate the file at build time from a template, keyed on arch. Names `vmlinuz-linux-t2` and `initramfs-linux-t2.img`. The filename is the pkgbase and the alpm hook keys off it, so aarch64 does not rename this file — it simply does not ship it, and releng's own `linux.preset` covers the stock kernel. Drop `linux-t2` from `arch_packages` on aarch64 and the boot entries point at `vmlinuz-linux` / `initramfs-linux.img`.
### 7. Configurator — `configs/airootfs/root/configurator` ### 7. Configurator — `configs/airootfs/root/configurator`
@@ -145,7 +145,7 @@ Code:
- `configs/efiboot/loader/loader.conf` — per-arch default entry - `configs/efiboot/loader/loader.conf` — per-arch default entry
- `configs/efiboot/loader/entries/01-archiso-x86_64-linux.conf` — generate aarch64 sibling at build time - `configs/efiboot/loader/entries/01-archiso-x86_64-linux.conf` — generate aarch64 sibling at build time
- `configs/grub/grub.cfg`, `loopback.cfg` — strip x86-only fragments on aarch64 - `configs/grub/grub.cfg`, `loopback.cfg` — strip x86-only fragments on aarch64
- `configs/airootfs/etc/mkinitcpio.d/linux.preset` — template by arch (drop linux-t2 on aarch64) - `configs/airootfs/etc/mkinitcpio.d/linux-t2.preset` — omit on aarch64 (drop linux-t2 there; releng's linux.preset covers the stock kernel)
- `configs/airootfs/root/configurator` — branch archinstall JSON mirror list on `uname -m`; optionally guard lspci T2 probe - `configs/airootfs/root/configurator` — branch archinstall JSON mirror list on `uname -m`; optionally guard lspci T2 probe
- `.github/workflows/nightly-build.yml` — matrix x86_64/aarch64 - `.github/workflows/nightly-build.yml` — matrix x86_64/aarch64