Commit graph

93 commits

Author SHA1 Message Date
veilor-org
fae677fb68 v0.5.28 (final): patch anaconda transaction_progress.py + exclude man-db
THE actual root cause of the man-db transaction failure that killed
three consecutive VM installs (v0.5.26 / v0.5.27 / v0.5.28).
Confirmed via 7-agent research wave:

- Fedora 43 ships RPM 6.0, which changed scriptlet failure
  propagation. Scriptlets that previously emitted "Non-critical
  error" warnings now bubble up as transaction-level errors. dnf5
  issue #2507 documents the change. Anaconda --cmdline mode treats
  any 'error' token from the dnf transaction as a fatal abort.
- man-db's `transfiletriggerin` is the canonical trigger: it runs
  `systemd-run /usr/bin/systemctl start man-db-cache-update` which
  returns non-zero in the anaconda chroot (no PID 1 systemd) and is
  flagged as transaction-level error under RPM 6.0.
- We previously patched anaconda's transaction_progress.py on the
  BUILD HOST so livecd-creator could finish its own transaction.
  That patch lives only on the host running the build — never landed
  in the live rootfs the user installs from. Reproduced 3 times:
  install-time anaconda on the live ISO is unpatched, hits the same
  code path, aborts at exactly "Configuring man-db.x86_64".

Two-layer fix:

1. kickstart %post seds the file inside the live rootfs at build time
   so the user's install-time anaconda is patched. Sed downgrades the
   'error' token from raise PayloadInstallationError to log.warning.

2. Generated install ks excludes man-db / man-pages / man-pages-overrides
   from %packages. Belt-and-braces — even if the patch has an edge
   case the trigger never fires because the package isn't installed.
   Users install man pages post-firstboot.

Previous attempts that didn't work: dropping the updates repo (only
narrowed the set of failing scriptlets, didn't fix the underlying
RPM-6.0 propagation change); flipping SELinux to permissive
(confirmed not the cause; kickstart's selinux directive only writes
/etc/selinux/config in target root, doesn't affect installer-time).

Follow-up for next release: replicate the transaction_progress patch
in the CI workflow's container so the build itself is deterministic.
Currently the workflow has been greening on luck.

Files: kickstart/veilor-os.ks (+25 lines), overlay/usr/local/bin/veilor-installer (+10 lines).
Verified: bash -n clean, ksvalidator clean.
2026-05-05 03:46:00 +01:00
veilor-org
931a19ec93 v0.5.28 (cont): drop updates repo from generated install ks
Anaconda's transaction died at "Configuring man-db.x86_64" in both
v0.5.26 and v0.5.27 VM tests, reliably, days apart, against a freshly
populated package cache. Same failure pattern, same package, with
nothing in the visible error other than "The transaction process has
ended with errors..". Pattern matches the same Fedora `updates` repo
issue that the CI build kickstart already worked around by stripping
the `updates` line entirely (`.github/workflows/build-iso.yml` line
~109).

The installer-generated kickstart was adding the line back and
re-introducing the bug for every user install. This commit aligns
the install-time ks with the build-time ks: only the base `releases`
repo is consumed by anaconda. Users who want updates run `dnf
upgrade` post-install (or the v0.6 `veilor-update` wrapper).

Trade-off: first-boot package versions are frozen to the Fedora 43
release date instead of including post-release updates. Acceptable —
the alternative is "install reliably fails" which makes any
freshness conversation moot.

Verified locally: `bash -n` passes, ks template still well-formed.
End-to-end re-validation goes through the next CI ISO + VM test run.
2026-05-05 02:52:22 +01:00
veilor-org
e848c7ffc3 v0.5.28 (partial): lock locale to en_US, roadmap post-install menu
Install-flow change + roadmap update. The roadmap entry is the
durable record; the code change is the immediate effect.

## Locale picker removed

The "[4/4] Locale" prompt is gone. Locale is hardcoded to en_US.UTF-8
for the install. Two reasons:

1. The picker only offered en_GB and en_US, both of which install
   identically apart from the langtag string and a couple of date /
   currency conventions that nobody who's mid-install is thinking
   about. It's a fake choice that adds a screen.
2. `localectl set-locale` post-install handles every locale on earth
   in one command. The v0.7 `veilor-postinstall` first-login menu (see
   roadmap below) will offer a locale + keyboard layout switch with
   live preview, which is the right place for that decision.

Step counters updated [1/4]→[1/3], [2/4]→[2/3], [3/4]→[3/3]. The Locale
row stays in the confirm-summary box because users still want to see
what they're getting installed.

## Roadmap

- New section v0.5.27–v0.5.28 — documents the install-path
  stabilisation work explicitly so the bridge between "first green
  ISO" and "looks polished" is not invisible. Calls out the LUKS BLS
  fix that landed in v0.5.27 + the gum-input replacement scheduled
  for v0.5.28.
- v0.6 — `veilor-doctor` description expanded: this is the
  post-install audit tool. Every user runs it weekly to see drift
  from baseline.
- v0.6 — new entry `veilor-postinstall`: EndeavourOS-style first-login
  welcome menu, single TUI screen, asks once. Covers the "I just
  installed, what do I configure" gap in one explicit step instead of
  scattered docs.
2026-05-05 02:48:36 +01:00
veilor-org
1881c14ea7 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
veilor-org
c89c73ee84 v0.5.26: log to /run, tolerate tee failure
User hit `/usr/local/bin/veilor-installer: line 33: /usr/bin/tee:
input/output error` on real-hardware install. Cause: LOG was
`/var/log/veilor-installer.log`, which on the live ISO is backed by an
overlay over squashfs. A bad sector / flaky USB → tee write fails →
process substitution dies → installer aborts before the menu renders.

Two changes:

1. Move LOG to /run/veilor-installer.log — pure tmpfs, never touches
   the live medium. Same path also unaffected by /var fill or overlay
   weirdness.

2. Wrap the `exec > >(tee -a $LOG) 2>&1` redirect in a writability
   probe. If the log can't be appended to (tmpfs OOM, fd exhaustion,
   anything), skip the tee and run the installer without on-disk
   persistence rather than crashing.

