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.
This commit is contained in:
veilor-org 2026-05-05 15:36:24 +01:00 committed by s8n
parent 0b568b016b
commit 89949dc8f2
12 changed files with 385 additions and 4 deletions

View file

@ -58,7 +58,7 @@ user --name=admin --groups=wheel --gecos="veilor admin" --password="" --plaintex
# framebuffer — symptom: black screen with blinking cursor for ~30s
# while the menu IS in fact rendered, just not painted. virtio-vga in
# QEMU doesn't trigger this so it never reproed in VM.
bootloader --location=mbr --append="lockdown=integrity slab_nomerge randomize_kstack_offset=on vsyscall=none plymouth.enable=0 fbcon=nodefer"
bootloader --location=mbr --append="lockdown=integrity module.sig_enforce=1 slab_nomerge randomize_kstack_offset=on vsyscall=none plymouth.enable=0 fbcon=nodefer i915.modeset=1 amdgpu.modeset=1 nvidia-drm.modeset=1 rd.vconsole.keymap=us"
# ── Live ISO partitioning (flat — for live rootfs build only) ──
# NOTE: This is the *live* image kickstart. Final installed system uses

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- veilor-os: trusted zone with tailscale0 pre-bound.
Default zone stays drop (per 10-harden-base.sh). Tailscale's
interface is added here so `tailscale up` traffic isn't dropped.
Without this entry the firewalld drop zone blocks the tailnet
traffic and the user sees: "tailscale up succeeded, but I can't
reach hs.s8n.ru". (Agent 9, 2026-05-05 wave.) -->
<zone target="ACCEPT">
<short>Trusted</short>
<description>All network connections are accepted. veilor-os pre-binds tailscale0 here so the mesh layer-1 (Tailscale via Headscale) works out-of-box without manual firewalld zone juggling.</description>
<interface name="tailscale0"/>
</zone>

View file

@ -0,0 +1,60 @@
# veilor-os — Breeze window decoration override
# Tighter borders, solid black title bar, minimal buttons, smallest border.
# Merged into /etc/xdg/breezerc (system default) by 30-apply-v03-theme.sh.
[Common]
# Tighter outline; subtle separator only when active.
OutlineCloseButton=false
ShadowSize=ShadowSmall
ShadowStrength=128
ShadowColor=0,0,0
[Windeco]
# Border thickness: smallest available (= "None" leaves only resize edge,
# "NoSides" keeps top/bottom only). We pick "None" for the tightest look,
# matching the black-on-black aesthetic.
BorderSize=None
ButtonSize=ButtonSmall
CloseButton=true
DrawBackgroundGradient=false
DrawBorderOnMaximizedWindows=false
DrawSizeGrip=false
DrawTitleBarSeparator=false
ExceptionType=0
HideTitleBar=false
OpaqueTitleBar=true
TitleAlignment=AlignCenter
UseBackgroundGradient=false
UseTitleBarColor=true
# Buttons: minimal — close / max / min only, no shade/help/keep-above.
ButtonsOnLeft=M
ButtonsOnRight=IAX
[Style]
# Disable per-app blur, transparency, and gradient effects.
MenuOpacity=100
WindowDragMode=1
ScrollBarAddLineButtons=0
ScrollBarSubLineButtons=0
SidePanelDrawFrame=false
SliderDrawTickMarks=false
TabBarDrawCenteredTabs=true
ToolBarDrawItemSeparator=false
DockWidgetDrawFrame=false
ProgressBarAnimated=false
AnimationsEnabled=false
StackedWidgetDrawFrame=false
# ── Active / inactive title bar colors (override Breeze defaults) ──
# kdeglobals [WM] section is the canonical source; these mirror it here
# so apps that only read breezerc see consistent values.
[Windeco][Active]
TitleBarColor=0,0,0
TitleBarTextColor=216,216,216
TitleBarBorderColor=104,107,111
[Windeco][Inactive]
TitleBarColor=15,17,18
TitleBarTextColor=161,169,177
TitleBarBorderColor=42,46,50

View file

@ -0,0 +1,29 @@
[General]
ColorScheme=veilor-black
Name=veilor black
AccentColor=104,107,111
LastUsedCustomAccentColor=104,107,111
font=Fira Code,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
fixed=Fira Code,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
menuFont=Fira Code,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
smallestReadableFont=Fira Code,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
toolBarFont=Fira Code,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
[Icons]
Theme=breeze-dark
[KDE]
LookAndFeelPackage=org.kde.breezedark.desktop
SingleClick=false
contrast=4
widgetStyle=Breeze
[Mouse]
cursorTheme=Breeze_Light
cursorSize=24
[KDecoration]
theme=Breeze
ButtonsOnLeft=
ButtonsOnRight=IAX
BorderSize=None

View file

