feat(installer): persist install logs to USB by default
- new helper overlay/usr/share/veilor-os/scripts/persist-install-logs.sh detects boot USB (BOOT=/findfs, /run/install/repo, /sys/block removable), copies /tmp/anaconda.log + program/storage/packaging/dnf/syslog/X + journalctl -b + dmesg + lsblk/blkid/mount + /proc/cmdline into /veilor-install-logs/<UTC-ts>/ on the stick; mirrors backup into /mnt/sysroot/var/log/veilor-install-logs/ so logs survive even on RO USB or detect failure - toggle: kernel cmdline veilor.install_logs=on|off (default ON until v1.0 final); never fails install on log persistence error - kickstart/install-ostreecontainer-installer.ks: add %post --nochroot block calling helper with toggle-aware inline fallback if helper missing - .github/workflows/build-installer-iso.yml: switch bib config from [customizations.user] to [customizations.installer.kickstart] so our new %post --nochroot actually lands in the produced ISO; admin user now created by ks user directive (locked + chage 0); ostreecontainer line stripped (bib auto-appends it); kernel-cmdline-default limitation documented (osbuild/bootc-image-builder#899) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
865c9507af
commit
c272050890
3 changed files with 328 additions and 8 deletions
44
.github/workflows/build-installer-iso.yml
vendored
44
.github/workflows/build-installer-iso.yml
vendored
|
|
@ -69,14 +69,42 @@ jobs:
|
|||
# it locally to compose the installer ISO.
|
||||
podman pull ghcr.io/veilor-org/veilor-os:43 || \
|
||||
podman pull git.s8n.ru/veilor-org/veilor-os:43
|
||||
# Generate a minimal config.toml for bootc-image-builder that
|
||||
# tells Anaconda to ask for LUKS pw + admin pw.
|
||||
cat > /tmp/bib-config.toml <<'TOML'
|
||||
[[customizations.user]]
|
||||
name = "admin"
|
||||
password = ""
|
||||
groups = ["wheel"]
|
||||
TOML
|
||||
# Generate config.toml for bootc-image-builder.
|
||||
#
|
||||
# We use [customizations.installer.kickstart] (NOT
|
||||
# [customizations.user]) because we need our own %post --nochroot
|
||||
# block to persist install logs back to the boot USB. Per upstream
|
||||
# docs, [customizations.user] and [customizations.installer.kickstart]
|
||||
# are mutually exclusive (see osbuild/bootc-image-builder#528) — so
|
||||
# the admin user is now created by a kickstart `user` directive
|
||||
# below, locked + chage 0 so first SDDM login forces a real pw.
|
||||
#
|
||||
# bootc-image-builder auto-appends `ostreecontainer ...` to the
|
||||
# contents we provide; we MUST NOT include that line ourselves
|
||||
# (we strip it from the source kickstart with sed).
|
||||
#
|
||||
# NOTE on kernel cmdline default: ideally we'd set
|
||||
# `veilor.install_logs=on` as an installer-kernel default, but
|
||||
# `[customizations.kernel].append` targets the INSTALLED system's
|
||||
# kargs.d, not the live ISO's grub.cfg (osbuild/bootc-image-builder
|
||||
# #899 still open). The persist-install-logs.sh helper defaults to
|
||||
# ON when the toggle is absent, so the desired default is achieved
|
||||
# without needing installer-cmdline injection. Operators flip to
|
||||
# off at boot via GRUB edit: append `veilor.install_logs=off`.
|
||||
KS_SRC="kickstart/install-ostreecontainer-installer.ks"
|
||||
KS_FILTERED="$(grep -v '^ostreecontainer' "$KS_SRC")"
|
||||
# Insert a locked admin user directive under the rootpw block —
|
||||
# Anaconda's interactive Users spoke is unavailable in unattended
|
||||
# bib mode, so we pre-create admin and let chage -d 0 force a pw
|
||||
# change at first login.
|
||||
USER_LINE='user --name=admin --groups=wheel --plaintext --password="" --lock'
|
||||
KS_FILTERED="$(printf '%s\n' "$KS_FILTERED" | awk -v ul="$USER_LINE" '/^rootpw --lock$/ { print; print ul; next } { print }')"
|
||||
{
|
||||
echo '[customizations.installer.kickstart]'
|
||||
echo 'contents = """'
|
||||
printf '%s\n' "$KS_FILTERED"
|
||||
echo '"""'
|
||||
} > /tmp/bib-config.toml
|
||||
podman run --rm \
|
||||
--privileged \
|
||||
--pull=newer \
|
||||
|
|
|
|||
|
|
@ -78,3 +78,46 @@ set -uo pipefail
|
|||
echo veilor-install > /etc/hostname
|
||||
chage -d 0 admin 2>/dev/null || true
|
||||
%end
|
||||
|
||||
# ── %post --nochroot — persist install logs to USB (toggle: veilor.install_logs=on|off) ──
|
||||
#
|
||||
# Runs OUTSIDE the target chroot so /tmp/anaconda.log etc. on the live
|
||||
# ramdisk are accessible alongside /mnt/sysroot. Calls the helper that
|
||||
# ships in the veilor-os OCI image overlay; if the helper is missing
|
||||
# (corrupt overlay, stripped image, etc.) we fall back to a minimal
|
||||
# inline copy. NEVER fail the install over log persistence.
|
||||
#
|
||||
# Default: ON until v1.0 final. Disable per-boot:
|
||||
# edit GRUB / press 'e', append: veilor.install_logs=off
|
||||
%post --nochroot --erroronfail=no
|
||||
set -uo pipefail
|
||||
|
||||
VEILOR_HELPER="/mnt/sysroot/usr/share/veilor-os/scripts/persist-install-logs.sh"
|
||||
[ -x "$VEILOR_HELPER" ] || VEILOR_HELPER="/mnt/sysimage/usr/share/veilor-os/scripts/persist-install-logs.sh"
|
||||
|
||||
if [ -x "$VEILOR_HELPER" ]; then
|
||||
"$VEILOR_HELPER" || true
|
||||
else
|
||||
# Inline fallback — toggle-aware, backup-only (no USB write attempt).
|
||||
TS="$(date -u +%Y-%m-%dT%H-%M-%SZ)"
|
||||
SR=/mnt/sysroot; [ -d "$SR" ] || SR=/mnt/sysimage
|
||||
DST="${SR}/var/log/veilor-install-logs/${TS}"
|
||||
TOGGLE=on
|
||||
for tok in $(cat /proc/cmdline 2>/dev/null); do
|
||||
case "$tok" in veilor.install_logs=off|veilor.install_logs=0|veilor.install_logs=false|veilor.install_logs=no) TOGGLE=off ;; esac
|
||||
done
|
||||
if [ "$TOGGLE" = "on" ]; then
|
||||
mkdir -p "$DST" 2>/dev/null || true
|
||||
for f in /tmp/anaconda.log /tmp/program.log /tmp/storage.log \
|
||||
/tmp/packaging.log /tmp/syslog /tmp/dnf.log \
|
||||
/tmp/ks.cfg /run/veilor-installer.log; do
|
||||
[ -e "$f" ] && cp -a "$f" "$DST/" 2>/dev/null || true
|
||||
done
|
||||
dmesg > "$DST/dmesg.txt" 2>/dev/null || true
|
||||
journalctl --no-pager -b > "$DST/journalctl-b.txt" 2>/dev/null || true
|
||||
echo "[veilor] inline fallback used — helper missing at $VEILOR_HELPER" \
|
||||
> "$DST/manifest.txt"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
%end
|
||||
|
|
|
|||
249
overlay/usr/share/veilor-os/scripts/persist-install-logs.sh
Executable file
249
overlay/usr/share/veilor-os/scripts/persist-install-logs.sh
Executable file
|
|
@ -0,0 +1,249 @@
|
|||
#!/usr/bin/env bash
|
||||
# persist-install-logs.sh — copy Anaconda install logs back to the boot USB
|
||||
#
|
||||
# Runs from %post --nochroot near the end of the Anaconda install. At that
|
||||
# point /tmp/*.log on the live ramdisk has the full evidence trail
|
||||
# (anaconda.log, program.log, storage.log, packaging.log, dnf.log,
|
||||
# syslog, etc.) — and is about to be lost forever when the user reboots
|
||||
# into the freshly installed system.
|
||||
#
|
||||
# We:
|
||||
# 1. Honour the kernel cmdline toggle veilor.install_logs=on|off
|
||||
# (default: on, until v1.0 final flips the default to off).
|
||||
# 2. Detect the boot USB device (BOOT=, BOOT_IMAGE=, /run/install/repo,
|
||||
# then /sys/block/*/removable=1 fallback).
|
||||
# 3. Try to remount it rw and copy logs into
|
||||
# /veilor-install-logs/<UTC-ISO8601>/ on the USB.
|
||||
# 4. ALSO copy a backup into /mnt/sysroot/var/log/veilor-install-logs/
|
||||
# so logs survive in the installed system even if the USB is RO,
|
||||
# missing, or write-failed.
|
||||
# 5. NEVER fail the install over this. Every error is logged + ignored.
|
||||
#
|
||||
# Disable at boot: edit GRUB / press 'e', append: veilor.install_logs=off
|
||||
# Disable in kickstart: comment out the call in install-ostreecontainer-installer.ks
|
||||
#
|
||||
# Author: veilor-os agent A2 (2026-05-08)
|
||||
# License: AGPLv3 — same as veilor-os
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# ── trace ── everything to stderr; Anaconda captures stderr to program.log
|
||||
log() { printf '[persist-install-logs] %s\n' "$*" >&2; }
|
||||
trap 'log "WARN: line $LINENO failed (rc=$?) — continuing"' ERR
|
||||
|
||||
TS="$(date -u +%Y-%m-%dT%H-%M-%SZ)"
|
||||
SYSROOT="${VEILOR_SYSROOT:-/mnt/sysroot}"
|
||||
[ -d "$SYSROOT" ] || SYSROOT="/mnt/sysimage" # legacy Anaconda path
|
||||
|
||||
BACKUP_DIR="${SYSROOT}/var/log/veilor-install-logs/${TS}"
|
||||
mkdir -p "$BACKUP_DIR" 2>/dev/null || true
|
||||
|
||||
# ── 1. toggle ──────────────────────────────────────────────────────────────
|
||||
parse_toggle() {
|
||||
# default ON until v1.0 final
|
||||
local cmdline val
|
||||
cmdline="$(cat /proc/cmdline 2>/dev/null || true)"
|
||||
for tok in $cmdline; do
|
||||
case "$tok" in
|
||||
veilor.install_logs=*) val="${tok#veilor.install_logs=}" ;;
|
||||
esac
|
||||
done
|
||||
val="${val:-on}"
|
||||
case "$val" in
|
||||
on|true|1|yes) echo on ;;
|
||||
off|false|0|no) echo off ;;
|
||||
*) log "unknown veilor.install_logs=$val — defaulting to on"; echo on ;;
|
||||
esac
|
||||
}
|
||||
|
||||
TOGGLE="$(parse_toggle)"
|
||||
if [ "$TOGGLE" = "off" ]; then
|
||||
log "veilor.install_logs=off — log persistence skipped"
|
||||
exit 0
|
||||
fi
|
||||
log "veilor.install_logs=on — persisting install logs (ts=${TS})"
|
||||
|
||||
# ── 2. collect log payload into staging dir ───────────────────────────────
|
||||
STAGE="$(mktemp -d -t veilor-install-logs.XXXXXX 2>/dev/null || echo /tmp/veilor-install-logs-stage)"
|
||||
mkdir -p "$STAGE"
|
||||
|
||||
collect() {
|
||||
local src="$1" dst="$2"
|
||||
if [ -e "$src" ]; then
|
||||
cp -a "$src" "$STAGE/$dst" 2>/dev/null || \
|
||||
log "could not copy $src"
|
||||
fi
|
||||
}
|
||||
|
||||
# Anaconda /tmp logs (live env)
|
||||
for f in anaconda.log program.log storage.log packaging.log syslog \
|
||||
dnf.log dnf.librepo.log dnf.rpm.log dnf.hawkey.log \
|
||||
X.log ifcfg.log lvm.log yum.log; do
|
||||
collect "/tmp/$f" "$f"
|
||||
done
|
||||
# Kickstart-related
|
||||
collect /tmp/ks.cfg ks.cfg
|
||||
collect /tmp/ks-script.log ks-script.log
|
||||
collect /tmp/kickstart_pre.log kickstart_pre.log
|
||||
collect /tmp/kickstart_post.log kickstart_post.log
|
||||
# veilor TUI installer log (live ISO writes this to /run)
|
||||
collect /run/veilor-installer.log veilor-installer.log
|
||||
|
||||
# Runtime evidence
|
||||
{
|
||||
echo "── /proc/cmdline ──"
|
||||
cat /proc/cmdline 2>/dev/null
|
||||
echo
|
||||
echo "── /proc/version ──"
|
||||
cat /proc/version 2>/dev/null
|
||||
echo
|
||||
echo "── /etc/os-release ──"
|
||||
cat /etc/os-release 2>/dev/null
|
||||
echo
|
||||
echo "── timestamp (UTC) ──"
|
||||
date -u
|
||||
} > "$STAGE/system-info.txt" 2>/dev/null || true
|
||||
|
||||
dmesg --ctime > "$STAGE/dmesg.txt" 2>/dev/null || \
|
||||
dmesg > "$STAGE/dmesg.txt" 2>/dev/null || true
|
||||
|
||||
journalctl --no-pager -b > "$STAGE/journalctl-b.txt" 2>/dev/null || true
|
||||
|
||||
lsblk -fJ > "$STAGE/lsblk.json" 2>/dev/null || true
|
||||
blkid > "$STAGE/blkid.txt" 2>/dev/null || true
|
||||
mount > "$STAGE/mount.txt" 2>/dev/null || true
|
||||
|
||||
# Manifest
|
||||
{
|
||||
echo "veilor-os install log bundle"
|
||||
echo "timestamp_utc=${TS}"
|
||||
echo "host_uname=$(uname -a 2>/dev/null)"
|
||||
echo "files:"
|
||||
(cd "$STAGE" && ls -la 2>/dev/null)
|
||||
} > "$STAGE/manifest.txt"
|
||||
|
||||
# Backup copy regardless of USB success
|
||||
cp -a "$STAGE/." "$BACKUP_DIR/" 2>/dev/null && \
|
||||
log "backup written to ${BACKUP_DIR}" || \
|
||||
log "WARN: could not write backup to ${BACKUP_DIR}"
|
||||
|
||||
# ── 3. detect boot USB ─────────────────────────────────────────────────────
|
||||
detect_usb_dev() {
|
||||
local cmdline tok val dev
|
||||
cmdline="$(cat /proc/cmdline 2>/dev/null || true)"
|
||||
|
||||
# 3a) BOOT=LABEL=... or BOOT=UUID=... explicit
|
||||
for tok in $cmdline; do
|
||||
case "$tok" in
|
||||
BOOT=*)
|
||||
val="${tok#BOOT=}"
|
||||
dev="$(findfs "$val" 2>/dev/null || true)"
|
||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 3b) Anaconda mounts the install medium at /run/install/repo
|
||||
if mountpoint -q /run/install/repo 2>/dev/null; then
|
||||
dev="$(findmnt -no SOURCE /run/install/repo 2>/dev/null || true)"
|
||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
||||
fi
|
||||
if mountpoint -q /run/install/sources/mount-0000-iso 2>/dev/null; then
|
||||
dev="$(findmnt -no SOURCE /run/install/sources/mount-0000-iso 2>/dev/null || true)"
|
||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
||||
fi
|
||||
|
||||
# 3c) BOOT_IMAGE=(hdX,Y)/path — extract base device from kernel arg via
|
||||
# /run/initramfs/livedev (dracut-live writes this)
|
||||
if [ -r /run/initramfs/livedev ]; then
|
||||
dev="$(cat /run/initramfs/livedev 2>/dev/null)"
|
||||
[ -n "$dev" ] && [ -b "$dev" ] && { echo "$dev"; return 0; }
|
||||
fi
|
||||
|
||||
# 3d) /sys/block walk for first removable device with mounted partition
|
||||
local d part
|
||||
for d in /sys/block/*/removable; do
|
||||
[ "$(cat "$d" 2>/dev/null)" = "1" ] || continue
|
||||
local base
|
||||
base="$(basename "$(dirname "$d")")"
|
||||
for part in /sys/block/"$base"/"$base"*; do
|
||||
[ -d "$part" ] || continue
|
||||
local pname="/dev/$(basename "$part")"
|
||||
[ -b "$pname" ] && { echo "$pname"; return 0; }
|
||||
done
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
USB_DEV="$(detect_usb_dev || true)"
|
||||
if [ -z "${USB_DEV:-}" ]; then
|
||||
log "could not detect boot USB device — backup-only mode (see ${BACKUP_DIR})"
|
||||
exit 0
|
||||
fi
|
||||
log "detected boot USB partition: ${USB_DEV}"
|
||||
|
||||
# Walk to parent disk if we got a partition — we want the data partition not
|
||||
# the ESP. For an Anaconda-spun installer USB the ISO is hybrid: the ISO9660
|
||||
# partition holds the squashfs (RO), and there's usually an ESP. Strategy:
|
||||
# try mounting the partition we got first; if it's RO we accept that and
|
||||
# attempt remount; if remount fails we give up gracefully.
|
||||
|
||||
# ── 4. mount USB and write logs ────────────────────────────────────────────
|
||||
MOUNT_POINT="/run/veilor-install-logs-mount"
|
||||
mkdir -p "$MOUNT_POINT"
|
||||
|
||||
mount_rw() {
|
||||
local dev="$1"
|
||||
if mount "$dev" "$MOUNT_POINT" 2>/dev/null; then
|
||||
# check if rw
|
||||
if touch "$MOUNT_POINT/.veilor-write-test" 2>/dev/null; then
|
||||
rm -f "$MOUNT_POINT/.veilor-write-test"
|
||||
return 0
|
||||
fi
|
||||
# try remount rw
|
||||
if mount -o remount,rw "$MOUNT_POINT" 2>/dev/null && \
|
||||
touch "$MOUNT_POINT/.veilor-write-test" 2>/dev/null; then
|
||||
rm -f "$MOUNT_POINT/.veilor-write-test"
|
||||
return 0
|
||||
fi
|
||||
log "USB mounted RO and remount-rw failed: ${dev}"
|
||||
umount "$MOUNT_POINT" 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
if mount_rw "$USB_DEV"; then
|
||||
DEST="${MOUNT_POINT}/veilor-install-logs/${TS}"
|
||||
if mkdir -p "$DEST" 2>/dev/null && cp -a "$STAGE/." "$DEST/" 2>/dev/null; then
|
||||
sync
|
||||
log "logs persisted to USB: ${USB_DEV}:/veilor-install-logs/${TS}"
|
||||
else
|
||||
log "WARN: USB mounted rw but write failed — keeping backup at ${BACKUP_DIR}"
|
||||
fi
|
||||
umount "$MOUNT_POINT" 2>/dev/null || true
|
||||
else
|
||||
# Try the parent disk's other partitions (some installer USBs have a
|
||||
# writable data partition separate from the ISO9660 squashfs partition).
|
||||
parent="$(echo "$USB_DEV" | sed -E 's/[0-9]+$//; s/p$//')"
|
||||
if [ -b "$parent" ]; then
|
||||
for cand in "$parent"*[0-9]; do
|
||||
[ -b "$cand" ] || continue
|
||||
[ "$cand" = "$USB_DEV" ] && continue
|
||||
if mount_rw "$cand"; then
|
||||
DEST="${MOUNT_POINT}/veilor-install-logs/${TS}"
|
||||
if mkdir -p "$DEST" 2>/dev/null && cp -a "$STAGE/." "$DEST/" 2>/dev/null; then
|
||||
sync
|
||||
log "logs persisted to USB partition: ${cand}:/veilor-install-logs/${TS}"
|
||||
fi
|
||||
umount "$MOUNT_POINT" 2>/dev/null || true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
log "USB write path unavailable — relying on backup at ${BACKUP_DIR}"
|
||||
fi
|
||||
|
||||
rmdir "$MOUNT_POINT" 2>/dev/null || true
|
||||
rm -rf "$STAGE" 2>/dev/null || true
|
||||
exit 0
|
||||
Loading…
Reference in a new issue