From 3328ffb4601a1611ed02671845d197b0d973c256 Mon Sep 17 00:00:00 2001 From: veilor-org Date: Sat, 2 May 2026 03:20:42 +0100 Subject: [PATCH] v0.5.0-alpha: TTY1 installer (omarchy/archinstall-style) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds: - overlay/usr/local/sbin/veilor-installer — bash+whiptail TUI - overlay/etc/systemd/system/getty@tty1.service.d/veilor-installer.conf → replaces tty1 login with installer - ks: newt + parted + cryptsetup + lvm2 + btrfs-progs packages - ks: systemctl set-default multi-user.target (TTY1 lands first; user picks "Try live — desktop" from menu to isolate graphical.target) - ks: chmod +x veilor-installer in chroot %post Flow: 1. Boot ISO → TTY1 → ASCII banner + menu: 1) Install to disk 2) Try live — desktop 3) Try live — shell 4) Reboot 5) Power off 2. Install path: collects disk/hostname/LUKS/admin pw/locale via whiptail, generates /run/install/veilor-generated.ks, execs anaconda --kickstart= 3. Reboots into hardened install with full init_on_alloc/free cmdline Known limitations (v0.5.0-alpha): - Generated ks doesn't yet copy overlay/scripts into target (anaconda installs base Fedora, missing veilor branding/hardening). Fix in v0.5.1. - whiptail = ugly. v0.5.1 swaps to gum (Go TUI) for omarchy-tier UX. - No mid-install progress bar; anaconda runs unattended in same tty. --- kickstart/veilor-os.ks | 14 +- .../veilor-installer.conf | 16 ++ overlay/usr/local/sbin/veilor-installer | 268 ++++++++++++++++++ 3 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 overlay/etc/systemd/system/getty@tty1.service.d/veilor-installer.conf create mode 100644 overlay/usr/local/sbin/veilor-installer diff --git a/kickstart/veilor-os.ks b/kickstart/veilor-os.ks index 5a88323..04f2ff9 100644 --- a/kickstart/veilor-os.ks +++ b/kickstart/veilor-os.ks @@ -92,6 +92,13 @@ syslinux isomd5sum xorriso +# veilor-installer dependencies (TTY1 TUI installer wrapping anaconda) +newt +parted +cryptsetup +lvm2 +btrfs-progs + # core hardening tools fail2ban @@ -198,7 +205,7 @@ echo " veilor-os install — %post" echo "════════════════════════════════════════════════════════" REPO=/usr/share/veilor-os -chmod +x $REPO/scripts/*.sh $REPO/scripts/selinux/*.sh /usr/local/bin/veilor-power /usr/local/sbin/veilor-firstboot +chmod +x $REPO/scripts/*.sh $REPO/scripts/selinux/*.sh /usr/local/bin/veilor-power /usr/local/sbin/veilor-firstboot /usr/local/sbin/veilor-installer # Live image plumbing (matches upstream Fedora live ks). Without these the # squashfs/EFI build fails — livesys-scripts ships systemd units lorax expects. @@ -236,6 +243,11 @@ fi # Without this, sddm stays inactive even though enabled. ln -sf /usr/lib/systemd/system/sddm.service /etc/systemd/system/display-manager.service +# Live ISO default target: multi-user (TTY1 = veilor-installer TUI lands first). +# User picks "Try live — desktop" from menu → systemctl isolate graphical.target. +# Real installs land on graphical.target by default (set by anaconda). +systemctl set-default multi-user.target + # zram swap (no disk swap; keys never leak to platter) dnf install -y zram-generator || true cat > /etc/systemd/zram-generator.conf << 'EOF' diff --git a/overlay/etc/systemd/system/getty@tty1.service.d/veilor-installer.conf b/overlay/etc/systemd/system/getty@tty1.service.d/veilor-installer.conf new file mode 100644 index 0000000..a2735ee --- /dev/null +++ b/overlay/etc/systemd/system/getty@tty1.service.d/veilor-installer.conf @@ -0,0 +1,16 @@ +# veilor-os: replace tty1 login prompt with installer TUI. +# Live ISO ONLY. Removed by anaconda during real install (this overlay +# isn't copied into target system — see kickstart/install.ks). +[Service] +ExecStart= +ExecStart=-/usr/local/sbin/veilor-installer +StandardInput=tty +StandardOutput=tty +StandardError=tty +TTYPath=/dev/tty1 +TTYReset=yes +TTYVHangup=yes +TTYVTDisallocate=yes +Type=idle +Restart=always +RestartSec=2 diff --git a/overlay/usr/local/sbin/veilor-installer b/overlay/usr/local/sbin/veilor-installer new file mode 100644 index 0000000..4112974 --- /dev/null +++ b/overlay/usr/local/sbin/veilor-installer @@ -0,0 +1,268 @@ +#!/usr/bin/env bash +# veilor-os installer — TUI wrapper around anaconda kickstart install. +# Runs on tty1 in place of getty (live ISO boot path). +# +# Flow: +# 1. ASCII banner +# 2. Menu: Install / Live shell / Reboot / Power off +# 3. If Install: collect answers via whiptail (disk, hostname, LUKS pw, +# admin pw, locale) +# 4. Generate /run/install/veilor-generated.ks from template + answers +# 5. Exec anaconda --kickstart=/run/install/veilor-generated.ks +# 6. On finish: reboot into installed system +# +# v0.5.0 — first cut. v0.5.1 swaps whiptail for gum (Go TUI, prettier). + +set -uo pipefail +export TERM="${TERM:-linux}" +LOG=/var/log/veilor-installer.log +exec > >(tee -a "$LOG") 2>&1 + +banner() { + clear + cat << 'EOF' + + ▌ ▌▙▀▖▌ ▐▌▛▀▖▌ ▖▙▀▖▖▖ + ▙▖▌█ ▐▖▟▘▙▄▘▌ ▌▌ ▙▟ + ▘ + veilor-os installer + hardened. branded. yours. + +EOF + echo "──────────────────────────────────────────" + echo +} + +require_tty() { + if ! [[ -t 0 && -t 1 ]]; then + echo "[ERR] veilor-installer must run on a real tty" >&2 + exit 1 + fi +} + +main_menu() { + local choice + choice=$(whiptail --title "veilor-os" \ + --menu "Welcome. What would you like to do?" 16 60 5 \ + "1" "Install veilor-os to disk" \ + "2" "Try live — desktop (KDE Plasma)" \ + "3" "Try live — shell" \ + "4" "Reboot" \ + "5" "Power off" \ + 3>&1 1>&2 2>&3) + echo "$choice" +} + +collect_answers() { + local disk hostname luks_pw admin_pw locale + local disks_list + + # ── Disk ── + disks_list=$(lsblk -dpno NAME,SIZE,MODEL | grep -E '^/dev/(sd|nvme|vd)' | awk '{print $1, $2"_"$3}') + if [[ -z $disks_list ]]; then + whiptail --title "veilor-os" --msgbox "No installable disks found." 8 50 + return 1 + fi + disk=$(whiptail --title "Select install disk" \ + --menu "WARNING: selected disk will be ERASED." 18 70 8 \ + $disks_list 3>&1 1>&2 2>&3) || return 1 + + # ── Hostname ── + hostname=$(whiptail --title "Hostname" \ + --inputbox "Set hostname:" 10 60 "veilor" \ + 3>&1 1>&2 2>&3) || return 1 + + # ── LUKS passphrase ── + luks_pw=$(whiptail --title "Disk encryption" \ + --passwordbox "LUKS passphrase (full-disk encryption):" 10 60 \ + 3>&1 1>&2 2>&3) || return 1 + [[ ${#luks_pw} -lt 8 ]] && { + whiptail --title "Weak passphrase" --msgbox "Min 8 chars." 8 40 + return 1 + } + + # ── Admin password ── + admin_pw=$(whiptail --title "Admin password" \ + --passwordbox "Admin user password (login after install):" 10 60 \ + 3>&1 1>&2 2>&3) || return 1 + [[ ${#admin_pw} -lt 8 ]] && { + whiptail --title "Weak password" --msgbox "Min 8 chars." 8 40 + return 1 + } + + # ── Locale ── + locale=$(whiptail --title "Locale" \ + --menu "Choose locale:" 14 50 4 \ + "en_GB.UTF-8" "English (UK)" \ + "en_US.UTF-8" "English (US)" \ + "de_DE.UTF-8" "Deutsch" \ + "fr_FR.UTF-8" "Francais" \ + 3>&1 1>&2 2>&3) || return 1 + + # ── Confirmation ── + whiptail --title "Confirm install" --yesno \ +"About to install veilor-os: + + Disk: $disk (will be ERASED) + Hostname: $hostname + Locale: $locale + LUKS: set + Admin pw: set + +Proceed?" 16 60 || return 1 + + # Export to caller via globals + SEL_DISK=$disk + SEL_HOSTNAME=$hostname + SEL_LUKS_PW=$luks_pw + SEL_ADMIN_PW=$admin_pw + SEL_LOCALE=$locale + return 0 +} + +generate_ks() { + # Build kickstart for actual disk install. + # NOTE: passwords go in via --plaintext to avoid storing crypted hash + # collisions; anaconda hashes per /etc/login.defs at install time. + local out=/run/install/veilor-generated.ks + mkdir -p /run/install + cat > "$out" << EOF +# veilor-os installer-generated kickstart +# DO NOT commit this file — secrets inline. +url --mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-43&arch=x86_64" +repo --name=fedora --baseurl="https://download.fedoraproject.org/pub/fedora/linux/releases/43/Everything/x86_64/os/" --install +repo --name=updates --baseurl="https://download.fedoraproject.org/pub/fedora/linux/updates/43/Everything/x86_64/" --install + +keyboard --xlayouts='us' +lang $SEL_LOCALE +timezone Europe/London --utc + +firstboot --disable +eula --agreed +selinux --enforcing +services --enabled=sshd,fail2ban,usbguard,tuned,auditd,firewalld,chronyd,sddm + +network --bootproto=dhcp --device=link --activate --hostname=$SEL_HOSTNAME +firewall --enabled --service=ssh + +rootpw --lock +user --name=admin --groups=wheel --gecos="veilor admin" --password="$SEL_ADMIN_PW" --plaintext + +# Full hardening cmdline (installed system, not live): +bootloader --location=mbr --append="lockdown=integrity slab_nomerge init_on_alloc=1 init_on_free=1 randomize_kstack_offset=on vsyscall=none" + +# Disk: zero, LUKS2 (argon2id), btrfs subvolumes +zerombr +clearpart --all --initlabel --drives=$(basename "$SEL_DISK") +part /boot/efi --fstype=efi --size=600 --asprimary +part /boot --fstype=ext4 --size=1024 --asprimary +part pv.veilor --grow --encrypted --luks-version=luks2 --pbkdf=argon2id --passphrase="$SEL_LUKS_PW" +volgroup veilor pv.veilor +logvol / --vgname=veilor --name=root --fstype=btrfs --grow + +%packages --excludedocs +@^kde-desktop-environment +@kde-apps +@core +@hardware-support +@standard +fail2ban +fail2ban-firewalld +usbguard +usbguard-tools +audit +policycoreutils-python-utils +tuned +chrony +firewalld +plymouth +git +vim-enhanced +tmux +htop +podman +NetworkManager +NetworkManager-wifi +fontconfig +fira-code-fonts +zram-generator +-abrt* +-snapd +-kde-connect +-mlocate +%end + +# Reboot when done +reboot +EOF + echo "[INFO] generated kickstart at $out" + return 0 +} + +run_install() { + whiptail --title "Installing" --infobox \ +"Installing veilor-os to $SEL_DISK ... +This will take 10-30 minutes. +Logs: /var/log/veilor-installer.log + /tmp/anaconda.log" 10 60 + sleep 2 + # Hand off to anaconda. --kickstart runs unattended. + if anaconda --kickstart=/run/install/veilor-generated.ks; then + whiptail --title "Done" --msgbox \ +"Install complete. System will reboot. +Remove the install media after shutdown." 10 50 + sleep 3 + systemctl reboot + else + whiptail --title "Install failed" --msgbox \ +"Anaconda exited non-zero. +Logs at /tmp/anaconda.log + /var/log/veilor-installer.log. +Press OK to drop to shell." 12 60 + return 1 + fi +} + +drop_to_shell() { + clear + cat << 'EOF' +═══════════════════════════════════════════════════ + veilor-os live shell +═══════════════════════════════════════════════════ + +You are in a live, in-memory environment. +Nothing persists across reboot. + +Re-run the installer: sudo veilor-installer +Reboot: sudo systemctl reboot +Power off: sudo systemctl poweroff + +EOF + exec /bin/bash --login +} + +# ── Entry ── +require_tty +banner + +launch_desktop() { + clear + echo "Launching KDE Plasma..." + sleep 1 + systemctl isolate graphical.target + # systemd-isolate switches target; sddm spawns on tty1. + # If user logs out, they come back here. Loop continues. +} + +while true; do + case "$(main_menu)" in + 1) + if collect_answers && generate_ks; then + run_install || continue + fi + ;; + 2) launch_desktop ;; + 3) drop_to_shell ;; + 4) systemctl reboot ;; + 5) systemctl poweroff ;; + *) drop_to_shell ;; + esac +done