Merge branch 'quattro' into unified-input-method-setup

This commit is contained in:
David Heinemeier Hansson committed 2026-10-10 15:51:52 +02:00
commit 101bb547bb
245 files changed
+19949 -375

No files matched your search

+290
View File
@@ -0,0 +1,290 @@
# Resumable re-key of the root LUKS volume from the staged install key to the
# owner's password. Sourcing performs no setup.
#
# Caller contract:
# - Set PROVISIONING_DIR (holds the staged luks-key), REKEY_STATE, LOG_FILE and
# password. Secrets reach cryptsetup through key files and process
# substitution, never argv, and the entry points turn tracing off.
# - Define luks_boot_layout, printing a fingerprint of the keyboard layout the
# boot files ask for the disk password in (the same value while it is
# unchanged, and never the layout's name).
# - Define log_step and say, plus the platform's boot-time unlock:
# luks_auto_unlock_present succeeds while any boot-time copy of the staged
# key or its unlock configuration remains; luks_auto_unlock_drop removes
# them and rebuilds the boot files, restoring the unlock before it fails.
# luks_record_slots <owner> records the kept slot wherever the platform's
# boot checks look for it, and succeeds where nothing does.
#
# The journal (REKEY_STATE) records only the phase, slot numbers and the layout
# fingerprint, never key material. Phases advance staged → owner → boot → done, each written durably
# after its step, so an attempt interrupted anywhere resumes from the last one;
# the retire and record steps before done are idempotent. A retry may still
# choose a new password while the staged key opens the disk, even after the
# boot step, so the kept slot is recorded only once it is final. The
# caller removes the journal with the rest of its provisioning state, so until
# then a retry can only use the password the disk holds.
# cryptsetup open tries enrolled tokens (TPM2, FIDO2, keyring) before the key
# and reports a token's slot whatever key it was given. Restricting it to a
# token type no token has leaves only the key to decide.
luks_key_slot() {
local out
out=$(LC_ALL=C cryptsetup open --test-passphrase --verbose --token-type passphrase-only --key-file "$1" "$2" 2>&1) || return 0
grep -o 'Key slot [0-9]* unlocked' <<<"$out" | grep -o '[0-9]*' | head -1 || true
}
luks_slot_for() {
local -
set +x
luks_key_slot <(printf '%s' "$1") "$2"
}
# LUKS2 lists keyslots under "Keyslots:", and a luks2-keyring token under
# "Tokens:" looks the same, so read only the keyslot section.
luks_dump_slots() {
cryptsetup luksDump "$1" | awk '
/^[^ \t]/ { keyslots = ($0 == "Keyslots:") }
keyslots && /^ +[0-9]+: luks2/ { sub(":", "", $1); print $1 }
/^Key Slot [0-9]+: ENABLED/ { sub(":", "", $3); print $3 }'
}
rekey_state_get() {
[[ -f $REKEY_STATE ]] || return 1
awk -F= -v k="$1" '$1 == k { print $2; exit }' "$REKEY_STATE"
}
# Set key/value pairs, keeping every other recorded key.
rekey_state_put() {
local line key tmp
local -A updates=()
local -a lines=()
while (( $# >= 2 )); do
updates[$1]=$2
shift 2
done
if [[ -f $REKEY_STATE ]]; then
while IFS= read -r line || [[ -n $line ]]; do
if [[ -n $line && -z ${updates[${line%%=*}]+set} ]]; then
lines+=("$line")
fi
done <"$REKEY_STATE"
fi
for key in "${!updates[@]}"; do
lines+=("$key=${updates[$key]}")
done
tmp=$(mktemp "$REKEY_STATE.XXXXXX") || return 1
if printf '%s\n' "${lines[@]}" >"$tmp" && chmod 600 "$tmp" && sync "$tmp" && mv -f "$tmp" "$REKEY_STATE"; then
sync "$(dirname "$REKEY_STATE")"
return
fi
rm -f "$tmp"
return 1
}
staged_key_slot() {
[[ -f $PROVISIONING_DIR/luks-key ]] || return 0
luks_key_slot "$PROVISIONING_DIR/luks-key" "$1"
}
# The staged key or its boot-time auto-unlock is still on this machine.
luks_staged_unlock_remains() {
[[ -e $PROVISIONING_DIR/luks-key ]] || luks_auto_unlock_present
}
# Re-key work is due while the staged unlock remains or the journal is open.
luks_rekey_pending() {
[[ -e $REKEY_STATE ]] || luks_staged_unlock_remains
}
# Whether an interrupted re-key can finish with $password. Until the staged key
# is retired it can add any password; afterwards only one the disk holds.
luks_rekey_accepts_password() {
local -
set +x
local device=$1
[[ -f $REKEY_STATE ]] || return 0
[[ -n $(luks_slot_for "$password" "$device") || -n $(staged_key_slot "$device") ]]
}
# Record the slot $password opens, adding it with the staged key when none does.
# A retry after a crash between the add and the journal write finds the slot
# instead of adding a duplicate, and a retry with a new password adds its own
# while the staged key still works; the retire step removes the stale one.
luks_rekey_owner() {
local device=$1 phase=$2 owner
owner=$(luks_slot_for "$password" "$device")
if [[ -z $owner ]]; then
if [[ -z $(staged_key_slot "$device") ]]; then
log_step "the password opens no LUKS slot and the staged key no longer unlocks $device"
say --foreground 1 "Use the disk password chosen earlier in setup."
return 1
fi
if ! cryptsetup luksAddKey --key-file "$PROVISIONING_DIR/luks-key" "$device" <(printf '%s' "$password"); then
log_step "could not add the owner's key to $device"
say --foreground 1 "Could not add your password to the disk; will retry."
return 1
fi
owner=$(luks_slot_for "$password" "$device")
if [[ -z $owner ]]; then
log_step "could not identify the owner's LUKS slot after adding it"
say --foreground 1 "Could not confirm the LUKS re-key; will retry."
return 1
fi
fi
if [[ $owner == "$(rekey_state_get staged_slot || true)" ]]; then
log_step "the owner's password is the staged install key; refusing to keep it"
say --foreground 1 "Choose a disk password different from the temporary install key."
return 1
fi
if [[ $phase == "staged" ]]; then
rekey_state_put owner_slot "$owner" phase owner
else
rekey_state_put owner_slot "$owner"
fi
}
luks_rekey_retire() {
local device=$1 owner slot slots
owner=$(rekey_state_get owner_slot || true)
if [[ -z $owner ]]; then
log_step "no owner slot is recorded; refusing to retire LUKS slots"
return 1
fi
if ! slots=$(luks_dump_slots "$device"); then
log_step "luksDump failed while retiring slots; keeping the staged key for retry"
say --foreground 1 "Could not enumerate LUKS slots; will retry."
return 1
fi
for slot in $slots; do
[[ $slot == "$owner" ]] && 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
}
# The staged key must open nothing before it is destroyed: only the owner slot
# remains and no boot-time copy or unlock configuration is left behind.
luks_rekey_verify() {
local device=$1 owner slots
owner=$(rekey_state_get owner_slot || true)
if ! slots=$(luks_dump_slots "$device") || [[ -z $owner || $slots != "$owner" ]]; then
log_step "LUKS slots other than the owner's remain on $device"
return 1
fi
if [[ -n $(staged_key_slot "$device") ]]; then
log_step "the staged LUKS key still unlocks $device"
return 1
fi
if luks_auto_unlock_present; then
log_step "the boot-time auto-unlock is still configured"
return 1
fi
}
# The boot files carry the console keyboard layout the disk password is typed
# in at boot, so rebuild them again when the owner chose another layout since
# they were built, or when an older journal recorded none. The phase stays.
luks_rekey_boot_layout() {
local layout=$1
[[ $(rekey_state_get boot_layout || true) != "$layout" ]] || return 0
log_step "the boot files were not built with the current keyboard layout; rebuilding them"
if ! luks_auto_unlock_drop; then
say --foreground 1 "Could not rebuild the boot files for your keyboard layout; will retry."
return 1
fi
sync
rekey_state_put boot_layout "$layout"
}
# Order: record the staged slot, add the owner's key, rebuild boot without the
# auto-unlock (keeping the staged slot as the fallback while that can fail),
# retire every other slot, then verify, record the kept slot for the platform,
# destroy the staged key and record done.
# Failing is loud: silently keeping the staged key would leave the disk
# effectively unencrypted.
luks_rekey() {
local -
set +x
local device=$1 phase staged layout
if ! layout=$(luks_boot_layout) || [[ -z $layout ]]; then
log_step "could not fingerprint the keyboard layout for the boot files"
say --foreground 1 "Could not read the keyboard layout for the boot files; will retry."
return 1
fi
phase=$(rekey_state_get phase || true)
case $phase in
"")
staged=$(staged_key_slot "$device")
if [[ -z $staged ]]; then
log_step "the staged LUKS key is missing or does not unlock $device"
say --foreground 1 "The staged LUKS key no longer unlocks $device."
return 1
fi
rekey_state_put staged_slot "$staged" phase staged || return 1
phase=staged
;;
staged | owner | boot | done) ;;
*)
log_step "unknown LUKS re-key phase '$phase' in $REKEY_STATE"
say --foreground 1 "The LUKS re-key journal is unreadable."
return 1
;;
esac
if [[ $phase != "done" ]]; then
luks_rekey_owner "$device" "$phase" || return 1
[[ $phase == "staged" ]] && phase=owner
if [[ $phase == "owner" ]] || luks_auto_unlock_present; then
if ! luks_auto_unlock_drop; then
say --foreground 1 "Could not rebuild the boot files without the install key; will retry."
return 1
fi
sync
rekey_state_put phase boot boot_layout "$layout" || return 1
else
luks_rekey_boot_layout "$layout" || return 1
fi
luks_rekey_retire "$device" || return 1
else
if [[ $(luks_slot_for "$password" "$device") != "$(rekey_state_get owner_slot || true)" ]]; then
log_step "the password does not open the owner slot the finished re-key recorded on $device"
say --foreground 1 "Use the disk password chosen earlier in setup."
return 1
fi
luks_rekey_boot_layout "$layout" || return 1
fi
if ! luks_rekey_verify "$device"; then
say --foreground 1 "Could not confirm the temporary install key was removed; will retry."
return 1
fi
if ! luks_record_slots "$(rekey_state_get owner_slot)"; then
log_step "the platform could not record the kept LUKS slot"
say --foreground 1 "Could not record the disk's key slot for the boot checks; will retry."
return 1
fi
if [[ -e $PROVISIONING_DIR/luks-key ]]; then
shred -u "$PROVISIONING_DIR/luks-key" 2>/dev/null || rm -f "$PROVISIONING_DIR/luks-key" || return 1
fi
rm -f "$REKEY_STATE".??????
[[ $phase == "done" ]] || rekey_state_put phase done
}
@@ -0,0 +1,21 @@
# Runs the hardware setup an image build deferred, on the machine it was built
# for, before owner setup and the login screen. Armed with its queue by
# omarchy-apply-hardware during the build (install/helpers/image-target.sh);
# retried on every boot until the queue is empty and any keyring an image build
# asked for exists, then it disables itself (the unit file stays; with neither
# file left it no longer runs).
[Unit]
Description=Omarchy first-boot hardware setup deferred from the image build
ConditionPathExists=|/var/lib/omarchy/image/deferred-steps
ConditionPathExists=|/var/lib/omarchy/image/pacman-keyring
Before=omarchy-provision-owner.service display-manager.service
[Service]
Type=oneshot
ExecStart=/usr/bin/omarchy-provision-hardware
TimeoutStartSec=infinity
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target