Persistence is a nice-to-have for post-mortem debugging; the installer
running is the must-have. This inverts the priority correctly.
2026-05-04 14:20:26 +01:00
veilor-org
b3509b4b06 v0.5.25: don't run veilor-firstboot on live ISO
Live ISO boot chain showing extra step:
  boot → text scroll → veilor-firstboot prompts admin pw → installer

veilor-firstboot.service was enabled in live ks but it's an INSTALLED
system feature (forces admin pw set on first real boot). Made no
sense to ask on live (no persistent admin user, throwaway VM, etc).

Live ks now: doesn't enable veilor-firstboot, masks the unit so
overlay-copied unit file can't auto-activate. Install ks chroot %post
already enables it (correct path).

After fix:
  boot → text scroll → installer banner directly
2026-05-04 04:08:40 +01:00
veilor-org
4dabbd8fcf v0.5.24: live ISO — text-mode boot + GRUB veilor branding
User wants full chained pipeline:
GRUB veilor-os → plymouth text → branded gum installer →
install progress → reboot → installed system text-clean.

Live ISO was missing pieces from the install ks polish. v0.5.24
brings live ks into parity:

- bootloader --append: add plymouth.enable=0 (kills fedora splash,
  exposes tty1 with gum installer banner immediately)
- chroot %post: GRUB_DISTRIBUTOR="veilor-os" (menu title)
- chroot %post: GRUB_CMDLINE_LINUX_DEFAULT="" (drop rhgb quiet)
- chroot %post: plymouth-set-default-theme details (text scroll
  fallback if plymouth.enable=0 ignored)
- grub2-mkconfig regen with new branding

Result on next ISO build:
- Boot from ISO → GRUB shows "veilor-os" entry
- Pick veilor-os → text scroll (no fedora splash)
- TTY1 lands on gum installer banner + menu (no plymouth swallow)
- Install completes → reboot → installed system already has the
  same text-mode boot + LUKS prompt config from v0.5.22-23
2026-05-04 02:26:00 +01:00
veilor-org
6197f7bf89 v0.5.23: explicit rd.luks.uuid injection in chroot %post
v0.5.22 plymouth details theme works (text scroll boot visible). But
LUKS prompt still never fires — dracut spins on dev-disk-by-uuid for
2+ min then drops to emergency shell.

Hypothesis: anaconda --cmdline mode skips/breaks the bootloader auto-
add of rd.luks.uuid arg. Without it, cryptsetup-generator in
initramfs has no UUID to create unlock unit for → no prompt → no
unlock → dracut times out.

Fix: chroot %post detects LUKS partition via blkid TYPE=crypto_LUKS,
injects `rd.luks.uuid=luks-<uuid>` into GRUB_CMDLINE_LINUX if not
already present. Belt-and-braces — if anaconda DID add it, sed
checks first.

Followed by grub2-mkconfig regen (already in script) so installed
grub.cfg picks up the new cmdline arg.
2026-05-04 00:36:13 +01:00
veilor-org
abfba24512 v0.5.22: plymouth details theme — scrolling text boot, LUKS visible
v0.5.21 set plymouth.enable=0 — plymouth-start.service still ran +
ate LUKS keystrokes. Boot fell to dracut emergency shell.

Better path: plymouth IS running but in TEXT mode via built-in
`details` theme (scrolling boot log, no graphics, no fedora logo).
LUKS prompt renders as text "Please enter passphrase for...:".
Plymouth still owns the prompt → keystrokes go through.

Changes:
- Drop plymouth.enable=0 from cmdline (let plymouth run)
- chroot %post: plymouth-set-default-theme details
- Drop rhgb quiet from GRUB_CMDLINE_LINUX_DEFAULT (all kernel msgs visible)
- dracut --force --regenerate-all (new theme baked into initramfs)

Result: text scroll boot → text LUKS prompt → text scroll → SDDM.
Onyx aesthetic. Branded plymouth theme deferred to v0.6.
2026-05-03 23:10:23 +01:00
veilor-org
68ebe6fdbe v0.5.21: plymouth.enable=0 — text boot like onyx, plymouth pkg kept
User wants onyx-style boot: pure text scroll → LUKS prompt → text scroll
→ SDDM. No fedora splash, no plymouth UI.

Solution: keep plymouth PACKAGE installed (Fedora's dracut module
ships LUKS-prompt machinery via plymouth), but disable plymouthd at
runtime via kernel cmdline `plymouth.enable=0`.

Effect:
- plymouthd starts → reads cmdline → exits
- systemd-ask-password sees no plymouth daemon → falls back to
  systemd-tty-ask-password-agent on /dev/console
- LUKS prompt rendered as text "Please enter passphrase for /dev/dm-0: "
- All kernel/systemd messages visible
- SDDM still launches at graphical.target (real install)

Applied to both:
- LIVE ks bootloader --append (so live boot text-mode + installer
  visible on tty1, no splash hiding it)
- Generated install ks bootloader --append (so installed system
  text-boots with LUKS prompt)

v0.6 will rebrand plymouth theme + re-enable for branded splash. For
v0.5.0 ship: minimal/text aesthetic matches user's onyx daily driver.
2026-05-03 21:59:58 +01:00
veilor-org
26d6ff277b v0.5.20: revert plymouth removal — back to Fedora defaults for LUKS
v0.5.10-v0.5.19 progressively ripped plymouth out (kernel cmdline,
masks, dracut omit, package -plymouth, dracut regen) trying to get
text-mode LUKS prompt. Each layer surfaced a new dependency:

- plymouth.enable=0 didn't disable plymouth-start.service
- /dev/null mask survived but plymouth ran from initramfs anyway
- omit_dracutmodules in chroot didn't take (anaconda regen overrode)
- package removal worked but dropped LUKS prompt machinery entirely
- crypt+systemd-cryptsetup re-add via dracut regen-all also didn't

Each fix added complexity. Net: 6 commits of plymouth fighting,
boot still stuck at LUKS prompt.

Strategic revert: re-add plymouth, restore Fedora defaults. plymouth
is the standard LUKS-prompt path on Fedora — works out of box. v0.6
will swap fedora-logos + plymouth theme for veilor-themed splash.

This commit:
- Adds `plymouth` back to %packages
- Removes -plymouth/-plymouth-plugin-label/-plymouth-system-theme
- Removes plymouth.enable=0/rd.plymouth=0/logo.nologo/console=tty0
  from kernel cmdline
- Removes /etc/dracut.conf.d/99-veilor-no-plymouth.conf write
- Removes dracut --regenerate-all call
- Removes /dev/null symlink masks for plymouth-* units
- Keeps GRUB_DISTRIBUTOR=veilor-os branding
- Drops GRUB_TERMINAL_OUTPUT=console + GRUB_THEME edits

Result: Fedora stock LUKS path. plymouth shows fedora splash + LUKS
prompt → user types passphrase → boot continues.
2026-05-03 21:52:10 +01:00
veilor-org
15311f56e9 v0.5.19: dracut --regenerate-all (fix chroot glob expansion bug)
v0.5.18 added crypt + systemd-cryptsetup to dracut.conf.d/99-veilor-
no-plymouth.conf. Boot test still failed: dracut-initqueue stuck
waiting on dev-disk-by-uuid → systemd-cryptsetup never fired.

Diagnosis: %post chroot used bash glob `for kver in /lib/modules/*/`.
In chroot, shell may be dash + nullglob unset → unmatched glob
expands literally to "/lib/modules/*/" → dracut --kver "/lib/modules/*/"
fails silently with `|| true`. Initramfs never regenerated → still
contains the v0.5.14 omit_dracutmodules-only config without crypt.

Fix: dracut --force --regenerate-all (walks /lib/modules internally,
no shell glob needed). One call regens all kernel initramfses with
the new dracut.conf.d in scope.
2026-05-03 18:39:13 +01:00
veilor-org
2be5692c74 v0.5.18: add crypt + systemd-cryptsetup + ask-password agent to initramfs
v0.5.17 boot stuck at dracut-initqueue waiting for LUKS device that
never unlocks. Plymouth removal also dropped the dracut machinery
that prompts user for LUKS passphrase. Pure-text systemd-tty-ask-
password-agent works in real root but isn't bundled into initramfs.

Fix: dracut.conf.d/99-veilor-no-plymouth.conf:
  add_dracutmodules+=" crypt systemd-cryptsetup "
  install_items+=" /usr/bin/systemd-tty-ask-password-agent "

Result: dracut bundles systemd-cryptsetup + ask-password binary into
initramfs. cryptsetup-generator creates unit at boot, ask-password-
agent prompts on tty1 in text mode "Please enter passphrase for...:".
sendkey-friendly + works on real hardware.
2026-05-03 17:35:31 +01:00
veilor-org
8ebe3a9713 v0.5.17: text-mode boot — drop fedora branding, full hackery scroll
Per user: bottom-screen "fedora" logo + spinner during boot is fedora-
logos package + GRUB graphical theme. v0.5.17 strips it for now —
v0.6 will re-introduce plymouth with veilor-black theme + LUKS-prompt-
friendly config.

Changes:
1. Kernel cmdline: add `logo.nologo console=tty0`
   - logo.nologo: suppress kernel boot logo (Tux/Fedora leaf)
   - console=tty0: explicit text console output
   - `quiet` already absent → all kernel msgs scroll
2. /etc/default/grub patches in chroot %post:
   - GRUB_DISTRIBUTOR="veilor-os" (menu titles read "veilor-os" not
     "Fedora Linux 43...")
   - GRUB_THEME= (empty — no graphical theme)
   - GRUB_TERMINAL_OUTPUT="console" (text-mode menu, no gfxterm)
   - Drop GRUB_BACKGROUND
3. Regen grub.cfg + EFI grub.cfg with new branding

Result: pure text scroll boot, white-on-black, no fedora artifacts.
veilor-os menu title in GRUB picker. "Hackery dope" aesthetic.

For real users wanting splash → v0.6 ships plymouth + veilor theme.
2026-05-03 16:27:15 +01:00
veilor-org
77266faa4f v0.5.16: sshd UseDNS no — fix banner timeout on NAT/slirp 2026-05-03 15:41:15 +01:00
veilor-org
d07adf3b14 v0.5.15: inject cloud-init seed pubkey as admin sshkey at install time
v0.5.14 ships a working install but auto-install harness can't SSH-
validate post-reboot — admin user has no authorized_keys, hardened
sshd rejects all auth. SSH up + listening but no path to log in.

Fix: detect_seed_pubkey() searches /dev/sr* for a NoCloud cidata
volume (label "cidata"), parses ssh_authorized_keys: list from
user-data, returns first key. generate_ks() then embeds as

  sshkey --username=admin "ssh-ed25519 AAAA... user@host"

right after the user= directive. Anaconda creates
/home/admin/.ssh/authorized_keys with right perms (700/600).

Real users: drop a NoCloud seed iso next to install media (or via
USB), pubkey lands automatically. auto-install.sh: existing run-vm.sh
seed logic already builds the cidata iso with host pubkey.

If no seed → directive line empty → anaconda treats as no-op → SSH
validation blocked but install otherwise unaffected.
2026-05-03 14:35:36 +01:00
veilor-org
8861e12485 v0.5.14: remove plymouth package entirely
v0.5.13 added omit_dracutmodules+=plymouth + dracut --force regen in
chroot %post. Boot test still showed plymouth-start.service running.
Theory: the chroot dracut --force --kver loop didn't fire (kver glob
may have been empty in chroot), or anaconda regenerated initramfs
AFTER our %post and ignored our config drop-in.

Simpler fix: don't ship plymouth at all. Add `-plymouth
-plymouth-plugin-label -plymouth-system-theme` to kickstart %packages.
With no plymouth package on disk, dracut can't bundle it into
initramfs regardless of dracut.conf state.

The /etc/dracut.conf.d snippet + /dev/null masks from v0.5.12-13 stay
as belt-and-braces — harmless once plymouth is absent.
2026-05-03 11:12:35 +01:00
veilor-org
1a0cf689a8 v0.5.13: omit plymouth from dracut + regen initramfs
v0.5.12 added /dev/null symlinks for plymouth services on real root.
Boot test confirmed plymouth STILL starts: it lives in initramfs
(dracut module 90plymouth) which has its own bundled service files,
unaffected by /etc/systemd/system/ masks on the installed btrfs.

Two-layer fix:
1. /etc/dracut.conf.d/99-veilor-no-plymouth.conf:
   omit_dracutmodules+=" plymouth "
   Then `dracut -f --kver $kver` to regenerate initramfs sans plymouth.
2. Keep /dev/null symlinks for post-pivot real-root masking.

Result: LUKS prompt rendered as text by systemd-tty-ask-password-agent
on tty1 — sendkey-friendly, hardware-realistic.
2026-05-03 10:10:46 +01:00
veilor-org
e90d6ef662 v0.5.12: mask plymouth via /dev/null symlinks (systemctl mask N/A in chroot)
v0.5.11 used `systemctl mask plymouth-*.service` in generated kickstart
%post chroot block. systemctl needs systemd running, which it isn't in
anaconda chroot — calls failed silently (|| true).

Boot test confirmed: post-reboot showed both:
  Started plymouth-start.service - Show Plymouth Boot Screen
  Started systemd-ask-password-plymouth.path - Forward Password Requests

Fix: write /dev/null symlinks directly (`ln -sf /dev/null
/etc/systemd/system/<unit>`). Achieves what mask does without needing
systemd. Also adds the path-activated unit
(systemd-ask-password-plymouth.path) which actually pulls plymouth in
during dracut-initqueue.
2026-05-03 09:08:36 +01:00
veilor-org
f588f15a6e v0.5.11: mask plymouth services in chroot %post
v0.5.10 added plymouth.enable=0 + rd.plymouth=0 to kernel cmdline,
but `plymouth-start.service` still registered and ran in real-root
boot. LUKS prompt remained invisible — dracut-initqueue stuck
waiting for /dev/disk/by-uuid/<luks> to materialize.

Belt-and-suspenders: mask plymouth-{start,quit,quit-wait,read-write,
switch-root}.service in the generated kickstart's %post chroot so the
units can never start.

Effect: systemd-tty-ask-password-agent handles LUKS prompt directly
on tty1 with text "Please enter passphrase for disk ...:" — sendkey-
friendly + works on real hardware too.
2026-05-03 07:37:00 +01:00
veilor-org
38d702e14a v0.5.10: disable plymouth during early boot for text LUKS prompt
v0.5.9 GRUB-installs cleanly. Disk boots, dracut reaches
cryptsetup.target, systemd-ask-password-plymouth.path armed. But
plymouth never switches from boot-splash mode to password-prompt mode
— sendkey'd passphrases bounce, dracut waits forever on
dev-disk-by-uuid.

Workaround: pass `plymouth.enable=0 rd.plymouth=0` to kernel cmdline.
Eliminates plymouth-ask-password-plugin as a layer; LUKS prompt
appears as plain text on tty1 ("Please enter passphrase for disk... :").

Bonus: aligns with hardening posture. Plymouth is graphical eye-candy
running in pid 1's namespace during early boot. Fewer moving parts =
smaller attack surface. veilor-os defaults to text boot; users wanting
splash can re-enable post-install.
2026-05-03 06:32:32 +01:00
veilor-org
2511df6327 v0.5.9: drop --location=none from bootloader directive
Auto-install round 4 reached emergency dracut shell post-reboot:

  Warning: /dev/disk/by-uuid/ecbd65ba-... does not exist
  Generating "/run/initramfs/rdsosreport.txt"
  Entering emergency mode.

Root cause: `bootloader --location=none` in our generated kickstart
literally tells anaconda DO NOT INSTALL GRUB. Earlier reviewer agent
suggested `--location=none` thinking it meant "auto-detect EFI/BIOS",
but that's wrong — none means none.

Fix: drop --location entirely. Anaconda picks correct mode based on
detected disk layout (GPT-EFI → grub2-efi-x64, GPT-BIOS → grub2-pc).

Without GRUB written, the rebooted VM's UEFI firmware fell back to
loading initramfs straight from somewhere, but cmdline lacked
rd.luks.uuid= → couldn't find the encrypted root → emergency shell.
2026-05-03 05:20:48 +01:00
veilor-org
2784fbd6e9 ci: drop updates repo (3x 404 on its zchunk repodata) 2026-05-03 04:15:12 +01:00
veilor-org
f8fc89e399 v0.5.8: installer UX polish — pro design
User-locked design changes for serious/pro feel:

Banner:
- Full VEILOR OS wordmark (figlet ANSI Regular block)
- Version + date + live indicator: "veilor-os 0.5.8 · 2026-05-03 · live"
- No tagline, no credit
- Rounded gum border, dim grey accent

Menu:
- Drop "Welcome" header (banner = welcome enough)
- Reorder + simplify:
    Install
    live · KDE
    live · shell
    ──────
    Reboot
    Power off
- Cursor: ❯ (sharp angle, matches box-drawing weight)
- Middle-dot · separators (cleaner than en-dash)
- Visual separator line between primary/session actions

Install flow:
- Step indicators on each prompt: [1/4] [2/4] [3/4] [4/4]
- Disk: "[1/4] Select install disk · WILL BE ERASED"
- LUKS: "[2/4] Encryption · LUKS2 passphrase (min 8)"
- Admin: "[3/4] Admin user · password for 'admin'"
- Locale: "[4/4] Locale"

Confirm screen:
- Boxed (gum style --border rounded)
- "WILL BE ERASED" colored red (FG=1)
- "This action is irreversible" colored amber (FG=3)
- gum confirm with --affirmative "Yes, install" / --negative "Cancel"

Install progress:
- gum spin with --show-output during anaconda run
- Title: "Installing veilor-os to /dev/X · 10-30min · logs on tty2"
- Success: green-bordered "✓ Install complete" box, 5s reboot countdown

os-release: VERSION_ID 0.1 → 0.5.8
2026-05-03 03:46:36 +01:00
veilor-org
53949b0899 v0.5.7: drop LVM, native btrfs-on-LUKS partitioning
Auto-install round 3 hit:

  Configuring storage
  Creating disklabel on /dev/vda
  Creating luks on /dev/vda3
  Creating lvmpv on /dev/mapper/luks-...
  Creating btrfs on /dev/mapper/veilor-root
  Running in cmdline mode, no interactive debugging allowed.
  The exact error message is:
  mount failed: wrong fs type, bad option, bad superblock on
  /dev/mapper/veilor-root, missing codepage or helper program

LVM+btrfs combination causes mount failure under anaconda --cmdline.
mkfs.btrfs runs but post-create mount can't find a valid superblock.

Fix: drop LVM intermediary. Use native btrfs-on-LUKS — same pattern
Fedora KDE Spin uses by default. Cleaner, snapshot story unchanged
(btrfs subvols give us root/home split + rollback potential without
LVM's overhead).

New layout:
  vda1: efi (600M)
  vda2: ext4 /boot (1024M)
  vda3: LUKS2 → btrfs (label=veilor)
       ├── subvol root → /
       └── subvol home → /home

ksvalidator clean on the new template.
2026-05-03 02:45:16 +01:00
veilor-org
ac371bdc36 v0.5.6: anaconda --cmdline + XDG_RUNTIME_DIR for unattended install
v0.5.5 fixed AnacondaError: 'LANG'. Auto-install harness then
hit next crash:

  TypeError: expected str, bytes or os.PathLike object, not NoneType
  File "/usr/lib64/python3.14/site-packages/pyanaconda/display.py", line 223
    wl_socket_path = os.path.join(os.getenv("XDG_RUNTIME_DIR"), ...)

anaconda's display.setup_display() unconditionally tries to set up
Wayland socket path. tty1 has no XDG_RUNTIME_DIR set. None gets passed
to os.path.join → TypeError.

Two-part fix:
1. export XDG_RUNTIME_DIR=/run/user/0 + mkdir, so even if anaconda
   probes it, the env var has a valid string value.
2. Pass --cmdline to anaconda. Fully unattended text-only mode, no
   Wayland/X/TUI. Right fit for our gum-driven kickstart flow where
   ks is self-contained (disk, pw, locale all pre-answered).

Combined effect: anaconda goes straight from CLI parse → kickstart
execute → reboot. No display subsystem at all.

Surfaced by test/auto-install.sh round 2.
2026-05-03 01:35:52 +01:00
veilor-org
5e38412944 v0.5.5: export LANG before anaconda (fixes AnacondaError: 'LANG')
Auto-install harness ran through gum installer cleanly but anaconda
crashed at startup:

  File "/usr/lib64/python3.14/site-packages/pyanaconda/keyboard.py",
       line 152, in activate_keyboard
    sync_run_task(task_proxy)
  ...
  pyanaconda.modules.common.errors.general.AnacondaError: 'LANG'

Anaconda's keyboard.activate_keyboard() reads $LANG and bombs if unset.
TTY1 (where veilor-installer runs) inherits no locale by default;
gum/whiptail don't set it.

Fix: export LANG + LC_ALL = user's chosen locale (defaulting to
en_GB.UTF-8) before invoking anaconda.

Found via test/auto-install.sh end-to-end run.
2026-05-03 00:06:54 +01:00
veilor-org
dce276586f ci: chown build/out before split (container created as root) 2026-05-02 23:20:36 +01:00
veilor-org
9921745c9d test/auto-install.sh: auto-fetch + reassemble chunked ISO from ci-latest
Workflow now publishes ISO as 1900M chunks. Test harness needs to:
1. gh release download --pattern '*.iso.part-*'
2. cat parts back into single ISO
3. verify sha256 of all parts

If invoked with no ISO arg, auto-fetches from ci-latest release.
Falls back to local ISO path if given as arg (existing behavior).

Reassembled ISO lives at ~/veilor-iso/ci-latest/.
2026-05-02 22:50:37 +01:00
s8n
deef914064 v0.5.5: autonomous install test harness (#12)
test/auto-install.sh boots ISO, drives gum installer via QEMU
monitor sendkey with hardcoded test answers, waits for anaconda,
reboots into installed system, SSHs in, runs validation checklist.

Co-authored-by: veilor-org <admin@veilor.org>
2026-05-02 22:49:51 +01:00
veilor-org
da08047172 ci: split ISO into 1900M chunks for GH release upload
GH release asset size limit = 2 GiB. Veilor ISO ~2.8 GiB (KDE base +
hardening + grafted /veilor/ tree). zstd -19 only achieves 96.67%
compression (squashfs already xz-compressed). Splitting is the fix.

Workflow now:
- Splits ISO with `split -b 1900M -d --suffix-length=2`
- Drops original ISO before upload (would fail at >2 GiB)
- Includes per-part sha256 for reassembly verification
- Release notes include cat reassembly command

test/auto-install.sh will need follow-up commit to download + cat
the parts before booting.
2026-05-02 22:49:19 +01:00
veilor-org
73ac2cf96f ci: grant contents:write + drop artifact upload-on-failure
Two follow-ups to 75a68a1 (releases switchover):

1. action-gh-release got 403 "Resource not accessible by integration"
   because default GITHUB_TOKEN has read-only on contents. Added
   workflow-level `permissions: contents: write`.

2. Failure-path artifact upload still hit quota wall. Replaced with
   inline `tail` of build/out/build.log + anaconda program.log
   directly to job log. No artifact upload = no quota.
2026-05-02 22:13:44 +01:00
veilor-org
75a68a1187 ci: switch ISO publish from artifacts to GitHub Releases
Artifact storage quota (50GB Pro tier) maxed out with ~18 iterations
of 2.7GB ISOs. Quota recalc 6-12h not in our cadence. Builds succeed
but upload step fails — wasting CI minutes + blocking testing.

Switch to GitHub Releases (no storage quota):
- Every successful build on main updates rolling `ci-latest`
  prerelease draft. Replaces files in place.
- Tag-driven releases (v*.*.*) keep their existing publish path.
- Build logs remain as artifacts (small + opt-in failure only,
  retention=1d).

User can `gh release download ci-latest --repo veilor-org/veilor-os`
or browse to releases page. No more artifact quota wall.
2026-05-02 21:42:54 +01:00
veilor-org
0f4647577b v0.5.4: installer UX polish — terse menu, VEILOR OS wordmark, hostname auto
User boot-tested v0.5.2 + in-VM patch. Requested polish:

- Banner: replace slant-figlet `veilor-os` + "hardened. branded. yours."
  tagline with figlet ANSI Regular `VEILOR OS` wordmark (5-line block).
  No tagline. Border preserved by gum style call.
- Menu header: "Welcome. What would you like to do?" → "Welcome"
- Menu labels:
    "Install veilor-os to disk"     → "Install"
    "Try live — desktop (KDE Plasma)" → "live - (KDE)"
    "Try live — shell"              → "live - shell"
    "Reboot" / "Power off"          unchanged
- Hostname prompt removed — hardcoded to "veilor". User can change
  post-install via hostnamectl. Cuts one prompt from install flow.
  Confirmation summary drops the Hostname row.
- Locale options trimmed: en_GB.UTF-8, en_US.UTF-8 only (was 4 incl
  de_DE, fr_FR). i18n not v0.5 priority.

Verified in-VM rendering of the menu changes via sed-patch on v0.5.2
ISO. ksvalidator + bash -n clean.
2026-05-02 21:10:04 +01:00
veilor-org
125e5f93af ci: drop ISO artifact retention from 14 to 3 days
Hit GitHub Actions artifact storage quota (50GB Pro tier) at 26
artifacts × ~2.7GB = 42GB. Each push burns ~2.7GB; 14d retention
+ frequent iteration = inevitable quota exhaustion.

3-day retention covers QEMU + spare-laptop test cycles. For long-term
keep, attach to GH Releases on tag (PR #2 will wire that).
2026-05-02 07:21:44 +01:00
veilor-org
9fedb8592f v0.5.3: fix installer require_tty before tee redirect
QEMU boot test of v0.5.2 found service still status=1/FAILURE despite
file present at /usr/local/bin/veilor-installer. Root cause via
`bash -x`: `exec > >(tee -a "$LOG") 2>&1` ran BEFORE require_tty
check; process substitution replaces fd1 with a pipe, so [[ -t 1 ]]
returns false → require_tty bails out with [ERR] message.

Order was self-inflicted bug from v0.5.0. Fix: move require_tty
function definition + call BEFORE the tee redirect. Drop the
redundant require_tty call in the entry block (would fail post-redirect).
2026-05-02 06:22:47 +01:00
veilor-org
ec4291293e v0.5.2: move veilor-installer + veilor-firstboot to /usr/local/bin
QEMU boot test of v0.5.1 (commit 3cbffaf) revealed both scripts
missing from /usr/local/sbin/ on running system, despite being in
overlay/usr/local/sbin/ in the source tree.

Root cause: Fedora's filesystem package (or post-install scriptlet)
rewrites /usr/local/sbin → /usr/local/bin symlink AFTER kickstart
%post --nochroot's overlay copy runs. The cp -a placed files in
/usr/local/sbin/ as a real directory; the symlink replacement
deleted them.

Confirmed via tty diagnostic: `ls -la /usr/local` shows
`lrwxrwxrwx ... sbin -> bin` with bin mtime predating sbin symlink
ctime by ~5min — overlay copy ran first, scriptlet rewrote sbin
second.

Fix: move both binaries to overlay/usr/local/bin/ where they're
safe from the symlink rewrite. Update all references:
- kickstart/veilor-os.ks chmod path + chown + diagnostic ls
- overlay/etc/systemd/system/getty@tty1.service.d/veilor-installer.conf ExecStart
- overlay/etc/systemd/system/veilor-firstboot.service ExecStart
- scripts/selinux/build-policy.sh fcontext + restorecon paths
- generated install ks template inside veilor-installer

Service drop-in stays at /etc/systemd/system/getty@tty1.service.d/
unchanged. The veilor-installer binary in /usr/local/bin/ is
discoverable via $PATH same as before.
2026-05-02 05:33:22 +01:00
s8n
3cbffaf714 sec: AppArmor profile skeletons + audit shipping draft + veilor-firstboot SELinux module (#3)
Co-authored-by: veilor-org <admin@veilor.org>
2026-05-02 04:39:39 +01:00
s8n
8127f32868 v0.6: pre-stage veilor-update + veilor-doctor CLI tools (#11)
Two user-facing commands shipped in overlay/usr/local/bin/.
Wraps dnf+flatpak update flow and read-only health diagnostic.
Uses gum if available, plain output otherwise. No kickstart wiring
yet beyond chmod — full integration in v0.6.0 release.

Co-authored-by: veilor-org <admin@veilor.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 04:39:33 +01:00
s8n
4c8002cda7 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
s8n
70abf8c496 ux: v0.3 polish — plymouth/sddm/konsole audit + wallpaper variants + branding logo (#4)
Co-authored-by: veilor-org <admin@veilor.org>
2026-05-02 04:39:21 +01:00
s8n
09f7c1f753 build: wire 30-apply-v03-theme.sh into ks %post + SSH key auto-inject in run-vm.sh (#1)
Co-authored-by: veilor-org <admin@veilor.org>
2026-05-02 04:38:23 +01:00
s8n
408a0e4862 feat(installer): pre-stage gum-based UX assets for v0.5.1 (#7)
Drops in branded assets the v0.5.1 installer rewrite (whiptail -> gum)
will consume: ASCII banner, sourceable GUM_* env-var palette matching
the veilor-black KDE color scheme, and an INSTALLER.md walkthrough.

The existing v0.5.0 veilor-installer script is intentionally untouched
so the swap can land in a separate, focused PR.

Co-authored-by: s8n-ru <279801990+s8n-ru@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 04:38:18 +01:00
s8n
d543e71f74 v0.5.1 build: vendor gum + graft /veilor/ onto ISO (#8)
* v0.5.1 build: vendor gum binary + graft /veilor/ onto ISO

- gum 0.17.0 pinned by sha256, downloaded into overlay/usr/local/bin/
  so installer can use Charm.sh TUI primitives.
- After livecd-creator produces ISO, extract+re-pack with /veilor/
  containing overlay+scripts+assets so installer-generated ks can
  copy them into target system at install time.

* fix: extract original ISO boot stanza programmatically (no hardcoded paths)

Reviewer found `-e images/efiboot.img` was wrong — Fedora livecd-creator
places efiboot.img in isolinux/ not images/. Plus missing
--mbr-force-bootable + -partition_* flags would produce hybrid MBR/GPT
mismatch refused by some BIOS firmwares.

Fix: extract original ISO's exact boot stanza via
`xorriso -report_el_torito as_mkisofs` and replay it via eval.
Guarantees exact match, immune to upstream Fedora layout changes.

---------

Co-authored-by: veilor-org <admin@veilor.org>
2026-05-02 04:33:44 +01:00
s8n
2d6f6b07f6 ci: quote $@ in tuned profile scripts (SC2068) (#10)
Pre-existing shellcheck failure blocking all PR merges. Standard
"double-quote array expansions" fix. No behavior change.

Co-authored-by: veilor-org <admin@veilor.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 04:17:22 +01:00
s8n
b4b5d7c007 ci: scope brand-leak lint to source dirs only (#6)
Lint flagged false positives on audit reports + CHANGELOG that
self-reference forbidden strings as findings. Restrict scan to
kickstart/, overlay/, scripts/, assets/, build/ — actual ship state.

Co-authored-by: veilor-org <admin@veilor.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 04:07:03 +01:00
s8n
a7e7d6e10c docs: CHANGELOG v0.2.0-v0.2.5, README rewrite, ROADMAP, release notes update (#5)
Co-authored-by: veilor-org <admin@veilor.org>
2026-05-02 03:42:39 +01:00
veilor-org
fc7c3f858b v0.5.0-beta: fix 4 installer blockers found during lint
Bugs found by agent linter on v0.5.0-alpha:

1. logvol missing --size: ksvalidator rejected. Added --size=8192 --grow.
2. bootloader --location=mbr on UEFI: conflicts with /boot/efi part.
   Switched to --location=none (anaconda auto-detects EFI vs BIOS).
3. lsblk awk truncated multi-word disk models ("WD PC SN740" → "WD").
   Now collapses model spaces to underscores, preserves full string.
   Also added mmcblk to disk regex (eMMC support).
4. Heredoc with $VAR expansion + passwords containing $/`/" corrupted
   generated ks. Now: single-quoted heredoc + sed placeholder
   substitution. Plus input validator rejects "$\` chars in passwords.

