veilor-os/kickstart/veilor-os.ks
veilor-org 9b9443b711 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

294 lines
12 KiB
Text

#version=DEVEL
# veilor-os kickstart — Fedora 43 KDE base, hardened, minimal.
# Build with livemedia-creator inside build/Containerfile.
# ── Install source ──
# Hard-code version (not $releasever) because lorax doesn't expand
# inside kickstart `url`/`repo` directives. Updates repo critical:
# base Fedora 43 ships selinux-policy 42.12 with pcre2-10.47-built
# file_contexts.bin, which fails chroot %triggerin against host's
# libselinux (built against pcre2 10.46). 43.7 in updates is rebuilt.
url --mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-43&arch=x86_64"
# Explicit `repo --name=fedora` lets livecd-creator see base repo (it only
# reads repo.repoList, ignores url= directive). livemedia-creator + Anaconda
# honor both. No behavior change for either tool.
# Use direct baseurl (kernel.org mirror) to avoid mirrorlist 404s during
# Fedora's metadata sync windows.
repo --name=fedora --baseurl="https://download.fedoraproject.org/pub/fedora/linux/releases/43/Everything/x86_64/os/" --install
repo --name=updates --baseurl="https://download.fedoraproject.org/pub/fedora/linux/updates/43/Everything/x86_64/" --install
# Local fix-repo: build-time-only workaround for host pcre2/libselinux skew.
# Stripped from CI ks via sed in build-iso.yml. NOT shipped state.
repo --name=veilor-fix --baseurl=file:///tmp/veilor-fix-repo --install --cost=1
# ── Locale / keyboard / time (template — adjust per build) ──
keyboard --xlayouts='us'
lang en_GB.UTF-8
timezone Europe/London --utc
# ── Install mode ──
# Note: no display mode (text/graphical/cmdline) — livemedia-creator forbids.
firstboot --disable
eula --agreed
# Build-time SELinux disabled to avoid PCRE2 regex version mismatch between
# host libselinux and chroot's selinux-policy file_contexts.bin (pcre2 10.46
# vs 10.47). veilor-firstboot.service triggers `fixfiles -F onboot` and
# `setenforce 1` on first boot to re-enable enforcing mode.
selinux --permissive
# veilor-firstboot + veilor-modules-lock enabled via %post after overlay
# copy (units don't exist yet at services-config phase).
services --enabled=sshd,fail2ban,usbguard,tuned,auditd,firewalld,chronyd,sddm
# ── Network / hostname ──
network --bootproto=dhcp --device=link --activate --hostname=veilor-os
firewall --enabled --service=ssh
# ── Identity (zero-prompt; only LUKS passphrase asked at install) ──
# Note: `auth` command removed in pykickstart 3.x — defaults (sha512 shadow) apply.
rootpw --lock
user --name=admin --groups=wheel --gecos="veilor admin" --password="" --plaintext
# ── Bootloader: kernel hardening flags ──
# Note: init_on_alloc/init_on_free removed from default live cmdline —
# they zero every memory page at boot which 5x'd KVM live boot time.
# Re-enable per-install via veilor-firstboot.service for production.
bootloader --location=mbr --append="lockdown=integrity slab_nomerge randomize_kstack_offset=on vsyscall=none plymouth.enable=0"
# ── Live ISO partitioning (flat — for live rootfs build only) ──
# NOTE: This is the *live* image kickstart. Final installed system uses
# a separate installer kickstart (kickstart/install.ks, planned v0.2.1)
# that does LUKS2 + btrfs subvols on the target disk.
part / --fstype=ext4 --size=8192
# ── Packages ──
%packages --excludedocs
@^kde-desktop-environment
@kde-apps
@core
@hardware-support
@standard
# live install plumbing (required by livemedia-creator --make-iso)
# CRITICAL: livesys-scripts + anaconda-live ship the systemd units lorax expects
# at squashfs creation. Without them, EFI/BOOT not built and ISO wrap fails.
# (Upstream Fedora's fedora-live-kde.ks includes these via fedora-live-base.ks.)
livesys-scripts
anaconda-live
@anaconda-tools
kernel-modules
kernel-modules-extra
glibc-all-langpacks
dracut-live
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
isomd5sum
xorriso
# veilor-installer dependencies (TTY1 TUI installer wrapping anaconda)
newt
parted
cryptsetup
lvm2
btrfs-progs
# core hardening tools
fail2ban
fail2ban-firewalld
usbguard
usbguard-tools
audit
policycoreutils-python-utils
tuned
chrony
firewalld
plymouth
# admin essentials
git
vim-enhanced
tmux
htop
podman
skopeo
NetworkManager
NetworkManager-wifi
# fonts
fontconfig
freetype
fira-code-fonts
# remove fluff
# Note: KDE Plasma 6 hard-deps on cups/geoclue2/ModemManager/PackageKit
# transitively (plasma-print-manager, xdg-desktop-portal, NM-wwan etc),
# so package removal breaks depsolve. Daemons disabled at runtime via
# scripts/20-harden-kernel.sh instead.
-abrt*
-snapd
-kde-connect
-open-vm-tools-desktop
-mlocate
%end
# ── Post-install (nochroot): copy overlay tree into installed root ──
%post --nochroot --erroronfail
set -uo pipefail
# DEST: livecd-creator sets INSTALL_ROOT, livemedia-creator uses /mnt/sysimage.
DEST="${INSTALL_ROOT:-/mnt/sysimage}"
[[ -d $DEST ]] || { echo "[ERR] DEST=$DEST does not exist (livecd-creator? livemedia-creator?)" >&2; exit 1; }
# Try multiple source paths:
# /run/install/repo/veilor — boot ISO (--virt mode)
# /work — bind mount in CI container
# $(dirname kickstart)/.. — local --no-virt builds
SRC=""
for candidate in /run/install/repo/veilor /work /mnt/work; do
if [[ -d $candidate/overlay ]]; then
SRC=$candidate
break
fi
done
# Fallback: derive from kickstart path. Anaconda passes ks via --kickstart=<path>.
if [[ -z $SRC ]]; then
KS_PATH=$(ps -ef | grep -oP -- '--kickstart[= ]\K[^ ]+' | head -1)
if [[ -n $KS_PATH && -d $(dirname "$KS_PATH")/../overlay ]]; then
SRC=$(realpath "$(dirname "$KS_PATH")/..")
fi
fi
if [[ -z $SRC ]]; then
echo "[ERR] cannot locate veilor-os repo source — overlay/scripts not copied" >&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: $?"
ls -la "$SRC/assets" "$SRC/scripts" 2>&1 || echo "[ERR] assets/scripts missing in $SRC"
cp -a "$SRC/assets" "$DEST/usr/share/veilor-os/" || echo "[ERR] assets cp failed: $?"
cp -a "$SRC/scripts" "$DEST/usr/share/veilor-os/" || echo "[ERR] scripts cp failed: $?"
ls -la "$DEST/usr/share/veilor-os/" 2>&1 || echo "[ERR] dest dir missing post-cp"
# Force root ownership on everything we copied — `cp -a` preserves
# CI runner uid (1001), which makes sudo refuse to read /etc/sudoers.d.
chown -R 0:0 "$DEST/etc" "$DEST/usr/share/veilor-os" "$DEST/usr/local/bin" 2>&1 || echo "[WARN] chown failed"
set +x
# Persist nochroot log into installed system for diagnostics
{
echo "=== %post --nochroot trace ==="
date
echo "SRC=$SRC DEST=$DEST"
ls -la "$DEST/usr/share/veilor-os/" 2>&1
ls -la "$DEST/usr/local/bin/" 2>&1
} > "$DEST/var/log/veilor-nochroot.log" 2>&1 || true
%end
# ── Post-install (chroot): apply hardening, theme, branding ──
%post
set -uo pipefail
exec > >(tee -a /var/log/veilor-install.log) 2>&1
echo "════════════════════════════════════════════════════════"
echo " veilor-os install — %post"
echo "════════════════════════════════════════════════════════"
REPO=/usr/share/veilor-os
chmod +x $REPO/scripts/*.sh $REPO/scripts/selinux/*.sh /usr/local/bin/veilor-power /usr/local/bin/veilor-update /usr/local/bin/veilor-doctor /usr/local/bin/veilor-firstboot /usr/local/bin/veilor-installer
# Live image plumbing (matches upstream Fedora live ks). Without these the
# squashfs/EFI build fails — livesys-scripts ships systemd units lorax expects.
systemctl enable livesys.service livesys-late.service 2>/dev/null || true
systemctl enable tmp.mount 2>/dev/null || true
# /etc/machine-id reset on first boot (live image baseline)
> /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
bash $REPO/scripts/30-apply-v03-theme.sh || echo "[WARN] v03-theme apply failed"
# Force admin password set on first boot.
# livecd-creator does NOT honor `user` kickstart directive (it's a LIVE
# image, no installer step). Create admin manually in chroot %post.
# Note: SDDM rejects blank passwords by default (PAM nullok off), so we
# set throwaway pw `veilor` + chage -d 0 to force reset on first login.
if ! getent passwd admin >/dev/null; then
useradd -m -G wheel -s /bin/bash -c "veilor admin" admin
echo 'admin:veilor' | chpasswd
chage -d 0 admin
echo "[INFO] admin user created (default pw=veilor, expired)"
fi
# Symlink display-manager.service → sddm.service. graphical.target Wants=
# display-manager but the alias doesn't get auto-created when sddm package
# is installed via livecd-creator (vs Anaconda installer which handles it).
# Without this, sddm stays inactive even though enabled.
ln -sf /usr/lib/systemd/system/sddm.service /etc/systemd/system/display-manager.service
# Live ISO default target: multi-user (TTY1 = veilor-installer TUI lands first).
# User picks "Try live — desktop" from menu → systemctl isolate graphical.target.
# Real installs land on graphical.target by default (set by anaconda).
systemctl set-default multi-user.target
# Branding: GRUB menu title + plymouth `details` text theme (no graphical
# splash). Pure text-scroll boot exposes the gum installer immediately on
# tty1 instead of plymouth swallowing it.
sed -i \
-e 's|^GRUB_DISTRIBUTOR=.*|GRUB_DISTRIBUTOR="veilor-os"|' \
-e 's|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=""|' \
/etc/default/grub 2>/dev/null || true
plymouth-set-default-theme details 2>/dev/null || true
[ -f /boot/grub2/grub.cfg ] && grub2-mkconfig -o /boot/grub2/grub.cfg 2>/dev/null || true
# zram swap (no disk swap; keys never leak to platter)
dnf install -y zram-generator || true
cat > /etc/systemd/zram-generator.conf << 'EOF'
[zram0]
zram-size = min(ram, 8192)
compression-algorithm = zstd
EOF
# Enable services
# veilor-firstboot.service NOT enabled on live ISO — it prompts admin pw
# which makes no sense on a live boot. Real installs enable it in their
# generated kickstart's chroot %post (see overlay/usr/local/bin/veilor-installer).
systemctl enable veilor-modules-lock.service
systemctl enable sshd fail2ban usbguard tuned auditd firewalld chronyd
# Mask veilor-firstboot on live so even if it landed in /etc/systemd/system
# (overlay drag), it can't activate.
systemctl mask veilor-firstboot.service 2>/dev/null || true
# Default tuned profile = balanced (AC/battery udev rule will override)
tuned-adm profile veilor-balanced 2>/dev/null || true
# Lock root explicitly (kickstart --lock should already do this)
passwd -l root
# Sanity: zero references to onyx / personal IPs in installed system
if grep -rqi 'onyx\|192\.168\.0\.\|fedora\.local' /etc/veilor* /etc/tuned/profiles/veilor-* 2>/dev/null; then
echo "[ERR] brand leak detected in /etc — investigate"
fi
echo "════════════════════════════════════════════════════════"
echo " veilor-os install complete"
echo "════════════════════════════════════════════════════════"
%end