live-image: real /init + verbose boot for first-boot diagnosis
CI / Lint + build + test (push) Successful in 1m23s
Release / release (push) Successful in 4m49s

Host boots past kernel init and then stalls silently. ACPI DSDT error
about TXHC.RHUB.SS01 is benign noise (Tiger Lake firmware bug) — the
actual problem is that nothing between kernel handoff and (maybe)
systemd is visible on the console.

Two changes:

1. Replace the /init → sbin/init symlink with a real shell script
   (live-image/mkosi.extra/init) that mounts /proc /sys /dev /dev/pts
   /dev/shm /run before execing systemd. Systemd has fallback mount
   code for these, but when it fails the failure is silent. Doing it
   explicitly in /init keeps failures visible and avoids the fragile
   symlink-resolution trick.

2. Drop 'quiet' from the kernel cmdline and add loglevel=7 plus
   systemd.log_target=kmsg + journald.forward_to_console=1 so every
   early-boot message reaches both tty0 and ttyS0. Will be dialed
   back once boot is stable.

Also: .gitattributes pins LF on live-image/, .gitea/, Makefile, and
*.sh so Windows checkouts don't break shell scripts and Makefile
recipes with CRLF. /init also gets chmod 0755 in repack-initrd as a
belt-and-braces against mode loss on non-Linux checkouts.
This commit is contained in:
2026-04-18 14:31:40 -04:00
parent 43ea845ac0
commit a88e24bef4
4 changed files with 53 additions and 6 deletions
+7
View File
@@ -0,0 +1,7 @@
# live-image/ runs on Linux regardless of dev host. Windows CRLF
# auto-conversion breaks shell script interpretation and Makefile
# recipes, so pin LF for the whole tree.
live-image/** text eol=lf
.gitea/** text eol=lf
*.sh text eol=lf
Makefile text eol=lf
+10 -3
View File
@@ -35,13 +35,20 @@ func BuildScript(p IPXEParams) string {
if p.TLSCertFPR != "" { if p.TLSCertFPR != "" {
cmdline = append(cmdline, fmt.Sprintf("vetting.cert_fpr=%s", p.TLSCertFPR)) cmdline = append(cmdline, fmt.Sprintf("vetting.cert_fpr=%s", p.TLSCertFPR))
} }
// Reduce kernel log noise during the test run; keep loglevel high enough // Verbose kernel + systemd logging on both the video console and the
// for boot failures to still show up on the console. // serial port so first-boot failures on unfamiliar hardware aren't
// invisible. Drop `quiet` entirely — once boot is stable we can
// re-add it. systemd.log_target=kmsg makes early systemd go through
// the same dmesg buffer as the kernel, so nothing is lost before
// journald comes up.
cmdline = append(cmdline, cmdline = append(cmdline,
"console=tty0", "console=tty0",
"console=ttyS0,115200n8", "console=ttyS0,115200n8",
"ip=dhcp", "ip=dhcp",
"quiet", "loglevel=7",
"systemd.log_level=info",
"systemd.log_target=kmsg",
"systemd.journald.forward_to_console=1",
) )
var b strings.Builder var b strings.Builder
+5 -3
View File
@@ -32,9 +32,11 @@ repack-initrd:
@# copies (release tarball) don't have to follow links. @# copies (release tarball) don't have to follow links.
@cp -fL build/vmlinuz build/vmlinuz.real && mv build/vmlinuz.real build/vmlinuz @cp -fL build/vmlinuz build/vmlinuz.real && mv build/vmlinuz.real build/vmlinuz
@rm -f build/initrd.img build/initrd.img.old build/vmlinuz.old @rm -f build/initrd.img build/initrd.img.old build/vmlinuz.old
@# Kernel execs /init from the unpacked initramfs. systemd-sysv puts @# /init ships via mkosi.extra/init as a shell script that sets up
@# init at /sbin/init; add the top-level symlink the kernel looks for. @# api-vfs mounts and execs systemd. Force the exec bit on here —
@[ -e build/init ] || ln -sf sbin/init build/init @# Windows checkouts don't preserve +x, and mkosi.extra copies the
@# source-tree mode straight through.
@chmod 0755 build/init
@# Skip /boot: holds the old kernel + stub initrd, both superseded. @# Skip /boot: holds the old kernel + stub initrd, both superseded.
@# Write to a sibling temp path so find doesn't race with the @# Write to a sibling temp path so find doesn't race with the
@# archive being written into the dir it's walking. @# archive being written into the dir it's walking.
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
# /init — PID 1 entry point for the everything-in-initramfs live image.
#
# The kernel unpacks our cpio.zst into a ramfs, sees no root= parameter,
# and execs /init. Classic initramfs-tools has an elaborate /init that
# mounts a real rootfs and pivot_roots into it; we don't do that —
# there IS no other rootfs, the initramfs IS the rootfs. All we need
# to do is set up the api-vfs mounts systemd expects before PID 1 and
# hand off.
#
# Running systemd directly as PID 1 from kernel /init works (systemd
# detects it's PID 1 and boots normally), but only if /proc, /sys, /dev
# are pre-mounted. Systemd has fallback mount code for these, but it's
# fragile in the ramfs-rootfs case — doing it here explicitly makes
# first-boot failures easier to see and harder to hit.
set -e
echo "vetting-init: bootstrapping api-vfs"
mount -t proc -o nosuid,noexec,nodev proc /proc
mount -t sysfs -o nosuid,noexec,nodev sysfs /sys
mount -t devtmpfs -o mode=0755,nosuid devtmpfs /dev
mkdir -p /dev/pts /dev/shm /run
mount -t devpts -o gid=5,mode=620,nosuid,noexec devpts /dev/pts
mount -t tmpfs -o mode=1777,nosuid,nodev tmpfs /dev/shm
mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs /run
echo "vetting-init: handing off to systemd"
exec /lib/systemd/systemd