#!/usr/bin/bash # veilor-postinstall — first-login TUI on v0.7+ atomic systems. # # Runs ONCE on first SDDM login via the user-mode systemd unit # `veilor-postinstall.service`. Asks the operator for the small set # of decisions we deliberately defer from install time: # - keyboard / locale # - hostname override # - GPU drivers (NVIDIA layered via rpm-ostree, mesa = no-op) # - package preset (dev / media / homelab — additive, opt-out) # - bluetooth opt-in # - USBGuard policy snapshot # - veilor-doctor first run # Writes /var/lib/veilor/postinstall-complete on success and disables # its own autostart unit. Idempotent: safe to re-run. # # Style: gum if present, plain bash read fallback. No decorative ASCII. set -uo pipefail export TERM="${TERM:-linux}" STATE_DIR=/var/lib/veilor DONE_MARKER="$STATE_DIR/postinstall-complete" LOG=/var/log/veilor-postinstall.log have() { command -v "$1" >/dev/null 2>&1; } GUM=$(have gum && echo gum || echo "") # Always log + tee to stdout for live progress. mkdir -p "$STATE_DIR" 2>/dev/null || true exec > >(tee -a "$LOG") 2>&1 if [[ -e $DONE_MARKER && ${1:-} != "--force" ]]; then echo "veilor-postinstall already ran (marker: $DONE_MARKER). Pass --force to re-run." exit 0 fi # ── Wrappers ──────────────────────────────────────────────────────── choose() { local header=$1; shift if [[ -n $GUM ]]; then gum choose --header "$header" "$@" else echo echo "$header" local i=1 for opt in "$@"; do printf ' %d) %s\n' "$i" "$opt"; ((i++)); done local n read -rp " choice (1-$#): " n [[ $n -ge 1 && $n -le $# ]] || return 1 eval "echo \${$n}" fi } ask() { local prompt=$1 default=${2:-} if [[ -n $GUM ]]; then gum input --header "$prompt" --value "$default" else local v read -rp "$prompt [$default] " v echo "${v:-$default}" fi } confirm() { local prompt=$1 if [[ -n $GUM ]]; then gum confirm "$prompt" && return 0 || return 1 else read -rp "$prompt [y/N] " y [[ ${y,,} == y* ]] fi } say() { if [[ -n $GUM ]]; then gum style --foreground 212 --bold "$1" else printf '\n=== %s ===\n' "$1" fi } # Need root for several actions; re-exec under sudo if not root. if [[ $EUID -ne 0 ]]; then say "veilor-postinstall: sudo required" exec sudo -E bash "$0" "$@" fi say "veilor-postinstall — one-time setup" echo " This runs once. Each step is skippable. Defaults are sane." echo # ── 1. Keyboard layout ────────────────────────────────────────────── KB=$(choose "Keyboard layout" us gb de fr es ru "skip") || KB=skip if [[ $KB != skip ]]; then localectl set-keymap "$KB" 2>/dev/null || true echo " [OK] keymap = $KB" fi # ── 2. Locale ─────────────────────────────────────────────────────── LOC=$(choose "Locale" en_US.UTF-8 en_GB.UTF-8 de_DE.UTF-8 fr_FR.UTF-8 "skip") || LOC=skip if [[ $LOC != skip ]]; then localectl set-locale LANG="$LOC" 2>/dev/null || true echo " [OK] locale = $LOC" fi # ── 3. Hostname ───────────────────────────────────────────────────── HN=$(ask "Hostname" "veilor") if [[ -n $HN && $HN != $(hostnamectl --static 2>/dev/null) ]]; then hostnamectl set-hostname "$HN" echo " [OK] hostname = $HN" fi # ── 4. GPU drivers ────────────────────────────────────────────────── GPU=$(choose "GPU drivers" "Skip (use mesa defaults)" "NVIDIA proprietary (akmod-nvidia)" "Intel/AMD mesa (no-op)") || GPU=skip case "$GPU" in *NVIDIA*) say "Layering NVIDIA driver — this takes a few minutes" rpm-ostree install --idempotent akmod-nvidia xorg-x11-drv-nvidia-cuda \ && echo " [OK] NVIDIA driver layered (reboot to use)" \ || echo " [WARN] NVIDIA layer failed; check rpm-ostree status" ;; *) echo " (skipped GPU layering)" ;; esac # ── 5. Package presets (multi-select) ─────────────────────────────── say "Package presets — pick any combination (skip = none)" PRESET_DEV="git tmux vim-enhanced htop podman skopeo" PRESET_MEDIA="vlc obs-studio" PRESET_HOMELAB="wireguard-tools jq yq tmux" PICKED=() confirm "Install dev preset? ($PRESET_DEV)" && PICKED+=($PRESET_DEV) || true confirm "Install media preset? ($PRESET_MEDIA)" && PICKED+=($PRESET_MEDIA) || true confirm "Install homelab preset? ($PRESET_HOMELAB)" && PICKED+=($PRESET_HOMELAB) || true if (( ${#PICKED[@]} > 0 )); then # de-dupe UNIQ=$(printf '%s\n' "${PICKED[@]}" | sort -u | tr '\n' ' ') say "Layering: $UNIQ" rpm-ostree install --idempotent $UNIQ \ && echo " [OK] preset packages layered (reboot to use)" \ || echo " [WARN] preset layer failed; check rpm-ostree status" fi # ── 6. Bluetooth ──────────────────────────────────────────────────── if confirm "Enable Bluetooth?"; then systemctl enable --now bluetooth.service 2>/dev/null || true echo " [OK] bluetooth enabled" else echo " (skipped bluetooth)" fi # ── 7. USBGuard snapshot ──────────────────────────────────────────── say "USBGuard policy snapshot" echo " Plug in EVERY USB device you trust right now (keyboard," echo " mouse, dock, yubikey, etc.) before continuing." if confirm "Snapshot current USB devices into the allowlist?"; then usbguard generate-policy > /etc/usbguard/rules.conf \ && echo " [OK] policy written to /etc/usbguard/rules.conf" \ || echo " [WARN] generate-policy failed" systemctl restart usbguard 2>/dev/null || true fi # ── 8. veilor-doctor ──────────────────────────────────────────────── if confirm "Run veilor-doctor now?"; then veilor-doctor || true fi # ── Done ──────────────────────────────────────────────────────────── date -u +"%Y-%m-%dT%H:%M:%SZ" > "$DONE_MARKER" say "veilor-postinstall complete" echo " Marker written: $DONE_MARKER" echo " Disabling autostart unit so this never runs again." systemctl --user --global disable veilor-postinstall.service 2>/dev/null || true systemctl disable veilor-postinstall.service 2>/dev/null || true echo echo " If you layered any packages or drivers, reboot to activate."