v0.5.1: gum installer + full veilor-os kickstart generation (#9)
* v0.5.1: gum installer + full veilor-os ks generation
Two changes, one commit (matches v0.5.1 milestone):
1. Swap whiptail → gum (charm.sh)
- Source /usr/share/veilor-os/assets/installer/colors.gum at top so all
prompts pick up branded GUM_* env vars.
- Render banner.txt via `gum style --border rounded`.
- Wrap every prompt behind prompt_choose / prompt_input / prompt_password
/ prompt_confirm / prompt_message / prompt_error helpers that dispatch
gum→whiptail based on `command -v gum`. Defensive: minimal images
without /usr/local/bin/gum still get a working TUI.
- Main menu items now use literal labels (case-matched), not 1..5 tags.
2. Generated kickstart now installs full veilor-os
Previously emitted a vanilla F43 KDE + ~12 hardening packages with no
overlay/scripts/branding. Now mirrors live ks (kickstart/veilor-os.ks
63-141) for %packages, plus:
- %post --nochroot copies overlay/, scripts/, assets/ from
/run/install/repo/veilor (single source — boot ISO mount path).
- %post (chroot) runs scripts/10-harden-base.sh, 20-harden-kernel.sh,
selinux/build-policy.sh, kde-theme-apply.sh.
- `chage -d 0 admin` so first login forces password change. (Account
itself is created by anaconda from the `user` directive — admin pw
collected via gum is passed through --plaintext.)
- `systemctl set-default graphical.target` (real install boots SDDM,
not the TTY1 installer like live).
- Drops live-only entries (livesys-scripts, anaconda-live, dracut-live,
isomd5sum, xorriso, livesys.service enables).
Tested: bash -n clean; ksvalidator on a substituted-placeholder copy
exits 0.
gum binary itself (/usr/local/bin/gum) is vendored by a separate
build-side change — not in this PR.
* fix: escape sed special chars + reject & | / in passwords
Reviewer found a password like aA1!@#%^&*()_-+={}[] becomes
aA1!@#%^__ADMIN_PW__*()_-+={}[] because sed expands & to matched
pattern. Two layers of defense:
1. validate_pw rejects & | / newline at input
2. sed_escape() helper escapes any remaining special chars before
substitution
---------
Co-authored-by: veilor-org <admin@veilor.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
70abf8c496
commit
4c8002cda7
1 changed files with 367 additions and 83 deletions
|
|
@ -3,34 +3,65 @@
|
||||||
# Runs on tty1 in place of getty (live ISO boot path).
|
# Runs on tty1 in place of getty (live ISO boot path).
|
||||||
#
|
#
|
||||||
# Flow:
|
# Flow:
|
||||||
# 1. ASCII banner
|
# 1. ASCII banner (assets/installer/banner.txt)
|
||||||
# 2. Menu: Install / Live shell / Reboot / Power off
|
# 2. Menu: Install / Live desktop / Live shell / Reboot / Power off
|
||||||
# 3. If Install: collect answers via whiptail (disk, hostname, LUKS pw,
|
# 3. If Install: collect answers via gum (disk, hostname, LUKS pw,
|
||||||
# admin pw, locale)
|
# admin pw, locale)
|
||||||
# 4. Generate /run/install/veilor-generated.ks from template + answers
|
# 4. Generate /run/install/veilor-generated.ks from template + answers
|
||||||
|
# (full veilor-os install: package list + overlay + scripts + harden)
|
||||||
# 5. Exec anaconda --kickstart=/run/install/veilor-generated.ks
|
# 5. Exec anaconda --kickstart=/run/install/veilor-generated.ks
|
||||||
# 6. On finish: reboot into installed system
|
# 6. On finish: reboot into installed system
|
||||||
#
|
#
|
||||||
# v0.5.0 — first cut. v0.5.1 swaps whiptail for gum (Go TUI, prettier).
|
# v0.5.1 — gum (charm.sh) replaces whiptail; whiptail kept as fallback.
|
||||||
|
# Generated kickstart now installs full veilor-os (matches live ks).
|
||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
export TERM="${TERM:-linux}"
|
export TERM="${TERM:-linux}"
|
||||||
LOG=/var/log/veilor-installer.log
|
LOG=/var/log/veilor-installer.log
|
||||||
exec > >(tee -a "$LOG") 2>&1
|
exec > >(tee -a "$LOG") 2>&1
|
||||||
|
|
||||||
|
# ── Branded styling for gum ─────────────────────────────────────────────
|
||||||
|
# colors.gum sets GUM_* env vars — pure-black palette from veilor-black KDE.
|
||||||
|
# Sourced at top so every prompt below picks up branded colors.
|
||||||
|
COLORS=/usr/share/veilor-os/assets/installer/colors.gum
|
||||||
|
[[ -r $COLORS ]] && source "$COLORS"
|
||||||
|
|
||||||
|
BANNER_FILE=/usr/share/veilor-os/assets/installer/banner.txt
|
||||||
|
|
||||||
|
# Detect TUI backend once. gum is preferred; whiptail is the fallback so the
|
||||||
|
# installer keeps working on minimal images or if /usr/local/bin/gum is
|
||||||
|
# missing (e.g. broken vendored binary, /usr remount issues).
|
||||||
|
if command -v gum >/dev/null 2>&1; then
|
||||||
|
TUI=gum
|
||||||
|
elif command -v whiptail >/dev/null 2>&1; then
|
||||||
|
TUI=whiptail
|
||||||
|
else
|
||||||
|
echo "[ERR] neither gum nor whiptail available — cannot run TUI" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
banner() {
|
banner() {
|
||||||
clear
|
clear
|
||||||
cat << 'EOF'
|
if [[ -r $BANNER_FILE ]]; then
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
# gum style draws a rounded border + padding around the banner.
|
||||||
|
gum style --border rounded --margin "1" --padding "1 2" \
|
||||||
|
--border-foreground "${VEILOR_DIM:-240}" \
|
||||||
|
--foreground "${VEILOR_FG:-15}" \
|
||||||
|
"$(cat "$BANNER_FILE")"
|
||||||
|
else
|
||||||
|
cat "$BANNER_FILE"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Fallback ASCII if banner.txt missing (older overlay).
|
||||||
|
cat << 'EOF'
|
||||||
|
|
||||||
▌ ▌▙▀▖▌ ▐▌▛▀▖▌ ▖▙▀▖▖▖
|
|
||||||
▙▖▌█ ▐▖▟▘▙▄▘▌ ▌▌ ▙▟
|
|
||||||
▘
|
|
||||||
veilor-os installer
|
veilor-os installer
|
||||||
hardened. branded. yours.
|
hardened. branded. yours.
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
echo "──────────────────────────────────────────"
|
fi
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require_tty() {
|
require_tty() {
|
||||||
|
|
@ -40,82 +71,176 @@ require_tty() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── TUI wrapper functions ───────────────────────────────────────────────
|
||||||
|
# Each prompt_* call abstracts gum / whiptail. Always emit the chosen value
|
||||||
|
# on stdout; non-zero exit on cancel/ESC. Callers use `||` to propagate.
|
||||||
|
|
||||||
|
# prompt_choose <header> <opt1> [opt2 ...]
|
||||||
|
# Single-select menu. Returns the selected option literal on stdout.
|
||||||
|
prompt_choose() {
|
||||||
|
local header=$1; shift
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
gum choose --header "$header" "$@"
|
||||||
|
else
|
||||||
|
# whiptail menu needs tag/desc pairs. Use the option as both.
|
||||||
|
local args=()
|
||||||
|
local opt
|
||||||
|
for opt in "$@"; do args+=("$opt" "$opt"); done
|
||||||
|
whiptail --title "veilor-os" --menu "$header" 18 70 8 \
|
||||||
|
"${args[@]}" 3>&1 1>&2 2>&3
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# prompt_choose_pairs <header> <tag1> <desc1> [tag2 desc2 ...]
|
||||||
|
# Tag/description menu. Returns the chosen tag.
|
||||||
|
# Used when display label differs from machine-readable value (e.g. disk
|
||||||
|
# path /dev/nvme0n1 vs description "476G WDC PC SN740").
|
||||||
|
prompt_choose_pairs() {
|
||||||
|
local header=$1; shift
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
# gum has no tag/desc concept — render "tag — desc" lines, parse.
|
||||||
|
local lines=() i=1
|
||||||
|
while (( i <= $# )); do
|
||||||
|
local tag=${!i}; ((i++))
|
||||||
|
local desc=${!i}; ((i++))
|
||||||
|
lines+=("$tag — $desc")
|
||||||
|
done
|
||||||
|
local picked
|
||||||
|
picked=$(printf '%s\n' "${lines[@]}" | gum choose --header "$header") || return 1
|
||||||
|
# Strip " — desc" suffix to recover the tag.
|
||||||
|
echo "${picked%% — *}"
|
||||||
|
else
|
||||||
|
whiptail --title "veilor-os" --menu "$header" 18 70 8 \
|
||||||
|
"$@" 3>&1 1>&2 2>&3
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# prompt_input <header> [default]
|
||||||
|
prompt_input() {
|
||||||
|
local header=$1 default=${2:-}
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
gum input --header "$header" --value "$default"
|
||||||
|
else
|
||||||
|
whiptail --title "veilor-os" --inputbox "$header" 10 60 "$default" \
|
||||||
|
3>&1 1>&2 2>&3
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# prompt_password <header>
|
||||||
|
prompt_password() {
|
||||||
|
local header=$1
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
gum input --password --header "$header"
|
||||||
|
else
|
||||||
|
whiptail --title "veilor-os" --passwordbox "$header" 10 60 \
|
||||||
|
3>&1 1>&2 2>&3
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# prompt_confirm <message>
|
||||||
|
# Exits 0 on yes, 1 on no — matches whiptail --yesno semantics.
|
||||||
|
prompt_confirm() {
|
||||||
|
local msg=$1
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
gum confirm "$msg"
|
||||||
|
else
|
||||||
|
whiptail --title "Confirm" --yesno "$msg" 16 60
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# prompt_message <message>
|
||||||
|
# Non-blocking notice. gum has no msgbox; print styled + sleep.
|
||||||
|
prompt_message() {
|
||||||
|
local msg=$1
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
gum style --foreground "${VEILOR_FG:-15}" --border rounded \
|
||||||
|
--border-foreground "${VEILOR_DIM:-240}" --padding "1 2" -- "$msg"
|
||||||
|
sleep 2
|
||||||
|
else
|
||||||
|
whiptail --title "veilor-os" --msgbox "$msg" 10 60
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# prompt_error <message>
|
||||||
|
# Same as message but with a red foreground for visibility.
|
||||||
|
prompt_error() {
|
||||||
|
local msg=$1
|
||||||
|
if [[ $TUI == gum ]]; then
|
||||||
|
gum style --foreground 1 --border rounded --border-foreground 1 \
|
||||||
|
--padding "1 2" -- "$msg"
|
||||||
|
sleep 2
|
||||||
|
else
|
||||||
|
whiptail --title "Error" --msgbox "$msg" 10 60
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
main_menu() {
|
main_menu() {
|
||||||
local choice
|
prompt_choose "Welcome. What would you like to do?" \
|
||||||
choice=$(whiptail --title "veilor-os" \
|
"Install veilor-os to disk" \
|
||||||
--menu "Welcome. What would you like to do?" 16 60 5 \
|
"Try live — desktop (KDE Plasma)" \
|
||||||
"1" "Install veilor-os to disk" \
|
"Try live — shell" \
|
||||||
"2" "Try live — desktop (KDE Plasma)" \
|
"Reboot" \
|
||||||
"3" "Try live — shell" \
|
"Power off"
|
||||||
"4" "Reboot" \
|
|
||||||
"5" "Power off" \
|
|
||||||
3>&1 1>&2 2>&3)
|
|
||||||
echo "$choice"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_answers() {
|
collect_answers() {
|
||||||
local disk hostname luks_pw admin_pw locale
|
local disk hostname luks_pw admin_pw locale
|
||||||
local disks_list
|
local disks_pairs
|
||||||
|
|
||||||
# ── Disk ──
|
# ── Disk ──
|
||||||
# Build "tag description" pairs for whiptail. Model strings have spaces
|
# Build whiptail-style "tag desc" pairs. prompt_choose_pairs reshapes
|
||||||
# (e.g. "WD PC SN740"), so collapse model to underscores for menu.
|
# for gum if needed.
|
||||||
disks_list=$(lsblk -dpno NAME,SIZE,MODEL | grep -E '^/dev/(sd|nvme|vd|mmcblk)' | \
|
# shellcheck disable=SC2207
|
||||||
awk '{name=$1; size=$2; $1=""; $2=""; sub(/^ +/,""); gsub(/ /,"_"); model=$0; if(model=="")model="unknown"; print name, size"_"model}')
|
disks_pairs=($(lsblk -dpno NAME,SIZE,MODEL | grep -E '^/dev/(sd|nvme|vd|mmcblk)' | \
|
||||||
if [[ -z $disks_list ]]; then
|
awk '{name=$1; size=$2; $1=""; $2=""; sub(/^ +/,""); gsub(/ /,"_"); model=$0; if(model=="")model="unknown"; print name, size"_"model}'))
|
||||||
whiptail --title "veilor-os" --msgbox "No installable disks found." 8 50
|
if [[ ${#disks_pairs[@]} -eq 0 ]]; then
|
||||||
|
prompt_error "No installable disks found."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
disk=$(whiptail --title "Select install disk" \
|
disk=$(prompt_choose_pairs "Select install disk (will be ERASED)" "${disks_pairs[@]}") || return 1
|
||||||
--menu "WARNING: selected disk will be ERASED." 18 70 8 \
|
|
||||||
$disks_list 3>&1 1>&2 2>&3) || return 1
|
|
||||||
|
|
||||||
# ── Hostname ──
|
# ── Hostname ──
|
||||||
hostname=$(whiptail --title "Hostname" \
|
hostname=$(prompt_input "Set hostname" "veilor") || return 1
|
||||||
--inputbox "Set hostname:" 10 60 "veilor" \
|
|
||||||
3>&1 1>&2 2>&3) || return 1
|
|
||||||
|
|
||||||
# Reject shell-special chars in passwords. Generated kickstart writes
|
# Reject shell-special and sed-special chars in passwords. Generated
|
||||||
# them via heredoc + sed substitution; bare $, ", \, ` would corrupt
|
# kickstart writes them via heredoc + sed substitution; bare $, ", \, `
|
||||||
# the ks line or partially expand. 8-char min for entropy.
|
# would corrupt the ks line or partially expand at heredoc time.
|
||||||
|
# &, |, /, newline are sed-special: & expands to the matched pattern
|
||||||
|
# (so `aA1!@#%^&*()` becomes `aA1!@#%^__ADMIN_PW__*()`), | is our
|
||||||
|
# delimiter, / would match if delimiter changes, newline breaks the
|
||||||
|
# sed expression. sed_escape() below adds defense-in-depth, but we
|
||||||
|
# also reject these at input so the user sees an immediate error
|
||||||
|
# rather than a corrupted ks file. 8-char min for entropy.
|
||||||
validate_pw() {
|
validate_pw() {
|
||||||
local pw=$1 label=$2
|
local pw=$1 label=$2
|
||||||
if [[ ${#pw} -lt 8 ]]; then
|
if [[ ${#pw} -lt 8 ]]; then
|
||||||
whiptail --title "Weak $label" --msgbox "Min 8 chars." 8 40
|
prompt_error "Weak $label — minimum 8 characters."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ $pw =~ [\"\$\\\`] ]]; then
|
if [[ $pw =~ [\"\$\\\`\&\|/$'\n'] ]]; then
|
||||||
whiptail --title "Invalid $label" --msgbox \
|
prompt_error "Invalid $label — cannot contain: \" \$ \\ \` & | / newline"
|
||||||
"Cannot contain: \" \$ \\ \`" 8 50
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── LUKS passphrase ──
|
# ── LUKS passphrase ──
|
||||||
luks_pw=$(whiptail --title "Disk encryption" \
|
luks_pw=$(prompt_password "LUKS passphrase (full-disk encryption)") || return 1
|
||||||
--passwordbox "LUKS passphrase (full-disk encryption):" 10 60 \
|
|
||||||
3>&1 1>&2 2>&3) || return 1
|
|
||||||
validate_pw "$luks_pw" "passphrase" || return 1
|
validate_pw "$luks_pw" "passphrase" || return 1
|
||||||
|
|
||||||
# ── Admin password ──
|
# ── Admin password ──
|
||||||
admin_pw=$(whiptail --title "Admin password" \
|
admin_pw=$(prompt_password "Admin user password (login after install)") || return 1
|
||||||
--passwordbox "Admin user password (login after install):" 10 60 \
|
|
||||||
3>&1 1>&2 2>&3) || return 1
|
|
||||||
validate_pw "$admin_pw" "password" || return 1
|
validate_pw "$admin_pw" "password" || return 1
|
||||||
|
|
||||||
# ── Locale ──
|
# ── Locale ──
|
||||||
locale=$(whiptail --title "Locale" \
|
locale=$(prompt_choose "Choose locale" \
|
||||||
--menu "Choose locale:" 14 50 4 \
|
"en_GB.UTF-8" \
|
||||||
"en_GB.UTF-8" "English (UK)" \
|
"en_US.UTF-8" \
|
||||||
"en_US.UTF-8" "English (US)" \
|
"de_DE.UTF-8" \
|
||||||
"de_DE.UTF-8" "Deutsch" \
|
"fr_FR.UTF-8") || return 1
|
||||||
"fr_FR.UTF-8" "Francais" \
|
|
||||||
3>&1 1>&2 2>&3) || return 1
|
|
||||||
|
|
||||||
# ── Confirmation ──
|
# ── Confirmation ──
|
||||||
whiptail --title "Confirm install" --yesno \
|
prompt_confirm "About to install veilor-os:
|
||||||
"About to install veilor-os:
|
|
||||||
|
|
||||||
Disk: $disk (will be ERASED)
|
Disk: $disk (will be ERASED)
|
||||||
Hostname: $hostname
|
Hostname: $hostname
|
||||||
|
|
@ -123,7 +248,7 @@ collect_answers() {
|
||||||
LUKS: set
|
LUKS: set
|
||||||
Admin pw: set
|
Admin pw: set
|
||||||
|
|
||||||
Proceed?" 16 60 || return 1
|
Proceed?" || return 1
|
||||||
|
|
||||||
# Export to caller via globals
|
# Export to caller via globals
|
||||||
SEL_DISK=$disk
|
SEL_DISK=$disk
|
||||||
|
|
@ -134,10 +259,26 @@ Proceed?" 16 60 || return 1
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# sed_escape — escape sed special chars in a replacement string.
|
||||||
|
# Replacement-side metacharacters: & (matched pattern), \ (escape),
|
||||||
|
# | (our chosen delimiter), / (alternate delimiter — escape too in case
|
||||||
|
# delimiter ever changes). Newline is rejected in validate_pw because
|
||||||
|
# escaping it portably across BSD/GNU sed is fiddly.
|
||||||
|
# Order matters: \ must be escaped FIRST so we don't double-escape the
|
||||||
|
# backslashes we're about to emit for &, |, /.
|
||||||
|
sed_escape() {
|
||||||
|
printf '%s' "$1" | sed -e 's/[\\&|/]/\\&/g'
|
||||||
|
}
|
||||||
|
|
||||||
generate_ks() {
|
generate_ks() {
|
||||||
# Build kickstart for actual disk install.
|
# Build kickstart for actual disk install.
|
||||||
# NOTE: passwords go in via --plaintext to avoid storing crypted hash
|
# NOTE: passwords go in via --plaintext to avoid storing crypted hash
|
||||||
# collisions; anaconda hashes per /etc/login.defs at install time.
|
# collisions; anaconda hashes per /etc/login.defs at install time.
|
||||||
|
#
|
||||||
|
# %packages mirrors live ks lines 63-141 (full hardening pkg list,
|
||||||
|
# minus livesys/anaconda-live which are live-only).
|
||||||
|
# %post --nochroot copies overlay + scripts + assets from boot ISO.
|
||||||
|
# %post (chroot) runs the same hardening scripts the live build runs.
|
||||||
local out=/run/install/veilor-generated.ks
|
local out=/run/install/veilor-generated.ks
|
||||||
local disk_basename
|
local disk_basename
|
||||||
disk_basename=$(basename "$SEL_DISK")
|
disk_basename=$(basename "$SEL_DISK")
|
||||||
|
|
@ -179,12 +320,40 @@ part pv.veilor --grow --encrypted --luks-version=luks2 --pbkdf=argon2id --passph
|
||||||
volgroup veilor pv.veilor
|
volgroup veilor pv.veilor
|
||||||
logvol / --vgname=veilor --name=root --fstype=btrfs --size=8192 --grow
|
logvol / --vgname=veilor --name=root --fstype=btrfs --size=8192 --grow
|
||||||
|
|
||||||
|
# ── Packages — mirrors live ks (kickstart/veilor-os.ks 63-141), minus
|
||||||
|
# live-only entries (livesys-scripts, anaconda-live, @anaconda-tools,
|
||||||
|
# dracut-live, isomd5sum, xorriso) which are pointless on a real disk.
|
||||||
%packages --excludedocs
|
%packages --excludedocs
|
||||||
@^kde-desktop-environment
|
@^kde-desktop-environment
|
||||||
@kde-apps
|
@kde-apps
|
||||||
@core
|
@core
|
||||||
@hardware-support
|
@hardware-support
|
||||||
@standard
|
@standard
|
||||||
|
|
||||||
|
kernel-modules
|
||||||
|
kernel-modules-extra
|
||||||
|
glibc-all-langpacks
|
||||||
|
dracut-config-generic
|
||||||
|
kernel
|
||||||
|
grub2-efi-x64
|
||||||
|
grub2-efi-x64-modules
|
||||||
|
grub2-pc
|
||||||
|
grub2-pc-modules
|
||||||
|
grub2-tools
|
||||||
|
grub2-tools-extra
|
||||||
|
shim-x64
|
||||||
|
efibootmgr
|
||||||
|
syslinux
|
||||||
|
|
||||||
|
# veilor-installer dependencies (kept on installed system so
|
||||||
|
# `sudo veilor-installer` from a recovery shell still works).
|
||||||
|
newt
|
||||||
|
parted
|
||||||
|
cryptsetup
|
||||||
|
lvm2
|
||||||
|
btrfs-progs
|
||||||
|
|
||||||
|
# core hardening tools
|
||||||
fail2ban
|
fail2ban
|
||||||
fail2ban-firewalld
|
fail2ban-firewalld
|
||||||
usbguard
|
usbguard
|
||||||
|
|
@ -195,57 +364,172 @@ tuned
|
||||||
chrony
|
chrony
|
||||||
firewalld
|
firewalld
|
||||||
plymouth
|
plymouth
|
||||||
|
|
||||||
|
# admin essentials
|
||||||
git
|
git
|
||||||
vim-enhanced
|
vim-enhanced
|
||||||
tmux
|
tmux
|
||||||
htop
|
htop
|
||||||
podman
|
podman
|
||||||
|
skopeo
|
||||||
NetworkManager
|
NetworkManager
|
||||||
NetworkManager-wifi
|
NetworkManager-wifi
|
||||||
|
|
||||||
|
# fonts
|
||||||
fontconfig
|
fontconfig
|
||||||
|
freetype
|
||||||
fira-code-fonts
|
fira-code-fonts
|
||||||
|
|
||||||
|
# zram (no swap-on-disk)
|
||||||
zram-generator
|
zram-generator
|
||||||
|
|
||||||
|
# remove fluff
|
||||||
|
# Note: KDE Plasma 6 hard-deps on cups/geoclue2/ModemManager/PackageKit
|
||||||
|
# transitively; daemons disabled at runtime via 20-harden-kernel.sh.
|
||||||
-abrt*
|
-abrt*
|
||||||
-snapd
|
-snapd
|
||||||
-kde-connect
|
-kde-connect
|
||||||
|
-open-vm-tools-desktop
|
||||||
-mlocate
|
-mlocate
|
||||||
|
|
||||||
|
%end
|
||||||
|
|
||||||
|
# ── Post-install (nochroot): copy overlay + scripts + assets from boot ISO.
|
||||||
|
# Mirror of live ks 144-196 trimmed to single-source: ISO mounted at
|
||||||
|
# /run/install/repo, veilor/ subtree contains overlay/, scripts/, assets/.
|
||||||
|
# (See build/Containerfile — ISO build copies the repo into veilor/.)
|
||||||
|
%post --nochroot --erroronfail
|
||||||
|
set -uo pipefail
|
||||||
|
DEST="${INSTALL_ROOT:-/mnt/sysimage}"
|
||||||
|
[[ -d $DEST ]] || { echo "[ERR] DEST=$DEST does not exist" >&2; exit 1; }
|
||||||
|
|
||||||
|
SRC=/run/install/repo/veilor
|
||||||
|
if [[ ! -d $SRC/overlay ]]; then
|
||||||
|
echo "[ERR] $SRC/overlay missing — boot ISO did not include veilor/ tree" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] using SRC=$SRC DEST=$DEST"
|
||||||
|
set -x
|
||||||
|
cp -a "$SRC/overlay/." "$DEST/" || echo "[ERR] overlay cp failed: $?"
|
||||||
|
mkdir -p "$DEST/usr/share/veilor-os" || echo "[ERR] mkdir failed: $?"
|
||||||
|
cp -a "$SRC/assets" "$DEST/usr/share/veilor-os/" || echo "[ERR] assets cp failed: $?"
|
||||||
|
cp -a "$SRC/scripts" "$DEST/usr/share/veilor-os/" || echo "[ERR] scripts cp failed: $?"
|
||||||
|
ls -la "$DEST/usr/share/veilor-os/" 2>&1 || echo "[ERR] dest dir missing post-cp"
|
||||||
|
# Force root ownership on everything we copied — `cp -a` preserves
|
||||||
|
# CI runner uid (1001), which makes sudo refuse to read /etc/sudoers.d.
|
||||||
|
chown -R 0:0 "$DEST/etc" "$DEST/usr/share/veilor-os" "$DEST/usr/local/bin" "$DEST/usr/local/sbin" 2>&1 || echo "[WARN] chown failed"
|
||||||
|
set +x
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "=== %post --nochroot trace ==="
|
||||||
|
date
|
||||||
|
echo "SRC=$SRC DEST=$DEST"
|
||||||
|
ls -la "$DEST/usr/share/veilor-os/" 2>&1
|
||||||
|
ls -la "$DEST/usr/local/sbin/" 2>&1
|
||||||
|
} > "$DEST/var/log/veilor-nochroot.log" 2>&1 || true
|
||||||
|
%end
|
||||||
|
|
||||||
|
# ── Post-install (chroot): apply hardening, theme, branding ────────────
|
||||||
|
%post
|
||||||
|
set -uo pipefail
|
||||||
|
exec > >(tee -a /var/log/veilor-install.log) 2>&1
|
||||||
|
|
||||||
|
echo "════════════════════════════════════════════════════════"
|
||||||
|
echo " veilor-os install — %post (real install)"
|
||||||
|
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 \
|
||||||
|
/usr/local/sbin/veilor-installer 2>/dev/null || true
|
||||||
|
|
||||||
|
# /etc/machine-id reset on first boot
|
||||||
|
> /etc/machine-id
|
||||||
|
|
||||||
|
# Apply hardening
|
||||||
|
bash $REPO/scripts/10-harden-base.sh
|
||||||
|
bash $REPO/scripts/20-harden-kernel.sh
|
||||||
|
|
||||||
|
# Build SELinux module
|
||||||
|
bash $REPO/scripts/selinux/build-policy.sh || echo "[WARN] SELinux build failed; load on first boot"
|
||||||
|
|
||||||
|
# Apply KDE theme + DuckSans + os-release branding
|
||||||
|
bash $REPO/scripts/kde-theme-apply.sh
|
||||||
|
|
||||||
|
# Symlink display-manager.service → sddm.service. (Anaconda usually
|
||||||
|
# handles this when sddm is the only DM, but be explicit.)
|
||||||
|
ln -sf /usr/lib/systemd/system/sddm.service /etc/systemd/system/display-manager.service
|
||||||
|
|
||||||
|
# Real install boots straight to SDDM (NOT to TTY1 installer like live).
|
||||||
|
systemctl set-default graphical.target
|
||||||
|
|
||||||
|
# zram swap (no disk swap; keys never leak to platter)
|
||||||
|
cat > /etc/systemd/zram-generator.conf << 'EOF'
|
||||||
|
[zram0]
|
||||||
|
zram-size = min(ram, 8192)
|
||||||
|
compression-algorithm = zstd
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Enable services
|
||||||
|
systemctl enable veilor-firstboot.service 2>/dev/null || true
|
||||||
|
systemctl enable veilor-modules-lock.service 2>/dev/null || true
|
||||||
|
systemctl enable sshd fail2ban usbguard tuned auditd firewalld chronyd sddm
|
||||||
|
|
||||||
|
# Default tuned profile = balanced (AC/battery udev rule will override)
|
||||||
|
tuned-adm profile veilor-balanced 2>/dev/null || true
|
||||||
|
|
||||||
|
# Force admin to set a fresh password on first login. The `user` directive
|
||||||
|
# above already created the account with the user's chosen password —
|
||||||
|
# `chage -d 0` expires it so SDDM/passwd prompts for change immediately.
|
||||||
|
chage -d 0 admin 2>/dev/null || true
|
||||||
|
|
||||||
|
# Lock root explicitly (kickstart --lock should already do this)
|
||||||
|
passwd -l root
|
||||||
|
|
||||||
|
# Sanity: zero references to onyx / personal IPs in installed system
|
||||||
|
if grep -rqi 'onyx\|192\.168\.0\.\|fedora\.local' /etc/veilor* /etc/tuned/profiles/veilor-* 2>/dev/null; then
|
||||||
|
echo "[ERR] brand leak detected in /etc — investigate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "════════════════════════════════════════════════════════"
|
||||||
|
echo " veilor-os install complete"
|
||||||
|
echo "════════════════════════════════════════════════════════"
|
||||||
%end
|
%end
|
||||||
|
|
||||||
# Reboot when done
|
# Reboot when done
|
||||||
reboot
|
reboot
|
||||||
KSEOF
|
KSEOF
|
||||||
# Substitute placeholders. Use | as sed delimiter (passwords might
|
# Substitute placeholders. Use | as sed delimiter. validate_pw()
|
||||||
# contain /). Forbidden chars in passwords (validated upstream): "$\`
|
# already rejects "$\`&|/\n at input — sed_escape() is defence in
|
||||||
# — sed safe.
|
# depth in case future code paths feed unsanitised values (e.g.
|
||||||
|
# locale/hostname from a file, or a relaxed validator).
|
||||||
sed -i \
|
sed -i \
|
||||||
-e "s|__LOCALE__|$SEL_LOCALE|" \
|
-e "s|__LOCALE__|$(sed_escape "$SEL_LOCALE")|" \
|
||||||
-e "s|__HOSTNAME__|$SEL_HOSTNAME|" \
|
-e "s|__HOSTNAME__|$(sed_escape "$SEL_HOSTNAME")|" \
|
||||||
-e "s|__DISK_BASENAME__|$disk_basename|" \
|
-e "s|__DISK_BASENAME__|$(sed_escape "$disk_basename")|" \
|
||||||
-e "s|__LUKS_PW__|$SEL_LUKS_PW|" \
|
-e "s|__LUKS_PW__|$(sed_escape "$SEL_LUKS_PW")|" \
|
||||||
-e "s|__ADMIN_PW__|$SEL_ADMIN_PW|" \
|
-e "s|__ADMIN_PW__|$(sed_escape "$SEL_ADMIN_PW")|" \
|
||||||
"$out"
|
"$out"
|
||||||
echo "[INFO] generated kickstart at $out"
|
echo "[INFO] generated kickstart at $out"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
run_install() {
|
run_install() {
|
||||||
whiptail --title "Installing" --infobox \
|
prompt_message "Installing veilor-os to $SEL_DISK ...
|
||||||
"Installing veilor-os to $SEL_DISK ...
|
|
||||||
This will take 10-30 minutes.
|
This will take 10-30 minutes.
|
||||||
Logs: /var/log/veilor-installer.log + /tmp/anaconda.log" 10 60
|
Logs: /var/log/veilor-installer.log + /tmp/anaconda.log"
|
||||||
sleep 2
|
sleep 1
|
||||||
# Hand off to anaconda. --kickstart runs unattended.
|
# Hand off to anaconda. --kickstart runs unattended.
|
||||||
if anaconda --kickstart=/run/install/veilor-generated.ks; then
|
if anaconda --kickstart=/run/install/veilor-generated.ks; then
|
||||||
whiptail --title "Done" --msgbox \
|
prompt_message "Install complete. System will reboot.
|
||||||
"Install complete. System will reboot.
|
Remove the install media after shutdown."
|
||||||
Remove the install media after shutdown." 10 50
|
|
||||||
sleep 3
|
sleep 3
|
||||||
systemctl reboot
|
systemctl reboot
|
||||||
else
|
else
|
||||||
whiptail --title "Install failed" --msgbox \
|
prompt_error "Anaconda exited non-zero.
|
||||||
"Anaconda exited non-zero.
|
|
||||||
Logs at /tmp/anaconda.log + /var/log/veilor-installer.log.
|
Logs at /tmp/anaconda.log + /var/log/veilor-installer.log.
|
||||||
Press OK to drop to shell." 12 60
|
Press OK to drop to shell."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
@ -268,10 +552,6 @@ EOF
|
||||||
exec /bin/bash --login
|
exec /bin/bash --login
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Entry ──
|
|
||||||
require_tty
|
|
||||||
banner
|
|
||||||
|
|
||||||
launch_desktop() {
|
launch_desktop() {
|
||||||
clear
|
clear
|
||||||
echo "Launching KDE Plasma..."
|
echo "Launching KDE Plasma..."
|
||||||
|
|
@ -281,17 +561,21 @@ launch_desktop() {
|
||||||
# If user logs out, they come back here. Loop continues.
|
# If user logs out, they come back here. Loop continues.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Entry ──
|
||||||
|
require_tty
|
||||||
|
banner
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$(main_menu)" in
|
case "$(main_menu)" in
|
||||||
1)
|
"Install veilor-os to disk")
|
||||||
if collect_answers && generate_ks; then
|
if collect_answers && generate_ks; then
|
||||||
run_install || continue
|
run_install || continue
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
2) launch_desktop ;;
|
"Try live — desktop (KDE Plasma)") launch_desktop ;;
|
||||||
3) drop_to_shell ;;
|
"Try live — shell") drop_to_shell ;;
|
||||||
4) systemctl reboot ;;
|
"Reboot") systemctl reboot ;;
|
||||||
5) systemctl poweroff ;;
|
"Power off") systemctl poweroff ;;
|
||||||
*) drop_to_shell ;;
|
*) drop_to_shell ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue