Files
omarchy-iso/configs/airootfs/root/configurator
T
ZacharyZhang-NY e22a13dfeb Ask for the language first, and install in Chinese in a CJK console
The first screen offers English or Simplified Chinese. Chinese reruns
the configurator in cage + foot, which draw Chinese, with Noto Sans
Mono CJK unpacked from the offline mirror's noto-fonts-cjk; no font
joins the live image. There the keyboard list keeps US-keymap layouts
and preselects Chinese (Simplified, Pinyin), every later screen and
the install dashboard read in Chinese, and the target gets
LANG=zh_CN.UTF-8 through locale_config. Without a display or the
font, Chinese continues in English on the VT. The summary shows the
keyboard by its label instead of its keymap.

Needs omarchy's omarchy_prompt_language and omarchy_text in
install/provisioning/setup-form.sh.
2026-10-12 01:21:41 -04:00

1382 lines
50 KiB
Bash

#!/bin/bash
# On the live ISO, .automated_script.sh exports OMARCHY_PATH and the package
# targets before running us. The defaults keep bin/omarchy-iso-configurator
# dry runs working outside the ISO.
OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/omarchy}"
OMARCHY_RUNTIME_PACKAGE="${OMARCHY_RUNTIME_PACKAGE:-omarchy}"
OMARCHY_SETTINGS_PACKAGE="${OMARCHY_SETTINGS_PACKAGE:-omarchy-settings}"
LOGO_PATH="$OMARCHY_PATH/logo.txt"
# The setup form — keyboard, account, hostname, and timezone questions, plus the
# rules their answers are checked against — shared verbatim with the first-boot
# owner setup that finishes a deferred install. build-iso.sh vendors it out of
# the runtime package this ISO bundles, so the two can never offer different
# layouts or accept different usernames. On the ISO that vendored copy is what
# loads; a dry run outside the ISO falls back to the omarchy checkout
# bin/omarchy-iso-configurator already points OMARCHY_PATH at.
SETUP_FORM="${SETUP_FORM:-/usr/share/omarchy-iso/setup-form.sh}"
[[ -r $SETUP_FORM ]] || SETUP_FORM="$OMARCHY_PATH/install/provisioning/setup-form.sh"
if [[ ! -r $SETUP_FORM ]]; then
echo "ERROR: shared setup form not found at $SETUP_FORM" >&2
exit 1
fi
source "$SETUP_FORM"
# Chinese text for OMARCHY_INSTALL_LANG=zh; disk-step failures stay English for support logs.
OMARCHY_ZH+=(
["Aborted installation"]="安装已中止"
["You can retry later by running: ./.automated_script.sh"]="稍后可运行 ./.automated_script.sh 重试"
["Let's setup your machine..."]="开始设置这台机器……"
["Press Ctrl+C to prepare this machine for another owner."]="按 Ctrl+C 为另一位使用者准备这台机器。"
["This prepares the machine for another owner."]="这会为另一位使用者准备这台机器。"
["The system installs now, but setup is delayed until first boot."]="系统现在安装,设置留到首次开机。"
["Yes, prepare for another owner"]="是,为他人准备"
["No, keep setting up"]="否,继续设置"
["Prepare this machine for another owner?"]="为另一位使用者准备这台机器?"
["Let's setup your user account..."]="开始设置用户账户……"
["Field"]="项目"
["Value"]="值"
["Username"]="用户名"
["Password"]="密码"
["Full name"]="姓名"
["Email address"]="邮箱"
["Hostname"]="主机名"
["Keyboard"]="键盘"
["Region"]="区域"
["[Skipped]"]="[已跳过]"
["Yes"]="是"
["No, change it"]="否,去修改"
["Does this look right?"]="以上信息正确吗?"
["Not enough free space on %s"]="%s 的空闲空间不足"
["%s has %s of usable free space; Omarchy needs at least 32GB."]="%s 有 %s 可用空闲空间;Omarchy 至少需要 32GB。"
["Open the partition tool to free at least 32GB on %s, then try again."]="打开分区工具,在 %s 上腾出至少 32GB,然后重试。"
["Back"]="返回"
["Open partition tool"]="打开分区工具"
["Return to installation mode?"]="返回安装方式选择?"
["Checking for BitLocker on %s"]="检查 %s 上的 BitLocker"
["BitLocker signature detected on %s."]="在 %s 上发现 BitLocker 签名。"
["Turn BitLocker off in Windows and wait for the drive to finish decrypting, then try again."]="在 Windows 中关闭 BitLocker,等磁盘解密完成后重试。"
["Suspending BitLocker is not enough — the drive stays encrypted."]="只暂停 BitLocker 不够,磁盘仍是加密的。"
["Aborted: BitLocker is enabled on this disk."]="已中止:这块磁盘开启了 BitLocker。"
["Checking existing EFI partitions on %s"]="检查 %s 上已有的 EFI 分区"
["Found a Windows ESP at %s — leaving it untouched."]="在 %s 发现 Windows ESP,保持不动。"
["Omarchy will create its own dedicated EFI partition in free space."]="Omarchy 会在空闲空间中创建自己的 EFI 分区。"
["A new EFI partition will be created in free space."]="会在空闲空间中新建 EFI 分区。"
["Analyzing free space on %s"]="分析 %s 的空闲空间"
["Install Omarchy in the %s of free space."]="在 %s 空闲空间中安装 Omarchy。"
["Press Ctrl+C for unencrypted install."]="按 Ctrl+C 改为不加密安装。"
["Yes, install"]="是,开始安装"
["Yes, install without encryption"]="是,不加密安装"
["Confirm installing on %s"]="确认安装到 %s"
["Creating partitions on %s"]="在 %s 上创建分区"
["Setting up LUKS2 on %s"]="在 %s 上设置 LUKS2 加密"
["Creating Btrfs filesystem and subvolumes"]="创建 Btrfs 文件系统和子卷"
["Let's select where to install Omarchy..."]="选择 Omarchy 的安装位置……"
["Select install disk"]="选择安装磁盘"
["Partition tool for %s"]="%s 的分区工具"
["Create unallocated free space for Omarchy, then write changes and quit."]="为 Omarchy 腾出未分配空间,然后写入更改并退出。"
["Do not create an Omarchy partition here — leave the target area as Free space."]="不要在这里创建 Omarchy 分区,目标区域保持为 Free space。"
["Tip: free-space install needs at least 32GB unallocated, plus 2GB more if no ESP already exists."]="提示:空闲空间安装至少需要 32GB 未分配空间;没有 ESP 时再加 2GB。"
["Open cfdisk"]="打开 cfdisk"
["Open partition tool for %s?"]="打开 %s 的分区工具?"
["Full disk install"]="整盘安装"
["Free space install (alongside existing data)"]="空闲空间安装(保留现有数据)"
["Choose a different disk"]="换一块磁盘"
["Let's select how to install Omarchy..."]="选择 Omarchy 的安装方式……"
["Select installation mode on %s"]="选择 %s 上的安装方式"
["Everything will be overwritten. There is no recovery possible."]="磁盘上的一切都会被覆盖,无法恢复。"
["Confirm overwriting %s"]="确认覆盖 %s"
)
# Partition creation helpers, shared with test/partition-numbering-test.sh so
# the numbering logic can be exercised without a real disk.
DISK_PARTITIONING="${DISK_PARTITIONING:-/usr/share/omarchy-iso/disk-partitioning.sh}"
[[ -r $DISK_PARTITIONING ]] ||
DISK_PARTITIONING="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../usr/share/omarchy-iso/disk-partitioning.sh"
if [[ ! -r $DISK_PARTITIONING ]]; then
echo "ERROR: disk partitioning helpers not found at $DISK_PARTITIONING" >&2
exit 1
fi
source "$DISK_PARTITIONING"
LOGO_WIDTH=$(awk '{ if (length > max) max = length } END { print max+0 }' "$LOGO_PATH")
export GUM_CONFIRM_PROMPT_FOREGROUND="6"
export GUM_CONFIRM_SELECTED_FOREGROUND="0"
export GUM_CONFIRM_SELECTED_BACKGROUND="2"
export GUM_CONFIRM_UNSELECTED_FOREGROUND="7"
export GUM_CONFIRM_UNSELECTED_BACKGROUND="0"
# The console can grow after boot (the framebuffer settles late, especially in
# VMs), so remeasure at every redraw rather than trusting startup geometry.
# gum draws on /dev/tty, so measure that rather than inherited COLUMNS.
measure_terminal() {
TERM_WIDTH=$(stty size 2>/dev/null </dev/tty | awk '{print $2}')
(( TERM_WIDTH > 0 )) || TERM_WIDTH=${COLUMNS:-80}
PADDING_LEFT=$(((TERM_WIDTH - LOGO_WIDTH) / 2))
(( PADDING_LEFT < 0 )) && PADDING_LEFT=0
PADDING_LEFT_SPACES=$(printf "%*s" "$PADDING_LEFT" "")
PADDING="0 0 0 $PADDING_LEFT"
export GUM_CHOOSE_PADDING="$PADDING"
export GUM_FILTER_PADDING="$PADDING"
export GUM_INPUT_PADDING="$PADDING"
export GUM_SPIN_PADDING="$PADDING"
export GUM_TABLE_PADDING="$PADDING"
export GUM_CONFIRM_PADDING="$PADDING"
}
measure_terminal
# The live VT resizes late during boot: tty1 starts as the 80-column boot
# console and only grows once the kernel framebuffer/KMS takes over (noticeably
# late in VMs). The logo is wider than 80 columns, so a first draw against that
# transient narrow console wraps the logo ("smushed") and can't center it
# (padding clamps to 0, so it hugs the left). A terminal emulator (or sudo's
# pty) has the same problem the other way: it reports a stale 24x80 for a few
# hundred ms after start before its real winsize is set. Either way, hold the
# first draw until the width settles at (or above) the logo width. A real VT
# that never widens (e.g. nomodeset) still proceeds after the cap.
wait_for_stable_terminal() {
local width last="" stable=0 waited=0
while (( waited < 5000 )); do
width=$(stty size 2>/dev/null </dev/tty | awk '{print $2}')
[[ $width =~ ^[0-9]+$ ]] || width=0
if [[ $width == "$last" ]] && (( width >= LOGO_WIDTH )); then
(( ++stable >= 3 )) && break
else
stable=0
fi
last=$width
sleep 0.2
(( waited += 200 ))
done
}
clear_logo() {
measure_terminal
printf "\033[H\033[2J"
gum style --foreground 2 --padding "1 0 0 $PADDING_LEFT" "$(<"$LOGO_PATH")"
}
# Vertically-centered welcome shown once before the keyboard step — the same
# frame the deferred-provisioning first boot opens on, so install and first boot
# bookend the setup identically. Logo, the Omarchy tagline, and a single button,
# centered like the boot logo. No animation.
greeter() {
measure_terminal
local rows logo_h content_h top cols logo_row tagline_row hint_row
local tagline hint tpad hpad anim
cols=$TERM_WIDTH
rows=$(stty size 2>/dev/null </dev/tty | awk '{print $1}')
[[ $rows =~ ^[0-9]+$ ]] || rows=${LINES:-24}
logo_h=$(wc -l <"$LOGO_PATH")
# logo + blank + tagline + blank + hint
content_h=$((logo_h + 4))
top=$(((rows - content_h) / 2))
(( top < 0 )) && top=0
logo_row=$((top + 1))
tagline_row=$((top + logo_h + 2))
hint_row=$((top + logo_h + 4))
printf '\033[?25l\033[H\033[2J'
printf '\033[%d;1H' "$logo_row"
gum style --foreground 2 --padding "0 0 0 $PADDING_LEFT" "$(<"$LOGO_PATH")"
tagline="Beautiful, Fun & Agentic Linux by DHH"
tpad=$(((cols - ${#tagline}) / 2)); (( tpad < 0 )) && tpad=0
printf '\033[%d;%dH%s' "$tagline_row" "$((tpad + 1))" "$tagline"
hint="Press Return to Start Install"
hpad=$(((cols - ${#hint}) / 2)); (( hpad < 0 )) && hpad=0
printf '\033[%d;%dH\033[2m%s\033[0m' "$hint_row" "$((hpad + 1))" "$hint"
# ColorShift the logo: a green base (indexed color 2) with a cyan accent (6)
# drifting through, settling on green. Indexed ANSI colors, not hex — the
# framebuffer console can't render truecolor faithfully (it crushes the
# palette to a muddy lavender), but the 16 indexed colors map to the Tokyo
# Night palette and render true. --xterm-colors is what keeps them indexed:
# ttfx resolves even indexed stops to truecolor otherwise, which would put the
# settle color out of step with the `gum style --foreground 2` logo underneath.
# One long-running invocation (many cycles) so it never restarts — a restart is
# what flashed. The effect reads from /dev/null so it never swallows the
# Return; --reuse-canvas paints upward from the saved cursor, anchored one row
# below the logo.
printf '\033[%d;1H\0337' "$((logo_row + logo_h))"
# Run ttfx directly (not inside a `while` subshell) so $anim is ttfx's own
# PID: killing a wrapping subshell would orphan ttfx, which then keeps
# painting the logo over the keyboard step. --cycles is high enough that it
# never ends on its own before Return.
# Two columns narrower than the console: ttfx centers its text two columns
# right of plain centering, so a full-width canvas would sit the animated
# logo a column off the gum-drawn one underneath it.
ttfx -i "$LOGO_PATH" \
--canvas-width "$((cols > 2 ? cols - 2 : cols))" \
--anchor-text c \
--frame-rate 60 \
--reuse-canvas \
--xterm-colors \
colorshift \
--gradient-stops 2 10 6 10 \
--gradient-frames 3 \
--cycles 1000 \
--final-gradient-stops 2 \
</dev/null >/dev/tty 2>/dev/null &
anim=$!
IFS= read -r _ </dev/tty || true
# Tolerate ttfx's kill signal in `wait` so a future `set -e` here can't abort.
kill "$anim" 2>/dev/null
wait "$anim" 2>/dev/null || true
# ttfx leaves the tty in raw/no-echo mode when killed; restore it or the gum
# prompts in the keyboard step that follows silently die.
stty sane </dev/tty 2>/dev/null || true
# Reset the screen so the leftover animation frame doesn't linger under the
# keyboard step that follows.
printf '\033[0m\033[H\033[2J\033[?25h'
}
abort() {
gum style "${1:-$(omarchy_text "Aborted installation")}"
echo
gum style "$(omarchy_text "You can retry later by running: ./.automated_script.sh")"
exit 1
}
step() {
clear_logo
echo
gum style --padding "0 0 0 $PADDING_LEFT" "$1"
echo
}
# Padded body text to line up with the logo and gum widgets.
say() {
gum style --padding "0 0 0 $PADDING_LEFT" "$@"
}
notice() {
clear_logo
echo
gum spin --spinner "pulse" --title "$1" -- sleep "${2:-2}"
echo
}
# STEP 1: KEYBOARD LAYOUT
# The target's LANG follows the language picked on the first screen.
target_lang() {
if [[ $language == "zh" ]]; then
echo zh_CN.UTF-8
else
echo en_US.UTF-8
fi
}
# .automated_script.sh moves Chinese into the CJK console and English back to the VT, which applies keymaps.
apply_language() {
if [[ $language == "zh" && -n ${OMARCHY_CJK_RELAUNCH:-} ]]; then
exit "$OMARCHY_CJK_RELAUNCH"
fi
if [[ $language == "en" && -n ${OMARCHY_VT_RETURN:-} ]]; then
exit "$OMARCHY_VT_RETURN"
fi
if [[ $language == "zh" && -n ${OMARCHY_CJK_CONSOLE:-} ]]; then
export OMARCHY_INSTALL_LANG=zh
else
unset OMARCHY_INSTALL_LANG
fi
}
keyboard_form() {
# Ctrl+C is the hidden entry into deferred provisioning, offered on every
# visit: the user step unwinds back here and nothing is on disk yet. Callers
# re-check defer_provisioning after this returns.
local status
while true; do
clear_logo
echo
say "$(omarchy_text "Let's setup your machine...")"
say --foreground 8 "$(omarchy_text "Press Ctrl+C to prepare this machine for another owner.")"
echo
status=0
if ! $language_chosen; then
omarchy_prompt_language && status=0 || status=$?
((status == 0)) && apply_language
fi
# Esc, Ctrl+C or a later "No, change it" asks the language again.
language_chosen=false
if ((status == 0)); then
local layouts=$OMARCHY_KEYBOARD_LAYOUTS selected="English (US)"
[[ $language == "zh" ]] && selected="Chinese (Simplified, Pinyin)"
# cage keeps its US layout, so the CJK console offers only US-keymap keyboards.
[[ -n ${OMARCHY_CJK_CONSOLE:-} ]] && layouts=$(awk -F'|' '$2 == "us"' <<<"$layouts")
OMARCHY_KEYBOARD_LAYOUTS=$layouts omarchy_prompt_keyboard "$selected" && status=0 || status=$?
fi
((status == 0)) && break
# Esc means "back", and nothing precedes the first screen, so re-ask.
((status == OMARCHY_FORM_BACK)) && continue
if ((status == OMARCHY_FORM_SIGNAL)); then
if confirm_prepare_for_another_owner; then
defer_provisioning=true
return 0
fi
continue # declined: back to the keyboard picker
fi
abort
done
# Only attempt to load keyboard layout if we're on a real console
# loadkeys only works on Linux virtual consoles (tty), not in terminal emulators
if [[ $(tty 2>/dev/null) == "/dev/tty"* ]]; then
loadkeys "$keyboard" 2>/dev/null
fi
}
# Ctrl+C on the first screen offers to prepare the machine for another owner.
# Confirm it before committing, so an accidental Ctrl+C doesn't silently switch
# the whole install.
confirm_prepare_for_another_owner() {
clear_logo
echo
say "$(omarchy_text "This prepares the machine for another owner.")"
say --foreground 8 "$(omarchy_text "The system installs now, but setup is delayed until first boot.")"
echo
gum confirm --affirmative "$(omarchy_text "Yes, prepare for another owner")" --negative "$(omarchy_text "No, keep setting up")" \
"$(omarchy_text "Prepare this machine for another owner?")"
}
# The user step. Deferred-provisioning installs skip it entirely — the machine's first owner
# picks their keyboard and creates their user in the first-boot setup, so the
# operator sets nothing user-specific.
user_form() {
step "$(omarchy_text "Let's setup your user account...")"
# Each prompt reports 0 (set), OMARCHY_FORM_BACK (Esc), or OMARCHY_FORM_SIGNAL
# (Ctrl+C). Both non-zero cases unwind to user_step, which decides what they
# mean here; the form itself never aborts.
omarchy_prompt_username || return $?
omarchy_prompt_password || return $?
# Hash the password with SHA-512 crypt ($6$), which is what openssl passwd offers
password_hash=$(printf '%s' "$password" | openssl passwd -6 -stdin)
omarchy_prompt_identity || return $?
omarchy_prompt_hostname || return $?
omarchy_prompt_timezone || return $?
}
# Returns 0 on confirmed account details, or as soon as the keyboard screen
# arms deferred provisioning.
user_step() {
local status
while true; do
user_form && status=0 || status=$?
if ((status != 0)); then
# Esc unwinds to the start of the form. Ctrl+C has no side channel in the
# user step (only the keyboard and disk screens give it a meaning), so it
# keeps its long-standing behaviour of ending the install.
((status == OMARCHY_FORM_BACK)) || abort
keyboard_form
$defer_provisioning && return 0
continue
fi
# The timezone selects the region (mirrors and repositories), using the
# same lookup as the orchestrator. Global installs get no extra row.
region=$(cd /usr/share/omarchy-iso 2>/dev/null && python -m orchestrator.region "$timezone") || region="global"
region_row=""
if [[ $region != "global" ]]; then
region_row="\n$(omarchy_text Region),$region"
fi
# Add manual padding since gum table -p doesn't respect padding
local skipped
skipped=$(omarchy_text "[Skipped]")
echo -e "$(omarchy_text Field),$(omarchy_text Value)
$(omarchy_text Username),$username
$(omarchy_text Password),$(printf "%${#password}s" | tr ' ' '*')
$(omarchy_text "Full name"),${full_name:-$skipped}
$(omarchy_text "Email address"),${email_address:-$skipped}
$(omarchy_text Hostname),$hostname
$(omarchy_text Timezone),$timezone$region_row
$(omarchy_text Keyboard),\"$keyboard_label\"" |
gum table -s "," -p | sed "s/^/${PADDING_LEFT_SPACES}/"
echo
if gum confirm --affirmative "$(omarchy_text "Yes")" --negative "$(omarchy_text "No, change it")" "$(omarchy_text "Does this look right?")"; then
break
else
keyboard_form
$defer_provisioning && return 0
fi
done
}
# STEP 3: DISK
get_disk_info() {
local device="$1"
local size vendor model label
# Get basic disk information
size=$(lsblk -dno SIZE "$device" 2>/dev/null)
vendor=$(lsblk -dno VENDOR "$device" 2>/dev/null | sed 's/ *$//')
model=$(lsblk -dno MODEL "$device" 2>/dev/null | sed 's/ *$//')
# Combine vendor and model, avoiding duplication
label=""
if [[ -n $vendor && -n $model ]]; then
if [[ $model == *$vendor* ]]; then
label="$model"
else
label="$vendor $model"
fi
elif [[ -n $model ]]; then
label="$model"
elif [[ -n $vendor ]]; then
label="$vendor"
fi
# Format display string
local display="$device"
[[ -n $size ]] && display="$display ($size)"
[[ -n $label ]] && display="$display - $label"
# Append compact partition summary
local part_summary
part_summary=$(lsblk -nro TYPE,NAME,FSTYPE,MOUNTPOINT "$device" 2>/dev/null | \
awk '$1=="part" { printf "%s%s%s", s, ($3==""?"unknown":$3), ($4==""?"":"("$4")"); s=", " }')
[[ -n $part_summary ]] && display+=" [$part_summary]"
echo "$display"
}
get_root_disk() {
local device="$1"
local parent
[[ -n $device ]] || return 1
# /run/archiso/bootmnt usually resolves to the boot partition, but the
# installer picker works with whole disks. Walk back to the parent disk so
# the install media itself never shows up as a wipe target.
device=$(readlink -f "$device" 2>/dev/null || printf "%s\n" "$device")
while true; do
parent=$(lsblk -dno PKNAME "$device" 2>/dev/null | tail -n1)
[[ -n $parent ]] || break
device="/dev/$parent"
done
if [[ $(lsblk -dno TYPE "$device" 2>/dev/null) == "disk" ]]; then
printf "%s\n" "$device"
fi
}
# partition_path lives in disk-partitioning.sh alongside the creation helpers.
# BitLocker writes the "-FVE-FS-" signature at offset 3 of the partition.
detect_bitlocker() {
local p sig
for p in $(lsblk -lnpo NAME,TYPE "$disk" | awk '$2=="part"{print $1}'); do
sig=$(dd if="$p" bs=1 skip=3 count=8 status=none 2>/dev/null)
if [[ "$sig" == "-FVE-FS-" ]]; then
echo "$p"
return 0
fi
done
return 1
}
# Look for an existing Windows ESP (FAT partition containing EFI/Microsoft) on $disk.
detect_windows_esp() {
local p tmp_mp
for p in $(blkid -t TYPE=vfat -o device 2>/dev/null); do
[[ "$p" == "$disk"* ]] || continue
tmp_mp=$(mktemp -d)
if mount -o ro "$p" "$tmp_mp" 2>/dev/null; then
if [[ -d "$tmp_mp/EFI/Microsoft" ]]; then
umount "$tmp_mp"; rmdir "$tmp_mp"
echo "$p"
return 0
fi
umount "$tmp_mp"
fi
rmdir "$tmp_mp" 2>/dev/null || true
done
return 1
}
to_gb() {
awk -v bytes="$1" 'BEGIN {printf "%.1fGB", bytes/1024/1024/1024}'
}
# T2 Macs need their own kernel for keyboard/wifi drivers.
detect_kernel() {
if lspci -nn 2>/dev/null | grep -q "106b:180[12]"; then
echo "linux-t2"
else
echo "linux-omarchy"
fi
}
# wait_for_device lives in disk-partitioning.sh: it now reports failure, and
# every caller here aborts on it.
# disk_step and create_partition fail through this rather than abort() so a
# half-built layout is undone before the user is handed back their disk.
disk_abort_hook() {
# Release the target before reclaiming its partitions: a mounted filesystem
# or an open LUKS mapping would keep parted from removing them.
umount -R /mnt 2>/dev/null || true
umount /mnt/btrfs-root 2>/dev/null || true
rmdir /mnt/btrfs-root 2>/dev/null || true
cryptsetup close omarchy_root 2>/dev/null || true
rollback_created_parts "$disk"
abort "$1"
}
# Save the configurator answers the installer consumes: git identity and
# encryption choice as plain files, account details as archinstall credentials.
# Deferred-provisioning installs defer the user to first boot: no credentials, and on encrypted
# targets the orchestrator generates a throwaway LUKS passphrase itself.
write_user_files() {
echo "$full_name" >user_full_name.txt
echo "$email_address" >user_email_address.txt
printf '%s\n' "$encrypt_installation" >user_encrypt_installation.txt
if $defer_provisioning; then
cat <<-_EOF_ >user_credentials.json
{
"users": []
}
_EOF_
return
fi
local password_escaped password_hash_escaped username_escaped
password_escaped=$(echo -n "$password" | jq -Rsa)
password_hash_escaped=$(echo -n "$password_hash" | jq -Rsa)
username_escaped=$(echo -n "$username" | jq -Rsa)
local credentials_encryption_line=""
if [[ $encrypt_installation == true ]]; then
credentials_encryption_line=" \"encryption_password\": $password_escaped,"
fi
cat <<-_EOF_ >user_credentials.json
{
$credentials_encryption_line
"root_enc_password": $password_hash_escaped,
"users": [
{
"enc_password": $password_hash_escaped,
"groups": [],
"sudo": true,
"username": $username_escaped
}
]
}
_EOF_
}
# Show what a dry run would have written, then leave no trace behind.
print_dry_run_files() {
echo -e "User Configuration:"
cat user_configuration.json
echo -e "\n\nUser Credentials:"
cat user_credentials.json
echo -e "\n\nUser Full Name:"
cat user_full_name.txt
echo -e "\nUser Email Address:"
cat user_email_address.txt
echo -e "\nDisk Encryption:"
cat user_encrypt_installation.txt
rm -f user_configuration.json user_credentials.json user_full_name.txt user_email_address.txt user_encrypt_installation.txt
}
# Shared error screen for every "can't fit Omarchy here" outcome of the
# partition install. Offers the partition tool as the meaningful second
# action so users have a way to actually fix the situation.
not_enough_space() {
local available="${1:-0}"
step "$(omarchy_text "Not enough free space on %s" "$disk")"
say --foreground 1 "$(omarchy_text "%s has %s of usable free space; Omarchy needs at least 32GB." "$disk" "$(to_gb $available)")"
say "$(omarchy_text "Open the partition tool to free at least 32GB on %s, then try again." "$disk")"
echo
if ! gum confirm --affirmative "$(omarchy_text "Back")" --negative "$(omarchy_text "Open partition tool")" "$(omarchy_text "Return to installation mode?")"; then
open_partition_tool
fi
return 1
}
# Decide half of the free-space install: checks, free-space analysis, and the
# encryption confirm. Sets the globals run_partition_execute needs. Returns 1
# to fall back to the install-mode picker.
run_partition_decide() {
step "$(omarchy_text "Checking for BitLocker on %s" "$disk")"
local bl_part
bl_part=$(detect_bitlocker || true)
if [[ -n "$bl_part" ]]; then
say --foreground 1 "$(omarchy_text "BitLocker signature detected on %s." "$bl_part")"
say "$(omarchy_text "Turn BitLocker off in Windows and wait for the drive to finish decrypting, then try again.")"
say "$(omarchy_text "Suspending BitLocker is not enough — the drive stays encrypted.")"
abort "$(omarchy_text "Aborted: BitLocker is enabled on this disk.")"
fi
# Omarchy always creates its own dedicated ESP in free space and never
# adopts an existing (Windows) ESP. Sharing the Windows ESP is fragile:
# Windows Update/repair can reclaim or reformat it, the typical 100-260MiB
# Windows ESP is far too small for our Unified Kernel Images, and a shared
# ESP forces the install unencrypted. A separate Linux ESP keeps Windows
# and Omarchy fully isolated and keeps LUKS on the table.
step "$(omarchy_text "Checking existing EFI partitions on %s" "$disk")"
local detected_win_esp
detected_win_esp=$(detect_windows_esp || true)
if [[ -n "$detected_win_esp" ]]; then
say "$(omarchy_text "Found a Windows ESP at %s — leaving it untouched." "$detected_win_esp")"
say "$(omarchy_text "Omarchy will create its own dedicated EFI partition in free space.")"
else
say "$(omarchy_text "A new EFI partition will be created in free space.")"
fi
step "$(omarchy_text "Analyzing free space on %s" "$disk")"
partprobe "$disk" 2>/dev/null || true
sleep 1
# An unlabeled disk has no partition table for parted to scan, so we
# synthesize a whole-disk free region and mklabel gpt before mkpart.
local FREE_SPACE_INFO pt_type disk_size_b
needs_mklabel=false
pt_type=$(lsblk -dno PTTYPE "$disk" 2>/dev/null)
if [[ -z "$pt_type" ]]; then
disk_size_b=$(lsblk -bdno SIZE "$disk" 2>/dev/null)
if [[ -n "$disk_size_b" && "$disk_size_b" -gt 0 ]]; then
local _mib=$((1024 * 1024))
local _start=$_mib
local _end=$(( (disk_size_b - _mib) / _mib * _mib ))
FREE_SPACE_INFO="$_start $_end $((_end - _start))"
needs_mklabel=true
fi
else
# -s (script mode) keeps parted from prompting on stderr for warnings
# like a stale GPT backup; without it the configurator hangs invisibly.
FREE_SPACE_INFO=$(parted -ms "$disk" unit B print free </dev/null 2>/dev/null | \
awk -F: '
$NF ~ /free/ {
start=$2; end=$3; size=$4;
gsub(/B/, "", start);
gsub(/B/, "", end);
gsub(/B/, "", size);
if (size+0 > max_size+0) {
max_size=size; max_start=start; max_end=end;
}
}
END {
if (max_size+0 > 0) printf "%s %s %s\n", max_start, max_end, max_size;
}')
fi
if [[ -z "$FREE_SPACE_INFO" ]]; then
not_enough_space 0
return $?
fi
local FREE_START_B FREE_END_B FREE_SIZE_B
read -r FREE_START_B FREE_END_B FREE_SIZE_B <<<"$FREE_SPACE_INFO"
local mib=$((1024 * 1024)) gib=$((1024 * 1024 * 1024))
local ALIGN=$mib
local EFI_SIZE_B=$((2 * gib))
local MIN_INSTALL_B=$((32 * gib)) # Minimum total Omarchy allocation
EFI_START_B=$(( (FREE_START_B + ALIGN - 1) / ALIGN * ALIGN ))
EFI_END_B=$((EFI_START_B + EFI_SIZE_B))
ROOT_START_B=$(( (EFI_END_B + 1 + ALIGN - 1) / ALIGN * ALIGN ))
ROOT_END_B=$((FREE_END_B / ALIGN * ALIGN))
if (( ROOT_END_B <= ROOT_START_B )); then
not_enough_space "$FREE_SIZE_B"
return $?
fi
local INSTALL_MAX_B=$((ROOT_END_B - EFI_START_B))
if (( INSTALL_MAX_B < MIN_INSTALL_B )); then
not_enough_space "$INSTALL_MAX_B"
return $?
fi
# Default to encrypted and hide the unencrypted path behind Ctrl+C,
# matching the full-disk flow. The dedicated Omarchy ESP always supports
# LUKS, so encryption is always on the table here.
local mode="encrypted" affirmative confirm_status
while true; do
clear_logo
echo
say "$(omarchy_text "Install Omarchy in the %s of free space." "$(to_gb $INSTALL_MAX_B)")"
case $mode in
encrypted)
say --foreground 8 "$(omarchy_text "Press Ctrl+C for unencrypted install.")"
affirmative="$(omarchy_text "Yes, install")"
;;
unencrypted)
affirmative="$(omarchy_text "Yes, install without encryption")"
;;
esac
echo
gum confirm --affirmative "$affirmative" --negative "$(omarchy_text "No, change it")" \
"$(omarchy_text "Confirm installing on %s" "$disk")"
confirm_status=$?
case $confirm_status in
0)
[[ $mode == "encrypted" ]] && encrypt_installation=true || encrypt_installation=false
break
;;
1)
return 1
;;
130)
case $mode in
encrypted) mode="unencrypted" ;;
unencrypted) mode="encrypted" ;;
esac
;;
*)
abort
;;
esac
done
if [[ "$encrypt_installation" == "true" ]]; then
esp_mount_in_target=/boot
else
esp_mount_in_target=/efi
fi
kernel_choice=$(detect_kernel)
# Partition numbers are not knowable until parted assigns them (it fills the
# lowest free GPT slot, and freeing space by deleting a partition leaves one
# below the highest). run_partition_execute reads them back after creation;
# a dry run has nothing to read.
efi_part_num=""
root_part_num=""
efi_dev="<assigned at creation>"
root_partition_device="<assigned at creation>"
root_mapper="<assigned at creation>"
if [[ "$encrypt_installation" == "true" ]]; then
root_mapper="/dev/mapper/omarchy_root"
fi
}
# Execute half of the free-space install: partition, format, mount, and write
# the pre-mounted (protected) install configuration. Runs after the user step
# so LUKS can be formatted with the user's password.
run_partition_execute() {
local dry=false subvol
[[ "${1:-}" == "dry" ]] && dry=true
if $dry; then
$needs_mklabel && say "[dry] would initialize GPT on $disk"
say "[dry] would create an EFI partition and a root partition in the free region"
say "[dry] partition numbers are assigned by parted at creation, then read back"
say "[dry] encrypted=$encrypt_installation kernel=$kernel_choice"
else
step "$(omarchy_text "Creating partitions on %s" "$disk")"
if $needs_mklabel; then
disk_step "initializing GPT on $disk" parted --script "$disk" mklabel gpt
partprobe "$disk" 2>/dev/null || true
sleep 1
fi
# create_partition reports the number parted actually used; nothing here
# may assume it. It sets created_partition_number rather than printing so
# the rollback bookkeeping survives (a $(...) would subshell it away).
create_partition "$disk" "$EFI_START_B" "$EFI_END_B" fat32 OMARCHY_EFI ||
disk_abort_hook "Could not create the EFI partition on $disk"
efi_part_num="$created_partition_number"
create_partition "$disk" "$ROOT_START_B" "$ROOT_END_B" btrfs OMARCHY_ROOT ||
disk_abort_hook "Could not create the root partition on $disk"
root_part_num="$created_partition_number"
disk_step "flagging partition $efi_part_num as ESP" \
parted --script "$disk" set "$efi_part_num" esp on
efi_dev=$(partition_path "$disk" "$efi_part_num")
root_partition_device=$(partition_path "$disk" "$root_part_num")
partprobe "$disk"
sync
sleep 2
wait_for_device "$efi_dev" || disk_abort_hook "EFI partition $efi_dev never appeared"
wait_for_device "$root_partition_device" ||
disk_abort_hook "Root partition $root_partition_device never appeared"
# Both partitions are ours and brand new, but the space they occupy may
# carry signatures from whatever was deleted to free it.
disk_step "clearing stale signatures on $efi_dev" wipefs -af "$efi_dev"
disk_step "clearing stale signatures on $root_partition_device" wipefs -af "$root_partition_device"
if [[ "$encrypt_installation" == "true" ]]; then
step "$(omarchy_text "Setting up LUKS2 on %s" "$root_partition_device")"
# Kept as pipes rather than routed through disk_step: the passphrase must
# not become an argv the process table can show. Folding stderr into
# stdout still lands any error in the install log.
if ! printf "%s" "$password" |
cryptsetup luksFormat --type luks2 --batch-mode "$root_partition_device" - 2>&1; then
disk_abort_hook "Formatting LUKS2 on $root_partition_device failed"
fi
if ! printf "%s" "$password" |
cryptsetup open "$root_partition_device" omarchy_root - 2>&1; then
disk_abort_hook "Opening the encrypted root on $root_partition_device failed"
fi
root_mapper="/dev/mapper/omarchy_root"
else
root_mapper="$root_partition_device"
fi
step "$(omarchy_text "Creating Btrfs filesystem and subvolumes")"
disk_step "creating the Btrfs filesystem on $root_mapper" \
mkfs.btrfs -f -L OMARCHY "$root_mapper"
wait_for_device "$root_mapper" || disk_abort_hook "Root device $root_mapper never appeared"
mkdir -p /mnt/btrfs-root
disk_step "mounting $root_mapper" mount "$root_mapper" /mnt/btrfs-root
for subvol in @ @home @log @pkg; do
disk_step "creating subvolume $subvol" btrfs subvolume create "/mnt/btrfs-root/$subvol"
done
umount /mnt/btrfs-root
rmdir /mnt/btrfs-root
disk_step "mounting the target root" \
mount -o noatime,compress=zstd,subvol=@ "$root_mapper" /mnt
mkdir -p /mnt/home /mnt/var/log /mnt/var/cache/pacman/pkg /mnt"$esp_mount_in_target"
disk_step "mounting /home" \
mount -o noatime,compress=zstd,subvol=@home "$root_mapper" /mnt/home
disk_step "mounting /var/log" \
mount -o noatime,compress=zstd,subvol=@log "$root_mapper" /mnt/var/log
disk_step "mounting the package cache" \
mount -o noatime,compress=zstd,subvol=@pkg "$root_mapper" /mnt/var/cache/pacman/pkg
disk_step "creating the ESP filesystem on $efi_dev" mkfs.fat -F32 -n OMARCHY_EFI "$efi_dev"
disk_step "mounting the ESP" mount "$efi_dev" /mnt"$esp_mount_in_target"
# The orchestrator's first act is to verify this handoff. Check it here so
# a failure names the step that broke rather than surfacing later as
# "protected mode: /mnt is not a mountpoint".
mountpoint -q /mnt ||
disk_abort_hook "Target /mnt is not mounted; not starting the installer"
mountpoint -q "/mnt$esp_mount_in_target" ||
disk_abort_hook "ESP is not mounted at /mnt$esp_mount_in_target"
fi
local luks_uuid_json="null" luks_uuid
if [[ "$encrypt_installation" == "true" ]]; then
if $dry; then
luks_uuid="00000000-0000-0000-0000-000000000000"
else
luks_uuid=$(blkid -s UUID -o value "$root_partition_device")
fi
luks_uuid_json="\"$luks_uuid\""
fi
cat <<-_EOF_ >user_configuration.json
{
"app_config": null,
"archinstall-language": "English",
"auth_config": {},
"audio_config": { "audio": "pipewire" },
"bootloader_config": { "bootloader": "Limine", "uki": false, "removable": false },
"custom_commands": [],
"omarchy_install": {
"input_method": "${keyboard_input_method:-none}",
"input_xkb_layout": "${keyboard_xkb_layout:-}",
"mode": "protected",
"target_mount": "/mnt",
"boot": {
"esp_mount": "$esp_mount_in_target",
"esp_path": "/EFI/limine",
"efi_binary": "limine_x64.efi",
"enable_fallback": false
},
"storage": {
"esp_device": "$efi_dev",
"root_device": "$root_partition_device",
"root_mapper": "$root_mapper",
"luks_uuid": $luks_uuid_json,
"kernel": "$kernel_choice"
}
},
"disk_config": {
"config_type": "pre_mounted_config",
"mountpoint": "/mnt"
},
"hostname": "$hostname",
"kernels": [ "$kernel_choice" ],
"network_config": { "type": "iso" },
"ntp": true,
"parallel_downloads": 8,
"script": null,
"services": [],
"swap": true,
"timezone": "$timezone",
"locale_config": {
"kb_layout": "$keyboard",
"sys_enc": "UTF-8",
"sys_lang": "$(target_lang)"
},
"mirror_config": {
"custom_repositories": [],
"custom_servers": [
{"url": "https://mirror.omarchy.org/\$repo/os/\$arch"},
{"url": "https://mirror.rackspace.com/archlinux/\$repo/os/\$arch"},
{"url": "https://geo.mirror.pkgbuild.com/\$repo/os/\$arch"}
],
"mirror_regions": {},
"optional_repositories": []
},
"packages": [
"base-devel",
"git",
"omarchy-keyring",
"$OMARCHY_SETTINGS_PACKAGE",
"$OMARCHY_RUNTIME_PACKAGE"
],
"profile_config": {
"gfx_driver": null,
"greeter": null,
"profile": {}
},
"version": "3.0.9"
}
_EOF_
}
disk_form() {
step "$(omarchy_text "Let's select where to install Omarchy...")"
# Don't offer the install media as an option (Arch ISO mounts it here)
local boot_source
local exclude_disk
boot_source=$(findmnt -no SOURCE /run/archiso/bootmnt 2>/dev/null || true)
exclude_disk=$(get_root_disk "$boot_source")
# List all installable disks, excluding the boot device if present
local available_disks
available_disks=$(
lsblk -dpno NAME,TYPE |
awk '$2=="disk"{print $1}' |
grep -E '/dev/(sd|hd|vd|nvme|mmcblk|xv)' |
{ if [[ -n "$exclude_disk" ]]; then grep -Fvx "$exclude_disk"; else cat; fi; }
)
# Get available disks and format them with info
local disk_options=""
while IFS= read -r device; do
if [[ -n "$device" ]]; then
disk_info=$(get_disk_info "$device")
disk_options="$disk_options$disk_info"$'\n'
fi
done <<<"$available_disks"
selected_display=$(echo "$disk_options" | gum choose --header "$(omarchy_text "Select install disk")") || abort
disk=$(echo "$selected_display" | awk '{print $1}')
}
# STEP 4: INSTALL MODE
open_partition_tool() {
step "$(omarchy_text "Partition tool for %s" "$disk")"
gum style "$(omarchy_text "Create unallocated free space for Omarchy, then write changes and quit.")"
gum style --foreground 8 "$(omarchy_text "Do not create an Omarchy partition here — leave the target area as Free space.")"
gum style --foreground 8 "$(omarchy_text "Tip: free-space install needs at least 32GB unallocated, plus 2GB more if no ESP already exists.")"
echo
gum confirm --affirmative "$(omarchy_text "Open cfdisk")" --negative "$(omarchy_text "Back")" "$(omarchy_text "Open partition tool for %s?" "$disk")" || return 0
clear
cfdisk "$disk" || true
partprobe "$disk" 2>/dev/null || true
sync
sleep 1
}
# If the disk has no partition table or no unallocated free space, the only
# viable paths write the whole disk, so the free-space mode is not offered.
requires_full_disk_install() {
local pt_type free_space_b
pt_type=$(lsblk -dno PTTYPE "$disk" 2>/dev/null)
# a) No partition table: disk is empty
[[ -z "$pt_type" ]] && return 0
# b) No usable free space
free_space_b=$(parted -ms "$disk" unit B print free </dev/null 2>/dev/null | \
awk -F: '
$NF ~ /free/ {
size=$4;
gsub(/B/, "", size);
if (size+0 > max+0) max=size;
}
END {
print max+0;
}')
[[ -z "$free_space_b" || "$free_space_b" -eq 0 ]]
}
install_mode_form() {
local choices=("$(omarchy_text "Full disk install")")
# The free-space/protected path registers an EFI boot entry and is UEFI-only.
# Full-disk installs still support BIOS through the orchestrator's BIOS branch.
if [[ -d /sys/firmware/efi ]] && ! $full_disk_only; then
choices+=("$(omarchy_text "Free space install (alongside existing data)")")
fi
choices+=("$(omarchy_text "Choose a different disk")")
step "$(omarchy_text "Let's select how to install Omarchy...")"
install_mode=$(gum choose --header "$(omarchy_text "Select installation mode on %s" "$disk")" "${choices[@]}") || abort
}
confirm_disk_overwrite() {
local mode="encrypted"
local affirmative confirm_status
while true; do
clear_logo
echo
say "$(omarchy_text "Everything will be overwritten. There is no recovery possible.")"
if [[ $mode == "encrypted" ]]; then
say --foreground 8 "$(omarchy_text "Press Ctrl+C for unencrypted install.")"
affirmative="$(omarchy_text "Yes, install")"
else
affirmative="$(omarchy_text "Yes, install without encryption")"
fi
echo
gum confirm --affirmative "$affirmative" --negative "$(omarchy_text "No, change it")" "$(omarchy_text "Confirm overwriting %s" "$disk")"
confirm_status=$?
case $confirm_status in
0)
[[ $mode == "encrypted" ]] && encrypt_installation=true || encrypt_installation=false
return 0
;;
1)
return 1
;;
130)
[[ $mode == "encrypted" ]] && mode="unencrypted" || mode="encrypted"
;;
*)
abort
;;
esac
done
}
# Decide what and how to install: sets install_target (full_disk|free_space),
# install_target and encrypt_installation. Nothing is written to disk yet —
# the user step runs between this and the execution below. (deferred provisioning never
# reaches here; it's armed by Ctrl+C on the keyboard screen and goes straight to
# disk selection.)
select_installation() {
local full_disk_only
while true; do
if requires_full_disk_install; then
full_disk_only=true
else
full_disk_only=false
fi
# With no free-space option to offer, a full-disk install is the only
# mode, so skip the picker and go straight to the overwrite confirm.
if $full_disk_only; then
install_mode=$(omarchy_text "Full disk install")
else
install_mode_form
fi
case "$install_mode" in
"$(omarchy_text "Full disk install")")
if confirm_disk_overwrite; then
install_target="full_disk"
return 0
fi
# When the picker was skipped, declining the overwrite means the user
# wants a different disk.
$full_disk_only && disk_form
;;
"$(omarchy_text "Free space install (alongside existing data)")")
if run_partition_decide; then
install_target="free_space"
return 0
fi
;;
"$(omarchy_text "Choose a different disk")")
disk_form
;;
esac
done
}
# STEP 1: KEYBOARD (Ctrl+C here arms deferred provisioning and jumps to disk selection)
defer_provisioning=false
install_target="full_disk"
# A rerun arrives with the language chosen; otherwise English.
language="${OMARCHY_INSTALL_LANGUAGE:-en}"
language_chosen=false
# Let the live VT reach its settled width before the first screen so the logo
# isn't measured against the transient 80-column boot console.
wait_for_stable_terminal
if [[ -n ${OMARCHY_INSTALL_LANGUAGE:-} ]]; then
apply_language
language_chosen=true
else
greeter
fi
keyboard_form
# Normal install: keyboard (loaded above, so the LUKS passphrase is typed under
# the right layout) → user → disk → install mode. The user step can unwind to
# the keyboard screen and arm deferral there, hence the second check.
if ! $defer_provisioning; then
user_step
fi
if $defer_provisioning; then
# Deferred provisioning: no user setup, and the keyboard is deferred to the machine's first
# boot. A defined default keymap so it boots before then; the owner picks
# their real layout in the OOBE, which re-keys the console and, on encrypted
# installs, rebuilds the initramfs so the LUKS prompt matches.
keyboard="us"
keyboard_input_method="none"
keyboard_xkb_layout=""
username=""
password=""
password_hash=""
full_name=""
email_address=""
# Neutral defaults for the install; the owner overwrites both in the
# first-boot OOBE (omarchy-provision-owner).
hostname="omarchy"
timezone="UTC"
# Jump straight to disk selection + overwrite confirmation (encrypted by
# default, Ctrl+C toggles unencrypted). deferred provisioning is always a full-disk install.
disk_form
until confirm_disk_overwrite; do
disk_form
done
install_target="full_disk"
else
disk_form
select_installation
fi
clear
write_user_files
if [[ $install_target == "free_space" ]]; then
run_partition_execute "$@"
if [[ ${1:-} == "dry" ]]; then
print_dry_run_files
fi
exit 0
fi
# Setup partition layout
disk_size=$(lsblk -bdno SIZE "$disk")
mib=$((1024 * 1024))
gib=$((mib * 1024))
disk_size_in_mib=$((disk_size / mib * mib)) # Rounds to nearest MiB
gpt_backup_reserve=$((mib))
boot_partition_start=$((mib))
boot_partition_size=$((2 * gib))
main_partition_start=$((boot_partition_size + boot_partition_start))
main_partition_size=$((disk_size_in_mib - main_partition_start - gpt_backup_reserve))
kernel_choice=$(detect_kernel)
if [[ $encrypt_installation == true ]]; then
# Deferred-provisioning installs carry no password: the orchestrator generates a throwaway
# LUKS passphrase and stages it for the first-boot re-key.
if $defer_provisioning; then
encryption_password_line=""
else
encryption_password_line=",
\"encryption_password\": $(echo -n "$password" | jq -Rsa)"
fi
disk_encryption_config=$(cat <<-_EOF_
,
"disk_encryption": {
"encryption_type": "luks",
"lvm_volumes": [],
"iter_time": 2000,
"partitions": [ "8c2c2b92-1070-455d-b76a-56263bab24aa" ]$encryption_password_line
}
_EOF_
)
else
disk_encryption_config=""
fi
cat <<-_EOF_ >user_configuration.json
{
"app_config": null,
"archinstall-language": "English",
"auth_config": {},
"audio_config": { "audio": "pipewire" },
"bootloader_config": { "bootloader": "Limine", "uki": false, "removable": false },
"custom_commands": [],
"omarchy_install": {
"input_method": "${keyboard_input_method:-none}",
"input_xkb_layout": "${keyboard_xkb_layout:-}",
"mode": "full_disk",
"defer_provisioning": $defer_provisioning,
"target_mount": "/mnt",
"boot": {
"esp_mount": "/boot",
"esp_path": "/EFI/limine",
"efi_binary": "limine_x64.efi",
"enable_fallback": true
},
"storage": {
"kernel": "$kernel_choice"
}
},
"disk_config": {
"config_type": "default_layout",
"device_modifications": [
{
"device": "$disk",
"partitions": [
{
"btrfs": [],
"dev_path": null,
"flags": [ "boot", "esp" ],
"fs_type": "fat32",
"mount_options": [],
"mountpoint": "/boot",
"obj_id": "ea21d3f2-82bb-49cc-ab5d-6f81ae94e18d",
"size": {
"sector_size": { "unit": "B", "value": 512 },
"unit": "B",
"value": $boot_partition_size
},
"start": {
"sector_size": { "unit": "B", "value": 512 },
"unit": "B",
"value": $boot_partition_start
},
"status": "create",
"type": "primary"
},
{
"btrfs": [
{ "mountpoint": "/", "name": "@" },
{ "mountpoint": "/home", "name": "@home" },
{ "mountpoint": "/var/log", "name": "@log" },
{ "mountpoint": "/var/cache/pacman/pkg", "name": "@pkg" }
],
"dev_path": null,
"flags": [],
"fs_type": "btrfs",
"mount_options": [ "compress=zstd" ],
"mountpoint": null,
"obj_id": "8c2c2b92-1070-455d-b76a-56263bab24aa",
"size": {
"sector_size": { "unit": "B", "value": 512 },
"unit": "B",
"value": $main_partition_size
},
"start": {
"sector_size": { "unit": "B", "value": 512 },
"unit": "B",
"value": $main_partition_start
},
"status": "create",
"type": "primary"
}
],
"wipe": true
}
]$disk_encryption_config
},
"hostname": "$hostname",
"kernels": [ "$kernel_choice" ],
"network_config": { "type": "iso" },
"ntp": true,
"parallel_downloads": 8,
"script": null,
"services": [],
"swap": true,
"timezone": "$timezone",
"locale_config": {
"kb_layout": "$keyboard",
"sys_enc": "UTF-8",
"sys_lang": "$(target_lang)"
},
"mirror_config": {
"custom_repositories": [],
"custom_servers": [
{"url": "https://mirror.omarchy.org/\$repo/os/\$arch"},
{"url": "https://mirror.rackspace.com/archlinux/\$repo/os/\$arch"},
{"url": "https://geo.mirror.pkgbuild.com/\$repo/os/\$arch"}
],
"mirror_regions": {},
"optional_repositories": []
},
"packages": [
"base-devel",
"git",
"omarchy-keyring",
"$OMARCHY_SETTINGS_PACKAGE",
"$OMARCHY_RUNTIME_PACKAGE"
],
"profile_config": {
"gfx_driver": null,
"greeter": null,
"profile": {}
},
"version": "3.0.9"
}
_EOF_
if [[ $1 == "dry" ]]; then
print_dry_run_files
fi