Replaces the bare `NAME SIZE` lsblk listing in select_disk with a six-column table — NAME, SIZE, TYPE, VENDOR, MODEL, SERIAL — aligned via column -t. TYPE is derived from ROTA + TRAN (NVMe / USB / SSD / HDD). Empty vendor/model/serial fields render as `--` instead of collapsing the alignment. Filters loop, ram, zram, sr devices. Roadmap row moves to Shipped. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1250 lines
40 KiB
Bash
Executable File
1250 lines
40 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -e
|
|
|
|
# Nomarchy TTY Installer
|
|
# Golden path: BTRFS + LUKS2 encryption
|
|
#
|
|
# This is a minimal, single-path installer designed for TTY-only environments.
|
|
# For a customized installation, manually set up your disk and use the generated
|
|
# flake configuration as a starting point.
|
|
|
|
# Load the hardware-detection database — resolved relative to this script so it
|
|
# works whether we're invoked from /etc/install.sh on the live ISO or straight
|
|
# from a checkout.
|
|
_NOMARCHY_INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=hardware-db.sh
|
|
source "$_NOMARCHY_INSTALL_DIR/hardware-db.sh"
|
|
|
|
# Colors and styling
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
BLUE='\033[0;34m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
BOLD='\033[1m'
|
|
|
|
# Installer state
|
|
NOMARCHY_REPO=""
|
|
NOMARCHY_REV=""
|
|
HOSTNAME=""
|
|
TARGET_DRIVE=""
|
|
USERNAME=""
|
|
LUKS_PASSWORD=""
|
|
USER_PASSWORD=""
|
|
TIMEZONE="UTC"
|
|
KEYMAP_LAYOUT=""
|
|
KEYMAP_VARIANT=""
|
|
LOCALE=""
|
|
FORM_FACTOR=""
|
|
HARDWARE_MODULES=""
|
|
NOMARCHY_HW_OPTS=""
|
|
# "" = not yet answered; "true"/"false" set by configure_impermanence.
|
|
ENABLE_IMPERMANENCE=""
|
|
|
|
# Curated short lists for the keymap/locale prompts. "Other…" drops the user
|
|
# into the full `localectl` list via gum filter.
|
|
COMMON_KEYMAPS=(
|
|
us de fr gb es it pt br se no fi dk nl pl ru ua ch jp kr cn ar
|
|
)
|
|
COMMON_LOCALES=(
|
|
en_US.UTF-8 en_GB.UTF-8 de_DE.UTF-8 fr_FR.UTF-8 es_ES.UTF-8
|
|
it_IT.UTF-8 pt_BR.UTF-8 pt_PT.UTF-8 nl_NL.UTF-8 sv_SE.UTF-8
|
|
ja_JP.UTF-8 zh_CN.UTF-8 ko_KR.UTF-8
|
|
)
|
|
|
|
# CLI flags
|
|
DRY_RUN="false"
|
|
RESUME="false"
|
|
|
|
# Resumable answers — passwords are NEVER persisted; the user re-enters them.
|
|
STATE_FILE="/tmp/nomarchy-install.state.sh"
|
|
|
|
# ============================================================================
|
|
# CLI / PERSISTENCE
|
|
# ============================================================================
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: install.sh [--dry-run] [--resume] [-h|--help]
|
|
|
|
--dry-run Generate the flake into a tmpdir and run \`nix flake check\`.
|
|
Doesn't touch the disk, doesn't run nixos-install.
|
|
--resume Reuse answers from a previous interrupted run
|
|
(saved at $STATE_FILE — passwords excluded).
|
|
-h, --help Print this message.
|
|
USAGE
|
|
}
|
|
|
|
parse_args() {
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--dry-run) DRY_RUN="true" ;;
|
|
--resume) RESUME="true" ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
}
|
|
|
|
# Persist non-secret answers so an interrupted install can pick up where it
|
|
# left off. Uses `declare -p` so each line is a self-contained `declare --`
|
|
# statement that `source` re-establishes verbatim.
|
|
save_state() {
|
|
declare -p \
|
|
TARGET_DRIVE USERNAME HOSTNAME TIMEZONE \
|
|
KEYMAP_LAYOUT KEYMAP_VARIANT LOCALE FORM_FACTOR \
|
|
ENABLE_IMPERMANENCE HARDWARE_MODULES NOMARCHY_HW_OPTS NOMARCHY_REV \
|
|
> "$STATE_FILE"
|
|
}
|
|
|
|
load_state() {
|
|
if [[ "$RESUME" == "true" ]] && [[ -f "$STATE_FILE" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$STATE_FILE"
|
|
info "Resumed from $STATE_FILE"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# UTILITY FUNCTIONS
|
|
# ============================================================================
|
|
|
|
# Helper to run commands via nix run
|
|
nrun() {
|
|
local pkg="$1"
|
|
shift
|
|
nix run --extra-experimental-features "nix-command flakes" "nixpkgs#$pkg" -- "$@"
|
|
}
|
|
|
|
header() {
|
|
clear
|
|
nrun gum style \
|
|
--foreground 212 --border-foreground 212 --border double \
|
|
--align center --width 60 --margin "1 2" --padding "2 4" \
|
|
"NOMARCHY INSTALLER" "NixOS with Omarchy flavor"
|
|
echo ""
|
|
}
|
|
|
|
section() {
|
|
echo ""
|
|
nrun gum style --foreground 14 --bold "━━━ $1 ━━━"
|
|
echo ""
|
|
}
|
|
|
|
success() {
|
|
nrun gum style --foreground 10 "✓ $1"
|
|
}
|
|
|
|
error() {
|
|
nrun gum style --foreground 9 "✗ $1"
|
|
}
|
|
|
|
info() {
|
|
nrun gum style --foreground 12 "→ $1"
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 1: ENVIRONMENT CHECK
|
|
# ============================================================================
|
|
|
|
check_environment() {
|
|
section "Environment Check"
|
|
|
|
# Check for root
|
|
if [[ $EUID -ne 0 ]]; then
|
|
error "This installer must be run as root (use sudo)"
|
|
exit 1
|
|
fi
|
|
success "Running as root"
|
|
|
|
# Confirm we booted via UEFI. The disko config lays out a GPT + ESP and
|
|
# we install systemd-boot/grub-efi; on a BIOS-booted host that doesn't
|
|
# work and would partial-install before failing.
|
|
if [[ ! -d /sys/firmware/efi ]]; then
|
|
error "This installer requires a UEFI system (no /sys/firmware/efi)."
|
|
info "Reboot in UEFI mode (disable CSM/legacy in firmware setup) and try again."
|
|
exit 1
|
|
fi
|
|
success "UEFI boot detected"
|
|
|
|
# Find Nomarchy repo
|
|
if [[ -d "/etc/nomarchy" ]]; then
|
|
NOMARCHY_REPO="/etc/nomarchy"
|
|
elif [[ -d "$(dirname "$0")/.." ]] && [[ -f "$(dirname "$0")/../flake.nix" ]]; then
|
|
NOMARCHY_REPO="$(realpath "$(dirname "$0")/..")"
|
|
fi
|
|
|
|
if [[ -z "$NOMARCHY_REPO" ]]; then
|
|
error "Nomarchy repository not found"
|
|
exit 1
|
|
fi
|
|
success "Found Nomarchy at $NOMARCHY_REPO"
|
|
|
|
# Capture the exact commit we're installing from. The generated flake
|
|
# pins `nomarchy.url` to this revision so the installed system can't
|
|
# silently drift onto a newer (possibly breaking) main.
|
|
if command -v git >/dev/null 2>&1 && [[ -d "$NOMARCHY_REPO/.git" ]]; then
|
|
NOMARCHY_REV=$(git -C "$NOMARCHY_REPO" rev-parse HEAD 2>/dev/null || echo "")
|
|
fi
|
|
if [[ -n "$NOMARCHY_REV" ]]; then
|
|
success "Pinning Nomarchy to $NOMARCHY_REV"
|
|
else
|
|
info "Could not determine Nomarchy revision; downstream flake will track main."
|
|
fi
|
|
|
|
# Check internet
|
|
gum spin --spinner dot --title "Checking internet connection..." -- sleep 1
|
|
while ! ping -c 1 -W 2 1.1.1.1 &>/dev/null; do
|
|
error "No internet connection"
|
|
local choice
|
|
choice=$(gum choose "Open Network Manager (nmtui)" "Retry" "Exit")
|
|
case "$choice" in
|
|
*nmtui*) nmtui ;;
|
|
*Exit*) exit 1 ;;
|
|
esac
|
|
done
|
|
success "Internet connection verified"
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 2: DISK SELECTION
|
|
# ============================================================================
|
|
|
|
select_disk() {
|
|
section "Disk Selection"
|
|
|
|
if [[ -n "$TARGET_DRIVE" ]]; then
|
|
success "Resumed: $TARGET_DRIVE"
|
|
return
|
|
fi
|
|
|
|
# Build a richer drive table than the bare `NAME SIZE` lsblk default.
|
|
# Columns: NAME, SIZE, TYPE (NVMe/USB/SSD/HDD), VENDOR, MODEL, SERIAL.
|
|
# Empty fields render as "--" so column -t can still align them.
|
|
local raw rows=""
|
|
raw=$(lsblk -d -n -p -o NAME,SIZE,ROTA,TRAN,VENDOR,MODEL,SERIAL 2>/dev/null \
|
|
| grep -vE '^(/dev/(loop|ram|zram|sr))')
|
|
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
# NAME and SIZE are reliably whitespace-free; ROTA/TRAN are short
|
|
# tokens; VENDOR/MODEL/SERIAL can carry internal spaces. Pull the
|
|
# first four fields off the front, treat the rest as the
|
|
# vendor/model/serial trio split via the original lsblk column
|
|
# widths — easier to just re-query each device for clean values.
|
|
local dev size rota tran
|
|
read -r dev size rota tran _ <<<"$line"
|
|
|
|
local type vendor model serial
|
|
case "$tran" in
|
|
nvme) type="NVMe" ;;
|
|
usb) type="USB" ;;
|
|
sata|scsi)
|
|
if [[ "$rota" == "1" ]]; then type="HDD"; else type="SSD"; fi
|
|
;;
|
|
*)
|
|
if [[ "$rota" == "1" ]]; then type="HDD"; else type="SSD"; fi
|
|
;;
|
|
esac
|
|
|
|
vendor=$(lsblk -d -n -o VENDOR "$dev" 2>/dev/null | sed -E 's/^[[:space:]]+//;s/[[:space:]]+$//')
|
|
model=$(lsblk -d -n -o MODEL "$dev" 2>/dev/null | sed -E 's/^[[:space:]]+//;s/[[:space:]]+$//')
|
|
serial=$(lsblk -d -n -o SERIAL "$dev" 2>/dev/null | sed -E 's/^[[:space:]]+//;s/[[:space:]]+$//')
|
|
[[ -z "$vendor" ]] && vendor="--"
|
|
[[ -z "$model" ]] && model="--"
|
|
[[ -z "$serial" ]] && serial="--"
|
|
|
|
# Tab-separated for column -t -s, then collapse internal whitespace
|
|
# in MODEL so multi-space brand strings don't break alignment.
|
|
rows+=$(printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
"$dev" "$size" "$type" "$vendor" "${model//$'\t'/ }" "$serial")
|
|
rows+=$'\n'
|
|
done <<<"$raw"
|
|
|
|
if [[ -z "$rows" ]]; then
|
|
error "No installable drives found."
|
|
exit 1
|
|
fi
|
|
|
|
info "Available drives:"
|
|
echo ""
|
|
{
|
|
printf 'NAME\tSIZE\tTYPE\tVENDOR\tMODEL\tSERIAL\n'
|
|
printf '%s' "$rows"
|
|
} | column -t -s $'\t'
|
|
echo ""
|
|
|
|
# gum choose gets the same aligned rows so the picker reads like the table.
|
|
local picker
|
|
picker=$(printf '%s' "$rows" | column -t -s $'\t')
|
|
local choice
|
|
choice=$(printf '%s\n' "$picker" | gum choose --header "Select target drive")
|
|
TARGET_DRIVE=$(awk '{print $1}' <<<"$choice")
|
|
|
|
if [[ -z "$TARGET_DRIVE" ]]; then
|
|
error "No drive selected"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" != "true" ]]; then
|
|
echo ""
|
|
gum style --foreground 9 --bold "⚠ WARNING: All data on $TARGET_DRIVE will be DESTROYED!"
|
|
echo ""
|
|
if ! gum confirm "Are you sure you want to use $TARGET_DRIVE?"; then
|
|
error "Aborted"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
success "Selected: $TARGET_DRIVE"
|
|
save_state
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 3: LUKS PASSPHRASE
|
|
# ============================================================================
|
|
|
|
get_luks_passphrase() {
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
info "Dry run: skipping LUKS passphrase prompt."
|
|
LUKS_PASSWORD="dryrun-not-used"
|
|
return
|
|
fi
|
|
|
|
section "Disk Encryption"
|
|
|
|
info "Your disk will be encrypted with LUKS2."
|
|
info "Enter a strong passphrase (you'll need this at every boot)."
|
|
echo ""
|
|
|
|
local pass1 pass2
|
|
while true; do
|
|
pass1=$(gum input --password --placeholder "Enter LUKS passphrase")
|
|
[[ -z "$pass1" ]] && continue
|
|
|
|
pass2=$(gum input --password --placeholder "Confirm passphrase")
|
|
|
|
if [[ "$pass1" == "$pass2" ]]; then
|
|
LUKS_PASSWORD="$pass1"
|
|
break
|
|
else
|
|
error "Passphrases do not match. Try again."
|
|
fi
|
|
done
|
|
|
|
success "Encryption passphrase set"
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 4: USER CONFIGURATION
|
|
# ============================================================================
|
|
|
|
configure_user() {
|
|
section "User Configuration"
|
|
|
|
if [[ -z "$USERNAME" ]]; then
|
|
USERNAME=$(nrun gum input --placeholder "Enter username (lowercase, no spaces)")
|
|
if [[ -z "$USERNAME" ]] || [[ ! "$USERNAME" =~ ^[a-z][a-z0-9_-]*$ ]]; then
|
|
error "Invalid username"
|
|
exit 1
|
|
fi
|
|
fi
|
|
success "Username: $USERNAME"
|
|
|
|
if [[ -z "$HOSTNAME" ]]; then
|
|
HOSTNAME=$(nrun gum input --value "nomarchy" --placeholder "Hostname for this machine")
|
|
if [[ -z "$HOSTNAME" ]] || [[ ! "$HOSTNAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
|
error "Invalid hostname (use lowercase letters, digits, and hyphens only)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
success "Hostname: $HOSTNAME"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
info "Dry run: skipping user password prompt."
|
|
USER_PASSWORD="dryrun-not-used"
|
|
save_state
|
|
return
|
|
fi
|
|
|
|
# User password (can be same as LUKS or different)
|
|
info "Set a password for your user account"
|
|
local pass1 pass2
|
|
while true; do
|
|
pass1=$(nrun gum input --password --placeholder "Enter user password")
|
|
[[ -z "$pass1" ]] && continue
|
|
|
|
pass2=$(nrun gum input --password --placeholder "Confirm user password")
|
|
|
|
if [[ "$pass1" == "$pass2" ]]; then
|
|
USER_PASSWORD="$pass1"
|
|
break
|
|
else
|
|
error "Passwords do not match. Try again."
|
|
fi
|
|
done
|
|
|
|
success "User password set"
|
|
save_state
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 5a: KEYBOARD & LANGUAGE
|
|
# ============================================================================
|
|
|
|
# Curated short list first ("Other…" drops into the full localectl list).
|
|
# Applied IMMEDIATELY to the running session so the rest of the install types
|
|
# correctly — TTY uses loadkeys, Hyprland uses hyprctl. Both best-effort.
|
|
select_keymap_locale() {
|
|
section "Keyboard & Language"
|
|
|
|
if [[ -z "$KEYMAP_LAYOUT" ]]; then
|
|
local choice
|
|
choice=$(printf '%s\n' "${COMMON_KEYMAPS[@]}" "Other…" \
|
|
| nrun gum choose --header "Keyboard layout")
|
|
if [[ "$choice" == "Other…" ]]; then
|
|
KEYMAP_LAYOUT=$(localectl list-x11-keymap-layouts 2>/dev/null \
|
|
| nrun gum filter --placeholder "Search keyboard layout…")
|
|
else
|
|
KEYMAP_LAYOUT="$choice"
|
|
fi
|
|
[[ -z "$KEYMAP_LAYOUT" ]] && KEYMAP_LAYOUT="us"
|
|
fi
|
|
success "Keyboard layout: $KEYMAP_LAYOUT"
|
|
|
|
# Variant — optional. Only prompt if the layout actually has variants.
|
|
if [[ -z "$KEYMAP_VARIANT" ]]; then
|
|
local variants
|
|
variants=$(localectl list-x11-keymap-variants "$KEYMAP_LAYOUT" 2>/dev/null || true)
|
|
if [[ -n "$variants" ]]; then
|
|
local v
|
|
v=$(printf '(none)\n%s\n' "$variants" \
|
|
| nrun gum filter --placeholder "Variant (optional)" --value "(none)")
|
|
[[ "$v" == "(none)" || -z "$v" ]] || KEYMAP_VARIANT="$v"
|
|
fi
|
|
fi
|
|
[[ -n "$KEYMAP_VARIANT" ]] && success "Variant: $KEYMAP_VARIANT" || success "Variant: (default)"
|
|
|
|
# Apply to the live session, best-effort.
|
|
loadkeys "$KEYMAP_LAYOUT" 2>/dev/null || true
|
|
if [[ -n "${WAYLAND_DISPLAY:-}" ]] && command -v hyprctl >/dev/null 2>&1; then
|
|
hyprctl keyword input:kb_layout "$KEYMAP_LAYOUT" >/dev/null 2>&1 || true
|
|
hyprctl keyword input:kb_variant "$KEYMAP_VARIANT" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if [[ -z "$LOCALE" ]]; then
|
|
local choice
|
|
choice=$(printf '%s\n' "${COMMON_LOCALES[@]}" "Other…" \
|
|
| nrun gum choose --header "Language / locale")
|
|
if [[ "$choice" == "Other…" ]]; then
|
|
LOCALE=$(localectl list-locales 2>/dev/null \
|
|
| nrun gum filter --placeholder "Search locale…")
|
|
else
|
|
LOCALE="$choice"
|
|
fi
|
|
[[ -z "$LOCALE" ]] && LOCALE="en_US.UTF-8"
|
|
fi
|
|
success "Locale: $LOCALE"
|
|
|
|
save_state
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 5: TIMEZONE
|
|
# ============================================================================
|
|
|
|
select_timezone() {
|
|
section "Timezone"
|
|
|
|
if [[ -n "$TIMEZONE" ]] && [[ "$TIMEZONE" != "UTC" ]]; then
|
|
success "Resumed: $TIMEZONE"
|
|
return
|
|
fi
|
|
|
|
local timezones
|
|
timezones=$(timedatectl list-timezones 2>/dev/null || echo "UTC")
|
|
TIMEZONE=$(echo "$timezones" | gum filter --placeholder "Search timezone...")
|
|
|
|
[[ -z "$TIMEZONE" ]] && TIMEZONE="UTC"
|
|
success "Timezone: $TIMEZONE"
|
|
save_state
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 6: HARDWARE VENDOR
|
|
# ============================================================================
|
|
|
|
select_hardware() {
|
|
section "Hardware Configuration"
|
|
|
|
if [[ -n "$HARDWARE_MODULES" ]]; then
|
|
success "Resumed hardware modules"
|
|
return
|
|
fi
|
|
|
|
local dmi_vendor dmi_product detect_output
|
|
dmi_vendor=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || echo "Unknown")
|
|
dmi_product=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "Unknown")
|
|
info "DMI: $dmi_vendor / $dmi_product"
|
|
echo ""
|
|
|
|
# Auto-detect CPU, GPU, chassis, and known model from hardware-db.sh.
|
|
detect_output=$(nomarchy_detect_hw || true)
|
|
|
|
echo "Auto-detected:"
|
|
nomarchy_hw_summary <<< "$detect_output"
|
|
echo ""
|
|
|
|
# Collect modules + nomarchy options from the detector output.
|
|
local modules=() hw_opts=()
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
"MODULE "*) modules+=("${line#MODULE }") ;;
|
|
"OPT "*) hw_opts+=("${line#OPT }") ;;
|
|
esac
|
|
done <<< "$detect_output"
|
|
|
|
# Let the user accept, extend, or replace the detection.
|
|
local choice
|
|
choice=$(nrun gum choose --header "Hardware configuration" \
|
|
"Accept detected modules" \
|
|
"Add an extra nixos-hardware module" \
|
|
"Pick from the manual list (override)")
|
|
|
|
case "$choice" in
|
|
"Add an extra nixos-hardware module")
|
|
local extra
|
|
extra=$(nrun gum input --placeholder "e.g. asus-zephyrus-ga401 (no 'nixos-hardware.' prefix)")
|
|
[[ -n "$extra" ]] && modules+=("$extra")
|
|
;;
|
|
"Pick from the manual list (override)")
|
|
modules=()
|
|
hw_opts=()
|
|
_select_hardware_manual modules hw_opts
|
|
;;
|
|
esac
|
|
|
|
# De-duplicate while preserving order.
|
|
local seen="" uniq_mods=() m
|
|
for m in "${modules[@]}"; do
|
|
if [[ ":$seen:" != *":$m:"* ]]; then
|
|
uniq_mods+=("$m")
|
|
seen="$seen:$m"
|
|
fi
|
|
done
|
|
|
|
# Emit a list the heredoc in generate_flake_config splats into
|
|
# hardware-selection.nix's imports. The heredoc already indents the first
|
|
# line by 4 spaces — we add real newlines + 4 spaces (via $'\n ') for
|
|
# subsequent lines so every entry lines up.
|
|
HARDWARE_MODULES=""
|
|
for m in "${uniq_mods[@]}"; do
|
|
[[ -z "$HARDWARE_MODULES" ]] || HARDWARE_MODULES+=$'\n '
|
|
HARDWARE_MODULES+="inputs.nixos-hardware.nixosModules.${m}"
|
|
done
|
|
|
|
# Same treatment for nomarchy.hardware.* toggles.
|
|
NOMARCHY_HW_OPTS=""
|
|
local o
|
|
for o in "${hw_opts[@]}"; do
|
|
# opt is e.g. `isFramework=true` → `nomarchy.hardware.isFramework = true;`
|
|
local key="${o%%=*}" val="${o#*=}"
|
|
NOMARCHY_HW_OPTS+="nomarchy.hardware.${key} = ${val};"$'\n '
|
|
done
|
|
|
|
success "Hardware configuration set (${#uniq_mods[@]} module$([[ ${#uniq_mods[@]} -eq 1 ]] || echo s))"
|
|
save_state
|
|
}
|
|
|
|
# Manual fallback menu, kept for odd hardware the DB doesn't recognise yet.
|
|
# Writes into the two arrays named by its arguments (bash 4.3+ nameref).
|
|
_select_hardware_manual() {
|
|
local -n _mods_ref="$1"
|
|
local -n _opts_ref="$2"
|
|
|
|
local vendor
|
|
vendor=$(nrun gum choose --header "Pick vendor" \
|
|
"Framework" "Dell" "Lenovo" "Apple (T2 Mac)" "Microsoft Surface" "ASUS" "System76" "Other...")
|
|
|
|
case "$vendor" in
|
|
Framework)
|
|
local model
|
|
model=$(nrun gum choose \
|
|
"framework-16-7040-amd" \
|
|
"framework-13-amd-ai-300-series" \
|
|
"framework-13-7040-amd" \
|
|
"framework-13-13th-gen-intel" \
|
|
"framework-13-12th-gen-intel" \
|
|
"framework-13-11th-gen-intel")
|
|
_mods_ref+=("$model")
|
|
_opts_ref+=("isFramework=true")
|
|
;;
|
|
Dell)
|
|
local model
|
|
model=$(nrun gum choose \
|
|
"dell-xps-15-9500" "dell-xps-15-9510" "dell-xps-15-9520" \
|
|
"dell-xps-13-9310" "dell-xps-13-9370" "dell-xps-13-9380" \
|
|
"dell-precision-5530" "dell-latitude-7480")
|
|
_mods_ref+=("$model")
|
|
[[ "$model" == *xps* ]] && _opts_ref+=("isXPS=true")
|
|
;;
|
|
Lenovo)
|
|
local model
|
|
model=$(nrun gum choose \
|
|
"lenovo-thinkpad-x1-carbon-gen11" "lenovo-thinkpad-x1-carbon-gen10" \
|
|
"lenovo-thinkpad-x1-carbon-gen9" "lenovo-thinkpad-x1-extreme" \
|
|
"lenovo-thinkpad-t14-amd-gen3" "lenovo-thinkpad-t14-amd-gen2" \
|
|
"lenovo-thinkpad-t480" "lenovo-thinkpad-l13")
|
|
_mods_ref+=("$model")
|
|
;;
|
|
"Apple (T2 Mac)")
|
|
_mods_ref+=("apple-t2")
|
|
_opts_ref+=("isT2Mac=true")
|
|
;;
|
|
"Microsoft Surface")
|
|
local model
|
|
model=$(nrun gum choose \
|
|
"microsoft-surface-pro-9" "microsoft-surface-pro-8" "microsoft-surface-pro-7" \
|
|
"microsoft-surface-laptop-5" "microsoft-surface-laptop-4" "microsoft-surface-laptop-3")
|
|
_mods_ref+=("$model")
|
|
;;
|
|
ASUS)
|
|
local model
|
|
model=$(nrun gum choose \
|
|
"asus-zephyrus-ga403" "asus-zephyrus-ga402" "asus-zephyrus-ga401" \
|
|
"asus-zephyrus-ga503" "asus-rog-strix-g513" "asus-zenbook-ux")
|
|
_mods_ref+=("$model")
|
|
;;
|
|
System76)
|
|
_mods_ref+=("system76")
|
|
;;
|
|
"Other...")
|
|
local custom
|
|
custom=$(nrun gum input --placeholder "e.g. asus-zephyrus-ga401 (no 'nixos-hardware.' prefix)")
|
|
[[ -n "$custom" ]] && _mods_ref+=("$custom")
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 6b: FORM FACTOR (LAPTOP / DESKTOP)
|
|
# ============================================================================
|
|
|
|
# Auto-detects via /sys/class/power_supply/BAT* — same signal hardware-db.sh
|
|
# uses to pick common-pc-laptop vs common-pc. The user can flip the answer
|
|
# (e.g. a desktop with a UPS that exposes a BAT*, or a laptop that doesn't).
|
|
confirm_form_factor() {
|
|
section "Form Factor"
|
|
|
|
if [[ -n "$FORM_FACTOR" ]]; then
|
|
success "Resumed: $FORM_FACTOR"
|
|
return
|
|
fi
|
|
|
|
local default="desktop"
|
|
if compgen -G "/sys/class/power_supply/BAT*" >/dev/null; then
|
|
default="laptop"
|
|
fi
|
|
info "Auto-detected: $default"
|
|
|
|
if nrun gum confirm "Treat this machine as a $default?"; then
|
|
FORM_FACTOR="$default"
|
|
else
|
|
FORM_FACTOR=$([[ "$default" == "laptop" ]] && echo desktop || echo laptop)
|
|
fi
|
|
success "Form factor: $FORM_FACTOR"
|
|
save_state
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 7: IMPERMANENCE (OPTIONAL)
|
|
# ============================================================================
|
|
|
|
configure_impermanence() {
|
|
section "Impermanence (Optional)"
|
|
|
|
if [[ "$RESUME" == "true" ]] && [[ "$ENABLE_IMPERMANENCE" != "" ]]; then
|
|
success "Resumed: impermanence = $ENABLE_IMPERMANENCE"
|
|
return
|
|
fi
|
|
|
|
info "Impermanence erases your root filesystem on every boot."
|
|
info "Only explicitly persisted files survive reboots."
|
|
info "This provides a clean, reproducible system."
|
|
echo ""
|
|
|
|
if gum confirm "Enable Impermanence?"; then
|
|
ENABLE_IMPERMANENCE="true"
|
|
success "Impermanence enabled"
|
|
else
|
|
info "Impermanence disabled (traditional persistent root)"
|
|
fi
|
|
save_state
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 8: REVIEW & CONFIRM
|
|
# ============================================================================
|
|
|
|
review_configuration() {
|
|
section "Review Configuration"
|
|
|
|
echo " Drive: $TARGET_DRIVE (BTRFS + LUKS2)"
|
|
echo " Hostname: $HOSTNAME"
|
|
echo " Username: $USERNAME"
|
|
echo " Keymap: $KEYMAP_LAYOUT${KEYMAP_VARIANT:+ ($KEYMAP_VARIANT)}"
|
|
echo " Locale: $LOCALE"
|
|
echo " Timezone: $TIMEZONE"
|
|
echo " Form factor: $FORM_FACTOR"
|
|
echo " Impermanence: $ENABLE_IMPERMANENCE"
|
|
echo " Nomarchy rev: ${NOMARCHY_REV:-main (unpinned)}"
|
|
echo ""
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
info "Dry run: skipping destructive confirmation."
|
|
return
|
|
fi
|
|
|
|
nrun gum style --foreground 9 "This will DESTROY all data on $TARGET_DRIVE"
|
|
echo ""
|
|
|
|
if ! nrun gum confirm "Proceed with installation?"; then
|
|
error "Aborted"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# STEP 9: EXECUTION
|
|
# ============================================================================
|
|
|
|
execute_installation() {
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
execute_dry_run
|
|
return
|
|
fi
|
|
|
|
section "Installing Nomarchy"
|
|
|
|
# 9.1 Partition with disko
|
|
info "Partitioning disk..."
|
|
local disko_file tmp_disko
|
|
disko_file="$NOMARCHY_REPO/installer/disko-golden.nix"
|
|
tmp_disko=$(mktemp --suffix=.nix)
|
|
|
|
sed "s|@TARGET_DRIVE@|${TARGET_DRIVE}|g" "$disko_file" > "$tmp_disko"
|
|
|
|
# Provide the LUKS passphrase via tmpfs so the secret never touches a
|
|
# spinning disk. /dev/shm is tmpfs on the live ISO. We restrict perms
|
|
# to root and shred the file (overwrite) on the way out, even though
|
|
# it's already in RAM — defense in depth.
|
|
local luks_key="/dev/shm/nomarchy-luks.key"
|
|
install -m 600 /dev/null "$luks_key"
|
|
printf '%s' "$LUKS_PASSWORD" > "$luks_key"
|
|
disko --mode disko "$tmp_disko"
|
|
shred -u "$luks_key" 2>/dev/null || rm -f "$luks_key"
|
|
unset LUKS_PASSWORD
|
|
success "Disk partitioned"
|
|
|
|
# 9.2 Generate hardware config
|
|
info "Generating hardware configuration..."
|
|
mkdir -p /mnt/etc/nixos
|
|
nixos-generate-config --root /mnt
|
|
success "Hardware configuration generated"
|
|
|
|
# 9.3 Generate flake configuration
|
|
info "Creating system configuration..."
|
|
generate_flake_config
|
|
success "Configuration generated"
|
|
|
|
# 9.4 Resolve inputs once, here, and lock them. First boot then consumes
|
|
# the same flake.lock and doesn't re-resolve a newer upstream.
|
|
info "Resolving flake inputs (this pins nomarchy, nixpkgs, etc.)..."
|
|
(
|
|
cd /mnt/etc/nixos
|
|
nix --extra-experimental-features "nix-command flakes" flake lock >/dev/null
|
|
)
|
|
success "flake.lock written"
|
|
|
|
# 9.5 Initialize git repo so `nix` treats /etc/nixos as a flake worktree.
|
|
info "Initializing git repository..."
|
|
(
|
|
cd /mnt/etc/nixos
|
|
nrun git git init -q
|
|
nrun git git add .
|
|
nrun git git config user.name "Nomarchy Installer"
|
|
nrun git git config user.email "installer@nomarchy"
|
|
nrun git git commit -qm "Initial Nomarchy configuration"
|
|
)
|
|
success "Git repository initialized"
|
|
|
|
# 9.6 Handle impermanence
|
|
if [[ "$ENABLE_IMPERMANENCE" == "true" ]]; then
|
|
info "Setting up impermanence..."
|
|
mkdir -p /mnt/persist/etc
|
|
mv /mnt/etc/nixos /mnt/persist/etc/
|
|
mkdir -p /mnt/etc
|
|
ln -s /persist/etc/nixos /mnt/etc/nixos
|
|
success "Impermanence configured"
|
|
fi
|
|
|
|
# 9.7 Install the NixOS system from the freshly-generated flake.
|
|
info "Running nixos-install (this will take a while)..."
|
|
nixos-install --flake "/mnt/etc/nixos#$HOSTNAME" --no-root-passwd
|
|
success "NixOS installed"
|
|
|
|
# 9.8 Activate Home Manager for $USERNAME inside the new system so the
|
|
# user's first login already has Nomarchy's dotfiles. `home-manager
|
|
# switch` must run as the target user with a real $HOME, so we use
|
|
# `runuser` (sudo -u keeps the caller's HOME → files land in /root).
|
|
info "Activating Home Manager for $USERNAME..."
|
|
if nixos-enter --root /mnt -- bash -c "
|
|
set -e
|
|
install -d -o '$USERNAME' -g users -m 0755 '/home/$USERNAME'
|
|
runuser -u '$USERNAME' -- env HOME='/home/$USERNAME' \
|
|
nix --extra-experimental-features 'nix-command flakes' \
|
|
run 'home-manager/release-25.11' -- switch \
|
|
--flake '/etc/nixos#$USERNAME' --impure
|
|
"; then
|
|
success "Home Manager activated"
|
|
else
|
|
error "Home Manager activation failed (non-fatal)."
|
|
info "Run \`nomarchy-env-update\` after the first login to retry."
|
|
fi
|
|
|
|
# 9.9 Pre-flight: catch evaluation errors in the freshly-installed
|
|
# configuration *now*, while we can still fix them with `vi`, instead of
|
|
# at the user's first post-reboot rebuild.
|
|
info "Verifying configuration evaluates (nixos-rebuild dry-build)..."
|
|
if nixos-enter --root /mnt -- bash -c "
|
|
nixos-rebuild dry-build --flake /etc/nixos#$HOSTNAME 2>&1 | tail -20
|
|
"; then
|
|
success "Configuration evaluates cleanly"
|
|
else
|
|
error "Pre-flight rebuild check failed."
|
|
info "The system is installed; fix /etc/nixos before rebooting if possible."
|
|
fi
|
|
|
|
success "Installation complete!"
|
|
rm -f "$STATE_FILE"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Dry run: generate the flake into a tmpdir and parse-check it. Doesn't
|
|
# touch the disk; useful while iterating on the installer or to validate a
|
|
# saved state file before actually committing to the install.
|
|
# ----------------------------------------------------------------------------
|
|
execute_dry_run() {
|
|
section "Dry Run"
|
|
|
|
local tmp
|
|
tmp=$(mktemp -d -t nomarchy-dryrun.XXXXXX)
|
|
info "Generating configuration in $tmp"
|
|
|
|
# Mock /mnt so the existing generator writes into the tmpdir.
|
|
local fake_root="$tmp/mnt"
|
|
mkdir -p "$fake_root/etc/nixos"
|
|
ln -snf "$fake_root" "$tmp/.mntlink"
|
|
# generate_flake_config writes to /mnt/etc/nixos directly. We can't
|
|
# easily re-target without a refactor — bind-mount instead so the
|
|
# absolute paths in the function still resolve to our tmpdir.
|
|
mount --bind "$fake_root" /mnt 2>/dev/null || true
|
|
|
|
if [[ ! -d /mnt/etc/nixos ]]; then
|
|
mkdir -p /mnt/etc/nixos
|
|
fi
|
|
|
|
# Stub hardware-configuration.nix — `nixos-generate-config` requires
|
|
# actually-mounted target filesystems, so we provide a syntactically
|
|
# valid placeholder for parse-checking only.
|
|
cat > /mnt/etc/nixos/hardware-configuration.nix <<'EOF'
|
|
{ ... }: { boot.loader.systemd-boot.enable = true; fileSystems."/" = { device = "/dev/null"; fsType = "tmpfs"; }; }
|
|
EOF
|
|
|
|
generate_flake_config
|
|
|
|
info "Running \`nix-instantiate --parse\` on each generated file..."
|
|
local f rc=0
|
|
for f in flake.nix hardware-selection.nix system.nix home.nix; do
|
|
if nix-instantiate --parse "/mnt/etc/nixos/$f" >/dev/null 2>&1; then
|
|
success " $f"
|
|
else
|
|
error " $f failed to parse"
|
|
rc=1
|
|
fi
|
|
done
|
|
|
|
info "Generated files:"
|
|
ls -1 /mnt/etc/nixos/
|
|
|
|
umount /mnt 2>/dev/null || true
|
|
info "Output kept at $fake_root for inspection."
|
|
|
|
if (( rc != 0 )); then
|
|
error "Dry run reported parse errors."
|
|
exit "$rc"
|
|
fi
|
|
success "Dry run OK — no disk touched."
|
|
}
|
|
|
|
# ============================================================================
|
|
# GENERATE FLAKE CONFIGURATION
|
|
# ============================================================================
|
|
|
|
generate_flake_config() {
|
|
local impermanence_opt=""
|
|
[[ "$ENABLE_IMPERMANENCE" == "true" ]] && impermanence_opt="nomarchy.system.impermanence.enable = true;"
|
|
|
|
# Pin the upstream Nomarchy flake to the exact commit we're installing
|
|
# from so the first post-reboot `nixos-rebuild` doesn't silently pull a
|
|
# newer main. Fall back to tracking main if we couldn't resolve a SHA.
|
|
# Upstream lives on the self-hosted Gitea at git.bemagri.xyz; flakes
|
|
# consume it via the `git+https://` URL form.
|
|
local nomarchy_url
|
|
if [[ -n "$NOMARCHY_REV" ]]; then
|
|
nomarchy_url="git+https://git.bemagri.xyz/bernardo/Nomarchy.git?rev=$NOMARCHY_REV"
|
|
else
|
|
nomarchy_url="git+https://git.bemagri.xyz/bernardo/Nomarchy.git"
|
|
fi
|
|
|
|
# flake.nix — the generator uses a non-quoted heredoc so $HOSTNAME,
|
|
# $USERNAME, and $nomarchy_url expand inline.
|
|
cat > /mnt/etc/nixos/flake.nix <<FLAKE_EOF
|
|
{
|
|
description = "My Nomarchy Configuration";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
|
|
nomarchy.url = "$nomarchy_url";
|
|
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
|
|
home-manager = {
|
|
url = "github:nix-community/home-manager/release-25.11";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
};
|
|
|
|
# Two-track Nomarchy workflow:
|
|
# * System changes → sudo nixos-rebuild switch --flake /etc/nixos#$HOSTNAME
|
|
# * Dotfiles/themes → nomarchy-env-update (home-manager switch, no rebuild)
|
|
#
|
|
# Both consume the same \`pkgs\` below so overlays and allowUnfree stay in
|
|
# sync across the two paths.
|
|
outputs = { self, nixpkgs, nomarchy, home-manager, nixos-hardware, ... }@inputs:
|
|
let
|
|
system = "x86_64-linux";
|
|
pkgs = import nixpkgs {
|
|
inherit system;
|
|
overlays = [ nomarchy.overlays.default ];
|
|
config.allowUnfree = true;
|
|
};
|
|
in
|
|
{
|
|
nixosConfigurations.$HOSTNAME = nixpkgs.lib.nixosSystem {
|
|
inherit pkgs;
|
|
specialArgs = { inputs = nomarchy.inputs // inputs; };
|
|
modules = [
|
|
./hardware-configuration.nix
|
|
./hardware-selection.nix
|
|
nomarchy.nixosModules.system
|
|
./system.nix
|
|
];
|
|
};
|
|
|
|
# Standalone Home Manager — \`home-manager switch --flake /etc/nixos#$USERNAME\`
|
|
# (which is what \`nomarchy-env-update\` runs). Kept separate from the
|
|
# NixOS config so dotfile/theme iterations don't rebuild the system.
|
|
homeConfigurations.$USERNAME = home-manager.lib.homeManagerConfiguration {
|
|
inherit pkgs;
|
|
extraSpecialArgs = { inputs = nomarchy.inputs // inputs; };
|
|
modules = [
|
|
nomarchy.nixosModules.home
|
|
./home.nix
|
|
{
|
|
home.username = "$USERNAME";
|
|
home.homeDirectory = "/home/$USERNAME";
|
|
home.stateVersion = "25.11";
|
|
}
|
|
];
|
|
};
|
|
};
|
|
}
|
|
FLAKE_EOF
|
|
|
|
# hardware-selection.nix
|
|
cat > /mnt/etc/nixos/hardware-selection.nix << EOF
|
|
{ inputs, ... }:
|
|
{
|
|
imports = [
|
|
$HARDWARE_MODULES
|
|
];
|
|
$NOMARCHY_HW_OPTS
|
|
}
|
|
EOF
|
|
|
|
# system.nix — curated system-level options. Uncomment what you want and
|
|
# run \`sudo nixos-rebuild switch --flake /etc/nixos#$HOSTNAME\` to apply.
|
|
# XKB variant is optional — only emit when the user picked one.
|
|
local xkb_variant_line=""
|
|
if [[ -n "$KEYMAP_VARIANT" ]]; then
|
|
xkb_variant_line=" variant = \"$KEYMAP_VARIANT\";"
|
|
fi
|
|
|
|
cat > /mnt/etc/nixos/system.nix << EOF
|
|
{ pkgs, ... }:
|
|
{
|
|
networking.hostName = "$HOSTNAME";
|
|
time.timeZone = "$TIMEZONE";
|
|
|
|
# Keyboard & language — set by the installer.
|
|
console.keyMap = "$KEYMAP_LAYOUT";
|
|
i18n.defaultLocale = "$LOCALE";
|
|
services.xserver.xkb = {
|
|
layout = "$KEYMAP_LAYOUT";
|
|
$xkb_variant_line
|
|
};
|
|
|
|
# Physical form factor — gates UI affordances (battery widget, etc).
|
|
nomarchy.system.formFactor = "$FORM_FACTOR";
|
|
|
|
$impermanence_opt
|
|
|
|
# Compressed RAM swap. Near-free memory headroom on small machines and
|
|
# harmless on big ones — kernel only uses it under pressure. Disable if
|
|
# you've enabled disk swap or hibernation.
|
|
zramSwap.enable = true;
|
|
|
|
# System-wide packages. Most tools belong in home.nix instead — only put
|
|
# things here that need to be available to all users or to root (e.g. CLI
|
|
# tools used by sudo, system admin utilities).
|
|
environment.systemPackages = with pkgs; [
|
|
home-manager
|
|
# --- CLI tools useful as root ---
|
|
# wget
|
|
# curl
|
|
# rsync
|
|
# htop
|
|
# tree
|
|
# tmux
|
|
];
|
|
|
|
services.displayManager.autoLogin.enable = true;
|
|
services.displayManager.autoLogin.user = "$USERNAME";
|
|
|
|
users.users."$USERNAME" = {
|
|
isNormalUser = true;
|
|
initialPassword = "$USER_PASSWORD";
|
|
extraGroups = [ "networkmanager" "wheel" "video" "audio" "render" ];
|
|
};
|
|
|
|
# --- Optional system services ---
|
|
# Uncomment to enable. Some require extra groups on your user (see below).
|
|
|
|
# Containers / virtualization
|
|
# virtualisation.docker.enable = true; # adds "docker" group
|
|
# virtualisation.libvirtd.enable = true; # adds "libvirtd" group — needed for virt-manager
|
|
|
|
# Networking / sync
|
|
# services.tailscale.enable = true;
|
|
# services.syncthing = {
|
|
# enable = true;
|
|
# user = "$USERNAME";
|
|
# dataDir = "/home/$USERNAME";
|
|
# };
|
|
|
|
# Printing
|
|
# services.printing.enable = true;
|
|
|
|
# Flatpak (alternative app delivery)
|
|
# services.flatpak.enable = true;
|
|
# xdg.portal.enable = true;
|
|
|
|
# Gaming (system-level — pairs with home.packages.steam)
|
|
# programs.steam.enable = true;
|
|
# programs.gamemode.enable = true;
|
|
|
|
# --- Optional Nomarchy modules ---
|
|
# Each line is a one-shot toggle for a Tier 1 system feature.
|
|
|
|
# BTRFS timeline snapshots of /. Auto-skips on non-BTRFS.
|
|
# nomarchy.system.snapper.enable = true;
|
|
|
|
# Suspend-then-hibernate on lid/idle/power-key. Requires disk swap.
|
|
# nomarchy.system.hibernation.enable = true;
|
|
# nomarchy.system.hibernation.idleMinutes = 30;
|
|
|
|
# Rootless Podman with \`docker\` compatibility.
|
|
# nomarchy.system.containers.enable = true;
|
|
|
|
# libvirt + virt-manager + OVMF.
|
|
# nomarchy.system.virtualization.libvirt.enable = true;
|
|
|
|
# GNOME Keyring auto-unlock + gcr SSH agent (default: on).
|
|
# nomarchy.system.keyring.enable = false;
|
|
|
|
# fcitx5 input method (CJK / IME).
|
|
# nomarchy.system.inputMethod.enable = true;
|
|
|
|
# voxtype voice-typing wiring (you must install voxtype yourself).
|
|
# nomarchy.system.voxtype.enable = true;
|
|
|
|
system.stateVersion = "25.11";
|
|
}
|
|
EOF
|
|
|
|
# home.nix — curated app menu. Uncomment what you want and run
|
|
# `nomarchy-env-update` to apply.
|
|
#
|
|
# NB: not heredoc-quoted — we expand $FORM_FACTOR. Any literal `$` or
|
|
# backtick in the body must be escaped.
|
|
cat > /mnt/etc/nixos/home.nix << EOF
|
|
{ pkgs, ... }:
|
|
{
|
|
# Physical form factor — mirrors nomarchy.system.formFactor in system.nix.
|
|
# Gates UI affordances like the waybar battery widget.
|
|
nomarchy.formFactor = "$FORM_FACTOR";
|
|
|
|
# User-level packages (Home Manager).
|
|
#
|
|
# Nomarchy already ships a minimal desktop (firefox, thunar, mpv, imv, mako,
|
|
# hyprlock, swww, wl-clipboard, grim, slurp, rofi-wayland, etc.). The list
|
|
# below is a menu of extras — uncomment what you want and run
|
|
# \`nomarchy-env-update\`.
|
|
home.packages = with pkgs; [
|
|
# --- Enabled by default ---
|
|
btop # Resource monitor (TUI)
|
|
fastfetch # System info at login
|
|
chromium # Secondary browser
|
|
|
|
# --- Editors & dev ---
|
|
# vscode
|
|
# jetbrains.idea-community
|
|
# neovide
|
|
# zed-editor
|
|
# lazygit
|
|
# gh # GitHub CLI
|
|
# docker-compose
|
|
# postman
|
|
# dbeaver-bin
|
|
|
|
# --- Productivity ---
|
|
# obsidian
|
|
# libreoffice
|
|
# thunderbird
|
|
# zathura # PDF viewer
|
|
# zotero
|
|
# xournalpp
|
|
|
|
# --- Media ---
|
|
# vlc
|
|
# obs-studio
|
|
# gimp
|
|
# inkscape
|
|
# kdenlive
|
|
# spotify
|
|
# audacity
|
|
# yt-dlp
|
|
|
|
# --- Comms ---
|
|
# discord
|
|
# telegram-desktop
|
|
# signal-desktop
|
|
# slack
|
|
# zoom-us
|
|
|
|
# --- Security ---
|
|
# keepassxc
|
|
# bitwarden-desktop
|
|
# _1password-gui
|
|
|
|
# --- Gaming ---
|
|
# steam
|
|
# lutris
|
|
# heroic
|
|
|
|
# --- CLI / utilities ---
|
|
# ripgrep
|
|
# fd
|
|
# bat
|
|
# eza
|
|
# zoxide
|
|
# fzf
|
|
# httpie
|
|
# tldr
|
|
];
|
|
|
|
# --- Optional Nomarchy app modules ---
|
|
|
|
# opencode AI coding CLI integration (deploys ~/.config/opencode/opencode.json).
|
|
# The \`opencode\` package itself is not installed automatically — add it to
|
|
# \`home.packages\` above if you want it on PATH.
|
|
# nomarchy.apps.opencode.enable = true;
|
|
|
|
# Extra Home Manager modules go here (program configs, services, etc.).
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# ============================================================================
|
|
# FINISH
|
|
# ============================================================================
|
|
|
|
finish() {
|
|
header
|
|
|
|
nrun gum style --foreground 10 --bold --align center "INSTALLATION COMPLETE!"
|
|
echo ""
|
|
echo "Nomarchy has been successfully installed."
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Remove the installation media"
|
|
echo " 2. Reboot your computer"
|
|
echo " 3. Log in as: $USERNAME (host: $HOSTNAME)"
|
|
echo " 4. Your configuration lives at /etc/nixos/"
|
|
echo ""
|
|
echo "Rebuild commands:"
|
|
echo " • System: sudo nixos-rebuild switch --flake /etc/nixos#$HOSTNAME"
|
|
echo " • Dotfiles: nomarchy-env-update (runs home-manager switch)"
|
|
echo ""
|
|
echo "Tip: run 'nomarchy-themes-prebuild' once to pre-cache every theme"
|
|
echo " variant. Theme switches after that are instant (no rebuild)."
|
|
echo ""
|
|
|
|
if nrun gum confirm "Reboot now?"; then
|
|
reboot
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# MAIN
|
|
# ============================================================================
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
header
|
|
load_state
|
|
|
|
check_environment
|
|
select_disk
|
|
get_luks_passphrase
|
|
configure_user
|
|
select_keymap_locale
|
|
select_timezone
|
|
select_hardware
|
|
confirm_form_factor
|
|
configure_impermanence
|
|
review_configuration
|
|
execute_installation
|
|
|
|
# Skip the reboot prompt on a dry run — nothing to reboot into.
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
info "Dry run finished."
|
|
exit 0
|
|
fi
|
|
|
|
finish
|
|
}
|
|
|
|
main "$@"
|