Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/bin/bash
2# AC Native OS — Minimal reproducible build
3# Builds: C binary → initramfs → kernel → vmlinuz
4# Input: /src (repo), Output: /out/vmlinuz
5set -eu
6
7SRC="${AC_SRC:-/repo}"
8OUT="${AC_OUT:-/out}"
9NATIVE="$SRC/fedac/native"
10BUILD="$NATIVE/build"
11KVER="${KERNEL_VERSION:-6.19.9}"
12KMAJOR="${KVER%%.*}"
13MEDIA_LAYOUT_LIB="$NATIVE/scripts/media-layout.sh"
14
15log() { echo -e "\033[0;36m[ac-os]\033[0m $*"; }
16err() { echo -e "\033[0;31m[ac-os]\033[0m $*" >&2; }
17
18# shellcheck source=/workspaces/aesthetic-computer/fedac/native/scripts/media-layout.sh
19source "$MEDIA_LAYOUT_LIB"
20
21# Always build in /tmp inside the container to avoid bind-mount permission issues
22BUILD="/tmp/ac-build"
23mkdir -p "$BUILD" "$OUT"
24
25# ── Git info ──
26GIT_HASH="${AC_GIT_HASH:-$(cd "$SRC" 2>/dev/null && git rev-parse --short HEAD 2>/dev/null || echo unknown)}"
27BUILD_TS="${AC_BUILD_TS:-$(date -u '+%Y-%m-%dT%H:%M')}"
28BUILD_NAME="${AC_BUILD_NAME:-docker-build}"
29HANDLE="${AC_HANDLE:-jeffrey}"
30KERNEL_JOBS="${AC_KERNEL_JOBS:-$(nproc)}"
31
32show_kernel_error_context() {
33 local log_file="$1"
34 local line
35 local start
36 local end
37
38 line=$(grep -n -m 1 -E '(^|[^[:alpha:]])error:|Error [0-9]+|No rule to make target|undefined reference' "$log_file" | cut -d: -f1 || true)
39 if [ -n "$line" ]; then
40 start=$((line > 80 ? line - 80 : 1))
41 end=$((line + 160))
42 err " Kernel error context (lines ${start}-${end}):"
43 sed -n "${start},${end}p" "$log_file" >&2
44 fi
45 err " Kernel build tail (last 220 lines):"
46 tail -220 "$log_file" >&2 || true
47}
48
49run_make_with_heartbeat() {
50 local log_file="$1"
51 shift
52 local heartbeat_secs="${AC_KERNEL_HEARTBEAT_SECS:-20}"
53 local last_count="-1"
54 local last_line=""
55 local line_count
56 local current_line
57
58 : > "$log_file"
59 "$@" >"$log_file" 2>&1 &
60 local make_pid=$!
61
62 while kill -0 "$make_pid" 2>/dev/null; do
63 sleep "$heartbeat_secs"
64 [ -f "$log_file" ] || continue
65 line_count=$(wc -l <"$log_file" 2>/dev/null || echo 0)
66 current_line=$(tail -1 "$log_file" 2>/dev/null || true)
67 current_line="${current_line:0:180}"
68 if [ "$line_count" != "$last_count" ] || [ "$current_line" != "$last_line" ]; then
69 log " [kernel] running... lines=$line_count last=$current_line"
70 last_count="$line_count"
71 last_line="$current_line"
72 else
73 log " [kernel] running... lines=$line_count (no new lines yet)"
74 fi
75 done
76
77 if wait "$make_pid"; then
78 return 0
79 fi
80 return $?
81}
82
83# ── ccache setup (persisted via Docker volume at /ccache) ──
84export CCACHE_DIR="${CCACHE_DIR:-/ccache}"
85mkdir -p "$CCACHE_DIR"
86export CCACHE_MAXSIZE="${CCACHE_MAXSIZE:-2G}"
87export CCACHE_COMPRESS=1
88if command -v ccache &>/dev/null; then
89 CC_USE="ccache gcc"
90 log "ccache: enabled (dir=$CCACHE_DIR, max=$CCACHE_MAXSIZE)"
91 ccache -s 2>/dev/null | grep -E "cache size|hit rate" || true
92else
93 CC_USE="gcc"
94fi
95
96log "Building $BUILD_NAME ($GIT_HASH)"
97
98# ══════════════════════════════════════════════
99# Step 1: Compile ac-native binary
100# ══════════════════════════════════════════════
101log "Step 1/4: Compiling ac-native..."
102cd "$NATIVE"
103
104# Use cached QuickJS (from Docker image) or download
105if [ ! -f "$BUILD/quickjs/quickjs.h" ]; then
106 if [ -d /cache/quickjs ]; then
107 log " Using cached QuickJS..."
108 cp -a /cache/quickjs-2024-01-13 "$BUILD/"
109 ln -sf quickjs-2024-01-13 "$BUILD/quickjs"
110 else
111 log " Downloading QuickJS..."
112 cd "$BUILD"
113 curl -sL https://bellard.org/quickjs/quickjs-2024-01-13.tar.xz | tar xJ
114 ln -sf quickjs-2024-01-13 quickjs
115 fi
116 cd "$NATIVE"
117fi
118
119make -j$(nproc) CC="${CC_USE}" BUILDDIR="$BUILD" \
120 BUILD_TS="$BUILD_TS" GIT_HASH="$GIT_HASH" BUILD_NAME="$BUILD_NAME" \
121 > "$BUILD/.make.log" 2>&1 || true
122
123[ -f "$BUILD/ac-native" ] || { tail -60 "$BUILD/.make.log" >&2; err "Binary compilation failed"; exit 1; }
124log " Binary: $(stat -c%s "$BUILD/ac-native") bytes"
125log " NEEDED libs:"
126readelf -d "$BUILD/ac-native" 2>/dev/null | grep NEEDED | sed 's/.*\[/ /' | sed 's/\]//'
127ldd "$BUILD/ac-native" 2>&1 | grep -i "not found" && err " MISSING LIBS DETECTED" || log " All libs resolved"
128
129# CL build happens after initramfs (step 2) — see below
130
131# ══════════════════════════════════════════════
132# Step 2: Build initramfs from scratch
133# ══════════════════════════════════════════════
134log "Step 2/4: Building initramfs..."
135IROOT="$BUILD/initramfs-root"
136rm -rf "$IROOT"
137mkdir -p "$IROOT"/{bin,lib64,lib/firmware/i915,dev,proc,sys,tmp,run,scripts,etc,etc/pki/tls/certs}
138
139# ── 2a: Static busybox (no shared lib deps for shell) ──
140BUSYBOX=$(command -v busybox)
141cp "$BUSYBOX" "$IROOT/bin/busybox"
142for cmd in sh sleep mkdir mount umount cat echo ls cp mv rm ln chmod chown \
143 date dd find grep head kill ps sed sort tail tee test touch tr wc which \
144 mktemp printf seq stat basename dirname env expr true false readlink \
145 realpath rmdir uniq yes tar gzip gunzip hostname id ip modprobe \
146 mkswap swapon vi df du diff xargs nohup pgrep killall cut whoami awk \
147 sync poweroff reboot halt mknod udhcpc; do
148 ln -s busybox "$IROOT/bin/$cmd"
149done
150# udhcpc also expected at /sbin/ by wifi.c
151ln -sf ../bin/busybox "$IROOT/sbin/udhcpc" 2>/dev/null || true
152
153# ── 2a2: curl + wget for HTTP fetches (captive portals, OTA, API calls) ──
154CURL_BIN=$(command -v curl)
155if [ -n "$CURL_BIN" ]; then
156 cp "$CURL_BIN" "$IROOT/bin/curl"
157 for lib in $(ldd "$CURL_BIN" 2>/dev/null | grep -oP '/\S+'); do
158 [ -f "$lib" ] && cp -nL "$lib" "$IROOT/lib64/" 2>/dev/null || true
159 done
160fi
161# busybox wget as fallback
162ln -s busybox "$IROOT/bin/wget" 2>/dev/null || true
163# Minimal udhcpc script to configure interface after getting lease
164mkdir -p "$IROOT/usr/share/udhcpc"
165cat > "$IROOT/usr/share/udhcpc/default.script" << 'UDHCPC_SCRIPT'
166#!/bin/sh
167case "$1" in
168 bound|renew)
169 ip addr flush dev "$interface"
170 ip addr add "$ip/${mask:-24}" dev "$interface"
171 [ -n "$router" ] && ip route add default via "$router" dev "$interface"
172 # DNS: use DHCP-provided + fallback to Google/Cloudflare
173 {
174 for d in $dns; do echo "nameserver $d"; done
175 echo "nameserver 8.8.8.8"
176 echo "nameserver 1.1.1.1"
177 } > /etc/resolv.conf
178 ;;
179 deconfig)
180 ip addr flush dev "$interface"
181 ;;
182esac
183UDHCPC_SCRIPT
184chmod +x "$IROOT/usr/share/udhcpc/default.script"
185
186# ── 2b: Init script ──
187cp "$NATIVE/initramfs/init" "$IROOT/init"
188chmod +x "$IROOT/init"
189cp "$NATIVE/initramfs-scripts/"*.sh "$IROOT/scripts/" 2>/dev/null || true
190chmod +x "$IROOT/scripts/"*.sh 2>/dev/null || true
191
192# ── 2c: ac-native binary ──
193cp "$BUILD/ac-native" "$IROOT/ac-native"
194
195# ── 2d: Pieces ──
196cp "$NATIVE/pieces/prompt.mjs" "$IROOT/piece.mjs"
197mkdir -p "$IROOT/pieces"
198cp "$NATIVE/pieces/"*.mjs "$IROOT/pieces/" 2>/dev/null || true
199log " Pieces: $(ls "$IROOT/pieces/" | wc -l)"
200
201# ── 2e: /dev nodes (needed before devtmpfs mounts) ──
202mknod "$IROOT/dev/console" c 5 1 2>/dev/null || true
203mknod "$IROOT/dev/null" c 1 3 2>/dev/null || true
204mknod "$IROOT/dev/tty" c 5 0 2>/dev/null || true
205mknod "$IROOT/dev/zero" c 1 5 2>/dev/null || true
206
207# ── 2f: Dynamic linker ──
208cp -L /lib64/ld-linux-x86-64.so.2 "$IROOT/lib64/"
209
210# ── 2g: Shared libraries (FOLLOW symlinks with cp -L) ──
211# Direct deps of ac-native
212log " Copying shared libraries..."
213for lib in $(ldd "$BUILD/ac-native" 2>/dev/null | grep -oP '/\S+'); do
214 BASENAME=$(basename "$lib")
215 REAL=$(readlink -f "$lib")
216 [ -f "$REAL" ] && cp -L "$REAL" "$IROOT/lib64/$BASENAME"
217done
218
219# SDL3 + Mesa/GPU libs — included for GPU acceleration via dlopen.
220# ac-native runs as a child of init (not PID 1), so if Mesa DRI crashes,
221# init catches the signal and restarts without SDL (AC_NO_SDL=1).
222for lib in libSDL3.so.0 \
223 libgbm.so.1 libEGL.so.1 libEGL_mesa.so.0 libGLESv2.so.2 \
224 libGL.so.1 libGLX_mesa.so.0 libGLdispatch.so.0 libglapi.so.0 \
225 libdrm_intel.so.1 libdrm_amdgpu.so.1; do
226 REAL=$(readlink -f "/lib64/$lib" 2>/dev/null)
227 [ -f "$REAL" ] && cp -L "$REAL" "$IROOT/lib64/$lib"
228done
229
230# Mesa DRI drivers
231if [ -d /lib64/dri ]; then
232 mkdir -p "$IROOT/lib64/dri"
233 for drv in /lib64/dri/i915_dri.so /lib64/dri/iris_dri.so /lib64/dri/kms_swrast_dri.so /lib64/dri/swrast_dri.so; do
234 [ -f "$drv" ] && cp -L "$drv" "$IROOT/lib64/dri/"
235 done
236fi
237# Gallium megadriver
238GALLIUM=$(readlink -f /lib64/libgallium-*.so 2>/dev/null || true)
239[ -f "$GALLIUM" ] && cp -L "$GALLIUM" "$IROOT/lib64/"
240
241# Transitive deps — resolve everything in lib64
242log " Resolving transitive dependencies..."
243ADDED=1
244while [ "$ADDED" -gt 0 ]; do
245 ADDED=0
246 for elf in "$IROOT/lib64/"*.so* "$IROOT/lib64/dri/"*.so* "$IROOT/ac-native"; do
247 [ -f "$elf" ] || continue
248 for needed in $(readelf -d "$elf" 2>/dev/null | grep NEEDED | sed 's/.*\[\(.*\)\]/\1/'); do
249 if [ ! -f "$IROOT/lib64/$needed" ]; then
250 REAL=$(readlink -f "/lib64/$needed" 2>/dev/null)
251 if [ -f "$REAL" ]; then
252 cp -L "$REAL" "$IROOT/lib64/$needed"
253 ADDED=$((ADDED + 1))
254 fi
255 fi
256 done
257 done
258done
259
260# ── 2g2: Verify DRI driver deps ──
261if [ -f "$IROOT/lib64/dri/iris_dri.so" ]; then
262 log " Checking iris_dri.so dependencies..."
263 MISSING_DRI=""
264 for needed in $(LD_LIBRARY_PATH="$IROOT/lib64" ldd "$IROOT/lib64/dri/iris_dri.so" 2>/dev/null | grep "not found" | awk '{print $1}'); do
265 MISSING_DRI="$MISSING_DRI $needed"
266 # Try to find and copy the missing lib
267 REAL=$(readlink -f "/lib64/$needed" 2>/dev/null || readlink -f "/usr/lib64/$needed" 2>/dev/null)
268 if [ -f "$REAL" ]; then
269 cp -L "$REAL" "$IROOT/lib64/$needed"
270 log " Fixed: $needed"
271 else
272 log " MISSING: $needed (not found on build system)"
273 fi
274 done
275 if [ -n "$MISSING_DRI" ]; then
276 log " iris_dri.so had missing deps:$MISSING_DRI"
277 # Re-run transitive dep resolution after fixing
278 ADDED=1
279 while [ "$ADDED" -gt 0 ]; do
280 ADDED=0
281 for elf in "$IROOT/lib64/"*.so* "$IROOT/lib64/dri/"*.so*; do
282 [ -f "$elf" ] || continue
283 for needed in $(readelf -d "$elf" 2>/dev/null | grep NEEDED | sed 's/.*\[\(.*\)\]/\1/'); do
284 if [ ! -f "$IROOT/lib64/$needed" ]; then
285 REAL=$(readlink -f "/lib64/$needed" 2>/dev/null || readlink -f "/usr/lib64/$needed" 2>/dev/null)
286 if [ -f "$REAL" ]; then
287 cp -L "$REAL" "$IROOT/lib64/$needed"
288 ADDED=$((ADDED + 1))
289 fi
290 fi
291 done
292 done
293 done
294 else
295 log " iris_dri.so: all deps OK"
296 fi
297 # Final ldd check
298 FINAL_MISSING=$(LD_LIBRARY_PATH="$IROOT/lib64" ldd "$IROOT/lib64/dri/iris_dri.so" 2>&1 | grep -c "not found" || true)
299 log " iris_dri.so final check: ${FINAL_MISSING:-0} missing deps"
300fi
301
302# ── 2h: Verify NO broken symlinks ──
303BROKEN=$(find "$IROOT/lib64/" -type l ! -exec test -e {} \; -print 2>/dev/null | wc -l)
304if [ "$BROKEN" -gt 0 ]; then
305 err " WARNING: $BROKEN broken symlinks found, fixing..."
306 for broken in $(find "$IROOT/lib64/" -type l ! -exec test -e {} \; -print 2>/dev/null); do
307 name=$(basename "$broken")
308 real=$(readlink -f "/lib64/$name" 2>/dev/null || readlink -f "/usr/lib64/$name" 2>/dev/null)
309 if [ -f "$real" ]; then
310 rm -f "$broken"
311 cp "$real" "$broken"
312 else
313 rm -f "$broken"
314 log " Removed unfixable: $name"
315 fi
316 done
317fi
318
319LIB_COUNT=$(ls "$IROOT/lib64/"*.so* 2>/dev/null | wc -l)
320log " Libraries: $LIB_COUNT"
321
322# ── 2i: WiFi tools ──
323for tool in wpa_supplicant wpa_cli iw dhclient rfkill; do
324 SRC_BIN=$(command -v "$tool" 2>/dev/null || true)
325 if [ -n "$SRC_BIN" ]; then cp -L "$SRC_BIN" "$IROOT/bin/"; fi
326 # Copy their deps too
327 if [ -n "$SRC_BIN" ]; then
328 for dep in $(ldd "$SRC_BIN" 2>/dev/null | grep -oP '/\S+'); do
329 BASENAME=$(basename "$dep")
330 [ ! -f "$IROOT/lib64/$BASENAME" ] && cp -L "$(readlink -f "$dep")" "$IROOT/lib64/$BASENAME" 2>/dev/null || true
331 done
332 fi
333done
334
335# ── 2i2: Disk/EFI tools (for HD install + OTA flash) ──
336for tool in sfdisk mkfs.vfat efibootmgr partprobe; do
337 SRC_BIN=$(command -v "$tool" 2>/dev/null || true)
338 if [ -n "$SRC_BIN" ]; then
339 cp -L "$SRC_BIN" "$IROOT/bin/"
340 for dep in $(ldd "$SRC_BIN" 2>/dev/null | grep -oP '/\S+'); do
341 BASENAME=$(basename "$dep")
342 [ ! -f "$IROOT/lib64/$BASENAME" ] && cp -L "$(readlink -f "$dep")" "$IROOT/lib64/$BASENAME" 2>/dev/null || true
343 done
344 fi
345done
346
347# ── 2j: Firmware (trimmed to common Intel WiFi + GPU chips) ──
348log " Copying firmware..."
349FWDIR=""
350for d in /usr/lib/firmware /lib/firmware; do
351 [ -d "$d/i915" ] && FWDIR="$d" && break
352done
353if [ -n "$FWDIR" ]; then
354 # WiFi — only common Intel chip families (covers ThinkPad, NUC, Surface)
355 # Each family: 3160, 7260, 7265, 8000, 8265, 9000, 9260, ax200, ax201, ax210, ax211, be200
356 for chip in 3160 3168 7260 7265 8000C 8265 9000 9260 \
357 ax200 ax201 ax210 ax211 be200 gl; do
358 for fw in "$FWDIR"/iwlwifi-${chip}*.ucode "$FWDIR"/iwlwifi-${chip}*.ucode.zst "$FWDIR"/iwlwifi-${chip}*.ucode.xz; do
359 [ -f "$fw" ] && cp -L "$fw" "$IROOT/lib/firmware/"
360 done
361 done
362 # Regulatory
363 cp -L "$FWDIR/regulatory.db" "$IROOT/lib/firmware/" 2>/dev/null || true
364 cp -L "$FWDIR/regulatory.db.p7s" "$IROOT/lib/firmware/" 2>/dev/null || true
365 # GPU — only KBL/SKL/CFL/ICL/TGL/ADL/RPL (covers ~95% of target hardware)
366 for pattern in kbl skl cfl icl tgl adl dg1 rkl rpl mtl; do
367 for fw in "$FWDIR"/i915/${pattern}_*; do
368 [ -f "$fw" ] && cp -L "$fw" "$IROOT/lib/firmware/i915/"
369 done
370 done
371 # DMC firmware (display power management)
372 for fw in "$FWDIR"/i915/*_dmc_*.bin "$FWDIR"/i915/*_dmc_*.bin.zst; do
373 [ -f "$fw" ] && cp -L "$fw" "$IROOT/lib/firmware/i915/"
374 done
375else
376 log " WARNING: No firmware directory found!"
377fi
378# Decompress any .zst or .xz files
379for zst in "$IROOT/lib/firmware/"*.zst "$IROOT/lib/firmware/i915/"*.zst; do
380 [ -f "$zst" ] && zstd -d --rm "$zst" 2>/dev/null || true
381done
382for xzf in "$IROOT/lib/firmware/"*.xz "$IROOT/lib/firmware/i915/"*.xz; do
383 [ -f "$xzf" ] && xz -d "$xzf" 2>/dev/null || true
384done
385FW_COUNT=$(find "$IROOT/lib/firmware" -type f | wc -l)
386FW_SIZE=$(du -sh "$IROOT/lib/firmware" | cut -f1)
387log " Firmware: $FW_COUNT files ($FW_SIZE)"
388
389# ── 2k: SSL certs ──
390cp /etc/pki/tls/certs/ca-bundle.crt "$IROOT/etc/pki/tls/certs/" 2>/dev/null || true
391
392# ── 2l: ALSA config ──
393if [ -d /usr/share/alsa ]; then
394 mkdir -p "$IROOT/usr/share"
395 cp -a /usr/share/alsa "$IROOT/usr/share/" 2>/dev/null || true
396fi
397
398# ── 2m: /etc/group and /etc/passwd ──
399echo "root:x:0:" > "$IROOT/etc/group"
400echo "root:x:0:root" > "$IROOT/etc/passwd"
401
402# ── 2n: Claude Code ──
403CLAUDE_BIN=""
404for p in /claude-bin /usr/local/bin/claude-native /usr/local/bin/claude /root/.local/share/claude/versions/* /home/me/.local/share/claude/versions/*; do
405 [ -f "$p" ] && CLAUDE_BIN="$p" && break
406done
407if [ -n "$CLAUDE_BIN" ]; then
408 cp "$CLAUDE_BIN" "$IROOT/bin/claude"
409 chmod +x "$IROOT/bin/claude"
410 # Copy its shared libs
411 for lib in $(ldd "$CLAUDE_BIN" 2>/dev/null | grep -oP '/\S+'); do
412 [ -f "$lib" ] && cp -nL "$lib" "$IROOT/lib64/" 2>/dev/null || true
413 done
414 log " Claude Code: $(du -sh "$IROOT/bin/claude" | cut -f1)"
415else
416 log " Claude Code: not available (mount with -v /path/to/claude:/claude-bin)"
417fi
418
419# ── 2o: KidLisp bundle ──
420if command -v npx &>/dev/null && [ -f "$SRC/system/public/aesthetic.computer/lib/kidlisp.mjs" ]; then
421 log " Bundling KidLisp..."
422 cd "$SRC"
423 npx esbuild system/public/aesthetic.computer/lib/kidlisp.mjs \
424 --bundle --format=iife --global-name=KidLispModule --platform=neutral \
425 --external:https --external:http --external:net --external:fs --external:path \
426 --outfile=/tmp/kidlisp-bundle.js 2>&1 || true
427 if [ -f /tmp/kidlisp-bundle.js ]; then
428 mkdir -p "$IROOT/jslib"
429 cp /tmp/kidlisp-bundle.js "$IROOT/jslib/"
430 fi
431 cd "$NATIVE"
432fi
433
434# ── 2p: ES module lib files for pieces (clock.mjs needs these) ──
435# These are pure JS with no DOM/browser deps — work in QuickJS as-is.
436# The module loader resolves "../lib/X.mjs" → "/lib/X.mjs" in the initramfs.
437mkdir -p "$IROOT/lib"
438for libmjs in melody-parser.mjs notepat-convert.mjs note-colors.mjs num.mjs; do
439 SRC_LIB="$SRC/system/public/aesthetic.computer/lib/$libmjs"
440 if [ -f "$SRC_LIB" ]; then
441 cp "$SRC_LIB" "$IROOT/lib/$libmjs"
442 log " Bundled lib: $libmjs ($(stat -c%s "$SRC_LIB") bytes)"
443 fi
444done
445
446# ── 2q: Web pieces that work natively (clock.mjs unmodified) ──
447for webpiece in clock.mjs; do
448 SRC_PIECE="$SRC/system/public/aesthetic.computer/disks/$webpiece"
449 if [ -f "$SRC_PIECE" ]; then
450 cp "$SRC_PIECE" "$IROOT/pieces/$webpiece"
451 log " Bundled web piece: $webpiece ($(stat -c%s "$SRC_PIECE") bytes)"
452 fi
453done
454
455# ── Final verification ──
456BROKEN_FINAL=$(find "$IROOT" -type l ! -exec test -e {} \; -print 2>/dev/null | wc -l)
457TOTAL_FILES=$(find "$IROOT" -type f | wc -l)
458log " Initramfs: $TOTAL_FILES files, $BROKEN_FINAL broken symlinks"
459[ "$BROKEN_FINAL" -gt 0 ] && err " WARNING: broken symlinks remain!"
460
461# Write build metadata (C variant by default, CL overrides below)
462mkdir -p "$IROOT/etc"
463printf '%s\n%s\n%s\nc\n' "$BUILD_NAME" "$GIT_HASH" "$BUILD_TS" > "$IROOT/etc/ac-build"
464
465# ── Embed SBCL + Swank for live CL development ──
466log "Step 2b: Embedding SBCL + Swank..."
467
468# Build libquickjs.so for CL CFFI bindings
469QJSDIR="/cache/quickjs-2024-01-13"
470log " Building libquickjs.so..."
471gcc -shared -fPIC -O2 -Wl,-soname,libquickjs.so \
472 -o /lib64/libquickjs.so \
473 "$QJSDIR/quickjs.c" "$QJSDIR/libunicode.c" \
474 "$QJSDIR/libregexp.c" "$QJSDIR/cutils.c" "$QJSDIR/libbf.c" \
475 '-DCONFIG_VERSION="0.8.0"' -lm 2>&1 || { err "libquickjs.so build failed"; }
476
477CL_DIR="$NATIVE/cl"
478log " Building libquickjs-shim.so..."
479gcc -shared -fPIC -O2 -Wl,-soname,libquickjs-shim.so \
480 -o /lib64/libquickjs-shim.so \
481 "$CL_DIR/quickjs-shim.c" \
482 -I"$QJSDIR" -L/lib64 -lquickjs -lm 2>&1 || { err "shim build failed"; }
483
484ldconfig 2>/dev/null || true
485
486# Copy shared libs into initramfs
487cp /lib64/libquickjs.so "$IROOT/lib64/"
488cp /lib64/libquickjs-shim.so "$IROOT/lib64/"
489for lib in /lib64/libzstd.so*; do
490 [ -f "$lib" ] && cp -L "$lib" "$IROOT/lib64/" 2>/dev/null
491done
492
493# Build SBCL Swank server image (standalone binary with Swank preloaded)
494export LD_LIBRARY_PATH="/lib64:${LD_LIBRARY_PATH:-}"
495log " Building SBCL Swank image..."
496sbcl --non-interactive \
497 --eval '(load "/opt/quicklisp/setup.lisp")' \
498 --eval '(require :asdf)' \
499 --eval "(push #P\"$CL_DIR/\" asdf:*central-registry*)" \
500 --eval '(asdf:load-system :ac-native)' \
501 --eval "(sb-ext:save-lisp-and-die \"$BUILD/ac-swank\"
502 :toplevel #'ac-native:main
503 :executable t
504 :compression t)" \
505 2>&1 || { err "SBCL Swank build failed (non-fatal)"; }
506
507if [ -f "$BUILD/ac-swank" ]; then
508 cp "$BUILD/ac-swank" "$IROOT/ac-swank"
509 chmod +x "$IROOT/ac-swank"
510 log " SBCL Swank: $(stat -c%s "$BUILD/ac-swank") bytes"
511else
512 log " SBCL Swank: skipped (build failed, C-only build)"
513fi
514
515# Copy CL pieces (only runnable .lisp pieces from pieces/ dir, not cl/ library)
516if ls "$NATIVE/pieces/"*.lisp 1>/dev/null 2>&1; then
517 cp "$NATIVE/pieces/"*.lisp "$IROOT/pieces/"
518fi
519CL_PIECES=$(ls "$IROOT/pieces/"*.lisp 2>/dev/null | wc -l)
520log " CL pieces: $CL_PIECES"
521
522# ══════════════════════════════════════════════
523# Step 3: Pack initramfs (cpio + lz4)
524# ══════════════════════════════════════════════
525log "Step 3/4: Packing initramfs..."
526cd "$IROOT"
527find . -print0 | cpio --null -ov --format=newc 2>/dev/null | lz4 -l -9 -f - "$BUILD/initramfs.cpio.lz4"
528INITRAMFS_SIZE=$(stat -c%s "$BUILD/initramfs.cpio.lz4")
529log " Initramfs: $((INITRAMFS_SIZE / 1048576))MB compressed"
530
531# ══════════════════════════════════════════════
532# Step 4: Build kernel with embedded initramfs
533# ══════════════════════════════════════════════
534log "Step 4/4: Building kernel..."
535LINUX_DIR="$BUILD/linux-$KVER"
536
537# Use cached kernel source or download
538if [ ! -f "$LINUX_DIR/Makefile" ]; then
539 if [ -d "/cache/linux-$KVER" ]; then
540 log " Using cached Linux $KVER..."
541 cp -a "/cache/linux-$KVER" "$BUILD/"
542 else
543 log " Downloading Linux $KVER..."
544 cd "$BUILD"
545 curl -sL "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KVER}.tar.xz" | tar xJ
546 fi
547fi
548
549# Copy config
550cp "$NATIVE/kernel/config-minimal" "$LINUX_DIR/.config"
551
552# Stage built-in firmware blobs referenced by CONFIG_EXTRA_FIRMWARE
553# into a container-local directory and rewrite CONFIG_EXTRA_FIRMWARE_DIR.
554FIRMWARE_ABS="$BUILD/firmware"
555mkdir -p "$FIRMWARE_ABS"
556
557HOST_FWDIR=""
558for d in /usr/lib/firmware /lib/firmware; do
559 if [ -d "$d" ]; then
560 HOST_FWDIR="$d"
561 break
562 fi
563done
564
565copy_builtin_fw_blob() {
566 local rel="$1"
567 local src_base="$2"
568 local dst="$FIRMWARE_ABS/$rel"
569 mkdir -p "$(dirname "$dst")"
570 if [ -f "$src_base/$rel" ]; then
571 cp -L "$src_base/$rel" "$dst"
572 return 0
573 fi
574 if [ -f "$src_base/$rel.zst" ]; then
575 zstd -d "$src_base/$rel.zst" -o "$dst" 2>/dev/null && return 0
576 fi
577 if [ -f "$src_base/$rel.xz" ]; then
578 xz -dc "$src_base/$rel.xz" >"$dst" 2>/dev/null && return 0
579 fi
580 return 1
581}
582
583FW_LIST=$(sed -n 's/^CONFIG_EXTRA_FIRMWARE="\([^"]*\)"/\1/p' "$LINUX_DIR/.config" | head -1)
584if [ -n "$FW_LIST" ]; then
585 if [ -z "$HOST_FWDIR" ]; then
586 err "Kernel config requests built-in firmware but no /usr/lib/firmware or /lib/firmware directory was found."
587 exit 1
588 fi
589
590 missing_fw=""
591 for fw in $FW_LIST; do
592 if ! copy_builtin_fw_blob "$fw" "$HOST_FWDIR"; then
593 missing_fw="$missing_fw $fw"
594 fi
595 done
596
597 if [ -n "$missing_fw" ]; then
598 err "Missing built-in firmware blobs:$missing_fw"
599 err "Looked under: $HOST_FWDIR (including .zst/.xz variants)"
600 exit 1
601 fi
602
603 sed -i "s|^CONFIG_EXTRA_FIRMWARE_DIR=.*|CONFIG_EXTRA_FIRMWARE_DIR=\"$FIRMWARE_ABS\"|" "$LINUX_DIR/.config"
604 log " Built-in firmware dir: $FIRMWARE_ABS"
605 log " Built-in firmware files: $FW_LIST"
606fi
607
608# Copy initramfs into kernel tree
609cp "$BUILD/initramfs.cpio.lz4" "$LINUX_DIR/initramfs.cpio.lz4"
610
611# Configure — force-disable bloated GPU drivers that olddefconfig enables
612cd "$LINUX_DIR"
613KCFG_LOG="$BUILD/kernel-olddefconfig.log"
614make olddefconfig >"$KCFG_LOG" 2>&1 || { err "Kernel olddefconfig failed"; tail -80 "$KCFG_LOG" >&2; exit 1; }
615tail -3 "$KCFG_LOG" || true
616
617# Strip GPU drivers that Fedora defaults enable (they cause KALLSYMS overflow)
618scripts/config --disable DRM_AMDGPU
619scripts/config --disable DRM_NOUVEAU
620scripts/config --disable DRM_RADEON
621scripts/config --disable DRM_QXL
622scripts/config --disable DRM_BOCHS
623scripts/config --disable DRM_CIRRUS_QEMU
624scripts/config --disable DRM_VIRTIO_GPU
625scripts/config --enable DRM_SIMPLEDRM
626make olddefconfig >>"$KCFG_LOG" 2>&1 || { err "Kernel olddefconfig (post-config) failed"; tail -120 "$KCFG_LOG" >&2; exit 1; }
627tail -1 "$KCFG_LOG" || true
628
629# With ccache, skip make clean — stale objects get cache misses anyway,
630# and clean destroys the build tree that ccache relies on for hits.
631# Without ccache, clean to avoid config mismatch errors.
632if ! command -v ccache &>/dev/null; then
633 make clean 2>/dev/null || true
634fi
635
636# Build
637log " Compiling (${KERNEL_JOBS} cores)..."
638KERNEL_LOG="$BUILD/kernel-build.log"
639if ! run_make_with_heartbeat "$KERNEL_LOG" make -j"${KERNEL_JOBS}" CC="${CC_USE}" KALLSYMS_EXTRA_PASS=1 bzImage; then
640 err "Kernel compile failed while building bzImage (parallel pass)."
641 show_kernel_error_context "$KERNEL_LOG"
642 if [ "${KERNEL_JOBS}" -gt 1 ]; then
643 err "Retrying kernel build in serial mode (-j1, V=1) for deterministic diagnostics..."
644 make clean 2>/dev/null || true
645 KERNEL_LOG_RETRY="$BUILD/kernel-build-retry.log"
646 if ! run_make_with_heartbeat "$KERNEL_LOG_RETRY" make -j1 V=1 CC="${CC_USE}" KALLSYMS_EXTRA_PASS=1 bzImage; then
647 err "Kernel compile failed again in serial retry."
648 show_kernel_error_context "$KERNEL_LOG_RETRY"
649 exit 1
650 fi
651 KERNEL_LOG="$KERNEL_LOG_RETRY"
652 log " Serial retry succeeded."
653 else
654 exit 1
655 fi
656fi
657tail -3 "$KERNEL_LOG" || true
658
659# Copy output
660if [ ! -f arch/x86/boot/bzImage ]; then
661 err "Kernel build completed but arch/x86/boot/bzImage is missing."
662 show_kernel_error_context "$KERNEL_LOG"
663 exit 1
664fi
665cp arch/x86/boot/bzImage "$BUILD/vmlinuz"
666cp arch/x86/boot/bzImage "$OUT/vmlinuz" 2>/dev/null || true
667
668VMLINUZ_SIZE=$(stat -c%s "$BUILD/vmlinuz")
669SHA=$(sha256sum "$BUILD/vmlinuz" | awk '{print $1}')
670
671# ══════════════════════════════════════════════
672# Step 4b: Build slim kernel (no embedded initramfs) for Mac EFI boot
673# ══════════════════════════════════════════════
674log "Step 4b: Building slim kernel for Mac..."
675sed -i 's|^CONFIG_INITRAMFS_SOURCE=.*|CONFIG_INITRAMFS_SOURCE=""|' .config
676make olddefconfig >>"$KCFG_LOG" 2>&1 || { err "Kernel olddefconfig failed before slim build"; tail -120 "$KCFG_LOG" >&2; exit 1; }
677if ! command -v ccache &>/dev/null; then make clean 2>/dev/null || true; fi
678rm -f usr/initramfs_data.o usr/.initramfs_data.o.cmd
679if run_make_with_heartbeat "$BUILD/kernel-slim.log" make -j"${KERNEL_JOBS}" CC="${CC_USE}" bzImage; then
680 cp arch/x86/boot/bzImage "$BUILD/vmlinuz-slim"
681 cp arch/x86/boot/bzImage "$OUT/vmlinuz-slim" 2>/dev/null || true
682 SLIM_SIZE=$(stat -c%s "$BUILD/vmlinuz-slim")
683 log " Slim kernel: $((SLIM_SIZE / 1048576))MB"
684else
685 err "Slim kernel build failed (non-fatal)"
686fi
687# Restore config for any subsequent builds
688sed -i 's|^CONFIG_INITRAMFS_SOURCE=.*|CONFIG_INITRAMFS_SOURCE="initramfs.cpio.lz4"|' .config
689make olddefconfig >>"$KCFG_LOG" 2>&1 || { err "Kernel olddefconfig failed while restoring initramfs config"; tail -120 "$KCFG_LOG" >&2; exit 1; }
690
691# Export initramfs as gzip (for systemd-boot on Mac)
692log " Packing initramfs.cpio.gz..."
693lz4 -d "$BUILD/initramfs.cpio.lz4" -c 2>/dev/null | gzip -c > "$BUILD/initramfs.cpio.gz"
694cp "$BUILD/initramfs.cpio.gz" "$OUT/initramfs.cpio.gz" 2>/dev/null || true
695log " initramfs.cpio.gz: $(($(stat -c%s "$BUILD/initramfs.cpio.gz") / 1048576))MB"
696
697# ══════════════════════════════════════════════
698# Step 5: Generate UEFI-bootable ISO
699# ══════════════════════════════════════════════
700log "Step 5: Building ISO..."
701ISO_DIR="$BUILD/iso-root"
702EFI_IMG="$BUILD/efi.img"
703ISO_OUT="$BUILD/ac-os.iso"
704CONFIG_TMP="$BUILD/identity-config.json"
705
706rm -rf "$ISO_DIR" "$EFI_IMG" "$CONFIG_TMP"
707
708ac_media_write_identity_config "$CONFIG_TMP"
709ac_media_stage_boot_tree "$ISO_DIR" "$BUILD/vmlinuz" "$CONFIG_TMP"
710ac_media_create_fat_image "$ISO_DIR" "$EFI_IMG" "AC_NATIVE"
711
712# Build hybrid ISO with xorriso (EFI boot via El Torito + GPT for dd/USB)
713# Uses the same chainloader-first staged tree as ac-os generate_template_iso().
714if command -v xorriso &>/dev/null; then
715 ac_media_build_hybrid_iso "$ISO_DIR" "$EFI_IMG" "$ISO_OUT" "AC_NATIVE"
716else
717 # Fallback: raw EFI disk image (dd-able)
718 log " No xorriso — creating raw disk image"
719 cp "$EFI_IMG" "$ISO_OUT"
720fi
721
722rm -f "$CONFIG_TMP"
723
724if [ -f "$ISO_OUT" ]; then
725 ISO_SIZE=$(stat -c%s "$ISO_OUT")
726 ISO_SHA=$(sha256sum "$ISO_OUT" | awk '{print $1}')
727 cp "$ISO_OUT" "$OUT/ac-os.iso" 2>/dev/null || true
728 log " ISO: $((ISO_SIZE / 1048576))MB (sha256: ${ISO_SHA:0:16}...)"
729else
730 log " ISO generation failed — vmlinuz still available"
731fi
732
733log ""
734log "═══════════════════════════════════════════"
735log " Build complete: $BUILD_NAME"
736log " Kernel: $((VMLINUZ_SIZE / 1048576))MB"
737log " SHA256: $SHA"
738if [ -f "$ISO_OUT" ]; then
739 log " ISO: $((ISO_SIZE / 1048576))MB"
740fi
741if command -v ccache &>/dev/null; then
742 CCACHE_HIT=$(ccache -s 2>/dev/null | grep "cache hit rate" | head -1 || true)
743 CCACHE_SIZE=$(ccache -s 2>/dev/null | grep "cache size" | head -1 || true)
744 log " ccache: ${CCACHE_HIT:-n/a} | ${CCACHE_SIZE:-n/a}"
745fi
746log "═══════════════════════════════════════════"
747
748# Write metadata
749cat > "$OUT/build.json" << EOF
750{
751 "name": "$BUILD_NAME",
752 "git_hash": "$GIT_HASH",
753 "build_ts": "$BUILD_TS",
754 "size": $VMLINUZ_SIZE,
755 "sha256": "$SHA",
756 "iso_size": ${ISO_SIZE:-0},
757 "iso_sha256": "${ISO_SHA:-}",
758 "handle": "$HANDLE"
759}
760EOF