@ -0,0 +1,10 @@
# veilor-os Konsole default — branded profile pre-selected so first
# Konsole launch on a fresh user account opens the veilor profile,
# not Fedora's default white-bg Breeze.
[Desktop Entry]
DefaultProfile=Veilor.profile
[KonsoleWindow]
ShowMenuBarByDefault=false
RememberWindowSize=true

View file

@ -0,0 +1,29 @@
# veilor-os Plasma 6 kwin defaults — seeded into /etc/skel so first-login
# users inherit deliberate windowing behaviour rather than default Breeze
# animations. Per-user; user can override post-login.
[Compositing]
# OpenGL backend = standard for hardware accel; AnimationSpeed=0 = no
# slow window animations on every focus change.
Backend=OpenGL
AnimationSpeed=0
HiddenPreviews=5
LatencyPolicy=Low
WindowsBlockCompositing=true
[Plugins]
# Disable visual fluff that isn't security-relevant + costs perf.
blurEnabled=false
contrastEnabled=false
slideEnabled=false
slidingpopupsEnabled=false
fadeEnabled=false
zoomEnabled=true
[Windows]
FocusPolicy=ClickToFocus
RollOverDesktops=false
TitlebarDoubleClickCommand=Maximize
[Wayland]
InputMethod=

View file

@ -0,0 +1,104 @@
[General]
Anchor=0.5,0.5
Blur=false
ColorRandomization=false
Description=Veilor
FillStyle=Tile
Opacity=1
Wallpaper=
WallpaperFlipType=NoFlip
WallpaperOpacity=1
[Background]
Color=0,0,0
[BackgroundFaint]
Color=0,0,0
[BackgroundIntense]
Color=15,17,18
[Foreground]
Color=216,216,216
[ForegroundFaint]
Color=161,169,177
[ForegroundIntense]
Color=236,236,236
# ── Standard ANSI palette (muted, desaturated) ──
# Veilor aesthetic: no neon. Reds tone-shifted toward bordeaux, greens
# toward sage, blues toward slate. Greys lifted to remain readable.
[Color0]
Color=27,27,27
[Color0Faint]
Color=20,20,20
[Color0Intense]
Color=58,58,58
[Color1]
Color=176,55,69
[Color1Faint]
Color=130,40,52
[Color1Intense]
Color=205,87,99
[Color2]
Color=102,138,90
[Color2Faint]
Color=78,107,68
[Color2Intense]
Color=141,176,128
[Color3]
Color=185,158,98
[Color3Faint]
Color=140,118,72
[Color3Intense]
Color=216,193,134
[Color4]
Color=92,116,143
[Color4Faint]
Color=68,87,107
[Color4Intense]
Color=131,154,182
[Color5]
Color=141,113,150
[Color5Faint]
Color=104,84,112
[Color5Intense]
Color=176,148,186
[Color6]
Color=99,144,148
[Color6Faint]
Color=72,107,110
[Color6Intense]
Color=139,180,184
[Color7]
Color=200,200,200
[Color7Faint]
Color=161,169,177
[Color7Intense]
Color=236,236,236

View file

@ -0,0 +1,55 @@
[General]
Name=Veilor
Parent=FALLBACK/
Command=/bin/bash
Directory=
Icon=utilities-terminal
LocalTabTitleFormat=%w
RemoteTabTitleFormat=(%u) %h
ShowTerminalSizeHint=false
StartInCurrentSessionDir=true
TerminalCenter=false
TerminalMargin=4
[Appearance]
ColorScheme=Veilor
Font=Fira Code,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
LineSpacing=1
UseFontLineCharacters=true
[Cursor Options]
CursorShape=0
UseCustomCursorColor=true
CustomCursorColor=104,107,111
CustomCursorTextColor=216,216,216
[Scrolling]
HistoryMode=2
HistorySize=10000
ScrollBarPosition=2
HighlightScrolledLines=false
[Terminal Features]
BellMode=3
BlinkingCursorEnabled=false
BlinkingTextEnabled=false
FlowControlEnabled=false
UrlHintsModifiers=67108864
ReverseUrlHints=false
VerticalLine=false
[Interaction Options]
AutoCopySelectedText=false
CopyTextAsHTML=false
TrimLeadingSpacesInSelectedText=false
TrimTrailingSpacesInSelectedText=true
UnderlineFilesEnabled=true
UnderlineLinksEnabled=true
OpenLinksByDirectClickEnabled=false
WordCharacters=:@-./_~?&=%+#
[Encoding Options]
DefaultEncoding=UTF-8
[Keyboard]
KeyBindings=default

View file

@ -18,4 +18,9 @@ TTYReset=yes
TTYVHangup=yes
[Install]
WantedBy=multi-user.target
# Real installs default to graphical.target. Without this entry the unit
# never runs on installed systems — admin pw stays at install-time value
# + chage -d 0 expired, SDDM PAM bounces user to a chauthtok screen
# (recoverable but ugly). Live ISO + multi-user.target installs both
# resolve via this list. (Agent 2, 2026-05-05 wave.)
WantedBy=graphical.target multi-user.target