ksvalidator clean on sample generated ks.
bash -n clean.

CI build still in flight (3328ffb). This pushes a new commit; CI will
run again with these fixes. Net delay: zero (3328ffb's installer was
broken anyway, so its ISO unusable for install path).
2026-05-02 03:42:15 +01:00
veilor-org
3328ffb460 v0.5.0-alpha: TTY1 installer (omarchy/archinstall-style)
Adds:
- overlay/usr/local/sbin/veilor-installer — bash+whiptail TUI
- overlay/etc/systemd/system/getty@tty1.service.d/veilor-installer.conf
  → replaces tty1 login with installer
- ks: newt + parted + cryptsetup + lvm2 + btrfs-progs packages
- ks: systemctl set-default multi-user.target (TTY1 lands first; user
  picks "Try live — desktop" from menu to isolate graphical.target)
- ks: chmod +x veilor-installer in chroot %post

Flow:
1. Boot ISO → TTY1 → ASCII banner + menu:
   1) Install to disk    2) Try live — desktop   3) Try live — shell
   4) Reboot             5) Power off
2. Install path: collects disk/hostname/LUKS/admin pw/locale via whiptail,
   generates /run/install/veilor-generated.ks, execs anaconda --kickstart=
3. Reboots into hardened install with full init_on_alloc/free cmdline

Known limitations (v0.5.0-alpha):
- Generated ks doesn't yet copy overlay/scripts into target (anaconda
  installs base Fedora, missing veilor branding/hardening). Fix in v0.5.1.
- whiptail = ugly. v0.5.1 swaps to gum (Go TUI) for omarchy-tier UX.
- No mid-install progress bar; anaconda runs unattended in same tty.
2026-05-02 03:20:42 +01:00
veilor-org
ebf0032559 v0.2.7: fix SDDM autostart + blank-pw login
- display-manager.service symlink: livecd-creator skips alias creation
  vs Anaconda installer; without it sddm stays inactive at graphical.target
- admin user: replace `passwd -d` with throwaway pw `veilor` + chage -d 0
  (SDDM rejects blank pw by default, breaks first-login flow)

Tested in QEMU v0.2.5: confirmed sddm enabled but inactive after boot,
and blank-pw login at SDDM returns "Login Failed".
2026-05-02 03:08:08 +01:00