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:
co-authored by
Claude Opus 5
parent
c7c897cb4f
commit
7644b56d4e
+50
-108
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# The setup form: every question Omarchy asks a human to describe their machine
|
||||
# — keyboard, account, hostname, timezone — plus the rules those answers are
|
||||
# checked against. Shared by the two places that ask them: the ISO
|
||||
# configurator's user step and this package's first-boot owner setup
|
||||
# (omarchy-provision-owner). Sourced by both, so the copies cannot drift the way
|
||||
# the keyboard list already did.
|
||||
#
|
||||
# Every prompt reports one of three statuses, and both callers read them the
|
||||
# same way:
|
||||
#
|
||||
# 0 field is set, move on
|
||||
# 1 Esc — unwind to the start of the form
|
||||
# 130 Ctrl+C — a per-caller side channel (the installer arms deferred
|
||||
# provisioning or toggles encryption; first-boot setup offers a reboot)
|
||||
#
|
||||
# gum is the reason those two are the whole vocabulary: Esc and Ctrl+C are the
|
||||
# only keys any gum widget exits on, and Ctrl+C arrives as a byte in raw mode,
|
||||
# so it never reaches the shell as SIGINT. Act on the status, never on a trap.
|
||||
#
|
||||
# Callers supply `notice <message> <seconds>` for validation feedback, and set
|
||||
# the variables these prompts write: keyboard, keyboard_label, username,
|
||||
# password, password_confirmation, full_name, email_address, hostname, timezone.
|
||||
|
||||
OMARCHY_FORM_BACK=1
|
||||
OMARCHY_FORM_SIGNAL=130
|
||||
|
||||
# The English layouts lead, then everything else alphabetically. gum choose
|
||||
# paginates in --height-sized pages and jumps to the page holding --selected,
|
||||
# so an alphabetical English (US) landed deep enough to sit alone at the edge
|
||||
# of a page of layouts nobody scanning for it reads. Up here the default and
|
||||
# its variants are the first thing on screen no matter how the list grows.
|
||||
OMARCHY_KEYBOARD_LAYOUTS=$'English (US)|us
|
||||
English (UK)|uk
|
||||
English (US, Dvorak)|dvorak
|
||||
English (US, Colemak)|colemak
|
||||
Azerbaijani|azerty
|
||||
Belarusian|by
|
||||
Belgian|be-latin1
|
||||
Bulgarian|bg-cp1251
|
||||
Croatian|croat
|
||||
Czech|cz
|
||||
Danish|dk-latin1
|
||||
Dutch|nl
|
||||
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'
|
||||
|
||||
OMARCHY_USERNAME_PATTERN='^[a-z_][a-z0-9_-]*[$]?$'
|
||||
OMARCHY_RESERVED_USERNAMES='^(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)$'
|
||||
OMARCHY_HOSTNAME_PATTERN='^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$'
|
||||
OMARCHY_HOSTNAME_DEFAULT='omarchy'
|
||||
|
||||
# Installer targets are empty, so any account is fair game; first-boot setup
|
||||
# overrides this because its machine already has users.
|
||||
omarchy_username_taken() { return 1; }
|
||||
|
||||
# `x=$(gum ...) && status=0 || status=$?` rather than a bare assignment followed
|
||||
# by `status=$?`: one caller runs under `set -e`, where a cancelled prompt is a
|
||||
# failing assignment that would kill the script before the status is read.
|
||||
|
||||
omarchy_prompt_keyboard() {
|
||||
local choice status
|
||||
choice=$(printf '%s\n' "$OMARCHY_KEYBOARD_LAYOUTS" | cut -d'|' -f1 |
|
||||
gum choose --height 10 --selected "English (US)" --header "Select keyboard layout") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
keyboard_label="$choice"
|
||||
keyboard=$(printf '%s\n' "$OMARCHY_KEYBOARD_LAYOUTS" | awk -F'|' -v c="$choice" '$1==c{print $2; exit}')
|
||||
}
|
||||
|
||||
omarchy_prompt_username() {
|
||||
local status
|
||||
while true; do
|
||||
username=$(gum input --placeholder "Alphanumeric without spaces (like dhh)" --prompt.foreground="#845DF9" --prompt "Username> ") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
if [[ "$username" =~ $OMARCHY_USERNAME_PATTERN ]]; then
|
||||
if [[ "$username" =~ $OMARCHY_RESERVED_USERNAMES ]]; then
|
||||
notice "Username is reserved for system" 1
|
||||
elif omarchy_username_taken "$username"; then
|
||||
notice "That username already exists on this machine" 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
notice "Username must be alphanumeric with no spaces" 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
omarchy_prompt_password() {
|
||||
local status
|
||||
while true; do
|
||||
password=$(gum input --placeholder "Used for user + root, and disk encryption when enabled" --prompt.foreground="#845DF9" --password --prompt "Password> ") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
password_confirmation=$(gum input --placeholder "Must match the password you just typed" --prompt.foreground="#845DF9" --password --prompt "Confirm> ") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
if [[ -n "$password" && "$password" == "$password_confirmation" ]]; then
|
||||
return 0
|
||||
elif [[ -z "$password" ]]; then
|
||||
notice "Your password can't be blank!" 1
|
||||
else
|
||||
notice "Passwords didn't match!" 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Both fields are skippable with Return, so an empty value is a real answer and
|
||||
# only Esc/Ctrl+C end the prompt early.
|
||||
omarchy_prompt_identity() {
|
||||
local status
|
||||
full_name=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Full name> ") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
email_address=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Email address> ") && status=0 || status=$?
|
||||
return $status
|
||||
}
|
||||
|
||||
omarchy_prompt_hostname() {
|
||||
local status
|
||||
while true; do
|
||||
hostname=$(gum input --placeholder "Letters, digits, and dashes (or return for 'omarchy')" --prompt.foreground="#845DF9" --prompt "Hostname> ") && status=0 || status=$?
|
||||
((status == 0)) || return $status
|
||||
|
||||
if [[ -z $hostname ]]; then
|
||||
hostname="$OMARCHY_HOSTNAME_DEFAULT"
|
||||
return 0
|
||||
elif [[ "$hostname" =~ $OMARCHY_HOSTNAME_PATTERN ]]; then
|
||||
return 0
|
||||
else
|
||||
notice "Hostname must be 1-63 letters, digits, or dashes, and cannot start or end with a dash" 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# A fresh machine often hasn't joined a network yet, so the geo guess fails
|
||||
# often; guard it or a `set -e` caller dies before the filter fallback.
|
||||
omarchy_prompt_timezone() {
|
||||
local guess status
|
||||
guess=$(tzupdate -p 2>/dev/null) || guess=""
|
||||
|
||||
if [[ -n $guess ]]; then
|
||||
timezone=$(timedatectl list-timezones | gum choose --height 10 --selected "$guess" --header "Timezone") && status=0 || status=$?
|
||||
else
|
||||
timezone=$(timedatectl list-timezones | gum filter --height 10 --header "Timezone") && status=0 || status=$?
|
||||
fi
|
||||
((status == 0)) || return $status
|
||||
|
||||
[[ -n $timezone ]] || timezone="UTC"
|
||||
}
|
||||
Reference in New Issue
Block a user