View file

@ -0,0 +1,43 @@
# veilor-os USBGuard baseline rules
#
# Default policy is `block` (set in usbguard-daemon.conf via the
# overlay). Without any allow rule, every USB device — including the
# user's keyboard — is blocked at boot. That includes the desktop
# user with a USB keyboard at SDDM.
#
# This file allows HID-class interfaces (keyboard, mouse, touchpad,
# fingerprint reader, NFC, gamepad) without pinning to specific
# vendor:product/serial/hash. id-based rules survive dock replug and
# vendor-bump kernel changes, where hash+parent-hash rules don't —
# verified pain on onyx (memory: feedback_usbguard_dock.md). Same fix.
#
# After first login, the user runs:
# ujust veilor-usbguard-enroll
# (or `usbguard generate-policy --with-hash=false > rules.conf`)
# to add their own keyboard's id-rule and tighten the policy further.
#
# References:
# - usbguard-rules.conf(5)
# - https://usbguard.github.io/documentation/rule-language.html
# - veilor-os agent 9 audit, 2026-05-05
# HID class — keyboards, mice, pointers, gamepads, fingerprint, NFC.
# Interface descriptor 03:NN:NN where 03=HID. We accept any HID
# subclass + protocol so the rule is robust to future HID variants.
allow with-interface match-all { 03:*:* }
# Mass-storage prompt: ask the user before mounting a new flash drive.
# Reject blanket-allow (would silently allow USB Rubber Ducky).
# Accept only after user confirms via the gnome/plasma USB dialog.
# (USBGuard has no native "ask" verb; we leave mass-storage devices
# implicit-block here, the user runs `usbguard allow-device <id>`
# from a Plasma applet OR the firstboot wizard documents this flow.)
# Block known-bad. USB Killer signature shows up as a generic-HID
# composite descriptor + power draw out of spec. We can't reliably
# detect that from descriptors alone — relying on default-block
# semantics for now.
# DO NOT pin to specific id=, serial=, hash=, or parent-hash= here.
# That's the user's job post-firstboot for their actual hardware.
# Pre-shipped pinned rules break on every dock replug + kernel bump.

View file

@ -427,7 +427,7 @@ __SSHKEY_DIRECTIVE__
# 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.
bootloader --append="lockdown=integrity slab_nomerge init_on_alloc=1 init_on_free=1 randomize_kstack_offset=on vsyscall=none fbcon=nodefer"
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"
# Disk: zero, LUKS2 (argon2id), btrfs subvolumes (no LVM intermediary).
# Native btrfs-on-LUKS matches Fedora KDE Spin defaults; LVM+btrfs combo
@ -927,7 +927,33 @@ KSEOF
return 0
}
_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
}
run_install() {
# Capture logs to host on every exit path (success, failure, ^C).
trap _dump_logs_to_host EXIT
# Anaconda env setup (see comments below).
export LANG="${SEL_LOCALE:-en_GB.UTF-8}"
export LC_ALL="$LANG"

View file

@ -208,12 +208,20 @@ echo " Seed : ${SEED_ISO:-<none>}"
# We've lost logs three times in a row to anaconda failures + tmpfs
# reboots. Wiring this up so future failures auto-capture.
ANACONDA_LOG="$TEST_DIR/anaconda-vm-$(date +%Y%m%d-%H%M%S).log"
ANACONDA_LOG_DIR="$TEST_DIR/test-runs/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$ANACONDA_LOG_DIR"
ANACONDA_LOG_ARGS=(
# Belt: virtio-serial (anaconda's setupVirtio rsyslog forward, fragile —
# depends on rsyslog being installed in the live ISO).
-chardev "file,id=anaclog,path=$ANACONDA_LOG"
-device virtio-serial-pci,id=vs1
-device "virtserialport,chardev=anaclog,bus=vs1.0,name=org.fedoraproject.anaconda.log.0"
# Braces: virtio-9p host directory share. veilor-installer mounts this
# at /mnt/hostlogs and rsyncs /tmp/*.log there post-anaconda.
-virtfs "local,path=$ANACONDA_LOG_DIR,mount_tag=hostlogs,security_model=mapped-xattr,id=hostlogs"
)
echo " AnaLog: $ANACONDA_LOG"
echo " AnaLog : $ANACONDA_LOG"
echo " HostFS : $ANACONDA_LOG_DIR (9p tag: hostlogs)"
echo " Mode : ${SECBOOT:+secboot}${SECBOOT:-stock UEFI}"
echo " Inject: ${HOST_PUBKEY:+yes}${HOST_PUBKEY:-no (no host pubkey)}"