Extract the setup form shared with the ISO installer (#6669)

The keyboard layout list, the account and hostname validation rules, and the
gum prompts that ask for them all existed twice: once in the ISO
configurator's user step, once in first-boot owner setup. Nothing kept the
copies honest, and they had already drifted — a layout removed on one side
moved English (US) onto a page boundary on the other, burying the default at
the bottom of a screen of layouts.

install/provisioning/setup-form.sh is now the only copy. The PKGBUILD's
existing `cp -a install` ships it to /usr/share/omarchy/install/provisioning/,
and the ISO build vendors that very file out of the runtime package it
bundles, so an install and the first boot that finishes it cannot offer
different layouts or accept different usernames.

Cancel handling is unified along the way, which is what made the prompts
shareable at all. Every prompt reports 0 (answered), 1 (Esc — unwind to the
start of the form), or 130 (Ctrl+C — a side channel each caller defines).
Previously Esc and Ctrl+C were indistinguishable here: both re-asked the same
field, so there was no way back to an earlier answer. Ctrl+C now offers a
confirmed reboot instead. It cannot be a SIGINT trap — gum reads Ctrl+C as a
byte in raw mode, so the shell never receives the signal — so it hangs off the
exit status.

Status capture is written as `x=$(gum ...) && status=0 || status=$?` because
this script runs under `set -e`, where a cancelled prompt is a failing
assignment that would kill setup before the status could be read.

English (US) also leads the layout list now, ahead of the other English
variants. gum choose paginates in --height-sized pages and jumps to the page
holding --selected, so an alphabetical default landed wherever the list length
happened to put it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-08-10 11:19:23 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent c7c897cb4f
commit 7644b56d4e
2 changed files with 233 additions and 108 deletions
+50 -108
View File
@@ -33,6 +33,16 @@ fi
export PATH="$OMARCHY_PATH/bin:$PATH"
# The shared setup form — the same questions, prompts, and validation the ISO
# configurator's user step asks. The ISO vendors this very file out of the
# runtime package it bundles, so an install and the first boot that finishes it
# can never offer different layouts or accept different usernames.
source "$OMARCHY_PATH/install/provisioning/setup-form.sh"
# Unlike a fresh install target, this machine already has accounts; a pinned
# resume (below) bypasses the prompt entirely.
omarchy_username_taken() { getent passwd "$1" >/dev/null; }
LOGO_PATH="$OMARCHY_PATH/logo.txt"
LOGO_WIDTH=$(awk '{ if (length > max) max = length } END { print max+0 }' "$LOGO_PATH" 2>/dev/null || echo 0)
LOGO_HEIGHT=$(wc -l <"$LOGO_PATH" 2>/dev/null || echo 0)
@@ -539,62 +549,34 @@ greeter_screen() {
# (live VT + persisted) so the password typed next, and the LUKS re-key below,
# use the chosen layout.
keyboard_form() {
step "Let's setup your keyboard..."
local keyboards choice
keyboards=$'Azerbaijani|azerty
Belarusian|by
Belgian|be-latin1
Bulgarian|bg-cp1251
Croatian|croat
Czech|cz
Danish|dk-latin1
Dutch|nl
English (UK)|uk
English (US)|us
English (US, Dvorak)|dvorak
English (US, Colemak)|colemak
Estonian|et
Finnish|fi
French|fr
French (Canada)|cf
French (Switzerland)|fr_CH
Georgian|ge
German|de
German (Switzerland)|de_CH-latin1
Greek|gr
Hebrew|il
Hungarian|hu
Icelandic|is-latin1
Irish|ie
Italian|it
Japanese|jp106
Kazakh|kazakh
Kyrgyz|kyrgyz
Lao|la-latin1
Latvian|lv
Lithuanian|lt
Macedonian|mk-utf
Norwegian|no-latin1
Polish|pl
Portuguese|pt-latin1
Portuguese (Brazil)|br-abnt2
Romanian|ro
Russian|ru
Serbian|sr-latin
Slovak|sk-qwertz
Slovenian|slovene
Spanish|es
Spanish (Latin American)|la-latin1
Swedish|sv-latin1
Tajik|tj_alt-UTF8
Turkish|trq
Ukrainian|ua'
choice=$(printf '%s\n' "$keyboards" | cut -d'|' -f1 | gum choose --height 10 --selected "English (US)" --header "Select keyboard layout") || choice="English (US)"
keyboard=$(printf '%s\n' "$keyboards" | awk -F'|' -v c="$choice" '$1==c{print $2; exit}')
keyboard_label="$choice"
local status
while true; do
step "Let's setup your keyboard..."
omarchy_prompt_keyboard && status=0 || status=$?
((status == 0)) && break
# Esc means "back", and nothing precedes the first screen, so re-ask.
((status == OMARCHY_FORM_BACK)) && continue
# Ctrl+C is the way out of a screen the owner cannot get past. Rebooting is
# safe but not an escape from setup: this service runs again at next boot,
# and encrypted machines still auto-unlock from the staged keyfile until the
# re-key below. Confirm it so a stray Ctrl+C doesn't bounce the machine.
confirm_reboot && exec systemctl reboot
done
apply_keyboard "$keyboard"
}
confirm_reboot() {
clear_logo
echo
say "Setup starts again after the reboot."
echo
gum confirm --affirmative "Yes, reboot" --negative "No, keep setting up" "Reboot this machine?"
}
# Load the layout on the live VT and persist it for the installed system.
# systemd-firstboot writes both the console KEYMAP and the XKB layout Hyprland
# reads, matching what the ISO's configure_keyboard does at install time. A
@@ -624,67 +606,19 @@ user_form() {
say "Continuing setup for user: $username"
echo
else
while true; do
username=$(gum input --placeholder "Alphanumeric without spaces (like dhh)" --prompt.foreground="#845DF9" --prompt "Username> ") || continue
if [[ "$username" =~ ^[a-z_][a-z0-9_-]*[$]?$ ]]; then
if [[ "$username" =~ ^(root|bin|daemon|mail|ftp|http|nobody|dbus|systemd-coredump|systemd-network|systemd-oom|systemd-journal-remote|systemd-resolve|systemd-timesync|tss|uuidd|alpm|git|avahi|cups|lp|_talkd|polkitd|rtkit|qemu|brltty|gluster|rpc|libvirt-qemu|pcscd|nvidia-persistenced|sddm)$ ]]; then
notice "Username is reserved for system" 1
elif getent passwd "$username" >/dev/null; then
# provisioning state has no users, so any existing account is off limits;
# a pinned resume (above) bypasses this prompt entirely.
notice "That username already exists on this machine" 1
else
break
fi
else
notice "Username must be alphanumeric with no spaces" 1
fi
done
omarchy_prompt_username || return $?
fi
while true; do
password=$(gum input --placeholder "Used for user + root, and disk encryption when enabled" --prompt.foreground="#845DF9" --password --prompt "Password> ") || continue
password_confirmation=$(gum input --placeholder "Must match the password you just typed" --prompt.foreground="#845DF9" --password --prompt "Confirm> ") || continue
if [[ -n "$password" && "$password" == "$password_confirmation" ]]; then
break
elif [[ -z "$password" ]]; then
notice "Your password can't be blank!" 1
else
notice "Passwords didn't match!" 1
fi
done
full_name=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Full name> ") || full_name=""
email_address=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Email address> ") || email_address=""
omarchy_prompt_password || return $?
omarchy_prompt_identity || return $?
# Hostname and timezone are deferred to first boot with the rest of the user
# step: the deferred install seeds neutral placeholders (omarchy/UTC) and the
# owner overwrites them here. Same validation and geo-guess the ISO installer's
# user step uses.
while true; do
hostname=$(gum input --placeholder "Letters, digits, and dashes (or return for 'omarchy')" --prompt.foreground="#845DF9" --prompt "Hostname> ") || hostname=""
if [[ -z $hostname ]]; then
hostname="omarchy"; break
elif [[ $hostname =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$ ]]; then
break
else
notice "Hostname must be 1-63 letters, digits, or dashes, and cannot start or end with a dash" 1
fi
done
# owner overwrites them here.
omarchy_prompt_hostname || return $?
local geo_guessed_timezone
step "Let's set your timezone..."
# A fresh machine usually hasn't joined a network yet, so tzupdate fails often;
# guard it or `set -e` would abort setup before the gum-filter fallback below.
geo_guessed_timezone=$(tzupdate -p 2>/dev/null) || geo_guessed_timezone=""
if [[ -n $geo_guessed_timezone ]]; then
timezone=$(timedatectl list-timezones | gum choose --height 10 --selected "$geo_guessed_timezone" --header "Timezone") || timezone="$geo_guessed_timezone"
else
timezone=$(timedatectl list-timezones | gum filter --height 10 --header "Timezone") || timezone="UTC"
fi
[[ -n $timezone ]] || timezone="UTC"
omarchy_prompt_timezone || return $?
}
confirm_form() {
@@ -1059,9 +993,17 @@ run_provisioning() {
}
run_setup() {
local status
while true; do
keyboard_form
user_form
user_form && status=0 || status=$?
if ((status != 0)); then
# Esc unwinds to the keyboard step. Ctrl+C is the only other way out of a
# prompt, and it offers the reboot instead of returning a value.
((status == OMARCHY_FORM_SIGNAL)) && confirm_reboot && exec systemctl reboot
continue
fi
confirm_form && break
done