Compare commits

...

11 commits

Author SHA1 Message Date
Philip Peterson
d32b0c3811 try fix segfault 2026-09-16 03:35:43 -08:00
Philip Peterson
e74cd875fa More logging 2026-09-15 23:05:53 -08:00
Philip Peterson
4dc553cfb4 Log installer output and make guix system init verbose 2026-09-14 20:24:41 -08:00
Philip Peterson
7466fc79be Revert duplicate Starting-service patch (message already exists in shepherd) 2026-09-14 13:16:07 -08:00
Philip Peterson
e8755ca06f Make connman verbose on the console for networking diagnosis 2026-09-14 11:48:55 -08:00
Philip Peterson
1cbda80255 Add start script 2026-09-14 11:39:05 -08:00
Philip Peterson
f61cfd2218 Patch shepherd to log 'Starting service' during boot 2026-09-14 11:32:42 -08:00
Philip Peterson
d092f45b42 Drop keyring ref after integrity check so clone is fully self-contained 2026-09-14 00:09:45 -08:00
Philip Peterson
53a4086ee4 Commit patched tree as orphan so libgit2 doesn't chase the pinned commit's parent 2026-09-13 23:33:21 -08:00
Philip Peterson
02cc5d632a Point keyring-reference at main branch; keep real keyring as remote-tracking ref 2026-09-13 22:51:59 -08:00
Philip Peterson
44f19efd33 Fetch keyring shallowly to avoid slow full clone 2026-09-13 22:32:16 -08:00
6 changed files with 112 additions and 37 deletions

BIN
blank-32g.qcow2 Normal file

Binary file not shown.

BIN
blank-vars.fd Normal file

Binary file not shown.

View file

