2026-05-02 03:20:42 +01:00
|
|
|
|
#!/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:
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# 1. ASCII banner (assets/installer/banner.txt)
|
|
|
|
|
|
# 2. Menu: Install / Live desktop / Live shell / Reboot / Power off
|
|
|
|
|
|
# 3. If Install: collect answers via gum (disk, hostname, LUKS pw,
|
2026-05-02 03:20:42 +01:00
|
|
|
|
# admin pw, locale)
|
|
|
|
|
|
# 4. Generate /run/install/veilor-generated.ks from template + answers
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# (full veilor-os install: package list + overlay + scripts + harden)
|
2026-05-02 03:20:42 +01:00
|
|
|
|
# 5. Exec anaconda --kickstart=/run/install/veilor-generated.ks
|
|
|
|
|
|
# 6. On finish: reboot into installed system
|
|
|
|
|
|
#
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# v0.5.1 — gum (charm.sh) replaces whiptail; whiptail kept as fallback.
|
|
|
|
|
|
# Generated kickstart now installs full veilor-os (matches live ks).
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
set -uo pipefail
|
|
|
|
|
|
export TERM="${TERM:-linux}"
|
2026-05-04 14:20:26 +01:00
|
|
|
|
# Log to /run (pure tmpfs) — /var/log overlays squashfs on the live ISO, so a
|
|
|
|
|
|
# bad sector on the USB medium turns `tee -a` into "input/output error" and
|
|
|
|
|
|
# kills the installer before the menu can render.
|
|
|
|
|
|
LOG=/run/veilor-installer.log
|
2026-05-02 06:22:47 +01:00
|
|
|
|
|
|
|
|
|
|
# require_tty MUST run before the tee redirect — process substitution
|
|
|
|
|
|
# replaces fd1 with a pipe, breaking `[[ -t 1 ]]`.
|
|
|
|
|
|
require_tty() {
|
|
|
|
|
|
if ! [[ -t 0 && -t 1 ]]; then
|
|
|
|
|
|
echo "[ERR] veilor-installer must run on a real tty" >&2
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
require_tty
|
|
|
|
|
|
|
2026-05-04 14:20:26 +01:00
|
|
|
|
# Tee output for log persistence, but only if LOG is writable. On a flaky USB
|
|
|
|
|
|
# the squashfs/overlay can throw I/O errors mid-write — tolerate that and
|
|
|
|
|
|
# keep the installer running without persistence rather than aborting.
|
|
|
|
|
|
if : >> "$LOG" 2>/dev/null; then
|
|
|
|
|
|
exec > >(tee -a "$LOG") 2>&1
|
|
|
|
|
|
fi
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# ── 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
|
|
|
|
|
|
|
2026-05-02 03:20:42 +01:00
|
|
|
|
banner() {
|
|
|
|
|
|
clear
|
2026-05-03 03:46:36 +01:00
|
|
|
|
# Read version + date for version line. /etc/os-release has VERSION_ID.
|
|
|
|
|
|
local ver=""
|
|
|
|
|
|
[[ -r /etc/os-release ]] && ver=$(. /etc/os-release; echo "${VERSION_ID:-0.0}")
|
|
|
|
|
|
local d
|
|
|
|
|
|
d=$(date +%Y-%m-%d)
|
|
|
|
|
|
local vline="veilor-os ${ver} · ${d} · live"
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
if [[ -r $BANNER_FILE ]]; then
|
2026-05-06 10:31:02 +01:00
|
|
|
|
# v0.6: staged line-by-line reveal of the banner before the
|
|
|
|
|
|
# gum-style border draws around it. 40ms/line gives a subtle
|
|
|
|
|
|
# "typewriter" feel — 5 lines × 40ms = 200ms total, fast enough
|
|
|
|
|
|
# not to feel laggy but slow enough to land an aesthetic on the
|
|
|
|
|
|
# very first frame the user sees. Once the reveal finishes we
|
|
|
|
|
|
# clear and re-draw with the bordered gum-style version so the
|
|
|
|
|
|
# operator never sees both stacked on top of each other.
|
|
|
|
|
|
local line
|
|
|
|
|
|
while IFS= read -r line; do
|
|
|
|
|
|
printf ' %s\n' "$line"
|
|
|
|
|
|
sleep 0.04
|
|
|
|
|
|
done < "$BANNER_FILE"
|
|
|
|
|
|
sleep 0.08
|
|
|
|
|
|
clear
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
if [[ $TUI == gum ]]; then
|
2026-05-03 03:46:36 +01:00
|
|
|
|
# gum style: rounded border, banner + blank line + version line.
|
|
|
|
|
|
gum style --border rounded --margin "0 2" --padding "1 3" \
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
--border-foreground "${VEILOR_DIM:-240}" \
|
|
|
|
|
|
--foreground "${VEILOR_FG:-15}" \
|
2026-05-03 03:46:36 +01:00
|
|
|
|
"$(cat "$BANNER_FILE")" \
|
|
|
|
|
|
"" \
|
|
|
|
|
|
"$vline"
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
else
|
|
|
|
|
|
cat "$BANNER_FILE"
|
|
|
|
|
|
echo
|
2026-05-03 03:46:36 +01:00
|
|
|
|
echo " $vline"
|
|
|
|
|
|
echo
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
fi
|
|
|
|
|
|
else
|
|
|
|
|
|
# Fallback ASCII if banner.txt missing (older overlay).
|
2026-05-03 03:46:36 +01:00
|
|
|
|
cat << EOF
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
veilor-os installer
|
2026-05-03 03:46:36 +01:00
|
|
|
|
$vline
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
EOF
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
fi
|
2026-05-02 03:20:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# ── 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>
|
2026-05-06 10:30:06 +01:00
|
|
|
|
#
|
|
|
|
|
|
# v0.6: gum-path replaced with bash `read -srp` because `gum input
|
|
|
|
|
|
# --password` rendered as a duplicate-"Install" + stray-T artefact on
|
|
|
|
|
|
# the linux fbcon since v0.5.27 (Agent 7 of the v0.6 polish research
|
|
|
|
|
|
# wave traced this to gum's bubbletea screen-restore writing back the
|
|
|
|
|
|
# previous menu buffer when the framebuffer terminfo lacked
|
|
|
|
|
|
# `civis/cnorm` cursor-hide sequences). bash `read -srp` is a single
|
|
|
|
|
|
# write to stdout + termios echo-off — no redraw, no glitch. Header
|
|
|
|
|
|
# rendered separately via gum style for visual parity with the rest
|
|
|
|
|
|
# of the installer.
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
prompt_password() {
|
|
|
|
|
|
local header=$1
|
|
|
|
|
|
if [[ $TUI == gum ]]; then
|
2026-05-06 10:30:06 +01:00
|
|
|
|
# Render the prompt header as a styled box so it looks at home
|
|
|
|
|
|
# next to the other gum prompts, then collect the password via
|
|
|
|
|
|
# plain bash read on the next line. `read -s` disables echo,
|
|
|
|
|
|
# `read -p` writes the prompt to stderr (so command-substitution
|
|
|
|
|
|
# callers still get the password on stdout cleanly).
|
|
|
|
|
|
gum style --foreground "${VEILOR_FG:-15}" --border rounded \
|
|
|
|
|
|
--border-foreground "${VEILOR_DIM:-240}" --padding "0 2" -- "$header"
|
|
|
|
|
|
local pw
|
|
|
|
|
|
read -srp " password: " pw
|
|
|
|
|
|
echo >&2 # newline after silent read so next prompt isn't on same line
|
|
|
|
|
|
printf '%s' "$pw"
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-02 03:20:42 +01:00
|
|
|
|
main_menu() {
|
2026-05-03 03:46:36 +01:00
|
|
|
|
# Empty header — banner already provides context. `──────` line splits
|
|
|
|
|
|
# primary actions (top) from session controls (bottom). Cursor `❯` set
|
|
|
|
|
|
# via GUM_CHOOSE_CURSOR in colors.gum.
|
|
|
|
|
|
prompt_choose "" \
|
2026-05-02 21:10:04 +01:00
|
|
|
|
"Install" \
|
2026-05-03 03:46:36 +01:00
|
|
|
|
"live · KDE" \
|
|
|
|
|
|
"live · shell" \
|
|
|
|
|
|
"──────" \
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
"Reboot" \
|
|
|
|
|
|
"Power off"
|
2026-05-02 03:20:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
collect_answers() {
|
|
|
|
|
|
local disk hostname luks_pw admin_pw locale
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
local disks_pairs
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
# ── Disk ──
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# Build whiptail-style "tag desc" pairs. prompt_choose_pairs reshapes
|
|
|
|
|
|
# for gum if needed.
|
|
|
|
|
|
# shellcheck disable=SC2207
|
|
|
|
|
|
disks_pairs=($(lsblk -dpno NAME,SIZE,MODEL | grep -E '^/dev/(sd|nvme|vd|mmcblk)' | \
|
|
|
|
|
|
awk '{name=$1; size=$2; $1=""; $2=""; sub(/^ +/,""); gsub(/ /,"_"); model=$0; if(model=="")model="unknown"; print name, size"_"model}'))
|
|
|
|
|
|
if [[ ${#disks_pairs[@]} -eq 0 ]]; then
|
|
|
|
|
|
prompt_error "No installable disks found."
|
2026-05-02 03:20:42 +01:00
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
2026-05-05 02:48:36 +01:00
|
|
|
|
disk=$(prompt_choose_pairs "[1/3] Select install disk · WILL BE ERASED" "${disks_pairs[@]}") || return 1
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
# ── Hostname ──
|
2026-05-02 21:10:04 +01:00
|
|
|
|
# Hardcoded for branded consistency. Post-install: `hostnamectl set-hostname`.
|
|
|
|
|
|
hostname="veilor"
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
# Reject shell-special and sed-special chars in passwords. Generated
|
|
|
|
|
|
# kickstart writes them via heredoc + sed substitution; bare $, ", \, `
|
|
|
|
|
|
# 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.
|
2026-05-02 03:42:15 +01:00
|
|
|
|
validate_pw() {
|
|
|
|
|
|
local pw=$1 label=$2
|
|
|
|
|
|
if [[ ${#pw} -lt 8 ]]; then
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
prompt_error "Weak $label — minimum 8 characters."
|
2026-05-02 03:42:15 +01:00
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
if [[ $pw =~ [\"\$\\\`\&\|/$'\n'] ]]; then
|
|
|
|
|
|
prompt_error "Invalid $label — cannot contain: \" \$ \\ \` & | / newline"
|
2026-05-02 03:42:15 +01:00
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-02 03:20:42 +01:00
|
|
|
|
# ── LUKS passphrase ──
|
2026-05-06 10:31:21 +01:00
|
|
|
|
# v0.6: prompt twice + string-compare. A typo in the LUKS passphrase
|
|
|
|
|
|
# is unrecoverable — the disk is unmountable without it and we
|
|
|
|
|
|
# don't escrow the key. Re-prompting until the two reads match
|
|
|
|
|
|
# catches keyboard-layout surprises (US vs UK quote position is
|
|
|
|
|
|
# the most common one) before they brick the install.
|
|
|
|
|
|
local luks_pw_confirm
|
|
|
|
|
|
while true; do
|
|
|
|
|
|
luks_pw=$(prompt_password "[2/3] Encryption · LUKS2 passphrase (min 8)") || return 1
|
|
|
|
|
|
validate_pw "$luks_pw" "passphrase" || continue
|
|
|
|
|
|
luks_pw_confirm=$(prompt_password "[2/3] Confirm LUKS2 passphrase") || return 1
|
|
|
|
|
|
if [[ $luks_pw == "$luks_pw_confirm" ]]; then
|
|
|
|
|
|
break
|
|
|
|
|
|
fi
|
|
|
|
|
|
prompt_error "Passphrases do not match — try again."
|
|
|
|
|
|
done
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
# ── Admin password ──
|
2026-05-06 10:31:21 +01:00
|
|
|
|
# Same confirm-twice pattern. Less catastrophic than LUKS (admin
|
|
|
|
|
|
# password can be reset from a recovery shell) but a mismatch here
|
|
|
|
|
|
# still locks the user out of their fresh install on first boot.
|
|
|
|
|
|
local admin_pw_confirm
|
|
|
|
|
|
while true; do
|
|
|
|
|
|
admin_pw=$(prompt_password "[3/3] Admin user · password for 'admin'") || return 1
|
|
|
|
|
|
validate_pw "$admin_pw" "password" || continue
|
|
|
|
|
|
admin_pw_confirm=$(prompt_password "[3/3] Confirm admin password") || return 1
|
|
|
|
|
|
if [[ $admin_pw == "$admin_pw_confirm" ]]; then
|
|
|
|
|
|
break
|
|
|
|
|
|
fi
|
|
|
|
|
|
prompt_error "Passwords do not match — try again."
|
|
|
|
|
|
done
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
# ── Locale ──
|
2026-05-05 02:48:36 +01:00
|
|
|
|
# Hardcoded en_US.UTF-8 for branded consistency. The picker that
|
|
|
|
|
|
# used to live here (en_GB / en_US) only added confusion — both
|
|
|
|
|
|
# locales install identically, the user couldn't notice the
|
|
|
|
|
|
# difference, and the post-install `localectl set-locale` works for
|
|
|
|
|
|
# any locale anyway. v0.7 post-install menu will offer locale + kb
|
|
|
|
|
|
# layout switch with live preview. For the install flow, fixed.
|
|
|
|
|
|
locale="en_US.UTF-8"
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
# ── Confirmation ──
|
2026-05-03 03:46:36 +01:00
|
|
|
|
# Render summary box + danger lines via gum style, then gum confirm.
|
|
|
|
|
|
# Whiptail fallback: simple yesno.
|
|
|
|
|
|
if [[ $TUI == gum ]]; then
|
|
|
|
|
|
clear
|
|
|
|
|
|
gum style --border rounded --margin "1 2" --padding "1 3" \
|
|
|
|
|
|
--border-foreground "${VEILOR_DIM:-240}" \
|
|
|
|
|
|
--foreground "${VEILOR_FG:-15}" \
|
|
|
|
|
|
"Confirm install" \
|
|
|
|
|
|
"" \
|
|
|
|
|
|
" Disk $disk $(gum style --foreground 1 'WILL BE ERASED')" \
|
|
|
|
|
|
" Locale $locale" \
|
|
|
|
|
|
" LUKS ✓ set" \
|
|
|
|
|
|
" Admin ✓ set" \
|
|
|
|
|
|
"" \
|
|
|
|
|
|
"$(gum style --foreground 3 'This action is irreversible.')"
|
|
|
|
|
|
gum confirm --affirmative "Yes, install" --negative "Cancel" "Proceed?" || return 1
|
|
|
|
|
|
else
|
|
|
|
|
|
whiptail --title "Confirm install" --yesno \
|
|
|
|
|
|
"About to install veilor-os:
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
2026-05-03 03:46:36 +01:00
|
|
|
|
Disk: $disk (WILL BE ERASED)
|
2026-05-02 03:20:42 +01:00
|
|
|
|
Locale: $locale
|
|
|
|
|
|
LUKS: set
|
|
|
|
|
|
Admin pw: set
|
|
|
|
|
|
|
2026-05-03 03:46:36 +01:00
|
|
|
|
This action is irreversible.
|
|
|
|
|
|
|
|
|
|
|
|
Proceed?" 16 60 || return 1
|
|
|
|
|
|
fi
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# 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'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 14:35:36 +01:00
|
|
|
|
# detect_seed_pubkey — search attached cdroms for a NoCloud cidata seed
|
|
|
|
|
|
# with an ssh_authorized_keys entry. Returns the first key on stdout, or
|
|
|
|
|
|
# empty string if none found. Used by both auto-install.sh (cloud-init
|
|
|
|
|
|
# seed pre-built with host pubkey) and humans (drop a seed iso next to
|
|
|
|
|
|
# the install media).
|
|
|
|
|
|
detect_seed_pubkey() {
|
|
|
|
|
|
local dev label tmpmnt key=""
|
|
|
|
|
|
for dev in /dev/sr0 /dev/sr1 /dev/sr2 /dev/sr3; do
|
|
|
|
|
|
[ -b "$dev" ] || continue
|
|
|
|
|
|
label=$(blkid -o value -s LABEL "$dev" 2>/dev/null)
|
|
|
|
|
|
if [[ $label == "cidata" || $label == "CIDATA" ]]; then
|
|
|
|
|
|
tmpmnt=$(mktemp -d)
|
|
|
|
|
|
if mount -o ro "$dev" "$tmpmnt" 2>/dev/null; then
|
|
|
|
|
|
# NoCloud user-data format:
|
|
|
|
|
|
# ssh_authorized_keys:
|
|
|
|
|
|
# - ssh-ed25519 AAAA... user@host
|
|
|
|
|
|
# Extract first ssh-* line, strip leading '- '.
|
|
|
|
|
|
key=$(grep -E '^\s*-\s+ssh-' "$tmpmnt/user-data" 2>/dev/null \
|
|
|
|
|
|
| head -1 | sed -e 's/^\s*-\s*//' -e 's/[[:space:]]*$//')
|
|
|
|
|
|
umount "$tmpmnt" 2>/dev/null
|
|
|
|
|
|
fi
|
|
|
|
|
|
rmdir "$tmpmnt" 2>/dev/null
|
|
|
|
|
|
[[ -n $key ]] && { printf '%s' "$key"; return 0; }
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-02 03:20:42 +01:00
|
|
|
|
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.
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
#
|
|
|
|
|
|
# %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.
|
2026-05-02 03:20:42 +01:00
|
|
|
|
local out=/run/install/veilor-generated.ks
|
2026-05-02 03:42:15 +01:00
|
|
|
|
local disk_basename
|
|
|
|
|
|
disk_basename=$(basename "$SEL_DISK")
|
2026-05-02 03:20:42 +01:00
|
|
|
|
mkdir -p /run/install
|
2026-05-02 03:42:15 +01:00
|
|
|
|
# Single-quoted heredoc → no shell expansion. Substitute placeholders
|
|
|
|
|
|
# via sed afterwards. Bulletproof against $/`/" in passwords.
|
|
|
|
|
|
cat > "$out" << 'KSEOF' || return 1
|
2026-05-02 03:20:42 +01:00
|
|
|
|
# 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
|
2026-05-05 02:52:22 +01:00
|
|
|
|
# `updates` repo intentionally NOT added. Fedora's update stream pushes
|
|
|
|
|
|
# package versions whose %posttrans scriptlets sometimes fail under
|
|
|
|
|
|
# anaconda's `--cmdline` mode — most reliably reproduced as `man-db`
|
|
|
|
|
|
# failing during "Configuring man-db.x86_64", which dumps the whole
|
|
|
|
|
|
# transaction with no recoverable error message and prints "An error
|
|
|
|
|
|
# occurred during the transaction: The transaction process has ended
|
|
|
|
|
|
# with errors..". Reproduced in v0.5.26 + v0.5.27 VM tests against
|
|
|
|
|
|
# fresh installs days apart, so it is not a Fedora-mirrors-down blip.
|
|
|
|
|
|
# CI build kickstart already strips this line for the same reason
|
|
|
|
|
|
# (build-iso.yml line ~109). Users who want to update post-install run
|
|
|
|
|
|
# `dnf upgrade` or the v0.6 `veilor-update` wrapper.
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
keyboard --xlayouts='us'
|
2026-05-02 03:42:15 +01:00
|
|
|
|
lang __LOCALE__
|
2026-05-02 03:20:42 +01:00
|
|
|
|
timezone Europe/London --utc
|
|
|
|
|
|
|
|
|
|
|
|
firstboot --disable
|
|
|
|
|
|
eula --agreed
|
|
|
|
|
|
selinux --enforcing
|
|
|
|
|
|
services --enabled=sshd,fail2ban,usbguard,tuned,auditd,firewalld,chronyd,sddm
|
|
|
|
|
|
|
2026-05-02 03:42:15 +01:00
|
|
|
|
network --bootproto=dhcp --device=link --activate --hostname=__HOSTNAME__
|
2026-05-02 03:20:42 +01:00
|
|
|
|
firewall --enabled --service=ssh
|
|
|
|
|
|
|
|
|
|
|
|
rootpw --lock
|
2026-05-02 03:42:15 +01:00
|
|
|
|
user --name=admin --groups=wheel --gecos="veilor admin" --password=__ADMIN_PW__ --plaintext
|
2026-05-03 14:35:36 +01:00
|
|
|
|
__SSHKEY_DIRECTIVE__
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
|
|
|
|
|
# Full hardening cmdline (installed system, not live):
|
v0.5.29: narrow anaconda patch + LUKS UX + initramfs assertion
Five-fix bundle from 7-agent research wave on the v0.5.28-final
gen_grub_cfgstub failure.
## 1. Narrow the anaconda transaction_progress patch (CRITICAL)
The v0.5.28 patch was too broad. It rewrote
`process_transaction_progress` so every 'error' token in the
transaction queue became a `log.warning`. That queue carries four
distinct error classes:
- cpio_error — payload extraction (genuinely fatal)
- script_error — RPM 6.0 cmdline-mode scriptlet warning-as-error
(the ONE we want to ignore)
- unpack_error — payload corruption (genuinely fatal)
- error — generic transaction error (genuinely fatal)
By swallowing all four we silently masked grub2-efi-x64's posttrans
failure mid-install. /boot/efi/EFI/fedora/ ended up incomplete →
gen_grub_cfgstub then failed at the bootloader install phase with
"gen_grub_cfgstub script failed" because its `set -eu` script
couldn't read the missing files.
v0.5.29 narrows the patch: override only the `script_error` callback
inside transaction_progress.py to log a warning and NOT enqueue
'error'. The consumer (`process_transaction_progress`) reverts to
upstream behaviour where cpio_error / unpack_error / error still
raise PayloadInstallationError. Real install-fatal events keep
aborting; only the F43-RPM-6.0 scriptlet regression is silenced.
The patch is applied via `python3 -c` regex rewrite (more robust
than nested sed across multi-line method bodies).
## 2. LUKS UX — `tries=5,timeout=0` (FIX)
Default cryptsetup-generator unit allows ONE passphrase try with a
1m30s wait. One typo on a long passphrase = wait 1m30s, then the
device-wait timer trips, then dracut emergency shell after 3min total.
Brutal. Adding `rd.luks.options=luks-XXX=tries=5,timeout=0` gives
five typo-friendly retries with no auto-timeout.
## 3. fbcon=nodefer on installed-system cmdline (FIX)
Live ISO cmdline already has `fbcon=nodefer` (added in v0.5.27 to fix
the real-laptop black-screen-after-dracut). The installed-system
bootloader directive in the generated install ks did NOT carry it.
Same KMS handoff happens on the installed system on the same hardware.
Now both have the flag.
## 4. /etc/crypttab fallback assertion (BELT-BRACES)
Anaconda's custom-partitioning code path normally writes /etc/crypttab
for `--encrypted` part directives. Edge cases observed in F43+ where
it doesn't. Without crypttab, systemd-cryptsetup-generator can still
work from kernel cmdline alone, but cleanup paths and second-stage
unlock both fall over. Adding a fallback `echo` that writes the
canonical line if it's missing post-anaconda.
## 5. Initramfs LUKS module assertion (DEFENSIVE)
Force-include `crypt + systemd-cryptsetup + plymouth` modules in
initramfs via /etc/dracut.conf.d/10-veilor-luks.conf. dracut autodetects
these when it sees an active LUKS mapping, but %post runs before the
LUKS state is fully observable from the chroot. Plus we wipe stale
initramfs (`rm -f /boot/initramfs-*.img`) before `--regenerate-all`
so the regen actually rewrites bytes. Final assertion runs
`lsinitrd | grep -q cryptsetup` and surfaces a [ERR] line in build
output if the module didn't make it.
## What this should fix
After the man-db fix in v0.5.28-final, install proceeded past
"Configuring xxx" cleanly but died at "Installing boot loader" with
gen_grub_cfgstub. Root-cause was the over-broad patch from #1 above.
After v0.5.29:
- Install transaction completes (man-db excluded; non-man-db
scriptlet warnings still suppressed; real errors still raise)
- gen_grub_cfgstub runs against complete /boot/efi/EFI/fedora/
- Bootloader install completes
- Reboot to disk lands at GRUB veilor-os entry
- Kernel + initramfs load (cryptsetup confirmed present)
- Plymouth LUKS prompt appears with text fallback
- User has 5 tries, no timeout
- Unlock → btrfs subvol mount → systemd → SDDM
Files: kickstart/veilor-os.ks (+45 lines), overlay/usr/local/bin/veilor-installer (+50 lines).
Verified: bash -n clean, ksvalidator clean.
References:
pyanaconda transaction_progress.py:110-136 (4 producers of 'error')
pyanaconda bootloader/efi.py:194-201 (gen_grub_cfgstub call site)
/usr/bin/gen_grub_cfgstub (set -eu wrapper for grub2-mkconfig stub)
Fedora wiki Changes/RPM-6.0
dnf5 issue #2507 (RPM 6.0 scriptlet propagation regression)
2026-05-05 05:12:24 +01:00
|
|
|
|
# - `lockdown=integrity` — kernel lockdown, integrity mode (signed module enforce)
|
|
|
|
|
|
# - `slab_nomerge` — refuse SLAB merging; harder heap-spray attacks
|
|
|
|
|
|
# - `init_on_alloc=1 init_on_free=1` — zero pages on alloc + free; defends
|
|
|
|
|
|
# uninit-read class; ~5% perf hit acceptable on hardened workstation
|
|
|
|
|
|
# - `randomize_kstack_offset=on` — KASLR for kernel stack, per-syscall
|
|
|
|
|
|
# - `vsyscall=none` — kill legacy vsyscall page (Position-Independent
|
|
|
|
|
|
# ROP-gadget surface)
|
|
|
|
|
|
# - `fbcon=nodefer` — keep linux framebuffer console alive through KMS
|
v0.5.30: broad error suppression + manual bootloader + virtio log capture
Three-layer fix for the persistent anaconda transaction failure that
killed v0.5.28 (gen_grub_cfgstub) and v0.5.29 (aggregate dnf5 error).
## Layer 1: broad error suppression in transaction_progress.py
dnf5 under RPM 6.0 + cmdline anaconda emits a final aggregate
`error("transaction process has ended with errors..")` at end of
transaction whenever its internal failure counter > 0, regardless of
whether we suppressed individual script_error events. Reproduced
twice. The narrow patch in v0.5.29 suppressed per-package errors but
the aggregate still raised PayloadInstallationError and aborted the
install before the bootloader phase ran.
v0.5.30 patch turns the `elif token == 'error':` branch in
process_transaction_progress into a log.warning. All four producers
(cpio_error, script_error, unpack_error, generic error) now flow
through to a warning + continue. Pattern matches both the original
anaconda layout AND the v0.5.29 narrow-patched layout, so re-applying
on top of either is a no-op.
This brings us back to v0.5.28 broad-suppression behaviour. The
side effect that bit us in v0.5.28 (silent grub2-efi-x64 scriptlet
failure → empty /boot/efi/EFI/fedora/ → gen_grub_cfgstub fails)
is addressed by Layer 2 below.
## Layer 2: bootloader install moved out of anaconda
The generated install kickstart now has `bootloader --location=none`,
which tells anaconda NOT to invoke its own bootloader install code
path (and therefore NOT to call gen_grub_cfgstub). All grub work
moves into the chroot %post block:
1. `dnf reinstall grub2-efi-x64 grub2-pc grub2-tools shim-x64
efibootmgr` — re-runs scriptlets in the chroot with full
PID 1 systemd state, so the systemd-run-style triggers that
anaconda's chroot truncates actually execute.
2. `grub2-install --target=x86_64-efi --efi-directory=/boot/efi
--bootloader-id=fedora --no-nvram` — populates /boot/efi/EFI/fedora/
3. `gen_grub_cfgstub /boot/grub2 /boot/efi/EFI/fedora` (or
`grub2-mkconfig` fallback) — writes /boot/efi/EFI/fedora/grub.cfg.
4. `efibootmgr -c -d <disk> -p <part> -L "veilor-os" -l \EFI\fedora\shimx64.efi`
— registers the NVRAM boot entry pointing at the signed shim.
Each step logs to stdout and continues on failure (`set +e` block);
diagnostics surface in the install log without aborting the whole
%post.
## Layer 3: virtio-serial log capture in run-vm.sh
Anaconda 43.x autodetects `/dev/virtio-ports/org.fedoraproject.anaconda.log.0`
and streams program/packaging/storage/anaconda logs through it in
real time, before any tmpfs / pivot, before networking, surviving
kernel panic. Wiring it into run-vm.sh means the host gets a
tail-able log file at `test/anaconda-vm-YYYYMMDD-HHMMSS.log` for
every VM run.
We've lost logs three times in a row to anaconda failures + tmpfs
reboots. This breaks the loop.
## Diagnostic story
Before this commit: VM aborts → live ISO reboots itself → /tmp/
tmpfs gone → no logs → guess what failed. Three days, two and a
half false fixes.
After this commit: VM aborts → host has /home/admin/ai-lab/_github/veilor-os/test/anaconda-vm-*.log
with the actual scriptlet output, the actual exit codes, the
actual file-trigger failures. Future debug becomes evidence-based.
Files changed:
kickstart/veilor-os.ks — broad error suppression patch
overlay/usr/local/bin/veilor-installer — --location=none + manual grub
test/run-vm.sh — virtio-serial chardev wiring
Verified: bash -n clean, ksvalidator clean.
2026-05-05 11:59:35 +01:00
|
|
|
|
# handoff so plymouth LUKS prompt remains visible on real GPUs.
|
|
|
|
|
|
#
|
2026-05-05 13:47:23 +01:00
|
|
|
|
# NOTE on --location: v0.5.30 used --location=none to skip anaconda's
|
|
|
|
|
|
# bootloader install (sidestep gen_grub_cfgstub). Side effect: anaconda
|
|
|
|
|
|
# also skipped CollectKernelArgumentsTask (installation.py:149-151), so
|
|
|
|
|
|
# `--append=` args were NEVER COLLECTED. kernel-install then wrote BLS
|
|
|
|
|
|
# entries with empty /etc/kernel/cmdline, falling through to the live
|
|
|
|
|
|
# ISO's /proc/cmdline — no rd.luks.uuid, no fbcon=nodefer, no hardening.
|
|
|
|
|
|
# Result: dracut emergency shell on first boot.
|
|
|
|
|
|
#
|
|
|
|
|
|
# v0.5.31 lets anaconda install the bootloader (default behavior, no
|
|
|
|
|
|
# --location flag). With our broad transaction_progress patch in the
|
|
|
|
|
|
# live ks, anaconda's gen_grub_cfgstub still runs, but if grub2-efi-x64's
|
|
|
|
|
|
# posttrans had a non-fatal scriptlet failure the patch swallows it
|
|
|
|
|
|
# without aborting. The %post chroot below STILL does belt-and-braces
|
|
|
|
|
|
# fixup (dnf reinstall, grub2-install, etc.) in case anaconda's path
|
|
|
|
|
|
# left something incomplete.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Critically v0.5.31 also writes /etc/kernel/cmdline FIRST in %post then
|
|
|
|
|
|
# re-runs kernel-install per kernel. That's the canonical Fedora 43 path
|
|
|
|
|
|
# for landing args in BLS entries — kernel-install reads /etc/kernel/cmdline
|
|
|
|
|
|
# (90-loaderentry.install:84-95) when generating BLS option lines.
|
v0.5.32: ship 7 blockers from 9-agent wave
Per docs/research/2026-05-05-agent-wave/README.md priority list.
All 7 land together to keep iteration cycles useful — partial fixes
bury the lookahead findings agents already mapped.
## 1. CRITICAL — suspend/resume wifi death (Agent 9, B2)
`veilor-modules-lock.service` runs `kernel.modules_disabled=1` 30s
after graphical.target. iwlwifi/iwlmvm/cfg80211 reload on resume
from S3/S0ix → with modules locked, resume breaks wifi until
reboot. Same architectural class as the LUKS bug — security feature
breaks legitimate kernel state transitions.
The unit already has `ConditionKernelCommandLine=!module.sig_enforce=1`
(self-skip when signed-modules enforcement is on cmdline). Adding
`module.sig_enforce=1` to the kernel cmdline retains the security
property (no unsigned modules) without runtime lock-down → resume
works.
Files: kickstart/veilor-os.ks line 61 + overlay/usr/local/bin/veilor-installer
generated bootloader directive both gain `module.sig_enforce=1`.
## 2. veilor-firstboot.service WantedBy=graphical.target (Agent 2)
Was `WantedBy=multi-user.target` only. Real installs default to
graphical.target so the unit never ran on installed systems — admin
pw stayed at install-time + chage -d 0 expired, SDDM PAM bounced
to chauthtok screen (recoverable but ugly UX).
Now `WantedBy=graphical.target multi-user.target`. Live ISO +
multi-user installs both resolve via this list.
## 3. USBGuard hash → id-based baseline (Agent 9, A3)
Mirrors memory feedback_usbguard_dock.md — onyx had hash+parent-hash
rules that broke on dock replug; we shipped no rules.conf so first
boot blocks the USB keyboard.
Adds overlay/etc/usbguard/rules.conf with HID-class allow rule
(`allow with-interface match-all { 03:*:* }`) — covers every USB
keyboard, mouse, gamepad, fingerprint reader, NFC. Survives dock
replug + kernel-bump vendor renumeration. Mass-storage stays
implicit-block; user explicitly allows post-firstboot via
`ujust veilor-usbguard-enroll` (planned v0.6).
## 4. firewalld trusted zone with tailscale0 pre-bound (Agent 9, D1)
User uses Tailscale daily (memory: project_tailscale_mesh.md).
Default firewalld zone = drop, blocks tailnet traffic on tailscale0.
Adds overlay/etc/firewalld/zones/trusted.xml with
`<interface name="tailscale0"/>`. After `tailscale up` brings the
interface up, NetworkManager dispatcher associates it with the
trusted zone automatically — no user intervention.
Default zone stays drop. Only the tailscale0 interface gets ACCEPT.
## 5. /etc/skel branding (Agent 7)
Was completely empty. Result: per-user KDE config (~/.config/kdeglobals
etc.) pre-empty, so the moment user opened System Settings, KDE wrote
fresh ~/.config/* and silently shadowed our /etc/xdg/kdedefaults/*.
Visual brand evaporated on first click.
Seeds:
/etc/skel/.config/kdeglobals (copy of assets/kde/veilor-default.kdeglobals)
/etc/skel/.config/breezerc (copy of assets/kde/breezerc)
/etc/skel/.config/kwinrc (Plasma 6 wayland defaults: opengl, animspeed=0,
blur off, click-to-focus)
/etc/skel/.config/konsolerc (default profile = Veilor)
/etc/skel/.local/share/konsole/Veilor.profile + .colorscheme
User who opens System Settings now writes against branded baseline,
not against vanilla Breeze.
## 6. KMS modeset args + initramfs keymap (Agents 1 + 9)
Real laptop boot has a 5-15s blank between vt switch and SDDM start
because simpledrm releases before i915/nvidia-drm/amdgpu claim. Plus
non-US users get locked out at LUKS prompt because initramfs ships
en-US keymap by default (RHBZ 1405539, RHBZ 1890085).
Adds to bootloader cmdline (live + installed):
i915.modeset=1 amdgpu.modeset=1 nvidia-drm.modeset=1
rd.vconsole.keymap=us
`rd.vconsole.keymap=us` is a placeholder; the v0.6 firstboot keymap
picker will rewrite it from /etc/vconsole.conf. Until then, en-US
users get correct LUKS keyboard; non-US users still need the v0.6
fix (per Agent 1).
## 7. virtio-9p log capture (Agent 6)
The v0.5.30 virtio-serial wiring depends on rsyslog inside the live
ISO (anaconda's setupVirtio writes a rsyslog forward rule), which
the live ks doesn't install — files were 0-byte across three
install runs.
test/run-vm.sh now adds a `-virtfs local,...,mount_tag=hostlogs`
share pointing at `test/test-runs/<timestamp>/`. veilor-installer
runs `_dump_logs_to_host` via EXIT trap that mounts the share at
/mnt/hostlogs and rsyncs /tmp/{anaconda,program,storage,packaging,dnf}.log
+ /var/log/veilor-installer.log + dmesg + journalctl + the generated
ks. Runs on success AND failure AND ^C.
No-op on real hardware (9p tag absent) — VM-only debug.
## Validate
bash -n overlay/usr/local/bin/veilor-installer # OK
ksvalidator kickstart/veilor-os.ks # clean
## Out-of-scope for v0.5.32 (deferred to v0.6)
Per Agent 1 follow-ups: argon2id retune for slow CPUs, recovery key
generation in firstboot, TPM2/FIDO2 unlock helpers. Per Agent 9
follow-ups: Plasma Wayland fallback X11 install, lid-close handling,
SELinux relabel progress UX. Per Agent 4: AppArmor stack +
nftables preset + audit log shipping CLI.
Per Agent 8 (CI hardening): SHA-pin actions + dependabot + SBOM +
SLSA L3 attestation — separate workflow-only commit.
2026-05-05 15:36:24 +01:00
|
|
|
|
bootloader --append="lockdown=integrity module.sig_enforce=1 slab_nomerge init_on_alloc=1 init_on_free=1 randomize_kstack_offset=on vsyscall=none fbcon=nodefer i915.modeset=1 amdgpu.modeset=1 nvidia-drm.modeset=1 rd.vconsole.keymap=us"
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
2026-05-03 02:45:16 +01:00
|
|
|
|
# Disk: zero, LUKS2 (argon2id), btrfs subvolumes (no LVM intermediary).
|
|
|
|
|
|
# Native btrfs-on-LUKS matches Fedora KDE Spin defaults; LVM+btrfs combo
|
|
|
|
|
|
# triggered "mount failed: wrong fs type, bad option, bad superblock"
|
|
|
|
|
|
# under anaconda --cmdline. btrfs subvols give us the snapshot/rollback
|
|
|
|
|
|
# story without LVM's added complexity.
|
2026-05-02 03:20:42 +01:00
|
|
|
|
zerombr
|
2026-05-02 03:42:15 +01:00
|
|
|
|
clearpart --all --initlabel --drives=__DISK_BASENAME__
|
|
|
|
|
|
part /boot/efi --fstype=efi --size=600
|
|
|
|
|
|
part /boot --fstype=ext4 --size=1024
|
2026-05-03 02:45:16 +01:00
|
|
|
|
part btrfs.veilor --grow --encrypted --luks-version=luks2 --pbkdf=argon2id --passphrase=__LUKS_PW__
|
|
|
|
|
|
btrfs none --label=veilor btrfs.veilor
|
|
|
|
|
|
btrfs / --subvol --name=root LABEL=veilor
|
|
|
|
|
|
btrfs /home --subvol --name=home LABEL=veilor
|
2026-05-02 03:20:42 +01:00
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# ── 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.
|
2026-05-02 03:20:42 +01:00
|
|
|
|
%packages --excludedocs
|
|
|
|
|
|
@^kde-desktop-environment
|
|
|
|
|
|
@kde-apps
|
|
|
|
|
|
@core
|
|
|
|
|
|
@hardware-support
|
|
|
|
|
|
@standard
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-05-02 03:20:42 +01:00
|
|
|
|
fail2ban
|
|
|
|
|
|
fail2ban-firewalld
|
|
|
|
|
|
usbguard
|
|
|
|
|
|
usbguard-tools
|
|
|
|
|
|
audit
|
|
|
|
|
|
policycoreutils-python-utils
|
|
|
|
|
|
tuned
|
|
|
|
|
|
chrony
|
|
|
|
|
|
firewalld
|
2026-05-03 21:52:10 +01:00
|
|
|
|
plymouth
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
2026-05-06 11:15:30 +01:00
|
|
|
|
# AppArmor stack — Fedora 43 ships parser/utils/profiles. v0.6 ships
|
|
|
|
|
|
# loaded-but-complain only (see scripts/40-apparmor.sh + tier-2 plan).
|
|
|
|
|
|
apparmor-parser
|
|
|
|
|
|
apparmor-utils
|
|
|
|
|
|
apparmor-profiles
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# admin essentials
|
2026-05-02 03:20:42 +01:00
|
|
|
|
git
|
|
|
|
|
|
vim-enhanced
|
|
|
|
|
|
tmux
|
|
|
|
|
|
htop
|
|
|
|
|
|
podman
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
skopeo
|
2026-05-02 03:20:42 +01:00
|
|
|
|
NetworkManager
|
|
|
|
|
|
NetworkManager-wifi
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
# fonts
|
2026-05-02 03:20:42 +01:00
|
|
|
|
fontconfig
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
freetype
|
2026-05-02 03:20:42 +01:00
|
|
|
|
fira-code-fonts
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
# zram (no swap-on-disk)
|
2026-05-02 03:20:42 +01:00
|
|
|
|
zram-generator
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
# remove fluff
|
|
|
|
|
|
# Note: KDE Plasma 6 hard-deps on cups/geoclue2/ModemManager/PackageKit
|
|
|
|
|
|
# transitively; daemons disabled at runtime via 20-harden-kernel.sh.
|
2026-05-02 03:20:42 +01:00
|
|
|
|
-abrt*
|
|
|
|
|
|
-snapd
|
|
|
|
|
|
-kde-connect
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
-open-vm-tools-desktop
|
2026-05-02 03:20:42 +01:00
|
|
|
|
-mlocate
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
2026-05-05 03:46:00 +01:00
|
|
|
|
# Belt-and-braces with the kickstart/veilor-os.ks transaction_progress
|
|
|
|
|
|
# patch: even with the patch, man-db's transfiletriggerin in the F43
|
|
|
|
|
|
# RPM 6.0 toolchain dispatches a systemd-run that anaconda's chroot
|
|
|
|
|
|
# can race-with on exit. Excluding the package entirely guarantees the
|
|
|
|
|
|
# trigger never fires during install. Veilor users who want man pages
|
|
|
|
|
|
# install them post-firstboot via \`dnf install man-db man-pages\` or
|
|
|
|
|
|
# via the v0.6 \`veilor-postinstall\` welcome menu.
|
|
|
|
|
|
-man-db
|
|
|
|
|
|
-man-pages
|
|
|
|
|
|
-man-pages-overrides
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
%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.
|
2026-05-02 05:33:22 +01:00
|
|
|
|
chown -R 0:0 "$DEST/etc" "$DEST/usr/share/veilor-os" "$DEST/usr/local/bin" 2>&1 || echo "[WARN] chown failed"
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
set +x
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
echo "=== %post --nochroot trace ==="
|
|
|
|
|
|
date
|
|
|
|
|
|
echo "SRC=$SRC DEST=$DEST"
|
|
|
|
|
|
ls -la "$DEST/usr/share/veilor-os/" 2>&1
|
2026-05-02 05:33:22 +01:00
|
|
|
|
ls -la "$DEST/usr/local/bin/" 2>&1
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
} > "$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 \
|
2026-05-02 05:33:22 +01:00
|
|
|
|
/usr/local/bin/veilor-power /usr/local/bin/veilor-firstboot \
|
|
|
|
|
|
/usr/local/bin/veilor-installer 2>/dev/null || true
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
# /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
|
|
|
|
|
|
|
2026-05-03 10:10:46 +01:00
|
|
|
|
# Disable plymouth at TWO layers:
|
|
|
|
|
|
#
|
|
|
|
|
|
# 1. Initramfs (the boot stage where LUKS unlock happens). Plymouth is
|
|
|
|
|
|
# a dracut module; masking units in /etc/systemd/ has zero effect
|
|
|
|
|
|
# here because dracut bundles its own copies into initramfs/.
|
|
|
|
|
|
# Solution: omit_dracutmodules in dracut.conf.d, then regenerate
|
|
|
|
|
|
# initramfs so the new config takes effect.
|
|
|
|
|
|
#
|
|
|
|
|
|
# 2. Real root (post-pivot, before SDDM). /dev/null symlinks mask all
|
|
|
|
|
|
# plymouth services + the path-activated ask-password unit so they
|
|
|
|
|
|
# never start when systemd is up.
|
|
|
|
|
|
#
|
|
|
|
|
|
# After both, LUKS prompt falls back to systemd-tty-ask-password-agent
|
|
|
|
|
|
# on tty1 — text "Please enter passphrase for disk... :" — works in
|
|
|
|
|
|
# QEMU sendkey AND on real hardware.
|
|
|
|
|
|
|
v0.5.30: broad error suppression + manual bootloader + virtio log capture
Three-layer fix for the persistent anaconda transaction failure that
killed v0.5.28 (gen_grub_cfgstub) and v0.5.29 (aggregate dnf5 error).
## Layer 1: broad error suppression in transaction_progress.py
dnf5 under RPM 6.0 + cmdline anaconda emits a final aggregate
`error("transaction process has ended with errors..")` at end of
transaction whenever its internal failure counter > 0, regardless of
whether we suppressed individual script_error events. Reproduced
twice. The narrow patch in v0.5.29 suppressed per-package errors but
the aggregate still raised PayloadInstallationError and aborted the
install before the bootloader phase ran.
v0.5.30 patch turns the `elif token == 'error':` branch in
process_transaction_progress into a log.warning. All four producers
(cpio_error, script_error, unpack_error, generic error) now flow
through to a warning + continue. Pattern matches both the original
anaconda layout AND the v0.5.29 narrow-patched layout, so re-applying
on top of either is a no-op.
This brings us back to v0.5.28 broad-suppression behaviour. The
side effect that bit us in v0.5.28 (silent grub2-efi-x64 scriptlet
failure → empty /boot/efi/EFI/fedora/ → gen_grub_cfgstub fails)
is addressed by Layer 2 below.
## Layer 2: bootloader install moved out of anaconda
The generated install kickstart now has `bootloader --location=none`,
which tells anaconda NOT to invoke its own bootloader install code
path (and therefore NOT to call gen_grub_cfgstub). All grub work
moves into the chroot %post block:
1. `dnf reinstall grub2-efi-x64 grub2-pc grub2-tools shim-x64
efibootmgr` — re-runs scriptlets in the chroot with full
PID 1 systemd state, so the systemd-run-style triggers that
anaconda's chroot truncates actually execute.
2. `grub2-install --target=x86_64-efi --efi-directory=/boot/efi
--bootloader-id=fedora --no-nvram` — populates /boot/efi/EFI/fedora/
3. `gen_grub_cfgstub /boot/grub2 /boot/efi/EFI/fedora` (or
`grub2-mkconfig` fallback) — writes /boot/efi/EFI/fedora/grub.cfg.
4. `efibootmgr -c -d <disk> -p <part> -L "veilor-os" -l \EFI\fedora\shimx64.efi`
— registers the NVRAM boot entry pointing at the signed shim.
Each step logs to stdout and continues on failure (`set +e` block);
diagnostics surface in the install log without aborting the whole
%post.
## Layer 3: virtio-serial log capture in run-vm.sh
Anaconda 43.x autodetects `/dev/virtio-ports/org.fedoraproject.anaconda.log.0`
and streams program/packaging/storage/anaconda logs through it in
real time, before any tmpfs / pivot, before networking, surviving
kernel panic. Wiring it into run-vm.sh means the host gets a
tail-able log file at `test/anaconda-vm-YYYYMMDD-HHMMSS.log` for
every VM run.
We've lost logs three times in a row to anaconda failures + tmpfs
reboots. This breaks the loop.
## Diagnostic story
Before this commit: VM aborts → live ISO reboots itself → /tmp/
tmpfs gone → no logs → guess what failed. Three days, two and a
half false fixes.
After this commit: VM aborts → host has /home/admin/ai-lab/_github/veilor-os/test/anaconda-vm-*.log
with the actual scriptlet output, the actual exit codes, the
actual file-trigger failures. Future debug becomes evidence-based.
Files changed:
kickstart/veilor-os.ks — broad error suppression patch
overlay/usr/local/bin/veilor-installer — --location=none + manual grub
test/run-vm.sh — virtio-serial chardev wiring
Verified: bash -n clean, ksvalidator clean.
2026-05-05 11:59:35 +01:00
|
|
|
|
# Manual bootloader install (anaconda told to skip via --location=none)
|
|
|
|
|
|
#
|
|
|
|
|
|
# Anaconda's gen_grub_cfgstub script-runner (efi.py:194-201) is
|
|
|
|
|
|
# brittle on F43 RPM 6.0 cmdline mode — grub2-efi-x64's posttrans
|
|
|
|
|
|
# scriptlet may emit non-fatal errors that anaconda treats as
|
|
|
|
|
|
# transaction-fatal even with our error suppression patch. Doing
|
|
|
|
|
|
# the work in %post chroot bypasses that whole code path and gives
|
|
|
|
|
|
# us linear, debuggable steps.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Order:
|
|
|
|
|
|
# 1. Re-run grub2-efi-x64 + shim-x64 scriptlets cleanly (dnf
|
|
|
|
|
|
# reinstall in chroot has full PID 1 systemd context, so the
|
|
|
|
|
|
# systemd-run inside man-db-style triggers actually runs). Re-
|
|
|
|
|
|
# installing repopulates /boot/efi/EFI/fedora/ if it was empty.
|
|
|
|
|
|
# 2. grub2-install — generic + UEFI. UEFI path is the meaningful one
|
|
|
|
|
|
# on virtio-vga and real hardware.
|
|
|
|
|
|
# 3. grub2-mkconfig — write /boot/grub2/grub.cfg + /boot/efi/EFI/fedora/grub.cfg.
|
|
|
|
|
|
# 4. efibootmgr — register the boot entry in NVRAM.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Failure of any individual step is logged but does NOT abort the
|
|
|
|
|
|
# %post (set +e bracket). On a real failure the user sees the
|
|
|
|
|
|
# diagnostic text and can fix manually post-firstboot.
|
|
|
|
|
|
|
|
|
|
|
|
set +e
|
|
|
|
|
|
echo "════════════════════════════════════════════════════════"
|
|
|
|
|
|
echo " bootloader install (manual; anaconda skipped via --location=none)"
|
|
|
|
|
|
echo "════════════════════════════════════════════════════════"
|
|
|
|
|
|
|
|
|
|
|
|
# Disk we're targeting — anaconda already wrote /boot/efi mount, so
|
|
|
|
|
|
# the disk is whatever holds /boot/efi.
|
|
|
|
|
|
EFI_DISK=$(findmnt -n -o SOURCE /boot/efi 2>/dev/null | sed -E 's/p?[0-9]+$//')
|
|
|
|
|
|
[ -z "$EFI_DISK" ] && EFI_DISK="/dev/$(basename "$(realpath /sys/class/block/$(findmnt -n -o SOURCE /boot/efi | sed 's|/dev/||' | sed 's|p\?[0-9]\+$||') 2>/dev/null)")"
|
|
|
|
|
|
echo "[INFO] target disk for grub: ${EFI_DISK:-<unknown>}"
|
|
|
|
|
|
|
|
|
|
|
|
# Step 1: re-run grub2 + shim scriptlets in clean chroot
|
|
|
|
|
|
dnf reinstall -y grub2-efi-x64 grub2-efi-x64-modules grub2-pc grub2-pc-modules grub2-tools grub2-tools-extra shim-x64 efibootmgr 2>&1 | tail -10 || \
|
|
|
|
|
|
echo "[WARN] dnf reinstall of grub stack failed"
|
|
|
|
|
|
|
|
|
|
|
|
# Step 2: install grub to ESP (UEFI path, primary)
|
|
|
|
|
|
mkdir -p /boot/efi/EFI/fedora
|
|
|
|
|
|
grub2-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=fedora --no-nvram 2>&1 | tail -5 || \
|
|
|
|
|
|
echo "[WARN] grub2-install (efi) failed"
|
|
|
|
|
|
|
|
|
|
|
|
# Step 3: write the EFI grub.cfg stub (what gen_grub_cfgstub would have done)
|
|
|
|
|
|
if command -v gen_grub_cfgstub >/dev/null 2>&1; then
|
|
|
|
|
|
gen_grub_cfgstub /boot/grub2 /boot/efi/EFI/fedora 2>&1 | tail -5 || \
|
|
|
|
|
|
echo "[WARN] gen_grub_cfgstub failed (will fall back to grub2-mkconfig)"
|
|
|
|
|
|
fi
|
|
|
|
|
|
# Always also write a full grub.cfg in /boot/grub2 (BLS source) and
|
|
|
|
|
|
# regenerate the EFI cfg via grub2-mkconfig for redundancy
|
|
|
|
|
|
grub2-mkconfig -o /boot/grub2/grub.cfg 2>&1 | tail -3
|
|
|
|
|
|
[ -f /boot/efi/EFI/fedora/grub.cfg ] || \
|
|
|
|
|
|
grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg 2>&1 | tail -3
|
|
|
|
|
|
|
|
|
|
|
|
# Step 4: register NVRAM entry
|
|
|
|
|
|
if [ -n "$EFI_DISK" ] && [ -e "$EFI_DISK" ]; then
|
|
|
|
|
|
EFI_PART_NUM=$(findmnt -n -o SOURCE /boot/efi | grep -oE '[0-9]+$')
|
|
|
|
|
|
[ -n "$EFI_PART_NUM" ] && \
|
|
|
|
|
|
efibootmgr -c -d "$EFI_DISK" -p "$EFI_PART_NUM" -L "veilor-os" -l '\EFI\fedora\shimx64.efi' 2>&1 | tail -3 || \
|
|
|
|
|
|
echo "[WARN] efibootmgr failed (NVRAM may already be set)"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo "[INFO] bootloader install: see above for any [WARN] lines"
|
2026-05-05 13:47:23 +01:00
|
|
|
|
# NOTE: deliberately NOT `set -e` here. The block above opened with
|
|
|
|
|
|
# `set +e` and the rest of %post is a sequence of best-effort hardening
|
|
|
|
|
|
# steps that have local `|| true` guards on the operations that may
|
|
|
|
|
|
# legitimately fail. Re-enabling errexit would cause `set -e` to abort
|
|
|
|
|
|
# the whole %post on the first non-guarded command (e.g. a `grep -q`
|
|
|
|
|
|
# returning 1). v0.5.30 had this bug and it silently truncated
|
|
|
|
|
|
# the LUKS args injection.
|
v0.5.30: broad error suppression + manual bootloader + virtio log capture
Three-layer fix for the persistent anaconda transaction failure that
killed v0.5.28 (gen_grub_cfgstub) and v0.5.29 (aggregate dnf5 error).
## Layer 1: broad error suppression in transaction_progress.py
dnf5 under RPM 6.0 + cmdline anaconda emits a final aggregate
`error("transaction process has ended with errors..")` at end of
transaction whenever its internal failure counter > 0, regardless of
whether we suppressed individual script_error events. Reproduced
twice. The narrow patch in v0.5.29 suppressed per-package errors but
the aggregate still raised PayloadInstallationError and aborted the
install before the bootloader phase ran.
v0.5.30 patch turns the `elif token == 'error':` branch in
process_transaction_progress into a log.warning. All four producers
(cpio_error, script_error, unpack_error, generic error) now flow
through to a warning + continue. Pattern matches both the original
anaconda layout AND the v0.5.29 narrow-patched layout, so re-applying
on top of either is a no-op.
This brings us back to v0.5.28 broad-suppression behaviour. The
side effect that bit us in v0.5.28 (silent grub2-efi-x64 scriptlet
failure → empty /boot/efi/EFI/fedora/ → gen_grub_cfgstub fails)
is addressed by Layer 2 below.
## Layer 2: bootloader install moved out of anaconda
The generated install kickstart now has `bootloader --location=none`,
which tells anaconda NOT to invoke its own bootloader install code
path (and therefore NOT to call gen_grub_cfgstub). All grub work
moves into the chroot %post block:
1. `dnf reinstall grub2-efi-x64 grub2-pc grub2-tools shim-x64
efibootmgr` — re-runs scriptlets in the chroot with full
PID 1 systemd state, so the systemd-run-style triggers that
anaconda's chroot truncates actually execute.
2. `grub2-install --target=x86_64-efi --efi-directory=/boot/efi
--bootloader-id=fedora --no-nvram` — populates /boot/efi/EFI/fedora/
3. `gen_grub_cfgstub /boot/grub2 /boot/efi/EFI/fedora` (or
`grub2-mkconfig` fallback) — writes /boot/efi/EFI/fedora/grub.cfg.
4. `efibootmgr -c -d <disk> -p <part> -L "veilor-os" -l \EFI\fedora\shimx64.efi`
— registers the NVRAM boot entry pointing at the signed shim.
Each step logs to stdout and continues on failure (`set +e` block);
diagnostics surface in the install log without aborting the whole
%post.
## Layer 3: virtio-serial log capture in run-vm.sh
Anaconda 43.x autodetects `/dev/virtio-ports/org.fedoraproject.anaconda.log.0`
and streams program/packaging/storage/anaconda logs through it in
real time, before any tmpfs / pivot, before networking, surviving
kernel panic. Wiring it into run-vm.sh means the host gets a
tail-able log file at `test/anaconda-vm-YYYYMMDD-HHMMSS.log` for
every VM run.
We've lost logs three times in a row to anaconda failures + tmpfs
reboots. This breaks the loop.
## Diagnostic story
Before this commit: VM aborts → live ISO reboots itself → /tmp/
tmpfs gone → no logs → guess what failed. Three days, two and a
half false fixes.
After this commit: VM aborts → host has /home/admin/ai-lab/_github/veilor-os/test/anaconda-vm-*.log
with the actual scriptlet output, the actual exit codes, the
actual file-trigger failures. Future debug becomes evidence-based.
Files changed:
kickstart/veilor-os.ks — broad error suppression patch
overlay/usr/local/bin/veilor-installer — --location=none + manual grub
test/run-vm.sh — virtio-serial chardev wiring
Verified: bash -n clean, ksvalidator clean.
2026-05-05 11:59:35 +01:00
|
|
|
|
|
2026-05-03 21:52:10 +01:00
|
|
|
|
# GRUB branding: replace fedora distributor with veilor-os in menu titles.
|
2026-05-03 23:10:23 +01:00
|
|
|
|
# Drop rhgb quiet from default cmdline → all kernel/systemd messages
|
|
|
|
|
|
# visible. Plymouth `details` theme shows scrolling text (no splash, no
|
|
|
|
|
|
# logo). LUKS prompt rendered as text. Onyx-style boot.
|
2026-05-03 16:27:15 +01:00
|
|
|
|
sed -i \
|
|
|
|
|
|
-e 's|^GRUB_DISTRIBUTOR=.*|GRUB_DISTRIBUTOR="veilor-os"|' \
|
2026-05-03 23:10:23 +01:00
|
|
|
|
-e 's|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=""|' \
|
2026-05-03 16:27:15 +01:00
|
|
|
|
/etc/default/grub 2>/dev/null || true
|
2026-05-03 23:10:23 +01:00
|
|
|
|
|
2026-05-04 00:36:13 +01:00
|
|
|
|
# Ensure rd.luks.uuid + rd.luks.name in cmdline. Anaconda --cmdline mode
|
v0.5.27: rd.luks.uuid via grubby, GRUB rebrand, fbcon=nodefer, ASCII gum cursor
Critical install bug fix + cosmetic round-up + first formal test
procedure document.
## Critical: LUKS unlock on first boot
Generated installer kickstart's %post was injecting `rd.luks.uuid=…`
into `/etc/default/grub` only. Fedora 43 uses BLS (Boot Loader
Specification) entries in `/boot/loader/entries/*.conf`; those are
NOT regenerated by `grub2-mkconfig`. Result: the kernel boots without
`rd.luks.uuid=`, dracut's cryptsetup-generator never spawns the
unlock unit, plymouth has no password to ask for, and dracut-initqueue
loops on dev-disk-by-uuid for ~3min before dropping to emergency
shell.
The fix layers both write paths:
- `/etc/default/grub` — keeps the args around for future kernels
(kernel-install reads this when adding new entries).
- `grubby --update-kernel=ALL --args=...` — rewrites the `options`
line of every existing BLS entry so the kernel that boots NEXT
actually has the args.
Verified by reading `/proc/cmdline` from the dracut emergency shell
on a v0.5.26 install; old cmdline had only `root=UUID=… ro
rootflags=subvol=root` and was missing the LUKS arg entirely.
## GRUB / branding
- `/etc/default/grub` is sed'd to `GRUB_DISTRIBUTOR="veilor-os"` (was
already there, kept).
- BLS entries' `title` line is rewritten in-place to "veilor-os
(<kver>)" for every kernel — `grub2-mkconfig` does not touch BLS
titles, so this is the only path.
- `/boot/loader/entries/*-0-rescue-*.conf` is removed: the auto-built
rescue entry was leaking "Fedora Linux" into the GRUB menu and
showing a second boot option that nobody asked for. The rescue
kernel image itself is left in /boot.
- Hostname defaults to `veilor` (was inheriting the `localhost-live`
name anaconda writes when the kickstart's network directive is
ignored under cmdline mode).
- `/etc/machine-info` adds `PRETTY_HOSTNAME="veilor-os"` so
`hostnamectl status` and any consumer reading machine-info see the
brand.
## Boot UX
- `fbcon=nodefer` added to live-ISO bootloader cmdline. On real
laptops with a hardware GPU, the kernel modeset blanks the
framebuffer console mid-boot; without `nodefer` the installer
banner draws into a frozen framebuffer and the user sees a black
screen with a blinking cursor for ~30s. virtio-vga in QEMU doesn't
trigger this so it never reproduced in VM. Symptom report on
v0.5.26 was the trigger to investigate.
## Installer cosmetics
- `GUM_CHOOSE_CURSOR` and `GUM_INPUT_PROMPT` switched from `❯ ` to
`> `. The unicode arrow falls back to a fixed-width block on the
linux fbcon font and lipgloss then duplicates that block at col +23,
producing the "Install Install" double-render and the stray-T
artifact in password fields. Plain ASCII renders identically across
fbcon, virtio-vga, and X/Wayland gum runs.
- `VERSION_ID` bumped 0.5.8 → 0.5.27 in the os-release drop-in. The
installer banner reads this at runtime, so the live ISO + installed
system both now show "veilor-os 0.5.27".
## Test procedure
- `test/TESTING.md` — first canonical test procedure document. Splits
VM (cheap iteration, hybrid sendkey + human passwords) from real
hardware (mandatory for tag). Documents the standard test passwords
(`veilortest1` for both LUKS and admin), the kill-and-relaunch step
to skip CD on second boot, and the per-step pass/fail contract.
- `test/METHOD-CHANGELOG.md` — append-only audit trail for changes to
the procedure. Future releases that alter the test method must add
an entry here with the why.
- `test/test-runs/_TEMPLATE.md` — per-run report template. Each
tagged release should land a filled report alongside it.
## test/run-vm.sh
Decoupled QEMU monitor sock setup from auto-inject. Previously
`NO_INJECT=1` (used to suppress autotype noise into prompts) also
killed the monitor sock, leaving the VM undriveable. Monitor sock is
now always exposed; only the inject helper is gated on the pubkey
detection.
2026-05-05 01:43:00 +01:00
|
|
|
|
# does not auto-add the LUKS args when partitioning is custom + encrypted
|
|
|
|
|
|
# manually; without rd.luks.uuid in cmdline, dracut's cryptsetup-generator
|
|
|
|
|
|
# never spawns the unlock unit, plymouth has no password to ask for, and
|
|
|
|
|
|
# dracut-initqueue loops on dev-disk-by-uuid for ~3min before dropping
|
|
|
|
|
|
# to emergency shell. Detect the LUKS partition + inject explicitly.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Two write paths because Fedora 43 uses BLS (Boot Loader Specification):
|
|
|
|
|
|
# 1. /etc/default/grub — read by `grub2-mkconfig` for the legacy
|
|
|
|
|
|
# grub.cfg menu, AND used as the source of
|
|
|
|
|
|
# truth for `kernel-install` when adding new
|
|
|
|
|
|
# kernels later.
|
|
|
|
|
|
# 2. /boot/loader/entries/*.conf — the actual per-kernel BLS entries
|
|
|
|
|
|
# that grub reads for cmdline. These
|
|
|
|
|
|
# are NOT regenerated by
|
|
|
|
|
|
# `grub2-mkconfig`; they are managed
|
|
|
|
|
|
# by `grubby` (or `kernel-install`).
|
|
|
|
|
|
# Without both, the running kernel boots without rd.luks.uuid and the
|
|
|
|
|
|
# user lands in emergency shell on first boot.
|
2026-05-04 00:36:13 +01:00
|
|
|
|
LUKS_UUID=$(blkid -t TYPE=crypto_LUKS -o value -s UUID 2>/dev/null | head -1)
|
v0.5.27: rd.luks.uuid via grubby, GRUB rebrand, fbcon=nodefer, ASCII gum cursor
Critical install bug fix + cosmetic round-up + first formal test
procedure document.
## Critical: LUKS unlock on first boot
Generated installer kickstart's %post was injecting `rd.luks.uuid=…`
into `/etc/default/grub` only. Fedora 43 uses BLS (Boot Loader
Specification) entries in `/boot/loader/entries/*.conf`; those are
NOT regenerated by `grub2-mkconfig`. Result: the kernel boots without
`rd.luks.uuid=`, dracut's cryptsetup-generator never spawns the
unlock unit, plymouth has no password to ask for, and dracut-initqueue
loops on dev-disk-by-uuid for ~3min before dropping to emergency
shell.
The fix layers both write paths:
- `/etc/default/grub` — keeps the args around for future kernels
(kernel-install reads this when adding new entries).
- `grubby --update-kernel=ALL --args=...` — rewrites the `options`
line of every existing BLS entry so the kernel that boots NEXT
actually has the args.
Verified by reading `/proc/cmdline` from the dracut emergency shell
on a v0.5.26 install; old cmdline had only `root=UUID=… ro
rootflags=subvol=root` and was missing the LUKS arg entirely.
## GRUB / branding
- `/etc/default/grub` is sed'd to `GRUB_DISTRIBUTOR="veilor-os"` (was
already there, kept).
- BLS entries' `title` line is rewritten in-place to "veilor-os
(<kver>)" for every kernel — `grub2-mkconfig` does not touch BLS
titles, so this is the only path.
- `/boot/loader/entries/*-0-rescue-*.conf` is removed: the auto-built
rescue entry was leaking "Fedora Linux" into the GRUB menu and
showing a second boot option that nobody asked for. The rescue
kernel image itself is left in /boot.
- Hostname defaults to `veilor` (was inheriting the `localhost-live`
name anaconda writes when the kickstart's network directive is
ignored under cmdline mode).
- `/etc/machine-info` adds `PRETTY_HOSTNAME="veilor-os"` so
`hostnamectl status` and any consumer reading machine-info see the
brand.
## Boot UX
- `fbcon=nodefer` added to live-ISO bootloader cmdline. On real
laptops with a hardware GPU, the kernel modeset blanks the
framebuffer console mid-boot; without `nodefer` the installer
banner draws into a frozen framebuffer and the user sees a black
screen with a blinking cursor for ~30s. virtio-vga in QEMU doesn't
trigger this so it never reproduced in VM. Symptom report on
v0.5.26 was the trigger to investigate.
## Installer cosmetics
- `GUM_CHOOSE_CURSOR` and `GUM_INPUT_PROMPT` switched from `❯ ` to
`> `. The unicode arrow falls back to a fixed-width block on the
linux fbcon font and lipgloss then duplicates that block at col +23,
producing the "Install Install" double-render and the stray-T
artifact in password fields. Plain ASCII renders identically across
fbcon, virtio-vga, and X/Wayland gum runs.
- `VERSION_ID` bumped 0.5.8 → 0.5.27 in the os-release drop-in. The
installer banner reads this at runtime, so the live ISO + installed
system both now show "veilor-os 0.5.27".
## Test procedure
- `test/TESTING.md` — first canonical test procedure document. Splits
VM (cheap iteration, hybrid sendkey + human passwords) from real
hardware (mandatory for tag). Documents the standard test passwords
(`veilortest1` for both LUKS and admin), the kill-and-relaunch step
to skip CD on second boot, and the per-step pass/fail contract.
- `test/METHOD-CHANGELOG.md` — append-only audit trail for changes to
the procedure. Future releases that alter the test method must add
an entry here with the why.
- `test/test-runs/_TEMPLATE.md` — per-run report template. Each
tagged release should land a filled report alongside it.
## test/run-vm.sh
Decoupled QEMU monitor sock setup from auto-inject. Previously
`NO_INJECT=1` (used to suppress autotype noise into prompts) also
killed the monitor sock, leaving the VM undriveable. Monitor sock is
now always exposed; only the inject helper is gated on the pubkey
detection.
2026-05-05 01:43:00 +01:00
|
|
|
|
if [ -n "$LUKS_UUID" ]; then
|
2026-05-05 13:47:23 +01:00
|
|
|
|
LUKS_ARGS="rd.luks.uuid=luks-${LUKS_UUID} rd.luks.options=luks-${LUKS_UUID}=tries=5,timeout=0"
|
|
|
|
|
|
HARDEN_ARGS="lockdown=integrity slab_nomerge init_on_alloc=1 init_on_free=1 randomize_kstack_offset=on vsyscall=none fbcon=nodefer"
|
|
|
|
|
|
|
|
|
|
|
|
# Find the running root UUID (the btrfs filesystem holding the root
|
|
|
|
|
|
# subvol). At this point in %post chroot, `/` is the target root;
|
|
|
|
|
|
# findmnt -o UUID resolves to the btrfs UUID anaconda chose.
|
|
|
|
|
|
ROOT_UUID=$(findmnt -n -o UUID /)
|
|
|
|
|
|
[ -z "$ROOT_UUID" ] && ROOT_UUID=$(blkid -s UUID -o value /dev/mapper/luks-${LUKS_UUID} 2>/dev/null)
|
|
|
|
|
|
|
|
|
|
|
|
# Three write paths, in priority order:
|
|
|
|
|
|
#
|
|
|
|
|
|
# Path A: /etc/kernel/cmdline (the canonical source of truth for
|
|
|
|
|
|
# `kernel-install`). Per /usr/lib/kernel/install.d/90-loaderentry.install
|
|
|
|
|
|
# lines 84-95, kernel-install reads /etc/kernel/cmdline first when
|
|
|
|
|
|
# authoring BLS entries. If we write this BEFORE re-running
|
|
|
|
|
|
# kernel-install, every BLS entry inherits our args. Persists
|
|
|
|
|
|
# across `dnf upgrade kernel`, `dnf reinstall grub2-*`, and any
|
|
|
|
|
|
# other path that re-fires kernel-install hooks.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Path B: /etc/default/grub (legacy GRUB_CMDLINE_LINUX). Read by
|
|
|
|
|
|
# `grub2-mkconfig` for the generated grub.cfg. Belt-and-braces;
|
|
|
|
|
|
# kernel-install ignores this, but grub2-mkconfig respects it.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Path C: grubby --update-kernel=ALL. Direct edit to BLS option
|
|
|
|
|
|
# lines. Acts as the last-writer in case our cmdline write didn't
|
|
|
|
|
|
# trigger a fresh kernel-install pass.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Earlier veilor-os versions only used B+C. v0.5.31 adds Path A as
|
|
|
|
|
|
# the primary, because v0.5.30 testing showed B+C are racy with
|
|
|
|
|
|
# anaconda's own CreateBLSEntriesTask which uses kernel-install
|
|
|
|
|
|
# internally and can rewrite entries from empty /etc/kernel/cmdline,
|
|
|
|
|
|
# producing options lines with no rd.luks.uuid even when grubby
|
|
|
|
|
|
# successfully ran.
|
|
|
|
|
|
|
|
|
|
|
|
# Path A
|
|
|
|
|
|
mkdir -p /etc/kernel
|
|
|
|
|
|
if [ -n "$ROOT_UUID" ]; then
|
|
|
|
|
|
echo "root=UUID=${ROOT_UUID} ro rootflags=subvol=root ${LUKS_ARGS} ${HARDEN_ARGS}" > /etc/kernel/cmdline
|
|
|
|
|
|
echo "[INFO] wrote /etc/kernel/cmdline (canonical kernel-install source)"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "[WARN] could not determine root UUID; /etc/kernel/cmdline not written"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Path B
|
v0.5.27: rd.luks.uuid via grubby, GRUB rebrand, fbcon=nodefer, ASCII gum cursor
Critical install bug fix + cosmetic round-up + first formal test
procedure document.
## Critical: LUKS unlock on first boot
Generated installer kickstart's %post was injecting `rd.luks.uuid=…`
into `/etc/default/grub` only. Fedora 43 uses BLS (Boot Loader
Specification) entries in `/boot/loader/entries/*.conf`; those are
NOT regenerated by `grub2-mkconfig`. Result: the kernel boots without
`rd.luks.uuid=`, dracut's cryptsetup-generator never spawns the
unlock unit, plymouth has no password to ask for, and dracut-initqueue
loops on dev-disk-by-uuid for ~3min before dropping to emergency
shell.
The fix layers both write paths:
- `/etc/default/grub` — keeps the args around for future kernels
(kernel-install reads this when adding new entries).
- `grubby --update-kernel=ALL --args=...` — rewrites the `options`
line of every existing BLS entry so the kernel that boots NEXT
actually has the args.
Verified by reading `/proc/cmdline` from the dracut emergency shell
on a v0.5.26 install; old cmdline had only `root=UUID=… ro
rootflags=subvol=root` and was missing the LUKS arg entirely.
## GRUB / branding
- `/etc/default/grub` is sed'd to `GRUB_DISTRIBUTOR="veilor-os"` (was
already there, kept).
- BLS entries' `title` line is rewritten in-place to "veilor-os
(<kver>)" for every kernel — `grub2-mkconfig` does not touch BLS
titles, so this is the only path.
- `/boot/loader/entries/*-0-rescue-*.conf` is removed: the auto-built
rescue entry was leaking "Fedora Linux" into the GRUB menu and
showing a second boot option that nobody asked for. The rescue
kernel image itself is left in /boot.
- Hostname defaults to `veilor` (was inheriting the `localhost-live`
name anaconda writes when the kickstart's network directive is
ignored under cmdline mode).
- `/etc/machine-info` adds `PRETTY_HOSTNAME="veilor-os"` so
`hostnamectl status` and any consumer reading machine-info see the
brand.
## Boot UX
- `fbcon=nodefer` added to live-ISO bootloader cmdline. On real
laptops with a hardware GPU, the kernel modeset blanks the
framebuffer console mid-boot; without `nodefer` the installer
banner draws into a frozen framebuffer and the user sees a black
screen with a blinking cursor for ~30s. virtio-vga in QEMU doesn't
trigger this so it never reproduced in VM. Symptom report on
v0.5.26 was the trigger to investigate.
## Installer cosmetics
- `GUM_CHOOSE_CURSOR` and `GUM_INPUT_PROMPT` switched from `❯ ` to
`> `. The unicode arrow falls back to a fixed-width block on the
linux fbcon font and lipgloss then duplicates that block at col +23,
producing the "Install Install" double-render and the stray-T
artifact in password fields. Plain ASCII renders identically across
fbcon, virtio-vga, and X/Wayland gum runs.
- `VERSION_ID` bumped 0.5.8 → 0.5.27 in the os-release drop-in. The
installer banner reads this at runtime, so the live ISO + installed
system both now show "veilor-os 0.5.27".
## Test procedure
- `test/TESTING.md` — first canonical test procedure document. Splits
VM (cheap iteration, hybrid sendkey + human passwords) from real
hardware (mandatory for tag). Documents the standard test passwords
(`veilortest1` for both LUKS and admin), the kill-and-relaunch step
to skip CD on second boot, and the per-step pass/fail contract.
- `test/METHOD-CHANGELOG.md` — append-only audit trail for changes to
the procedure. Future releases that alter the test method must add
an entry here with the why.
- `test/test-runs/_TEMPLATE.md` — per-run report template. Each
tagged release should land a filled report alongside it.
## test/run-vm.sh
Decoupled QEMU monitor sock setup from auto-inject. Previously
`NO_INJECT=1` (used to suppress autotype noise into prompts) also
killed the monitor sock, leaving the VM undriveable. Monitor sock is
now always exposed; only the inject helper is gated on the pubkey
detection.
2026-05-05 01:43:00 +01:00
|
|
|
|
if ! grep -q "rd.luks.uuid" /etc/default/grub 2>/dev/null; then
|
|
|
|
|
|
sed -i "s|^GRUB_CMDLINE_LINUX=\"|GRUB_CMDLINE_LINUX=\"${LUKS_ARGS} |" /etc/default/grub
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-05 13:47:23 +01:00
|
|
|
|
# Re-run kernel-install for every kernel — picks up new /etc/kernel/cmdline,
|
|
|
|
|
|
# rewrites BLS entries with our args. This is the load-bearing step.
|
|
|
|
|
|
for kver in /lib/modules/*/; do
|
|
|
|
|
|
kver=$(basename "$kver")
|
|
|
|
|
|
[ -f "/lib/modules/$kver/vmlinuz" ] || continue
|
|
|
|
|
|
kernel-install add "$kver" "/lib/modules/$kver/vmlinuz" 2>&1 | tail -3 || \
|
|
|
|
|
|
echo "[WARN] kernel-install add $kver failed"
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
# Path C: belt-and-braces grubby update in case kernel-install missed any
|
v0.5.27: rd.luks.uuid via grubby, GRUB rebrand, fbcon=nodefer, ASCII gum cursor
Critical install bug fix + cosmetic round-up + first formal test
procedure document.
## Critical: LUKS unlock on first boot
Generated installer kickstart's %post was injecting `rd.luks.uuid=…`
into `/etc/default/grub` only. Fedora 43 uses BLS (Boot Loader
Specification) entries in `/boot/loader/entries/*.conf`; those are
NOT regenerated by `grub2-mkconfig`. Result: the kernel boots without
`rd.luks.uuid=`, dracut's cryptsetup-generator never spawns the
unlock unit, plymouth has no password to ask for, and dracut-initqueue
loops on dev-disk-by-uuid for ~3min before dropping to emergency
shell.
The fix layers both write paths:
- `/etc/default/grub` — keeps the args around for future kernels
(kernel-install reads this when adding new entries).
- `grubby --update-kernel=ALL --args=...` — rewrites the `options`
line of every existing BLS entry so the kernel that boots NEXT
actually has the args.
Verified by reading `/proc/cmdline` from the dracut emergency shell
on a v0.5.26 install; old cmdline had only `root=UUID=… ro
rootflags=subvol=root` and was missing the LUKS arg entirely.
## GRUB / branding
- `/etc/default/grub` is sed'd to `GRUB_DISTRIBUTOR="veilor-os"` (was
already there, kept).
- BLS entries' `title` line is rewritten in-place to "veilor-os
(<kver>)" for every kernel — `grub2-mkconfig` does not touch BLS
titles, so this is the only path.
- `/boot/loader/entries/*-0-rescue-*.conf` is removed: the auto-built
rescue entry was leaking "Fedora Linux" into the GRUB menu and
showing a second boot option that nobody asked for. The rescue
kernel image itself is left in /boot.
- Hostname defaults to `veilor` (was inheriting the `localhost-live`
name anaconda writes when the kickstart's network directive is
ignored under cmdline mode).
- `/etc/machine-info` adds `PRETTY_HOSTNAME="veilor-os"` so
`hostnamectl status` and any consumer reading machine-info see the
brand.
## Boot UX
- `fbcon=nodefer` added to live-ISO bootloader cmdline. On real
laptops with a hardware GPU, the kernel modeset blanks the
framebuffer console mid-boot; without `nodefer` the installer
banner draws into a frozen framebuffer and the user sees a black
screen with a blinking cursor for ~30s. virtio-vga in QEMU doesn't
trigger this so it never reproduced in VM. Symptom report on
v0.5.26 was the trigger to investigate.
## Installer cosmetics
- `GUM_CHOOSE_CURSOR` and `GUM_INPUT_PROMPT` switched from `❯ ` to
`> `. The unicode arrow falls back to a fixed-width block on the
linux fbcon font and lipgloss then duplicates that block at col +23,
producing the "Install Install" double-render and the stray-T
artifact in password fields. Plain ASCII renders identically across
fbcon, virtio-vga, and X/Wayland gum runs.
- `VERSION_ID` bumped 0.5.8 → 0.5.27 in the os-release drop-in. The
installer banner reads this at runtime, so the live ISO + installed
system both now show "veilor-os 0.5.27".
## Test procedure
- `test/TESTING.md` — first canonical test procedure document. Splits
VM (cheap iteration, hybrid sendkey + human passwords) from real
hardware (mandatory for tag). Documents the standard test passwords
(`veilortest1` for both LUKS and admin), the kill-and-relaunch step
to skip CD on second boot, and the per-step pass/fail contract.
- `test/METHOD-CHANGELOG.md` — append-only audit trail for changes to
the procedure. Future releases that alter the test method must add
an entry here with the why.
- `test/test-runs/_TEMPLATE.md` — per-run report template. Each
tagged release should land a filled report alongside it.
## test/run-vm.sh
Decoupled QEMU monitor sock setup from auto-inject. Previously
`NO_INJECT=1` (used to suppress autotype noise into prompts) also
killed the monitor sock, leaving the VM undriveable. Monitor sock is
now always exposed; only the inject helper is gated on the pubkey
detection.
2026-05-05 01:43:00 +01:00
|
|
|
|
grubby --update-kernel=ALL --args="${LUKS_ARGS}" 2>&1 | tail -5 || true
|
|
|
|
|
|
|
2026-05-05 13:47:23 +01:00
|
|
|
|
# Verification: every BLS entry MUST carry the LUKS arg.
|
v0.5.29: narrow anaconda patch + LUKS UX + initramfs assertion
Five-fix bundle from 7-agent research wave on the v0.5.28-final
gen_grub_cfgstub failure.
## 1. Narrow the anaconda transaction_progress patch (CRITICAL)
The v0.5.28 patch was too broad. It rewrote
`process_transaction_progress` so every 'error' token in the
transaction queue became a `log.warning`. That queue carries four
distinct error classes:
- cpio_error — payload extraction (genuinely fatal)
- script_error — RPM 6.0 cmdline-mode scriptlet warning-as-error
(the ONE we want to ignore)
- unpack_error — payload corruption (genuinely fatal)
- error — generic transaction error (genuinely fatal)
By swallowing all four we silently masked grub2-efi-x64's posttrans
failure mid-install. /boot/efi/EFI/fedora/ ended up incomplete →
gen_grub_cfgstub then failed at the bootloader install phase with
"gen_grub_cfgstub script failed" because its `set -eu` script
couldn't read the missing files.
v0.5.29 narrows the patch: override only the `script_error` callback
inside transaction_progress.py to log a warning and NOT enqueue
'error'. The consumer (`process_transaction_progress`) reverts to
upstream behaviour where cpio_error / unpack_error / error still
raise PayloadInstallationError. Real install-fatal events keep
aborting; only the F43-RPM-6.0 scriptlet regression is silenced.
The patch is applied via `python3 -c` regex rewrite (more robust
than nested sed across multi-line method bodies).
## 2. LUKS UX — `tries=5,timeout=0` (FIX)
Default cryptsetup-generator unit allows ONE passphrase try with a
1m30s wait. One typo on a long passphrase = wait 1m30s, then the
device-wait timer trips, then dracut emergency shell after 3min total.
Brutal. Adding `rd.luks.options=luks-XXX=tries=5,timeout=0` gives
five typo-friendly retries with no auto-timeout.
## 3. fbcon=nodefer on installed-system cmdline (FIX)
Live ISO cmdline already has `fbcon=nodefer` (added in v0.5.27 to fix
the real-laptop black-screen-after-dracut). The installed-system
bootloader directive in the generated install ks did NOT carry it.
Same KMS handoff happens on the installed system on the same hardware.
Now both have the flag.
## 4. /etc/crypttab fallback assertion (BELT-BRACES)
Anaconda's custom-partitioning code path normally writes /etc/crypttab
for `--encrypted` part directives. Edge cases observed in F43+ where
it doesn't. Without crypttab, systemd-cryptsetup-generator can still
work from kernel cmdline alone, but cleanup paths and second-stage
unlock both fall over. Adding a fallback `echo` that writes the
canonical line if it's missing post-anaconda.
## 5. Initramfs LUKS module assertion (DEFENSIVE)
Force-include `crypt + systemd-cryptsetup + plymouth` modules in
initramfs via /etc/dracut.conf.d/10-veilor-luks.conf. dracut autodetects
these when it sees an active LUKS mapping, but %post runs before the
LUKS state is fully observable from the chroot. Plus we wipe stale
initramfs (`rm -f /boot/initramfs-*.img`) before `--regenerate-all`
so the regen actually rewrites bytes. Final assertion runs
`lsinitrd | grep -q cryptsetup` and surfaces a [ERR] line in build
output if the module didn't make it.
## What this should fix
After the man-db fix in v0.5.28-final, install proceeded past
"Configuring xxx" cleanly but died at "Installing boot loader" with
gen_grub_cfgstub. Root-cause was the over-broad patch from #1 above.
After v0.5.29:
- Install transaction completes (man-db excluded; non-man-db
scriptlet warnings still suppressed; real errors still raise)
- gen_grub_cfgstub runs against complete /boot/efi/EFI/fedora/
- Bootloader install completes
- Reboot to disk lands at GRUB veilor-os entry
- Kernel + initramfs load (cryptsetup confirmed present)
- Plymouth LUKS prompt appears with text fallback
- User has 5 tries, no timeout
- Unlock → btrfs subvol mount → systemd → SDDM
Files: kickstart/veilor-os.ks (+45 lines), overlay/usr/local/bin/veilor-installer (+50 lines).
Verified: bash -n clean, ksvalidator clean.
References:
pyanaconda transaction_progress.py:110-136 (4 producers of 'error')
pyanaconda bootloader/efi.py:194-201 (gen_grub_cfgstub call site)
/usr/bin/gen_grub_cfgstub (set -eu wrapper for grub2-mkconfig stub)
Fedora wiki Changes/RPM-6.0
dnf5 issue #2507 (RPM 6.0 scriptlet propagation regression)
2026-05-05 05:12:24 +01:00
|
|
|
|
drift=$(grep -L "rd.luks.uuid" /boot/loader/entries/*.conf 2>/dev/null)
|
|
|
|
|
|
if [ -n "$drift" ]; then
|
2026-05-05 13:47:23 +01:00
|
|
|
|
echo "[ERR] BLS entries missing rd.luks.uuid after all 3 paths: $drift"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "[OK] all BLS entries carry rd.luks.uuid"
|
v0.5.29: narrow anaconda patch + LUKS UX + initramfs assertion
Five-fix bundle from 7-agent research wave on the v0.5.28-final
gen_grub_cfgstub failure.
## 1. Narrow the anaconda transaction_progress patch (CRITICAL)
The v0.5.28 patch was too broad. It rewrote
`process_transaction_progress` so every 'error' token in the
transaction queue became a `log.warning`. That queue carries four
distinct error classes:
- cpio_error — payload extraction (genuinely fatal)
- script_error — RPM 6.0 cmdline-mode scriptlet warning-as-error
(the ONE we want to ignore)
- unpack_error — payload corruption (genuinely fatal)
- error — generic transaction error (genuinely fatal)
By swallowing all four we silently masked grub2-efi-x64's posttrans
failure mid-install. /boot/efi/EFI/fedora/ ended up incomplete →
gen_grub_cfgstub then failed at the bootloader install phase with
"gen_grub_cfgstub script failed" because its `set -eu` script
couldn't read the missing files.
v0.5.29 narrows the patch: override only the `script_error` callback
inside transaction_progress.py to log a warning and NOT enqueue
'error'. The consumer (`process_transaction_progress`) reverts to
upstream behaviour where cpio_error / unpack_error / error still
raise PayloadInstallationError. Real install-fatal events keep
aborting; only the F43-RPM-6.0 scriptlet regression is silenced.
The patch is applied via `python3 -c` regex rewrite (more robust
than nested sed across multi-line method bodies).
## 2. LUKS UX — `tries=5,timeout=0` (FIX)
Default cryptsetup-generator unit allows ONE passphrase try with a
1m30s wait. One typo on a long passphrase = wait 1m30s, then the
device-wait timer trips, then dracut emergency shell after 3min total.
Brutal. Adding `rd.luks.options=luks-XXX=tries=5,timeout=0` gives
five typo-friendly retries with no auto-timeout.
## 3. fbcon=nodefer on installed-system cmdline (FIX)
Live ISO cmdline already has `fbcon=nodefer` (added in v0.5.27 to fix
the real-laptop black-screen-after-dracut). The installed-system
bootloader directive in the generated install ks did NOT carry it.
Same KMS handoff happens on the installed system on the same hardware.
Now both have the flag.
## 4. /etc/crypttab fallback assertion (BELT-BRACES)
Anaconda's custom-partitioning code path normally writes /etc/crypttab
for `--encrypted` part directives. Edge cases observed in F43+ where
it doesn't. Without crypttab, systemd-cryptsetup-generator can still
work from kernel cmdline alone, but cleanup paths and second-stage
unlock both fall over. Adding a fallback `echo` that writes the
canonical line if it's missing post-anaconda.
## 5. Initramfs LUKS module assertion (DEFENSIVE)
Force-include `crypt + systemd-cryptsetup + plymouth` modules in
initramfs via /etc/dracut.conf.d/10-veilor-luks.conf. dracut autodetects
these when it sees an active LUKS mapping, but %post runs before the
LUKS state is fully observable from the chroot. Plus we wipe stale
initramfs (`rm -f /boot/initramfs-*.img`) before `--regenerate-all`
so the regen actually rewrites bytes. Final assertion runs
`lsinitrd | grep -q cryptsetup` and surfaces a [ERR] line in build
output if the module didn't make it.
## What this should fix
After the man-db fix in v0.5.28-final, install proceeded past
"Configuring xxx" cleanly but died at "Installing boot loader" with
gen_grub_cfgstub. Root-cause was the over-broad patch from #1 above.
After v0.5.29:
- Install transaction completes (man-db excluded; non-man-db
scriptlet warnings still suppressed; real errors still raise)
- gen_grub_cfgstub runs against complete /boot/efi/EFI/fedora/
- Bootloader install completes
- Reboot to disk lands at GRUB veilor-os entry
- Kernel + initramfs load (cryptsetup confirmed present)
- Plymouth LUKS prompt appears with text fallback
- User has 5 tries, no timeout
- Unlock → btrfs subvol mount → systemd → SDDM
Files: kickstart/veilor-os.ks (+45 lines), overlay/usr/local/bin/veilor-installer (+50 lines).
Verified: bash -n clean, ksvalidator clean.
References:
pyanaconda transaction_progress.py:110-136 (4 producers of 'error')
pyanaconda bootloader/efi.py:194-201 (gen_grub_cfgstub call site)
/usr/bin/gen_grub_cfgstub (set -eu wrapper for grub2-mkconfig stub)
Fedora wiki Changes/RPM-6.0
dnf5 issue #2507 (RPM 6.0 scriptlet propagation regression)
2026-05-05 05:12:24 +01:00
|
|
|
|
fi
|
2026-05-04 00:36:13 +01:00
|
|
|
|
fi
|
|
|
|
|
|
|
v0.5.29: narrow anaconda patch + LUKS UX + initramfs assertion
Five-fix bundle from 7-agent research wave on the v0.5.28-final
gen_grub_cfgstub failure.
## 1. Narrow the anaconda transaction_progress patch (CRITICAL)
The v0.5.28 patch was too broad. It rewrote
`process_transaction_progress` so every 'error' token in the
transaction queue became a `log.warning`. That queue carries four
distinct error classes:
- cpio_error — payload extraction (genuinely fatal)
- script_error — RPM 6.0 cmdline-mode scriptlet warning-as-error
(the ONE we want to ignore)
- unpack_error — payload corruption (genuinely fatal)
- error — generic transaction error (genuinely fatal)
By swallowing all four we silently masked grub2-efi-x64's posttrans
failure mid-install. /boot/efi/EFI/fedora/ ended up incomplete →
gen_grub_cfgstub then failed at the bootloader install phase with
"gen_grub_cfgstub script failed" because its `set -eu` script
couldn't read the missing files.
v0.5.29 narrows the patch: override only the `script_error` callback
inside transaction_progress.py to log a warning and NOT enqueue
'error'. The consumer (`process_transaction_progress`) reverts to
upstream behaviour where cpio_error / unpack_error / error still
raise PayloadInstallationError. Real install-fatal events keep
aborting; only the F43-RPM-6.0 scriptlet regression is silenced.
The patch is applied via `python3 -c` regex rewrite (more robust
than nested sed across multi-line method bodies).
## 2. LUKS UX — `tries=5,timeout=0` (FIX)
Default cryptsetup-generator unit allows ONE passphrase try with a
1m30s wait. One typo on a long passphrase = wait 1m30s, then the
device-wait timer trips, then dracut emergency shell after 3min total.
Brutal. Adding `rd.luks.options=luks-XXX=tries=5,timeout=0` gives
five typo-friendly retries with no auto-timeout.
## 3. fbcon=nodefer on installed-system cmdline (FIX)
Live ISO cmdline already has `fbcon=nodefer` (added in v0.5.27 to fix
the real-laptop black-screen-after-dracut). The installed-system
bootloader directive in the generated install ks did NOT carry it.
Same KMS handoff happens on the installed system on the same hardware.
Now both have the flag.
## 4. /etc/crypttab fallback assertion (BELT-BRACES)
Anaconda's custom-partitioning code path normally writes /etc/crypttab
for `--encrypted` part directives. Edge cases observed in F43+ where
it doesn't. Without crypttab, systemd-cryptsetup-generator can still
work from kernel cmdline alone, but cleanup paths and second-stage
unlock both fall over. Adding a fallback `echo` that writes the
canonical line if it's missing post-anaconda.
## 5. Initramfs LUKS module assertion (DEFENSIVE)
Force-include `crypt + systemd-cryptsetup + plymouth` modules in
initramfs via /etc/dracut.conf.d/10-veilor-luks.conf. dracut autodetects
these when it sees an active LUKS mapping, but %post runs before the
LUKS state is fully observable from the chroot. Plus we wipe stale
initramfs (`rm -f /boot/initramfs-*.img`) before `--regenerate-all`
so the regen actually rewrites bytes. Final assertion runs
`lsinitrd | grep -q cryptsetup` and surfaces a [ERR] line in build
output if the module didn't make it.
## What this should fix
After the man-db fix in v0.5.28-final, install proceeded past
"Configuring xxx" cleanly but died at "Installing boot loader" with
gen_grub_cfgstub. Root-cause was the over-broad patch from #1 above.
After v0.5.29:
- Install transaction completes (man-db excluded; non-man-db
scriptlet warnings still suppressed; real errors still raise)
- gen_grub_cfgstub runs against complete /boot/efi/EFI/fedora/
- Bootloader install completes
- Reboot to disk lands at GRUB veilor-os entry
- Kernel + initramfs load (cryptsetup confirmed present)
- Plymouth LUKS prompt appears with text fallback
- User has 5 tries, no timeout
- Unlock → btrfs subvol mount → systemd → SDDM
Files: kickstart/veilor-os.ks (+45 lines), overlay/usr/local/bin/veilor-installer (+50 lines).
Verified: bash -n clean, ksvalidator clean.
References:
pyanaconda transaction_progress.py:110-136 (4 producers of 'error')
pyanaconda bootloader/efi.py:194-201 (gen_grub_cfgstub call site)
/usr/bin/gen_grub_cfgstub (set -eu wrapper for grub2-mkconfig stub)
Fedora wiki Changes/RPM-6.0
dnf5 issue #2507 (RPM 6.0 scriptlet propagation regression)
2026-05-05 05:12:24 +01:00
|
|
|
|
# Verify anaconda wrote /etc/crypttab for the LUKS device. anaconda's
|
|
|
|
|
|
# custom-partitioning code path normally does this for `--encrypted`
|
|
|
|
|
|
# part directives; if it didn't (edge case, F43+ regressions), write
|
|
|
|
|
|
# a minimal entry so systemd-cryptsetup-generator can find the device
|
|
|
|
|
|
# at boot from the BLS args alone.
|
|
|
|
|
|
if [ -n "$LUKS_UUID" ] && ! grep -q "$LUKS_UUID" /etc/crypttab 2>/dev/null; then
|
|
|
|
|
|
echo "luks-${LUKS_UUID} UUID=${LUKS_UUID} none discard" >> /etc/crypttab
|
|
|
|
|
|
echo "[INFO] wrote /etc/crypttab fallback entry"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-03 23:10:23 +01:00
|
|
|
|
# Switch plymouth to text-only `details` theme (scrolling boot log, no
|
|
|
|
|
|
# graphics, no logo). Theme is built-in to plymouth package, no asset
|
|
|
|
|
|
# install needed. v0.6 will ship custom veilor-themed plymouth.
|
|
|
|
|
|
plymouth-set-default-theme details 2>/dev/null || true
|
v0.5.29: narrow anaconda patch + LUKS UX + initramfs assertion
Five-fix bundle from 7-agent research wave on the v0.5.28-final
gen_grub_cfgstub failure.
## 1. Narrow the anaconda transaction_progress patch (CRITICAL)
The v0.5.28 patch was too broad. It rewrote
`process_transaction_progress` so every 'error' token in the
transaction queue became a `log.warning`. That queue carries four
distinct error classes:
- cpio_error — payload extraction (genuinely fatal)
- script_error — RPM 6.0 cmdline-mode scriptlet warning-as-error
(the ONE we want to ignore)
- unpack_error — payload corruption (genuinely fatal)
- error — generic transaction error (genuinely fatal)
By swallowing all four we silently masked grub2-efi-x64's posttrans
failure mid-install. /boot/efi/EFI/fedora/ ended up incomplete →
gen_grub_cfgstub then failed at the bootloader install phase with
"gen_grub_cfgstub script failed" because its `set -eu` script
couldn't read the missing files.
v0.5.29 narrows the patch: override only the `script_error` callback
inside transaction_progress.py to log a warning and NOT enqueue
'error'. The consumer (`process_transaction_progress`) reverts to
upstream behaviour where cpio_error / unpack_error / error still
raise PayloadInstallationError. Real install-fatal events keep
aborting; only the F43-RPM-6.0 scriptlet regression is silenced.
The patch is applied via `python3 -c` regex rewrite (more robust
than nested sed across multi-line method bodies).
## 2. LUKS UX — `tries=5,timeout=0` (FIX)
Default cryptsetup-generator unit allows ONE passphrase try with a
1m30s wait. One typo on a long passphrase = wait 1m30s, then the
device-wait timer trips, then dracut emergency shell after 3min total.
Brutal. Adding `rd.luks.options=luks-XXX=tries=5,timeout=0` gives
five typo-friendly retries with no auto-timeout.
## 3. fbcon=nodefer on installed-system cmdline (FIX)
Live ISO cmdline already has `fbcon=nodefer` (added in v0.5.27 to fix
the real-laptop black-screen-after-dracut). The installed-system
bootloader directive in the generated install ks did NOT carry it.
Same KMS handoff happens on the installed system on the same hardware.
Now both have the flag.
## 4. /etc/crypttab fallback assertion (BELT-BRACES)
Anaconda's custom-partitioning code path normally writes /etc/crypttab
for `--encrypted` part directives. Edge cases observed in F43+ where
it doesn't. Without crypttab, systemd-cryptsetup-generator can still
work from kernel cmdline alone, but cleanup paths and second-stage
unlock both fall over. Adding a fallback `echo` that writes the
canonical line if it's missing post-anaconda.
## 5. Initramfs LUKS module assertion (DEFENSIVE)
Force-include `crypt + systemd-cryptsetup + plymouth` modules in
initramfs via /etc/dracut.conf.d/10-veilor-luks.conf. dracut autodetects
these when it sees an active LUKS mapping, but %post runs before the
LUKS state is fully observable from the chroot. Plus we wipe stale
initramfs (`rm -f /boot/initramfs-*.img`) before `--regenerate-all`
so the regen actually rewrites bytes. Final assertion runs
`lsinitrd | grep -q cryptsetup` and surfaces a [ERR] line in build
output if the module didn't make it.
## What this should fix
After the man-db fix in v0.5.28-final, install proceeded past
"Configuring xxx" cleanly but died at "Installing boot loader" with
gen_grub_cfgstub. Root-cause was the over-broad patch from #1 above.
After v0.5.29:
- Install transaction completes (man-db excluded; non-man-db
scriptlet warnings still suppressed; real errors still raise)
- gen_grub_cfgstub runs against complete /boot/efi/EFI/fedora/
- Bootloader install completes
- Reboot to disk lands at GRUB veilor-os entry
- Kernel + initramfs load (cryptsetup confirmed present)
- Plymouth LUKS prompt appears with text fallback
- User has 5 tries, no timeout
- Unlock → btrfs subvol mount → systemd → SDDM
Files: kickstart/veilor-os.ks (+45 lines), overlay/usr/local/bin/veilor-installer (+50 lines).
Verified: bash -n clean, ksvalidator clean.
References:
pyanaconda transaction_progress.py:110-136 (4 producers of 'error')
pyanaconda bootloader/efi.py:194-201 (gen_grub_cfgstub call site)
/usr/bin/gen_grub_cfgstub (set -eu wrapper for grub2-mkconfig stub)
Fedora wiki Changes/RPM-6.0
dnf5 issue #2507 (RPM 6.0 scriptlet propagation regression)
2026-05-05 05:12:24 +01:00
|
|
|
|
|
|
|
|
|
|
# Force-include LUKS + plymouth modules in initramfs. dracut autodetects
|
|
|
|
|
|
# crypt+plymouth from the running config, but custom-partitioning %post
|
|
|
|
|
|
# runs before dracut sees stable LUKS state, and stale initramfs files
|
|
|
|
|
|
# from anaconda's pre-install kernel may persist. Belt-and-braces.
|
|
|
|
|
|
mkdir -p /etc/dracut.conf.d
|
|
|
|
|
|
cat > /etc/dracut.conf.d/10-veilor-luks.conf <<'DRACUTEOF'
|
|
|
|
|
|
# veilor-os: guarantee LUKS + plymouth modules in initramfs
|
|
|
|
|
|
add_dracutmodules+=" crypt systemd-cryptsetup plymouth "
|
|
|
|
|
|
DRACUTEOF
|
|
|
|
|
|
|
|
|
|
|
|
# Regenerate initramfs with new theme + dracut.conf.d picks. Remove
|
|
|
|
|
|
# stale initramfs first so the regen actually rewrites bytes.
|
|
|
|
|
|
rm -f /boot/initramfs-*.img 2>/dev/null || true
|
|
|
|
|
|
dracut --force --regenerate-all 2>&1 | tail -5 || true
|
|
|
|
|
|
|
|
|
|
|
|
# Verify cryptsetup landed in initramfs. If not, LUKS unlock is impossible
|
|
|
|
|
|
# and the user gets emergency shell on first boot. Surfacing this early.
|
|
|
|
|
|
KVER=$(ls /lib/modules | head -1)
|
|
|
|
|
|
if [ -n "$KVER" ] && [ -f "/boot/initramfs-${KVER}.img" ]; then
|
|
|
|
|
|
if ! lsinitrd "/boot/initramfs-${KVER}.img" 2>/dev/null | grep -q cryptsetup; then
|
|
|
|
|
|
echo "[ERR] cryptsetup not found in initramfs — LUKS unlock will fail"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
2026-05-03 23:10:23 +01:00
|
|
|
|
|
2026-05-03 16:27:15 +01:00
|
|
|
|
# Regen grub.cfg with new branding (anaconda already wrote one; replace).
|
|
|
|
|
|
grub2-mkconfig -o /boot/grub2/grub.cfg 2>/dev/null || true
|
|
|
|
|
|
[ -f /boot/efi/EFI/fedora/grub.cfg ] && \
|
|
|
|
|
|
grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg 2>/dev/null || true
|
|
|
|
|
|
|
v0.5.27: rd.luks.uuid via grubby, GRUB rebrand, fbcon=nodefer, ASCII gum cursor
Critical install bug fix + cosmetic round-up + first formal test
procedure document.
## Critical: LUKS unlock on first boot
Generated installer kickstart's %post was injecting `rd.luks.uuid=…`
into `/etc/default/grub` only. Fedora 43 uses BLS (Boot Loader
Specification) entries in `/boot/loader/entries/*.conf`; those are
NOT regenerated by `grub2-mkconfig`. Result: the kernel boots without
`rd.luks.uuid=`, dracut's cryptsetup-generator never spawns the
unlock unit, plymouth has no password to ask for, and dracut-initqueue
loops on dev-disk-by-uuid for ~3min before dropping to emergency
shell.
The fix layers both write paths:
- `/etc/default/grub` — keeps the args around for future kernels
(kernel-install reads this when adding new entries).
- `grubby --update-kernel=ALL --args=...` — rewrites the `options`
line of every existing BLS entry so the kernel that boots NEXT
actually has the args.
Verified by reading `/proc/cmdline` from the dracut emergency shell
on a v0.5.26 install; old cmdline had only `root=UUID=… ro
rootflags=subvol=root` and was missing the LUKS arg entirely.
## GRUB / branding
- `/etc/default/grub` is sed'd to `GRUB_DISTRIBUTOR="veilor-os"` (was
already there, kept).
- BLS entries' `title` line is rewritten in-place to "veilor-os
(<kver>)" for every kernel — `grub2-mkconfig` does not touch BLS
titles, so this is the only path.
- `/boot/loader/entries/*-0-rescue-*.conf` is removed: the auto-built
rescue entry was leaking "Fedora Linux" into the GRUB menu and
showing a second boot option that nobody asked for. The rescue
kernel image itself is left in /boot.
- Hostname defaults to `veilor` (was inheriting the `localhost-live`
name anaconda writes when the kickstart's network directive is
ignored under cmdline mode).
- `/etc/machine-info` adds `PRETTY_HOSTNAME="veilor-os"` so
`hostnamectl status` and any consumer reading machine-info see the
brand.
## Boot UX
- `fbcon=nodefer` added to live-ISO bootloader cmdline. On real
laptops with a hardware GPU, the kernel modeset blanks the
framebuffer console mid-boot; without `nodefer` the installer
banner draws into a frozen framebuffer and the user sees a black
screen with a blinking cursor for ~30s. virtio-vga in QEMU doesn't
trigger this so it never reproduced in VM. Symptom report on
v0.5.26 was the trigger to investigate.
## Installer cosmetics
- `GUM_CHOOSE_CURSOR` and `GUM_INPUT_PROMPT` switched from `❯ ` to
`> `. The unicode arrow falls back to a fixed-width block on the
linux fbcon font and lipgloss then duplicates that block at col +23,
producing the "Install Install" double-render and the stray-T
artifact in password fields. Plain ASCII renders identically across
fbcon, virtio-vga, and X/Wayland gum runs.
- `VERSION_ID` bumped 0.5.8 → 0.5.27 in the os-release drop-in. The
installer banner reads this at runtime, so the live ISO + installed
system both now show "veilor-os 0.5.27".
## Test procedure
- `test/TESTING.md` — first canonical test procedure document. Splits
VM (cheap iteration, hybrid sendkey + human passwords) from real
hardware (mandatory for tag). Documents the standard test passwords
(`veilortest1` for both LUKS and admin), the kill-and-relaunch step
to skip CD on second boot, and the per-step pass/fail contract.
- `test/METHOD-CHANGELOG.md` — append-only audit trail for changes to
the procedure. Future releases that alter the test method must add
an entry here with the why.
- `test/test-runs/_TEMPLATE.md` — per-run report template. Each
tagged release should land a filled report alongside it.
## test/run-vm.sh
Decoupled QEMU monitor sock setup from auto-inject. Previously
`NO_INJECT=1` (used to suppress autotype noise into prompts) also
killed the monitor sock, leaving the VM undriveable. Monitor sock is
now always exposed; only the inject helper is gated on the pubkey
detection.
2026-05-05 01:43:00 +01:00
|
|
|
|
# Suppress the auto-generated "Fedora Linux 0-rescue-…" BLS entry. Fedora
|
|
|
|
|
|
# ships a rescue kernel image alongside every install; the BLS entry that
|
|
|
|
|
|
# points at it is created by `kernel-install` and shows up in GRUB as a
|
|
|
|
|
|
# second menu item. For a branded distro it's noisy + reveals "Fedora"
|
|
|
|
|
|
# in the menu. The rescue image itself is harmless to keep on disk.
|
2026-05-05 13:47:23 +01:00
|
|
|
|
# Match both `<machine-id>-0-rescue.conf` (current Fedora 43 layout) and
|
|
|
|
|
|
# `<machine-id>-0-rescue-<kver>.conf` (older layout). The earlier glob
|
|
|
|
|
|
# `*-0-rescue-*.conf` required a trailing hyphen and missed the new form.
|
|
|
|
|
|
rm -f /boot/loader/entries/*-0-rescue*.conf 2>/dev/null || true
|
v0.5.27: rd.luks.uuid via grubby, GRUB rebrand, fbcon=nodefer, ASCII gum cursor
Critical install bug fix + cosmetic round-up + first formal test
procedure document.
## Critical: LUKS unlock on first boot
Generated installer kickstart's %post was injecting `rd.luks.uuid=…`
into `/etc/default/grub` only. Fedora 43 uses BLS (Boot Loader
Specification) entries in `/boot/loader/entries/*.conf`; those are
NOT regenerated by `grub2-mkconfig`. Result: the kernel boots without
`rd.luks.uuid=`, dracut's cryptsetup-generator never spawns the
unlock unit, plymouth has no password to ask for, and dracut-initqueue
loops on dev-disk-by-uuid for ~3min before dropping to emergency
shell.
The fix layers both write paths:
- `/etc/default/grub` — keeps the args around for future kernels
(kernel-install reads this when adding new entries).
- `grubby --update-kernel=ALL --args=...` — rewrites the `options`
line of every existing BLS entry so the kernel that boots NEXT
actually has the args.
Verified by reading `/proc/cmdline` from the dracut emergency shell
on a v0.5.26 install; old cmdline had only `root=UUID=… ro
rootflags=subvol=root` and was missing the LUKS arg entirely.
## GRUB / branding
- `/etc/default/grub` is sed'd to `GRUB_DISTRIBUTOR="veilor-os"` (was
already there, kept).
- BLS entries' `title` line is rewritten in-place to "veilor-os
(<kver>)" for every kernel — `grub2-mkconfig` does not touch BLS
titles, so this is the only path.
- `/boot/loader/entries/*-0-rescue-*.conf` is removed: the auto-built
rescue entry was leaking "Fedora Linux" into the GRUB menu and
showing a second boot option that nobody asked for. The rescue
kernel image itself is left in /boot.
- Hostname defaults to `veilor` (was inheriting the `localhost-live`
name anaconda writes when the kickstart's network directive is
ignored under cmdline mode).
- `/etc/machine-info` adds `PRETTY_HOSTNAME="veilor-os"` so
`hostnamectl status` and any consumer reading machine-info see the
brand.
## Boot UX
- `fbcon=nodefer` added to live-ISO bootloader cmdline. On real
laptops with a hardware GPU, the kernel modeset blanks the
framebuffer console mid-boot; without `nodefer` the installer
banner draws into a frozen framebuffer and the user sees a black
screen with a blinking cursor for ~30s. virtio-vga in QEMU doesn't
trigger this so it never reproduced in VM. Symptom report on
v0.5.26 was the trigger to investigate.
## Installer cosmetics
- `GUM_CHOOSE_CURSOR` and `GUM_INPUT_PROMPT` switched from `❯ ` to
`> `. The unicode arrow falls back to a fixed-width block on the
linux fbcon font and lipgloss then duplicates that block at col +23,
producing the "Install Install" double-render and the stray-T
artifact in password fields. Plain ASCII renders identically across
fbcon, virtio-vga, and X/Wayland gum runs.
- `VERSION_ID` bumped 0.5.8 → 0.5.27 in the os-release drop-in. The
installer banner reads this at runtime, so the live ISO + installed
system both now show "veilor-os 0.5.27".
## Test procedure
- `test/TESTING.md` — first canonical test procedure document. Splits
VM (cheap iteration, hybrid sendkey + human passwords) from real
hardware (mandatory for tag). Documents the standard test passwords
(`veilortest1` for both LUKS and admin), the kill-and-relaunch step
to skip CD on second boot, and the per-step pass/fail contract.
- `test/METHOD-CHANGELOG.md` — append-only audit trail for changes to
the procedure. Future releases that alter the test method must add
an entry here with the why.
- `test/test-runs/_TEMPLATE.md` — per-run report template. Each
tagged release should land a filled report alongside it.
## test/run-vm.sh
Decoupled QEMU monitor sock setup from auto-inject. Previously
`NO_INJECT=1` (used to suppress autotype noise into prompts) also
killed the monitor sock, leaving the VM undriveable. Monitor sock is
now always exposed; only the inject helper is gated on the pubkey
detection.
2026-05-05 01:43:00 +01:00
|
|
|
|
|
|
|
|
|
|
# Hostname: default to "veilor" rather than the localhost-live / fedora
|
|
|
|
|
|
# fallback that anaconda writes. User can override post-install with
|
|
|
|
|
|
# `hostnamectl set-hostname`.
|
|
|
|
|
|
echo "veilor" > /etc/hostname
|
|
|
|
|
|
|
|
|
|
|
|
# /etc/machine-info — feeds `hostnamectl status` and some BLS title
|
|
|
|
|
|
# rendering paths. PRETTY_HOSTNAME is what GRUB titles read when
|
|
|
|
|
|
# GRUB_DISTRIBUTOR doesn't override (Fedora 43 BLS entry titles ignore
|
|
|
|
|
|
# GRUB_DISTRIBUTOR; this is the closest we can get without rewriting
|
|
|
|
|
|
# every entry's `title` line).
|
|
|
|
|
|
cat > /etc/machine-info <<'MACHINFO'
|
|
|
|
|
|
PRETTY_HOSTNAME="veilor-os"
|
|
|
|
|
|
MACHINFO
|
|
|
|
|
|
|
|
|
|
|
|
# Rewrite the `title` line of every remaining BLS entry to use our
|
|
|
|
|
|
# brand. `kernel-install` will write new entries with "Fedora Linux …"
|
|
|
|
|
|
# but the file we ship + every kernel update goes through grubby which
|
|
|
|
|
|
# preserves whatever title was last set.
|
|
|
|
|
|
for entry in /boot/loader/entries/*.conf; do
|
|
|
|
|
|
[ -f "$entry" ] || continue
|
|
|
|
|
|
# Pull kernel version out of the existing title (e.g. "6.17.1-300.fc43.x86_64")
|
|
|
|
|
|
kver=$(awk '/^version / {print $2}' "$entry")
|
|
|
|
|
|
sed -i "s|^title .*|title veilor-os (${kver})|" "$entry"
|
|
|
|
|
|
done
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# 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 "════════════════════════════════════════════════════════"
|
2026-05-02 03:20:42 +01:00
|
|
|
|
%end
|
|
|
|
|
|
|
|
|
|
|
|
# Reboot when done
|
|
|
|
|
|
reboot
|
2026-05-02 03:42:15 +01:00
|
|
|
|
KSEOF
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# Substitute placeholders. Use | as sed delimiter. validate_pw()
|
|
|
|
|
|
# already rejects "$\`&|/\n at input — sed_escape() is defence in
|
|
|
|
|
|
# depth in case future code paths feed unsanitised values (e.g.
|
|
|
|
|
|
# locale/hostname from a file, or a relaxed validator).
|
2026-05-03 14:35:36 +01:00
|
|
|
|
# Detect cloud-init seed pubkey (if attached) → embed as anaconda
|
|
|
|
|
|
# `sshkey --username=admin` directive. Adds key to admin's
|
|
|
|
|
|
# authorized_keys at install time so SSH validation works first boot.
|
|
|
|
|
|
# No seed = empty directive line (anaconda treats blank line as no-op).
|
|
|
|
|
|
local seed_pubkey sshkey_directive=""
|
|
|
|
|
|
seed_pubkey=$(detect_seed_pubkey 2>/dev/null) || true
|
|
|
|
|
|
if [[ -n $seed_pubkey ]]; then
|
|
|
|
|
|
echo "[INFO] using cloud-init seed pubkey for admin authorized_keys"
|
|
|
|
|
|
# sshkey directive — quote pubkey, no shell meta in pubkeys.
|
|
|
|
|
|
sshkey_directive="sshkey --username=admin \"${seed_pubkey}\""
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-02 03:42:15 +01:00
|
|
|
|
sed -i \
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
-e "s|__LOCALE__|$(sed_escape "$SEL_LOCALE")|" \
|
|
|
|
|
|
-e "s|__HOSTNAME__|$(sed_escape "$SEL_HOSTNAME")|" \
|
|
|
|
|
|
-e "s|__DISK_BASENAME__|$(sed_escape "$disk_basename")|" \
|
|
|
|
|
|
-e "s|__LUKS_PW__|$(sed_escape "$SEL_LUKS_PW")|" \
|
|
|
|
|
|
-e "s|__ADMIN_PW__|$(sed_escape "$SEL_ADMIN_PW")|" \
|
2026-05-03 14:35:36 +01:00
|
|
|
|
-e "s|__SSHKEY_DIRECTIVE__|$(sed_escape "$sshkey_directive")|" \
|
2026-05-02 03:42:15 +01:00
|
|
|
|
"$out"
|
2026-05-02 03:20:42 +01:00
|
|
|
|
echo "[INFO] generated kickstart at $out"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
v0.5.32: ship 7 blockers from 9-agent wave
Per docs/research/2026-05-05-agent-wave/README.md priority list.
All 7 land together to keep iteration cycles useful — partial fixes
bury the lookahead findings agents already mapped.
## 1. CRITICAL — suspend/resume wifi death (Agent 9, B2)
`veilor-modules-lock.service` runs `kernel.modules_disabled=1` 30s
after graphical.target. iwlwifi/iwlmvm/cfg80211 reload on resume
from S3/S0ix → with modules locked, resume breaks wifi until
reboot. Same architectural class as the LUKS bug — security feature
breaks legitimate kernel state transitions.
The unit already has `ConditionKernelCommandLine=!module.sig_enforce=1`
(self-skip when signed-modules enforcement is on cmdline). Adding
`module.sig_enforce=1` to the kernel cmdline retains the security
property (no unsigned modules) without runtime lock-down → resume
works.
Files: kickstart/veilor-os.ks line 61 + overlay/usr/local/bin/veilor-installer
generated bootloader directive both gain `module.sig_enforce=1`.
## 2. veilor-firstboot.service WantedBy=graphical.target (Agent 2)
Was `WantedBy=multi-user.target` only. Real installs default to
graphical.target so the unit never ran on installed systems — admin
pw stayed at install-time + chage -d 0 expired, SDDM PAM bounced
to chauthtok screen (recoverable but ugly UX).
Now `WantedBy=graphical.target multi-user.target`. Live ISO +
multi-user installs both resolve via this list.
## 3. USBGuard hash → id-based baseline (Agent 9, A3)
Mirrors memory feedback_usbguard_dock.md — onyx had hash+parent-hash
rules that broke on dock replug; we shipped no rules.conf so first
boot blocks the USB keyboard.
Adds overlay/etc/usbguard/rules.conf with HID-class allow rule
(`allow with-interface match-all { 03:*:* }`) — covers every USB
keyboard, mouse, gamepad, fingerprint reader, NFC. Survives dock
replug + kernel-bump vendor renumeration. Mass-storage stays
implicit-block; user explicitly allows post-firstboot via
`ujust veilor-usbguard-enroll` (planned v0.6).
## 4. firewalld trusted zone with tailscale0 pre-bound (Agent 9, D1)
User uses Tailscale daily (memory: project_tailscale_mesh.md).
Default firewalld zone = drop, blocks tailnet traffic on tailscale0.
Adds overlay/etc/firewalld/zones/trusted.xml with
`<interface name="tailscale0"/>`. After `tailscale up` brings the
interface up, NetworkManager dispatcher associates it with the
trusted zone automatically — no user intervention.
Default zone stays drop. Only the tailscale0 interface gets ACCEPT.
## 5. /etc/skel branding (Agent 7)
Was completely empty. Result: per-user KDE config (~/.config/kdeglobals
etc.) pre-empty, so the moment user opened System Settings, KDE wrote
fresh ~/.config/* and silently shadowed our /etc/xdg/kdedefaults/*.
Visual brand evaporated on first click.
Seeds:
/etc/skel/.config/kdeglobals (copy of assets/kde/veilor-default.kdeglobals)
/etc/skel/.config/breezerc (copy of assets/kde/breezerc)
/etc/skel/.config/kwinrc (Plasma 6 wayland defaults: opengl, animspeed=0,
blur off, click-to-focus)
/etc/skel/.config/konsolerc (default profile = Veilor)
/etc/skel/.local/share/konsole/Veilor.profile + .colorscheme
User who opens System Settings now writes against branded baseline,
not against vanilla Breeze.
## 6. KMS modeset args + initramfs keymap (Agents 1 + 9)
Real laptop boot has a 5-15s blank between vt switch and SDDM start
because simpledrm releases before i915/nvidia-drm/amdgpu claim. Plus
non-US users get locked out at LUKS prompt because initramfs ships
en-US keymap by default (RHBZ 1405539, RHBZ 1890085).
Adds to bootloader cmdline (live + installed):
i915.modeset=1 amdgpu.modeset=1 nvidia-drm.modeset=1
rd.vconsole.keymap=us
`rd.vconsole.keymap=us` is a placeholder; the v0.6 firstboot keymap
picker will rewrite it from /etc/vconsole.conf. Until then, en-US
users get correct LUKS keyboard; non-US users still need the v0.6
fix (per Agent 1).
## 7. virtio-9p log capture (Agent 6)
The v0.5.30 virtio-serial wiring depends on rsyslog inside the live
ISO (anaconda's setupVirtio writes a rsyslog forward rule), which
the live ks doesn't install — files were 0-byte across three
install runs.
test/run-vm.sh now adds a `-virtfs local,...,mount_tag=hostlogs`
share pointing at `test/test-runs/<timestamp>/`. veilor-installer
runs `_dump_logs_to_host` via EXIT trap that mounts the share at
/mnt/hostlogs and rsyncs /tmp/{anaconda,program,storage,packaging,dnf}.log
+ /var/log/veilor-installer.log + dmesg + journalctl + the generated
ks. Runs on success AND failure AND ^C.
No-op on real hardware (9p tag absent) — VM-only debug.
## Validate
bash -n overlay/usr/local/bin/veilor-installer # OK
ksvalidator kickstart/veilor-os.ks # clean
## Out-of-scope for v0.5.32 (deferred to v0.6)
Per Agent 1 follow-ups: argon2id retune for slow CPUs, recovery key
generation in firstboot, TPM2/FIDO2 unlock helpers. Per Agent 9
follow-ups: Plasma Wayland fallback X11 install, lid-close handling,
SELinux relabel progress UX. Per Agent 4: AppArmor stack +
nftables preset + audit log shipping CLI.
Per Agent 8 (CI hardening): SHA-pin actions + dependabot + SBOM +
SLSA L3 attestation — separate workflow-only commit.
2026-05-05 15:36:24 +01:00
|
|
|
|
_dump_logs_to_host() {
|
|
|
|
|
|
# If a virtio-9p share tagged "hostlogs" was attached by run-vm.sh,
|
|
|
|
|
|
# mount it and dump every anaconda + installer log into it. Runs on
|
|
|
|
|
|
# success AND failure (called via trap). No-op on real hardware where
|
|
|
|
|
|
# the 9p tag doesn't exist.
|
|
|
|
|
|
if ! grep -qw 9p /proc/filesystems 2>/dev/null; then return 0; fi
|
|
|
|
|
|
local m=/mnt/hostlogs
|
|
|
|
|
|
mkdir -p "$m"
|
|
|
|
|
|
mount -t 9p -o trans=virtio,version=9p2000.L hostlogs "$m" 2>/dev/null || return 0
|
|
|
|
|
|
cp -af /tmp/anaconda.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /tmp/program.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /tmp/storage.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /tmp/packaging.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /tmp/dnf.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /tmp/dnf.librepo.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /tmp/anaconda-cmdline.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /var/log/veilor-installer.log "$m/" 2>/dev/null || true
|
|
|
|
|
|
cp -af /run/install/veilor-generated.ks "$m/" 2>/dev/null || true
|
|
|
|
|
|
dmesg > "$m/dmesg.log" 2>/dev/null || true
|
|
|
|
|
|
journalctl -b > "$m/journal.log" 2>/dev/null || true
|
|
|
|
|
|
sync
|
|
|
|
|
|
umount "$m" 2>/dev/null || true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-02 03:20:42 +01:00
|
|
|
|
run_install() {
|
v0.5.32: ship 7 blockers from 9-agent wave
Per docs/research/2026-05-05-agent-wave/README.md priority list.
All 7 land together to keep iteration cycles useful — partial fixes
bury the lookahead findings agents already mapped.
## 1. CRITICAL — suspend/resume wifi death (Agent 9, B2)
`veilor-modules-lock.service` runs `kernel.modules_disabled=1` 30s
after graphical.target. iwlwifi/iwlmvm/cfg80211 reload on resume
from S3/S0ix → with modules locked, resume breaks wifi until
reboot. Same architectural class as the LUKS bug — security feature
breaks legitimate kernel state transitions.
The unit already has `ConditionKernelCommandLine=!module.sig_enforce=1`
(self-skip when signed-modules enforcement is on cmdline). Adding
`module.sig_enforce=1` to the kernel cmdline retains the security
property (no unsigned modules) without runtime lock-down → resume
works.
Files: kickstart/veilor-os.ks line 61 + overlay/usr/local/bin/veilor-installer
generated bootloader directive both gain `module.sig_enforce=1`.
## 2. veilor-firstboot.service WantedBy=graphical.target (Agent 2)
Was `WantedBy=multi-user.target` only. Real installs default to
graphical.target so the unit never ran on installed systems — admin
pw stayed at install-time + chage -d 0 expired, SDDM PAM bounced
to chauthtok screen (recoverable but ugly UX).
Now `WantedBy=graphical.target multi-user.target`. Live ISO +
multi-user installs both resolve via this list.
## 3. USBGuard hash → id-based baseline (Agent 9, A3)
Mirrors memory feedback_usbguard_dock.md — onyx had hash+parent-hash
rules that broke on dock replug; we shipped no rules.conf so first
boot blocks the USB keyboard.
Adds overlay/etc/usbguard/rules.conf with HID-class allow rule
(`allow with-interface match-all { 03:*:* }`) — covers every USB
keyboard, mouse, gamepad, fingerprint reader, NFC. Survives dock
replug + kernel-bump vendor renumeration. Mass-storage stays
implicit-block; user explicitly allows post-firstboot via
`ujust veilor-usbguard-enroll` (planned v0.6).
## 4. firewalld trusted zone with tailscale0 pre-bound (Agent 9, D1)
User uses Tailscale daily (memory: project_tailscale_mesh.md).
Default firewalld zone = drop, blocks tailnet traffic on tailscale0.
Adds overlay/etc/firewalld/zones/trusted.xml with
`<interface name="tailscale0"/>`. After `tailscale up` brings the
interface up, NetworkManager dispatcher associates it with the
trusted zone automatically — no user intervention.
Default zone stays drop. Only the tailscale0 interface gets ACCEPT.
## 5. /etc/skel branding (Agent 7)
Was completely empty. Result: per-user KDE config (~/.config/kdeglobals
etc.) pre-empty, so the moment user opened System Settings, KDE wrote
fresh ~/.config/* and silently shadowed our /etc/xdg/kdedefaults/*.
Visual brand evaporated on first click.
Seeds:
/etc/skel/.config/kdeglobals (copy of assets/kde/veilor-default.kdeglobals)
/etc/skel/.config/breezerc (copy of assets/kde/breezerc)
/etc/skel/.config/kwinrc (Plasma 6 wayland defaults: opengl, animspeed=0,
blur off, click-to-focus)
/etc/skel/.config/konsolerc (default profile = Veilor)
/etc/skel/.local/share/konsole/Veilor.profile + .colorscheme
User who opens System Settings now writes against branded baseline,
not against vanilla Breeze.
## 6. KMS modeset args + initramfs keymap (Agents 1 + 9)
Real laptop boot has a 5-15s blank between vt switch and SDDM start
because simpledrm releases before i915/nvidia-drm/amdgpu claim. Plus
non-US users get locked out at LUKS prompt because initramfs ships
en-US keymap by default (RHBZ 1405539, RHBZ 1890085).
Adds to bootloader cmdline (live + installed):
i915.modeset=1 amdgpu.modeset=1 nvidia-drm.modeset=1
rd.vconsole.keymap=us
`rd.vconsole.keymap=us` is a placeholder; the v0.6 firstboot keymap
picker will rewrite it from /etc/vconsole.conf. Until then, en-US
users get correct LUKS keyboard; non-US users still need the v0.6
fix (per Agent 1).
## 7. virtio-9p log capture (Agent 6)
The v0.5.30 virtio-serial wiring depends on rsyslog inside the live
ISO (anaconda's setupVirtio writes a rsyslog forward rule), which
the live ks doesn't install — files were 0-byte across three
install runs.
test/run-vm.sh now adds a `-virtfs local,...,mount_tag=hostlogs`
share pointing at `test/test-runs/<timestamp>/`. veilor-installer
runs `_dump_logs_to_host` via EXIT trap that mounts the share at
/mnt/hostlogs and rsyncs /tmp/{anaconda,program,storage,packaging,dnf}.log
+ /var/log/veilor-installer.log + dmesg + journalctl + the generated
ks. Runs on success AND failure AND ^C.
No-op on real hardware (9p tag absent) — VM-only debug.
## Validate
bash -n overlay/usr/local/bin/veilor-installer # OK
ksvalidator kickstart/veilor-os.ks # clean
## Out-of-scope for v0.5.32 (deferred to v0.6)
Per Agent 1 follow-ups: argon2id retune for slow CPUs, recovery key
generation in firstboot, TPM2/FIDO2 unlock helpers. Per Agent 9
follow-ups: Plasma Wayland fallback X11 install, lid-close handling,
SELinux relabel progress UX. Per Agent 4: AppArmor stack +
nftables preset + audit log shipping CLI.
Per Agent 8 (CI hardening): SHA-pin actions + dependabot + SBOM +
SLSA L3 attestation — separate workflow-only commit.
2026-05-05 15:36:24 +01:00
|
|
|
|
# Capture logs to host on every exit path (success, failure, ^C).
|
|
|
|
|
|
trap _dump_logs_to_host EXIT
|
2026-05-03 03:46:36 +01:00
|
|
|
|
# Anaconda env setup (see comments below).
|
2026-05-03 00:06:54 +01:00
|
|
|
|
export LANG="${SEL_LOCALE:-en_GB.UTF-8}"
|
|
|
|
|
|
export LC_ALL="$LANG"
|
2026-05-03 03:46:36 +01:00
|
|
|
|
# LANG / LC_ALL: anaconda's keyboard.activate_keyboard() raises
|
|
|
|
|
|
# AnacondaError: 'LANG' if missing. tty1 inherits no locale by default.
|
|
|
|
|
|
# XDG_RUNTIME_DIR: anaconda's display.setup_display() probes
|
|
|
|
|
|
# os.getenv("XDG_RUNTIME_DIR") + Wayland socket and crashes with
|
|
|
|
|
|
# TypeError on None. We're running unattended so no display needed,
|
|
|
|
|
|
# but env var still has to exist.
|
2026-05-03 01:35:52 +01:00
|
|
|
|
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
|
|
|
|
|
|
mkdir -p "$XDG_RUNTIME_DIR"
|
|
|
|
|
|
chmod 0700 "$XDG_RUNTIME_DIR"
|
2026-05-03 03:46:36 +01:00
|
|
|
|
|
|
|
|
|
|
if [[ $TUI == gum ]]; then
|
|
|
|
|
|
clear
|
|
|
|
|
|
# gum spin runs anaconda as subprocess. --show-output forwards
|
|
|
|
|
|
# stdout/stderr to /tmp/anaconda-cmdline.log so we can debug.
|
|
|
|
|
|
# --title shows live-updating header. --spinner dot is minimal.
|
|
|
|
|
|
local rc=0
|
|
|
|
|
|
gum spin --spinner dot \
|
|
|
|
|
|
--title "Installing veilor-os to $SEL_DISK · this takes 10-30 min · logs on tty2" \
|
|
|
|
|
|
--show-output \
|
|
|
|
|
|
-- bash -c 'anaconda --cmdline --kickstart=/run/install/veilor-generated.ks 2>&1 | tee /tmp/anaconda-cmdline.log' || rc=$?
|
|
|
|
|
|
if [[ $rc -eq 0 ]]; then
|
2026-05-06 10:32:30 +01:00
|
|
|
|
# v0.6: split the success screen into THREE stacked boxes.
|
|
|
|
|
|
#
|
|
|
|
|
|
# 1. Green success box — quiet confirmation.
|
|
|
|
|
|
# 2. Yellow eject box — promoted out of the buried
|
|
|
|
|
|
# one-liner the v0.5 success box used. Operators on
|
|
|
|
|
|
# both onyx and the friend's RTX 4080 rig missed the
|
|
|
|
|
|
# reminder and rebooted into the live ISO instead of
|
|
|
|
|
|
# the install. Now it's its own loud thick-bordered
|
|
|
|
|
|
# box that sits BELOW the success box and is
|
|
|
|
|
|
# impossible to miss.
|
|
|
|
|
|
# 3. Reboot countdown — embedded inside the green
|
|
|
|
|
|
# success box so the operator can see "complete +
|
|
|
|
|
|
# Xs to reboot" at a glance.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Each tick clears + redraws all three, so the eject-media
|
|
|
|
|
|
# box stays in front of the operator for the full 10-second
|
|
|
|
|
|
# window and isn't scrolled off by a banner refresh.
|
2026-05-06 10:32:11 +01:00
|
|
|
|
local secs
|
|
|
|
|
|
for secs in 10 9 8 7 6 5 4 3 2 1; do
|
|
|
|
|
|
clear
|
|
|
|
|
|
gum style --foreground 2 --border rounded --margin "1 2" --padding "1 3" \
|
|
|
|
|
|
"✓ Install complete" \
|
|
|
|
|
|
"" \
|
2026-05-06 10:32:30 +01:00
|
|
|
|
"Rebooting in ${secs}s..."
|
|
|
|
|
|
gum style --foreground 3 --border thick --margin "0 2" --padding "1 3" \
|
|
|
|
|
|
--border-foreground 3 \
|
|
|
|
|
|
" Remove the install media NOW " \
|
|
|
|
|
|
"" \
|
|
|
|
|
|
" Unplug the USB stick / eject the DVD before " \
|
|
|
|
|
|
" reboot, otherwise the system will boot back " \
|
|
|
|
|
|
" into the live ISO instead of your fresh install. "
|
2026-05-06 10:32:11 +01:00
|
|
|
|
sleep 1
|
|
|
|
|
|
done
|
2026-05-03 03:46:36 +01:00
|
|
|
|
systemctl reboot
|
|
|
|
|
|
else
|
|
|
|
|
|
prompt_error "Anaconda exited non-zero (status $rc).
|
|
|
|
|
|
Logs at /tmp/anaconda.log + /var/log/veilor-installer.log.
|
|
|
|
|
|
Press OK to drop to shell."
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
2026-05-02 03:20:42 +01:00
|
|
|
|
else
|
2026-05-03 03:46:36 +01:00
|
|
|
|
prompt_message "Installing veilor-os to $SEL_DISK ...
|
|
|
|
|
|
This will take 10-30 minutes.
|
|
|
|
|
|
Logs: /var/log/veilor-installer.log + /tmp/anaconda.log"
|
|
|
|
|
|
sleep 1
|
|
|
|
|
|
if anaconda --cmdline --kickstart=/run/install/veilor-generated.ks; then
|
|
|
|
|
|
prompt_message "Install complete. System will reboot.
|
|
|
|
|
|
Remove the install media after shutdown."
|
|
|
|
|
|
sleep 3
|
|
|
|
|
|
systemctl reboot
|
|
|
|
|
|
else
|
|
|
|
|
|
prompt_error "Anaconda exited non-zero.
|
2026-05-02 03:20:42 +01:00
|
|
|
|
Logs at /tmp/anaconda.log + /var/log/veilor-installer.log.
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
Press OK to drop to shell."
|
2026-05-03 03:46:36 +01:00
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
2026-05-02 03:20:42 +01:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
# ── Entry ──
|
2026-05-02 06:22:47 +01:00
|
|
|
|
# (require_tty already called above before exec redirect — see top of file)
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
banner
|
|
|
|
|
|
|
2026-05-02 03:20:42 +01:00
|
|
|
|
while true; do
|
|
|
|
|
|
case "$(main_menu)" in
|
2026-05-02 21:10:04 +01:00
|
|
|
|
"Install")
|
2026-05-02 03:20:42 +01:00
|
|
|
|
if collect_answers && generate_ks; then
|
|
|
|
|
|
run_install || continue
|
|
|
|
|
|
fi
|
|
|
|
|
|
;;
|
2026-05-03 03:46:36 +01:00
|
|
|
|
"live · KDE") launch_desktop ;;
|
|
|
|
|
|
"live · shell") drop_to_shell ;;
|
|
|
|
|
|
"──────") banner ;; # separator clicked: redraw, no-op
|
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>
2026-05-02 04:39:27 +01:00
|
|
|
|
"Reboot") systemctl reboot ;;
|
|
|
|
|
|
"Power off") systemctl poweroff ;;
|
2026-05-03 03:46:36 +01:00
|
|
|
|
"") banner ;; # ESC/cancel from menu: redraw
|
|
|
|
|
|
*) banner ;;
|
2026-05-02 03:20:42 +01:00
|
|
|
|
esac
|
|
|
|
|
|
done
|