@ -51,7 +51,7 @@ func normalizeArch(arch string) string {
func setup(arch string) *dagger.Container {
tarball := "guix-binary-" + guixVersion + "." + arch + "-linux.tar.xz"
return dag.Container(dagger.ContainerOpts{Platform: platformFor[arch]}).
From("debian:stable-slim").
From("debian:bookworm-slim").
WithEnvVariable("DEBIAN_FRONTEND", "noninteractive").
WithExec([]string{"apt-get", "update"}).
WithExec([]string{"apt-get", "install", "-y", "--no-install-recommends",

111
patch.sh
View file

@ -16,14 +16,16 @@ COMMIT="$(grep -oE '\(commit "[0-9a-f]{40}"\)' "$CHANNELS" | grep -oE '[0-9a-f]{
[ -n "$COMMIT" ] || { echo "error: no pinned commit in channels.scm" >&2; exit 1; }
echo "pinned Guix commit: $COMMIT"
# Clone the pinned commit (full history, so the checkout is not shallow and
# guix pull's own clone can fetch every branch) plus the keyring branch.
# Shallow-clone the pinned commit. The keyring branch is fetched only as a
# remote-tracking ref (for the local integrity check below), NOT as a local
# branch, so that guix pull's own clone of this repo only sees the 'patched'
# branch and never trips over the keyring tip.
rm -rf "$SRC"
git init -q "$SRC"
git -C "$SRC" remote add origin https://codeberg.org/guix/guix.git
git -C "$SRC" fetch -q origin "$COMMIT"
git -C "$SRC" fetch -q origin keyring:keyring
git -C "$SRC" checkout -q -b patched "$COMMIT"
git -C "$SRC" fetch -q --depth 1 origin "$COMMIT"
git -C "$SRC" fetch -q --depth 1 origin refs/heads/keyring:refs/remotes/origin/keyring
git -C "$SRC" checkout -q "$COMMIT"
# Import the official signing keys and verify the pinned commit's signature,
# so we patch a known-authentic base.
@ -45,48 +47,87 @@ if ! grep -q "Good signature" <<< "$VERIFY_OUTPUT"; then
fi
echo "verified pinned commit signature"
# Replace a fixed string that occurs on exactly one line, aborting if the count
# is not 1 (the assert mirrors the old python3 patches, without depending on a
# Python interpreter that turned out to segfault in the container). $needle is
# matched literally by grep; $sed_expr is the equivalent GNU sed substitution.
replace_once() {
local file="$1" needle="$2" sed_expr="$3" n
n="$(grep -Fc -- "$needle" "$file")"
if [ "$n" != "1" ]; then
echo "error: expected one '$needle' in $file, found $n" >&2
return 1
fi
sed -i "$sed_expr" "$file"
}
# Route shepherd's log to the console, so boot failures are visible on screen
# instead of only in /var/log/messages.
python3 - "$SRC" <<'PY'
import sys
from pathlib import Path
p = Path(sys.argv[1]) / "gnu/services/shepherd.scm"
s = p.read_text()
old = ''' ;; Start shepherd.
(execl #$(file-append shepherd "/bin/shepherd")
"shepherd" "--config"
#$(shepherd-configuration-file services shepherd)))))'''
new = ''' ;; Start shepherd. Log to the console so boot failures are visible.
(execl #$(file-append shepherd "/bin/shepherd")
"shepherd" "--config"
#$(shepherd-configuration-file services shepherd)
"--logfile" "/dev/console"))))'''
assert old in s, "shepherd-boot-gexp execl not found"
p.write_text(s.replace(old, new, 1))
print("patched shepherd-boot-gexp")
PY
replace_once "$SRC/gnu/services/shepherd.scm" \
' ;; Start shepherd.' \
's| ;; Start shepherd\.| ;; Start shepherd. Log to the console so boot failures are visible.|'
replace_once "$SRC/gnu/services/shepherd.scm" \
'#$(shepherd-configuration-file services shepherd)))))' \
's|#\$(shepherd-configuration-file services shepherd)))))|#\$(shepherd-configuration-file services shepherd)\n "--logfile" "/dev/console"))))|'
# Make the kernel verbose by default (drop 'quiet').
python3 - "$SRC" <<'PY'
import sys
from pathlib import Path
p = Path(sys.argv[1]) / "gnu/system.scm"
s = p.read_text()
old = ''' "quiet"))'''
new = ''' "verbose"))'''
assert s.count(old) == 1, f"expected one 'quiet' kernel arg, found {s.count(old)}"
p.write_text(s.replace(old, new, 1))
print("patched %default-kernel-arguments")
PY
replace_once "$SRC/gnu/system.scm" \
' "quiet"))' \
's| "quiet"))| "verbose"))|'
# Sign the patched commit with our key.
# Point the channel's keyring-reference at the main branch. The keyring is
# loaded (unconditionally) but never used for verification, because
# first-signed-commit equals the channel commit (an empty commit range). So an
# empty keyring off the 'patched' branch is fine and avoids needing a separate
# keyring branch that libgit2 refuses to fetch.
replace_once "$SRC/.guix-channel" \
' (keyring-reference "keyring")' \
's| (keyring-reference "keyring")| (keyring-reference "patched")|'
# Make connman verbose: run it with --debug and send its output to the console
# instead of /var/log/connman.log, so a networking hang explains itself.
replace_once "$SRC/gnu/services/networking.scm" \
'"--nodnsproxy"' \
's|"--nodnsproxy"|"--nodnsproxy" "--debug"|'
replace_once "$SRC/gnu/services/networking.scm" \
'#:log-file "/var/log/connman.log"' \
's|#:log-file "/var/log/connman\.log"|#:log-file "/dev/console"|'
# Make the installer's `guix system init` verbose so its progress (substitute
# downloads, builds) is visible instead of a silent hang.
replace_once "$SRC/gnu/installer/final.scm" \
'"--fallback"' \
's|"--fallback"|"--fallback" "--verbosity=3" "--max-silent-time=120"|'
# The installer normally runs the final `guix system init` in a PTY. That is
# nicer for progress bars, but it makes this failure mode harder to diagnose
# when the newt screen goes blank. For this debug image, use the plain pipe
# path and print the exact command right before it runs.
replace_once "$SRC/gnu/installer/final.scm" \
'(set! ret (run-command install-command #:tty? #t)))' \
's|(set! ret (run-command install-command #:tty? #t)))|(installer-log-line "debug: final install command: ~s" install-command)\n (format #t "debug: final install command: ~s~%" install-command)\n (force-output)\n (set! ret (run-command install-command #:tty? #f)))|'
# Log every line the installer's commands print to the installer log, so the
# full install output is captured and visible.
replace_once "$SRC/gnu/installer/utils.scm" \
'(list display)' \
's|(list display)|(list display %installer-log-line-hook)|'
# Sign the patched tree as an orphan commit (no parent), so that guix pull's
# clone never tries to fetch the pinned commit's absent parent.
gpg --batch --import /workspace/keys/private.key >/dev/null 2>&1
git -C "$SRC" checkout -q --orphan patched
git -C "$SRC" add -A
git -C "$SRC" \
-c user.email=debug@guix-iso.invalid \
-c user.name="guix-iso debug" \
-c user.signingkey="$FINGERPRINT" \
commit -q -S -m "debug: console shepherd log + verbose kernel"
# Drop the keyring ref (only needed for the integrity check above) and the
# shallow marker, so guix pull's clone only sees the self-contained orphan
# commit.
git -C "$SRC" update-ref -d refs/remotes/origin/keyring
rm -f "$SRC/.git/shallow"
NEW_COMMIT="$(git -C "$SRC" rev-parse HEAD)"
echo "patched commit: $NEW_COMMIT"

14
pull.sh
View file

@ -4,7 +4,19 @@ set -euo pipefail
export PATH="/var/guix/profiles/per-user/root/current-guix/bin:${PATH}"
# Clone + verify + patch + sign the pinned Guix, producing /tmp/channels-local.scm.
bash /workspace/patch.sh
# patch.sh is idempotent (it re-clones from scratch each time), so retry the
# whole thing to ride out transient segfaults in the container's python3.
PATCH_OK=0
for attempt in $(seq 1 3); do
echo "patch: attempt ${attempt}/3"
if bash /workspace/patch.sh; then
PATCH_OK=1
break
fi
echo "patch.sh failed (exit $?); retrying in 5s..." >&2
sleep 5
done
[ "${PATCH_OK}" = "1" ] || { echo "patch.sh failed after retries" >&2; exit 1; }
DAEMON_PID=""
start_daemon() {

22
start.sh Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
FW_DIR="/Applications/UTM.app/Contents/Resources/qemu"
DIR="$(cd "$(dirname "$0")" && pwd)"
# Create the 64 MiB NVRAM vars flash from the EDK2 template on first run.
if [ ! -s "$DIR/blank-vars.fd" ]; then
cp "$FW_DIR/edk2-arm-vars.fd" "$DIR/blank-vars.fd"
truncate -s 64M "$DIR/blank-vars.fd"
fi
exec qemu-system-aarch64 \
-machine virt \
-accel hvf -cpu host -smp 4 -m 4096 \
-drive if=pflash,format=raw,readonly=on,file="$FW_DIR/edk2-aarch64-code.fd" \
-drive if=pflash,format=raw,file="$DIR/blank-vars.fd" \
-drive if=virtio,format=qcow2,file=/Users/morkswork/Downloads/guix-install-aarch64-linux.qcow2 \
-drive if=virtio,format=qcow2,file="$DIR/blank-32g.qcow2" \
-nic user,model=virtio-net-pci \
-display none -serial stdio -monitor none \
"$@"