Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

ac-os: restore initramfs population from old build script

The initramfs was nearly empty (4 files) because build_initramfs()
only copied the binary and kidlisp — missing init script, /dev, /proc,
shared libs, WiFi bins, ALSA config, pieces, busybox, firmware, etc.

Restored the old build-and-flash.sh as build-and-flash-initramfs.sh
and call it from build_initramfs() for the full root filesystem setup.

Also: ac-say (TTS + screen flash), ac-blink, and firmware config fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1104
+11
fedac/native/ac-os
··· 73 73 log "Building initramfs..." 74 74 cd "${SCRIPT_DIR}" 75 75 76 + # The old build-and-flash.sh populates the initramfs root (dirs, init script, 77 + # pieces, shared libs, WiFi bins, ALSA config, firmware, busybox, etc.) 78 + # Run it with --skip-kernel --skip-binary to only do initramfs setup. 79 + # The shim (scripts/build-and-flash.sh) must NOT call back into ac-os. 80 + local OLD_SCRIPT="${SCRIPT_DIR}/scripts/build-and-flash-initramfs.sh" 81 + if [ -f "${OLD_SCRIPT}" ]; then 82 + log "Populating initramfs root..." 83 + sudo bash "${OLD_SCRIPT}" --skip-kernel --skip-binary 2>&1 \ 84 + | grep -E "Baked|pieces|WiFi|ALSA|libs|firmware|init|Bundled|error|config" || true 85 + fi 86 + 76 87 # KidLisp: npx not available under sudo, bundle as user then copy 77 88 local KIDLISP_SRC="${SCRIPT_DIR}/../../system/public/aesthetic.computer/lib/kidlisp.mjs" 78 89 if [ -f "${KIDLISP_SRC}" ] && command -v npx &>/dev/null; then
+50
fedac/native/ac-say
··· 1 + #!/bin/bash 2 + # ac-say — Speak text AND flash the screen on the Docker host 3 + # Usage: ac-say "Build complete!" 4 + # ac-say --blink green "Success" 5 + # ac-say --no-blink "quiet message" 6 + # echo "done" | ac-say 7 + 8 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 9 + BLINK_COLORS="red green" 10 + DO_BLINK=1 11 + 12 + # Parse flags 13 + while [ $# -gt 0 ]; do 14 + case "$1" in 15 + --blink) BLINK_COLORS="$2"; shift 2 ;; 16 + --no-blink) DO_BLINK=0; shift ;; 17 + -v|--voice) export VOICE="$2"; shift 2 ;; 18 + -h|--help) 19 + echo "Usage: ac-say [--blink 'colors'] [--no-blink] [-v voice] \"text\"" 20 + echo " echo \"text\" | ac-say" 21 + echo "" 22 + echo "Speaks text and flashes the screen on the Docker host." 23 + echo " --blink 'red green' Set flash colors (default: red green)" 24 + echo " --no-blink TTS only, no screen flash" 25 + echo " -v flite|espeak TTS engine (default: flite)" 26 + exit 0 ;; 27 + --) shift; break ;; 28 + -*) echo "Unknown flag: $1" >&2; exit 1 ;; 29 + *) break ;; 30 + esac 31 + done 32 + 33 + # Get text from args or stdin 34 + TEXT="$*" 35 + if [ -z "${TEXT}" ]; then 36 + TEXT=$(cat) 37 + fi 38 + 39 + if [ -z "${TEXT}" ]; then 40 + echo "ac-say: no text provided" >&2 41 + exit 1 42 + fi 43 + 44 + # Flash screen (background, non-blocking) 45 + if [ ${DO_BLINK} -eq 1 ]; then 46 + "${SCRIPT_DIR}/ac-blink" ${BLINK_COLORS} & 47 + fi 48 + 49 + # Speak 50 + "${SCRIPT_DIR}/ac-talk" "${TEXT}"
+989
fedac/native/scripts/build-and-flash-initramfs.sh
··· 1 + #!/bin/bash 2 + # build-and-flash.sh — Build ac-native from scratch and optionally flash to USB 3 + # 4 + # Works in Docker/Codespaces (no losetup/mount needed — uses mtools). 5 + # 6 + # Usage: 7 + # ./build-and-flash.sh # Build kernel only 8 + # ./build-and-flash.sh --flash /dev/sdb # Build + flash to USB 9 + # ./build-and-flash.sh --skip-kernel --flash /dev/sdb # Rebuild binary/initramfs, reuse kernel source 10 + # ./build-and-flash.sh --skip-binary --flash /dev/sdb # Reuse binary, rebuild initramfs+kernel 11 + # 12 + # Requirements: cpio lz4 jq musl-gcc (or gcc) libdrm-devel alsa-lib-devel 13 + # mtools (for --flash) 14 + 15 + set -euo pipefail 16 + 17 + RED='\033[0;31m' 18 + GREEN='\033[0;32m' 19 + YELLOW='\033[1;33m' 20 + NC='\033[0m' 21 + 22 + log() { echo -e "${GREEN}[build]${NC} $*"; } 23 + warn() { echo -e "${YELLOW}[build]${NC} $*"; } 24 + err() { echo -e "${RED}[build]${NC} $*" >&2; } 25 + 26 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 27 + NATIVE_DIR="$(dirname "${SCRIPT_DIR}")" 28 + BUILD_DIR="${NATIVE_DIR}/build" 29 + KERNEL_DIR="${NATIVE_DIR}/kernel" 30 + 31 + FLASH_DEV="" 32 + SKIP_KERNEL=0 33 + SKIP_BINARY=0 34 + USE_SDL=0 35 + PIECE_PATH="${NATIVE_DIR}/pieces/prompt.mjs" 36 + KERNEL_VERSION="${KERNEL_VERSION:-6.14.2}" 37 + HANDLE="${AC_HANDLE:-jeffrey}" 38 + HANDLE_COLORS_API="${HANDLE_COLORS_API:-https://aesthetic.computer/.netlify/functions/handle-colors}" 39 + 40 + while [ $# -gt 0 ]; do 41 + case "$1" in 42 + --flash) FLASH_DEV="$2"; shift 2 ;; 43 + --skip-kernel) SKIP_KERNEL=1; shift ;; 44 + --skip-binary) SKIP_BINARY=1; shift ;; 45 + --sdl) USE_SDL=1; shift ;; 46 + --piece) PIECE_PATH="$2"; shift 2 ;; 47 + --handle) HANDLE="$2"; shift 2 ;; 48 + --help|-h) 49 + echo "Usage: $0 [--flash /dev/sdX] [--skip-kernel] [--skip-binary] [--sdl] [--piece path.mjs] [--handle your-handle]" 50 + exit 0 ;; 51 + *) err "Unknown option: $1"; exit 1 ;; 52 + esac 53 + done 54 + 55 + mkdir -p "${BUILD_DIR}" 56 + 57 + # ============================================================ 58 + # Step 1: Check dependencies 59 + # ============================================================ 60 + log "Checking dependencies..." 61 + 62 + # Auto-install required packages if missing (Fedora/dnf) 63 + if command -v dnf &>/dev/null; then 64 + PKGS_NEEDED="" 65 + # Build tools 66 + command -v cpio &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} cpio" 67 + command -v lz4 &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} lz4" 68 + command -v bc &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} bc" 69 + command -v perl &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} perl" 70 + command -v make &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} make" 71 + command -v curl &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} curl" 72 + command -v jq &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} jq" 73 + # Kernel build headers 74 + [ -f /usr/include/libelf.h ] || PKGS_NEEDED="${PKGS_NEEDED} elfutils-libelf-devel" 75 + [ -f /etc/pki/tls/certs/ca-bundle.crt ] || PKGS_NEEDED="${PKGS_NEEDED} ca-certificates" 76 + # Native binary deps 77 + pkg-config --exists alsa 2>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} alsa-lib-devel" 78 + pkg-config --exists libdrm 2>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} libdrm-devel" 79 + [ -f /usr/include/flite/flite.h ] || PKGS_NEEDED="${PKGS_NEEDED} flite-devel" 80 + # WiFi tools + firmware 81 + command -v wpa_supplicant &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} wpa_supplicant" 82 + command -v dhclient &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} dhcp-client" 83 + command -v iw &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} iw" 84 + [ -d /lib/firmware ] && ls /lib/firmware/iwlwifi-cc-a0-* &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} iwlwifi-mvm-firmware" 85 + [ -f /lib/firmware/regulatory.db ] || PKGS_NEEDED="${PKGS_NEEDED} wireless-regdb" 86 + # ffmpeg libs for video recording 87 + pkg-config --exists libavcodec 2>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} ffmpeg-free-devel" 88 + # Flash tools 89 + if [ -n "${FLASH_DEV}" ]; then 90 + command -v mmd &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} mtools" 91 + command -v mkfs.vfat &>/dev/null || PKGS_NEEDED="${PKGS_NEEDED} dosfstools" 92 + fi 93 + 94 + if [ -n "${PKGS_NEEDED}" ]; then 95 + log "Installing missing packages:${PKGS_NEEDED}" 96 + sudo dnf install -y ${PKGS_NEEDED} || err "Failed to install packages" 97 + fi 98 + fi 99 + 100 + MISSING="" 101 + for cmd in cpio lz4 make curl jq bc perl; do 102 + command -v "$cmd" &>/dev/null || MISSING="${MISSING} ${cmd}" 103 + done 104 + if [ -n "${MISSING}" ]; then 105 + err "Missing required tools:${MISSING}" 106 + exit 1 107 + fi 108 + if [ -n "${FLASH_DEV}" ]; then 109 + for cmd in mmd mcopy mkfs.vfat sfdisk; do 110 + command -v "$cmd" &>/dev/null || MISSING="${MISSING} ${cmd}" 111 + done 112 + if [ -n "${MISSING}" ]; then 113 + err "Missing flash tools:${MISSING}" 114 + exit 1 115 + fi 116 + fi 117 + 118 + # ============================================================ 119 + # Step 2: Build ac-native binary (static, musl) 120 + # ============================================================ 121 + if [ "${SKIP_BINARY}" -eq 0 ]; then 122 + log "Building ac-native binary..." 123 + cd "${NATIVE_DIR}" 124 + CC_USE="${CC:-}" 125 + if [ -z "${CC_USE}" ]; then 126 + # Use musl-gcc for static linking only if it has linux/input.h 127 + if command -v musl-gcc &>/dev/null \ 128 + && echo '#include <linux/input.h>' | musl-gcc -E -x c - &>/dev/null; then 129 + CC_USE=musl-gcc 130 + else 131 + CC_USE=gcc 132 + fi 133 + fi 134 + MAKE_ARGS="CC=${CC_USE}" 135 + if [ "${USE_SDL}" -eq 1 ]; then 136 + MAKE_ARGS="${MAKE_ARGS} USE_SDL=1" 137 + fi 138 + # Only request static linking when using musl-gcc (gcc needs .so files) 139 + if [ "${CC_USE}" = "musl-gcc" ]; then 140 + MAKE_ARGS="${MAKE_ARGS} STATIC=1" 141 + fi 142 + make ${MAKE_ARGS} 143 + log "Binary: $(wc -c < "${BUILD_DIR}/ac-native") bytes" 144 + else 145 + log "Skipping binary build" 146 + fi 147 + 148 + if [ ! -f "${BUILD_DIR}/ac-native" ]; then 149 + err "ac-native binary not found. Run without --skip-binary." 150 + exit 1 151 + fi 152 + 153 + # ============================================================ 154 + # Step 3: Create initramfs 155 + # ============================================================ 156 + log "Creating initramfs..." 157 + INITRAMFS_DIR="${BUILD_DIR}/initramfs-root" 158 + 159 + # Preserve piece.mjs if it exists and no --piece override 160 + if [ -f "${INITRAMFS_DIR}/piece.mjs" ] && [ "${PIECE_PATH}" = "${INITRAMFS_DIR}/piece.mjs" ]; then 161 + PIECE_CONTENT="$(cat "${INITRAMFS_DIR}/piece.mjs")" 162 + fi 163 + 164 + rm -rf "${INITRAMFS_DIR}" 165 + mkdir -p "${INITRAMFS_DIR}"/{dev,proc,sys,tmp,mnt} 166 + 167 + # Copy binary 168 + cp "${BUILD_DIR}/ac-native" "${INITRAMFS_DIR}/ac-native" 169 + chmod +x "${INITRAMFS_DIR}/ac-native" 170 + 171 + # Copy piece 172 + if [ -n "${PIECE_CONTENT:-}" ]; then 173 + echo "${PIECE_CONTENT}" > "${INITRAMFS_DIR}/piece.mjs" 174 + elif [ -f "${PIECE_PATH}" ]; then 175 + cp "${PIECE_PATH}" "${INITRAMFS_DIR}/piece.mjs" 176 + else 177 + err "No piece.mjs found at ${PIECE_PATH}" 178 + exit 1 179 + fi 180 + 181 + # Copy all pieces to /pieces/ for system.jump() navigation 182 + PIECES_SRC="${NATIVE_DIR}/pieces" 183 + if [ -d "${PIECES_SRC}" ]; then 184 + mkdir -p "${INITRAMFS_DIR}/pieces" 185 + for p in "${PIECES_SRC}"/*.mjs; do 186 + [ -f "$p" ] && cp "$p" "${INITRAMFS_DIR}/pieces/" 187 + done 188 + log "Bundled pieces: $(ls "${INITRAMFS_DIR}/pieces/" | tr '\n' ' ')" 189 + fi 190 + 191 + # Copy web pieces that run unmodified on native (Wave 1 + clock) 192 + AC_DISKS_DIR="${NATIVE_DIR}/../../system/public/aesthetic.computer/disks" 193 + for web_piece in clock.mjs 3x3.mjs 404.mjs beat.mjs brick-breaker.mjs \ 194 + dync.mjs error.mjs gostop.mjs hop.mjs shh.mjs chart.mjs \ 195 + f3ral3xp.mjs hw.mjs ptt.mjs; do 196 + [ -f "${AC_DISKS_DIR}/${web_piece}" ] && cp "${AC_DISKS_DIR}/${web_piece}" "${INITRAMFS_DIR}/pieces/" 197 + done 198 + log "Bundled web pieces: $(ls "${INITRAMFS_DIR}/pieces/" | grep -c '.mjs') total" 199 + 200 + # Copy shared JS libraries needed by pieces (pure JS, no browser deps) 201 + AC_LIB_DIR="${NATIVE_DIR}/../../system/public/aesthetic.computer/lib" 202 + mkdir -p "${INITRAMFS_DIR}/lib" 203 + for lib_file in melody-parser.mjs notepat-convert.mjs note-colors.mjs; do 204 + if [ -f "${AC_LIB_DIR}/${lib_file}" ]; then 205 + cp "${AC_LIB_DIR}/${lib_file}" "${INITRAMFS_DIR}/lib/" 206 + fi 207 + done 208 + log "Bundled JS libs: $(ls "${INITRAMFS_DIR}/lib/"*.mjs 2>/dev/null | xargs -n1 basename | tr '\n' ' ')" 209 + 210 + # Bundle initramfs scripts (upload-log.sh etc.) 211 + SCRIPTS_SRC="${NATIVE_DIR}/initramfs-scripts" 212 + if [ -d "${SCRIPTS_SRC}" ]; then 213 + mkdir -p "${INITRAMFS_DIR}/scripts" 214 + cp "${SCRIPTS_SRC}"/*.sh "${INITRAMFS_DIR}/scripts/" 2>/dev/null || true 215 + chmod +x "${INITRAMFS_DIR}/scripts/"*.sh 2>/dev/null || true 216 + fi 217 + 218 + # Bake default config.json into initramfs (handle + colors) 219 + # This ensures "hi @handle" shows even without USB flash path or /mnt/config.json 220 + HANDLE_CLEAN="${HANDLE#@}" 221 + [ -z "${HANDLE_CLEAN}" ] && HANDLE_CLEAN="jeffrey" 222 + INITRAMFS_CONFIG="${INITRAMFS_DIR}/default-config.json" 223 + COLORS_JSON="" 224 + if [ -n "${HANDLE_CLEAN}" ]; then 225 + HANDLE_URI="$(printf '%s' "${HANDLE_CLEAN}" | jq -sRr @uri 2>/dev/null || echo "${HANDLE_CLEAN}")" 226 + COLORS_RESP="$(curl -fsSL --connect-timeout 5 --max-time 12 \ 227 + "${HANDLE_COLORS_API}?handle=${HANDLE_URI}" 2>/dev/null || true)" 228 + if [ -n "${COLORS_RESP}" ]; then 229 + COLORS_JSON="$(printf '%s' "${COLORS_RESP}" | jq -c '.colors // empty' 2>/dev/null || true)" 230 + [ "${COLORS_JSON}" = "null" ] && COLORS_JSON="" 231 + fi 232 + fi 233 + if [ -n "${COLORS_JSON}" ] && printf '%s' "${COLORS_JSON}" | jq -e 'type == "array"' >/dev/null 2>&1; then 234 + jq -cn --arg handle "${HANDLE_CLEAN}" --argjson colors "${COLORS_JSON}" \ 235 + '{handle:$handle, colors:$colors}' > "${INITRAMFS_CONFIG}" 236 + log "Baked config.json (handle: ${HANDLE_CLEAN}, colors: $(printf '%s' "${COLORS_JSON}" | jq 'length'))" 237 + else 238 + jq -cn --arg handle "${HANDLE_CLEAN}" '{handle:$handle}' > "${INITRAMFS_CONFIG}" 239 + log "Baked config.json (handle: ${HANDLE_CLEAN}, no colors)" 240 + fi 241 + 242 + # KidLisp bundling is handled by ac-os (runs as user, has npx in PATH). 243 + # build-and-flash.sh runs under sudo where npx is unavailable. 244 + mkdir -p "${INITRAMFS_DIR}/jslib" 245 + 246 + # Always create lib64 — even if ac-native is static, other bundled tools 247 + # (curl, iw, wpa_supplicant, busybox, git, jq) are dynamically linked. 248 + mkdir -p "${INITRAMFS_DIR}/lib64" 249 + 250 + # Copy shared libs (if dynamic build) 251 + if file "${BUILD_DIR}/ac-native" | grep -q "dynamically linked"; then 252 + log "Copying shared libraries for dynamic binary..." 253 + for lib in $(ldd "${BUILD_DIR}/ac-native" | grep -oP '/\S+'); do 254 + [ -f "$lib" ] && cp "$lib" "${INITRAMFS_DIR}/lib64/" 255 + done 256 + # Symlink /lib -> /lib64 (some lookups use /lib) 257 + ln -sf lib64 "${INITRAMFS_DIR}/lib" 258 + fi 259 + 260 + # Copy SDL2 + Mesa GPU libraries (if --sdl) 261 + if [ "${USE_SDL}" -eq 1 ]; then 262 + log "Bundling SDL2 + Mesa GPU stack..." 263 + mkdir -p "${INITRAMFS_DIR}/lib64/dri" 264 + 265 + # SDL2-compat + SDL3 (Fedora uses sdl2-compat over SDL3) 266 + for lib in libSDL2-2.0.so.0 libSDL3.so.0; do 267 + src=$(readlink -f "/usr/lib64/${lib}" 2>/dev/null) 268 + [ -f "$src" ] && cp "$src" "${INITRAMFS_DIR}/lib64/" && ln -sf "$(basename "$src")" "${INITRAMFS_DIR}/lib64/${lib}" 269 + done 270 + 271 + # EGL + GLES + GBM (dlopen'd by SDL3 KMSDRM backend) 272 + for lib in libEGL.so.1 libEGL_mesa.so.0 libGLESv2.so.2 libgbm.so.1 libGL.so.1 libGLX_mesa.so.0 libGLdispatch.so.0 libglapi.so.0; do 273 + src=$(readlink -f "/usr/lib64/${lib}" 2>/dev/null) 274 + [ -f "$src" ] && cp -n "$src" "${INITRAMFS_DIR}/lib64/" 2>/dev/null && ln -sf "$(basename "$src")" "${INITRAMFS_DIR}/lib64/${lib}" 2>/dev/null 275 + done 276 + 277 + # Mesa gallium (monolithic driver — contains iris/i915/swrast) 278 + GALLIUM=$(ls /usr/lib64/libgallium-*.so 2>/dev/null | head -1) 279 + [ -f "$GALLIUM" ] && cp "$GALLIUM" "${INITRAMFS_DIR}/lib64/" 280 + 281 + # DRI driver stubs (Mesa loads these, which then load libgallium) 282 + for drv in iris_dri.so i915_dri.so kms_swrast_dri.so swrast_dri.so libdril_dri.so; do 283 + src="/usr/lib64/dri/${drv}" 284 + if [ -L "$src" ]; then 285 + # Copy as symlink 286 + tgt=$(readlink "$src") 287 + ln -sf "$tgt" "${INITRAMFS_DIR}/lib64/dri/${drv}" 288 + elif [ -f "$src" ]; then 289 + cp "$src" "${INITRAMFS_DIR}/lib64/dri/" 290 + fi 291 + done 292 + 293 + # libexpat (needed by Mesa DRI loader) 294 + for lib in libexpat.so.1; do 295 + src=$(readlink -f "/usr/lib64/${lib}" 2>/dev/null) 296 + [ -f "$src" ] && cp -n "$src" "${INITRAMFS_DIR}/lib64/" 2>/dev/null && ln -sf "$(basename "$src")" "${INITRAMFS_DIR}/lib64/${lib}" 2>/dev/null 297 + done 298 + 299 + GPU_SIZE=$(du -sh "${INITRAMFS_DIR}/lib64/libgallium"* "${INITRAMFS_DIR}/lib64/libSDL"* "${INITRAMFS_DIR}/lib64/libEGL"* "${INITRAMFS_DIR}/lib64/dri/" 2>/dev/null | tail -1 | cut -f1) 300 + log "SDL2 + Mesa GPU stack bundled (gallium: $(du -sh "${GALLIUM}" 2>/dev/null | cut -f1))" 301 + fi 302 + 303 + # Copy ALSA config files (required for snd_pcm_open to resolve device names) 304 + if [ -d "/usr/share/alsa" ]; then 305 + log "Copying ALSA config files..." 306 + mkdir -p "${INITRAMFS_DIR}/usr/share/alsa" 307 + cp -r /usr/share/alsa/* "${INITRAMFS_DIR}/usr/share/alsa/" 308 + log "ALSA config: $(du -sh "${INITRAMFS_DIR}/usr/share/alsa" | cut -f1)" 309 + else 310 + warn "No /usr/share/alsa found — audio may not work" 311 + fi 312 + 313 + # Copy WiFi components (wpa_supplicant, dhclient, iw, ip, firmware) 314 + # Resolve actual binary paths (location varies by distro: /usr/sbin vs /usr/bin) 315 + resolve_bin() { command -v "$1" 2>/dev/null || true; } 316 + WIFI_BINS=( 317 + "$(resolve_bin wpa_supplicant)" 318 + "$(resolve_bin wpa_cli)" 319 + "$(resolve_bin dhclient)" 320 + "$(resolve_bin iw)" 321 + "$(resolve_bin ip)" 322 + ) 323 + WIFI_COPIED=0 324 + for bin in "${WIFI_BINS[@]}"; do 325 + if [ -f "$bin" ]; then 326 + mkdir -p "${INITRAMFS_DIR}/$(dirname "$bin")" 327 + cp "$bin" "${INITRAMFS_DIR}${bin}" 328 + chmod +x "${INITRAMFS_DIR}${bin}" 329 + WIFI_COPIED=$((WIFI_COPIED + 1)) 330 + # Copy their shared libraries too 331 + for lib in $(ldd "$bin" 2>/dev/null | grep -oP '/\S+'); do 332 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 333 + done 334 + fi 335 + done 336 + if [ "${WIFI_COPIED}" -gt 0 ]; then 337 + log "WiFi binaries: ${WIFI_COPIED} copied" 338 + 339 + # Copy Intel WiFi firmware (only latest version per card to save space) 340 + # 9260 = 9260-th-b0-jf-b0, AX200 = cc-a0, AX201 = QuZ-a0-hr-b0 / QuZ-a0-jf-b0 341 + mkdir -p "${INITRAMFS_DIR}/lib/firmware" 342 + # Regulatory database (required for WiFi scanning) 343 + for rdb in regulatory.db regulatory.db.p7s; do 344 + [ -f "/lib/firmware/$rdb" ] && cp "/lib/firmware/$rdb" "${INITRAMFS_DIR}/lib/firmware/" 345 + done 346 + for pattern in "iwlwifi-9260-th-b0-jf-b0-*" "iwlwifi-cc-a0-*" "iwlwifi-QuZ-a0-hr-b0-*" "iwlwifi-QuZ-a0-jf-b0-*"; do 347 + # Get the latest (highest version number) firmware file 348 + fw=$(ls -v /lib/firmware/${pattern}.ucode* 2>/dev/null | tail -1 || true) 349 + if [ -n "$fw" ] && [ -f "$fw" ]; then 350 + dest="${INITRAMFS_DIR}/lib/firmware/$(basename "${fw%.xz}")" 351 + xz -dk "$fw" -c > "$dest" 2>/dev/null || cp "$fw" "$dest" 352 + log " firmware: $(basename "$dest") ($(du -sh "$dest" | cut -f1))" 353 + fi 354 + done 355 + FW_SIZE=$(du -sh "${INITRAMFS_DIR}/lib/firmware" 2>/dev/null | cut -f1) 356 + log "WiFi firmware: ${FW_SIZE:-0}" 357 + 358 + # Copy extra firmware blobs from source tree (i915 GPU, Intel SOF audio, etc.) 359 + EXTRA_FW_DIR="${SCRIPT_DIR}/../firmware" 360 + if [ -d "${EXTRA_FW_DIR}" ]; then 361 + cp -r "${EXTRA_FW_DIR}/"* "${INITRAMFS_DIR}/lib/firmware/" 2>/dev/null || true 362 + EXTRA_FW_SIZE=$(du -sh "${INITRAMFS_DIR}/lib/firmware" 2>/dev/null | cut -f1) 363 + log "Total firmware (with source tree blobs): ${EXTRA_FW_SIZE}" 364 + fi 365 + 366 + # Copy ALL i915 GPU firmware from host (supports Kaby Lake, Coffee Lake, etc.) 367 + # The source tree only has MTL/ARL blobs; the oven has firmware for all generations. 368 + if [ -d "/lib/firmware/i915" ]; then 369 + mkdir -p "${INITRAMFS_DIR}/lib/firmware/i915" 370 + I915_COUNT=0 371 + for fw in /lib/firmware/i915/*; do 372 + [ -f "$fw" ] || continue 373 + dest="${INITRAMFS_DIR}/lib/firmware/i915/$(basename "${fw%.xz}")" 374 + dest="${dest%.zst}" 375 + if [ ! -f "$dest" ]; then 376 + case "$fw" in 377 + *.xz) xz -dk "$fw" -c > "$dest" 2>/dev/null || cp "$fw" "$dest" ;; 378 + *.zst) zstd -d "$fw" -o "$dest" 2>/dev/null || cp "$fw" "$dest" ;; 379 + *) cp "$fw" "$dest" ;; 380 + esac 381 + I915_COUNT=$((I915_COUNT + 1)) 382 + fi 383 + done 384 + log " i915 firmware: ${I915_COUNT} new blobs from /lib/firmware/i915/" 385 + fi 386 + 387 + # Copy Intel SOF audio firmware from host (topology + IPC files) 388 + for sof_dir in /lib/firmware/intel/sof /lib/firmware/intel/sof-ipc4 \ 389 + /lib/firmware/intel/sof-tplg /lib/firmware/intel/sof-ipc4-tplg; do 390 + if [ -d "$sof_dir" ]; then 391 + dest_dir="${INITRAMFS_DIR}/lib/firmware/intel/$(basename "$sof_dir")" 392 + mkdir -p "$dest_dir" 393 + for fw in "$sof_dir"/*; do 394 + [ -f "$fw" ] || continue 395 + dest="$dest_dir/$(basename "${fw%.xz}")" 396 + dest="${dest%.zst}" 397 + if [ ! -f "$dest" ]; then 398 + case "$fw" in 399 + *.xz) xz -dk "$fw" -c > "$dest" 2>/dev/null || cp "$fw" "$dest" ;; 400 + *.zst) zstd -d "$fw" -o "$dest" 2>/dev/null || cp "$fw" "$dest" ;; 401 + *) cp "$fw" "$dest" ;; 402 + esac 403 + fi 404 + done 405 + fi 406 + done 407 + SOF_SIZE=$(du -sh "${INITRAMFS_DIR}/lib/firmware/intel" 2>/dev/null | cut -f1 || true) 408 + log " Intel SOF audio firmware: ${SOF_SIZE:-none}" 409 + 410 + TOTAL_FW_SIZE=$(du -sh "${INITRAMFS_DIR}/lib/firmware" 2>/dev/null | cut -f1) 411 + log "Total firmware (all generations): ${TOTAL_FW_SIZE}" 412 + 413 + # Copy flite TTS libraries (core + slt female + kal male voices) 414 + for flib in libflite.so.2.2 libflite_cmulex.so.2.2 libflite_usenglish.so.2.2 libflite_cmu_us_slt.so.2.2 libflite_cmu_us_kal.so.2.2; do 415 + src="" 416 + for dir in /usr/lib64 /usr/lib/x86_64-linux-gnu; do 417 + [ -f "${dir}/${flib}" ] && src="${dir}/${flib}" && break 418 + done 419 + if [ -n "$src" ]; then 420 + cp "$src" "${INITRAMFS_DIR}/lib64/" 421 + # Create soname symlinks 422 + ln -sf "$flib" "${INITRAMFS_DIR}/lib64/$(echo $flib | sed 's/\.so\..*/\.so/')" 2>/dev/null || true 423 + fi 424 + done 425 + FLITE_SIZE=$(du -sh "${INITRAMFS_DIR}/lib64/libflite"* 2>/dev/null | tail -1 | cut -f1 || true) 426 + log "Flite TTS: ${FLITE_SIZE:-not found}" 427 + 428 + # Create /var/run for wpa_supplicant 429 + mkdir -p "${INITRAMFS_DIR}/var/run/wpa_supplicant" 430 + 431 + # Minimal dhclient-script: configure interface after DHCP lease obtained 432 + # dhclient requires this at /sbin/dhclient-script (or via -sf flag) 433 + mkdir -p "${INITRAMFS_DIR}/sbin" 434 + cat > "${INITRAMFS_DIR}/sbin/dhclient-script" << 'DHCLIENT_SCRIPT' 435 + #!/bin/sh 436 + # Minimal dhclient hook for ac-native bare metal 437 + case "$reason" in 438 + BOUND|RENEW|REBIND|REBOOT) 439 + ip addr flush dev "$interface" 2>/dev/null 440 + ip addr add "$new_ip_address/$new_subnet_mask" dev "$interface" 2>/dev/null 441 + [ -n "$new_routers" ] && ip route add default via "$new_routers" dev "$interface" 2>/dev/null 442 + mkdir -p /etc 443 + { 444 + # Use DHCP-provided DNS if available, always add public fallbacks 445 + [ -n "$new_domain_name_servers" ] && printf 'nameserver %s\n' $new_domain_name_servers 446 + printf 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n' 447 + } > /etc/resolv.conf 448 + echo "[dhclient] BOUND $interface ip=$new_ip_address dns=$new_domain_name_servers" >> /tmp/dhclient.log 449 + ;; 450 + EXPIRE|FAIL|RELEASE|STOP) 451 + ip addr flush dev "$interface" 2>/dev/null 452 + echo "[dhclient] $reason $interface" >> /tmp/dhclient.log 453 + ;; 454 + esac 455 + exit 0 456 + DHCLIENT_SCRIPT 457 + chmod +x "${INITRAMFS_DIR}/sbin/dhclient-script" 458 + log "dhclient-script: installed" 459 + 460 + # Need /bin/sh for system() calls — use busybox or link to bash if available 461 + mkdir -p "${INITRAMFS_DIR}/bin" 462 + if command -v busybox &>/dev/null; then 463 + cp "$(command -v busybox)" "${INITRAMFS_DIR}/bin/busybox" 464 + ln -sf busybox "${INITRAMFS_DIR}/bin/sh" 465 + for lib in $(ldd "$(command -v busybox)" 2>/dev/null | grep -oP '/\S+'); do 466 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 467 + done 468 + # Busybox applet symlinks — covers find, cp, mv, rm, ln, wc, tail, sort, 469 + # uniq, tr, tee, xargs, diff, stat, ps, tar, gzip, touch, env, basename, 470 + # dirname, expr, test, printf, date, dd, df, du, echo, true, false, etc. 471 + for cmd in find cp mv rm ln wc tail sort uniq tr tee xargs diff stat ps \ 472 + tar gzip gunzip touch env basename dirname expr test printf \ 473 + date dd df du echo true false readlink mktemp vi hostname \ 474 + id whoami chown rmdir realpath seq yes nohup md5sum sha256sum; do 475 + ln -sf busybox "${INITRAMFS_DIR}/bin/${cmd}" 2>/dev/null || true 476 + done 477 + log " busybox: $(du -sh "${INITRAMFS_DIR}/bin/busybox" | cut -f1) ($(ls "${INITRAMFS_DIR}/bin/" | wc -l) symlinks)" 478 + elif [ -f /bin/bash ]; then 479 + cp /bin/bash "${INITRAMFS_DIR}/bin/sh" 480 + for lib in $(ldd /bin/bash 2>/dev/null | grep -oP '/\S+'); do 481 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 482 + done 483 + fi 484 + 485 + # Claude Code needs /bin/bash — symlink to sh (busybox ash is close enough) 486 + [ -f "${INITRAMFS_DIR}/bin/sh" ] && ln -sf sh "${INITRAMFS_DIR}/bin/bash" 487 + 488 + # BPF trace tool (if built) 489 + BPF_TRACE="${NATIVE_DIR}/bpf/ac-trace" 490 + if [ -f "${BPF_TRACE}" ]; then 491 + cp "${BPF_TRACE}" "${INITRAMFS_DIR}/bin/ac-trace" 492 + chmod +x "${INITRAMFS_DIR}/bin/ac-trace" 493 + log " ac-trace: $(du -sh "${INITRAMFS_DIR}/bin/ac-trace" | cut -f1)" 494 + fi 495 + 496 + # Symlink WiFi binaries into /bin/ so system() calls find them via PATH 497 + for bin in "${WIFI_BINS[@]}"; do 498 + bname="$(basename "$bin")" 499 + [ -f "${INITRAMFS_DIR}${bin}" ] && ln -sf "$bin" "${INITRAMFS_DIR}/bin/${bname}" 2>/dev/null || true 500 + done 501 + 502 + # Need basic utilities for shell commands (grep, awk, pgrep, killall, ls, rfkill, curl, etc.) 503 + # efibootmgr: sets UEFI boot order after OTA flash (prevents stale vendor boot entries) 504 + for util in grep awk sed pgrep killall cat ls head cut rfkill which curl sleep mkdir chmod sfdisk mkfs.vfat efibootmgr; do 505 + UTIL_PATH="$(command -v "$util" 2>/dev/null || true)" 506 + if [ -n "$UTIL_PATH" ] && [ -f "$UTIL_PATH" ]; then 507 + cp "$UTIL_PATH" "${INITRAMFS_DIR}/bin/" 508 + for lib in $(ldd "$UTIL_PATH" 2>/dev/null | grep -oP '/\S+'); do 509 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 510 + done 511 + fi 512 + done 513 + else 514 + warn "No WiFi binaries found — WiFi will not work" 515 + fi 516 + 517 + # ── SSH daemon (dropbear) for remote access ── 518 + DROPBEAR_BIN="$(command -v dropbear 2>/dev/null || true)" 519 + DROPBEARKEY_BIN="$(command -v dropbearkey 2>/dev/null || true)" 520 + if [ -n "$DROPBEAR_BIN" ] && [ -n "$DROPBEARKEY_BIN" ]; then 521 + log "Bundling dropbear SSH daemon..." 522 + mkdir -p "${INITRAMFS_DIR}/usr/sbin" "${INITRAMFS_DIR}/etc/dropbear" 523 + cp "$DROPBEAR_BIN" "${INITRAMFS_DIR}/usr/sbin/dropbear" 524 + cp "$DROPBEARKEY_BIN" "${INITRAMFS_DIR}/usr/sbin/dropbearkey" 525 + chmod +x "${INITRAMFS_DIR}/usr/sbin/dropbear" "${INITRAMFS_DIR}/usr/sbin/dropbearkey" 526 + ln -sf /usr/sbin/dropbear "${INITRAMFS_DIR}/bin/dropbear" 2>/dev/null || true 527 + ln -sf /usr/sbin/dropbearkey "${INITRAMFS_DIR}/bin/dropbearkey" 2>/dev/null || true 528 + for lib in $(ldd "$DROPBEAR_BIN" 2>/dev/null | grep -oP '/\S+'); do 529 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 530 + done 531 + # Create passwd/group so dropbear can resolve uid 0 532 + echo "root:x:0:0:root:/:/bin/sh" > "${INITRAMFS_DIR}/etc/passwd" 533 + echo "root:x:0:" > "${INITRAMFS_DIR}/etc/group" 534 + log " dropbear: $(du -sh "${INITRAMFS_DIR}/usr/sbin/dropbear" | cut -f1)" 535 + else 536 + warn "dropbear not found — SSH remote access not available" 537 + fi 538 + 539 + # ── Claude Code utilities (git, ripgrep, jq) ── 540 + # These tools make Claude Code significantly more capable on the device. 541 + for util in git rg jq; do 542 + UTIL_PATH="$(command -v "$util" 2>/dev/null || true)" 543 + if [ -n "$UTIL_PATH" ] && [ -f "$UTIL_PATH" ]; then 544 + cp "$UTIL_PATH" "${INITRAMFS_DIR}/bin/" 545 + for lib in $(ldd "$UTIL_PATH" 2>/dev/null | grep -oP '/\S+'); do 546 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 547 + done 548 + log " ${util}: $(du -sh "${INITRAMFS_DIR}/bin/${util}" | cut -f1)" 549 + else 550 + warn "${util} not found — Claude Code will have reduced capability" 551 + fi 552 + done 553 + 554 + # ── Claude Code native binary ── 555 + # Download or use cached Bun SEA binary (no Node.js required on device) 556 + CLAUDE_CACHE="${BUILD_DIR}/claude-native" 557 + CLAUDE_GCS="https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases" 558 + if [ ! -f "${CLAUDE_CACHE}" ] || [ "$(find "${CLAUDE_CACHE}" -mtime +7 2>/dev/null)" ]; then 559 + CLAUDE_VER=$(curl -fsSL "${CLAUDE_GCS}/latest" 2>/dev/null || true) 560 + if [ -n "${CLAUDE_VER}" ]; then 561 + log "Downloading Claude Code ${CLAUDE_VER}..." 562 + curl -fsSL "${CLAUDE_GCS}/${CLAUDE_VER}/linux-x64/claude" -o "${CLAUDE_CACHE}.tmp" && \ 563 + mv "${CLAUDE_CACHE}.tmp" "${CLAUDE_CACHE}" && \ 564 + chmod +x "${CLAUDE_CACHE}" 565 + fi 566 + fi 567 + if [ -f "${CLAUDE_CACHE}" ]; then 568 + cp "${CLAUDE_CACHE}" "${INITRAMFS_DIR}/bin/claude" 569 + chmod +x "${INITRAMFS_DIR}/bin/claude" 570 + # DO NOT strip — Bun SEA binary; stripping destroys embedded JS 571 + for lib in $(ldd "${CLAUDE_CACHE}" 2>/dev/null | grep -oP '/\S+'); do 572 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 573 + done 574 + log " claude: $(du -sh "${INITRAMFS_DIR}/bin/claude" | cut -f1) (native binary)" 575 + else 576 + warn "Claude Code binary not available — Claude will not work on device" 577 + fi 578 + 579 + # Git credential helper — reads GitHub PAT from /github-pat (baked by ac-os) 580 + cat > "${INITRAMFS_DIR}/bin/git-credential-ac" << 'GIT_CRED' 581 + #!/bin/sh 582 + # Serves GitHub PAT for git HTTPS auth on AC Native OS 583 + case "$1" in 584 + get) 585 + while IFS= read -r line; do 586 + case "$line" in host=github.com*) ;; *) continue ;; esac 587 + done 588 + [ -f /github-pat ] || exit 1 589 + echo "protocol=https" 590 + echo "host=github.com" 591 + echo "username=x-access-token" 592 + printf "password=%s\n" "$(cat /github-pat | tr -d '\n')" 593 + ;; 594 + esac 595 + GIT_CRED 596 + chmod +x "${INITRAMFS_DIR}/bin/git-credential-ac" 597 + 598 + # Copy CA trust bundle for HTTPS curl calls (OS update/clock/version checks). 599 + CA_BUNDLE_SRC="" 600 + for p in /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt; do 601 + if [ -f "$p" ]; then 602 + CA_BUNDLE_SRC="$p" 603 + break 604 + fi 605 + done 606 + if [ -n "${CA_BUNDLE_SRC}" ]; then 607 + mkdir -p "${INITRAMFS_DIR}/etc/pki/tls/certs" \ 608 + "${INITRAMFS_DIR}/etc/pki/tls" \ 609 + "${INITRAMFS_DIR}/etc/ssl/certs" 610 + cp "${CA_BUNDLE_SRC}" "${INITRAMFS_DIR}/etc/pki/tls/certs/ca-bundle.crt" 611 + cp "${CA_BUNDLE_SRC}" "${INITRAMFS_DIR}/etc/pki/tls/cert.pem" 612 + cp "${CA_BUNDLE_SRC}" "${INITRAMFS_DIR}/etc/ssl/certs/ca-certificates.crt" 613 + log "CA bundle installed: /etc/pki/tls/certs/ca-bundle.crt" 614 + else 615 + warn "No CA bundle found — HTTPS fetches may fail" 616 + fi 617 + 618 + # Bundle ac-native's own shared library dependencies (needed when built dynamically) 619 + # Copies each .so to its canonical path inside initramfs, preserving directory structure 620 + if [ -f "${BUILD_DIR}/ac-native" ]; then 621 + for lib in $(ldd "${BUILD_DIR}/ac-native" 2>/dev/null | grep -oP '/[^ ()]+\.so[^ ()]*'); do 622 + [ -f "$lib" ] || continue 623 + dest_dir="${INITRAMFS_DIR}$(dirname "$lib")" 624 + mkdir -p "$dest_dir" 625 + cp -n "$lib" "$dest_dir/" 2>/dev/null || true 626 + # Also create unversioned .so symlink so dlopen() finds it 627 + base="$(basename "$lib")" 628 + novers="$(echo "$base" | sed 's/\.so\..*/\.so/')" 629 + [ "$novers" != "$base" ] && ln -sf "$base" "$dest_dir/$novers" 2>/dev/null || true 630 + done 631 + log "Bundled $(ldd "${BUILD_DIR}/ac-native" 2>/dev/null | grep -c '\.so') shared libs" 632 + fi 633 + 634 + # ── Wayland compositor stack (cage + seatd + Mesa GPU) ── 635 + # cage: Wayland kiosk compositor — runs ac-native as a Wayland client 636 + # seatd: seat daemon required by wlroots/cage for DRM access 637 + # These enable browser popups (OAuth), GPU acceleration, and multi-client Wayland. 638 + CAGE_BIN="$(command -v cage 2>/dev/null || true)" 639 + SEATD_BIN="$(command -v seatd 2>/dev/null || true)" 640 + 641 + if [ -n "${CAGE_BIN}" ] && [ -f "${CAGE_BIN}" ]; then 642 + log "Bundling cage Wayland compositor..." 643 + cp "${CAGE_BIN}" "${INITRAMFS_DIR}/bin/cage" 644 + chmod +x "${INITRAMFS_DIR}/bin/cage" 645 + for lib in $(ldd "${CAGE_BIN}" 2>/dev/null | grep -oP '/\S+'); do 646 + [ -f "$lib" ] && cp -n "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 647 + done 648 + log " cage: $(du -sh "${INITRAMFS_DIR}/bin/cage" | cut -f1)" 649 + else 650 + warn "cage not installed — Wayland compositor unavailable" 651 + fi 652 + 653 + if [ -n "${SEATD_BIN}" ] && [ -f "${SEATD_BIN}" ]; then 654 + cp "${SEATD_BIN}" "${INITRAMFS_DIR}/bin/seatd" 655 + chmod +x "${INITRAMFS_DIR}/bin/seatd" 656 + for lib in $(ldd "${SEATD_BIN}" 2>/dev/null | grep -oP '/\S+'); do 657 + [ -f "$lib" ] && cp -nL "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 658 + done 659 + log " seatd: $(du -sh "${INITRAMFS_DIR}/bin/seatd" | cut -f1)" 660 + else 661 + warn "seatd not installed — seat daemon unavailable" 662 + fi 663 + 664 + # Mesa GPU stack (EGL, GBM, DRI drivers) — needed for cage/Wayland GPU rendering 665 + # Search both Fedora (/usr/lib64/) and Debian (/usr/lib/x86_64-linux-gnu/) paths 666 + find_lib() { 667 + for dir in /usr/lib64 /usr/lib/x86_64-linux-gnu; do 668 + local f="${dir}/$1" 669 + [ -f "$f" ] && echo "$f" && return 0 670 + f="$(readlink -f "${dir}/$1" 2>/dev/null || true)" 671 + [ -f "$f" ] && echo "$f" && return 0 672 + done 673 + return 0 # not found is OK 674 + } 675 + find_dri() { 676 + for dir in /usr/lib64/dri /usr/lib/x86_64-linux-gnu/dri; do 677 + [ -e "${dir}/$1" ] && echo "${dir}/$1" && return 0 678 + done 679 + return 0 # not found is OK 680 + } 681 + 682 + log "Bundling Mesa GPU + Wayland libs..." 683 + mkdir -p "${INITRAMFS_DIR}/lib64/dri" 684 + 685 + for lib in libEGL.so.1 libEGL_mesa.so.0 libGLESv2.so.2 libgbm.so.1 libGL.so.1 \ 686 + libGLX_mesa.so.0 libGLdispatch.so.0 libglapi.so.0 \ 687 + libwayland-client.so.0 libwayland-cursor.so.0 libwayland-egl.so.1 \ 688 + libwayland-server.so.0 libxkbcommon.so.0 libinput.so.10 \ 689 + libexpat.so.1 libffi.so.8; do 690 + src="$(find_lib "$lib")" 691 + if [ -n "$src" ] && [ -f "$src" ]; then 692 + cp -nL "$src" "${INITRAMFS_DIR}/lib64/" 2>/dev/null || true 693 + ln -sf "$(basename "$src")" "${INITRAMFS_DIR}/lib64/${lib}" 2>/dev/null || true 694 + fi 695 + done 696 + 697 + # Mesa gallium (monolithic driver — contains iris/i915/swrast) 698 + GALLIUM=$(ls /usr/lib64/libgallium-*.so /usr/lib/x86_64-linux-gnu/libgallium-*.so 2>/dev/null | head -1 || true) 699 + [ -n "$GALLIUM" ] && [ -f "$GALLIUM" ] && cp "$GALLIUM" "${INITRAMFS_DIR}/lib64/" 700 + 701 + # DRI driver stubs 702 + for drv in iris_dri.so i915_dri.so kms_swrast_dri.so swrast_dri.so libdril_dri.so; do 703 + src="$(find_dri "$drv")" 704 + if [ -n "$src" ]; then 705 + if [ -L "$src" ]; then 706 + tgt=$(readlink "$src") 707 + # Copy the target file too 708 + tgt_full="$(dirname "$src")/$tgt" 709 + [ -f "$tgt_full" ] && cp -n "$tgt_full" "${INITRAMFS_DIR}/lib64/dri/" 2>/dev/null || true 710 + ln -sf "$tgt" "${INITRAMFS_DIR}/lib64/dri/${drv}" 711 + elif [ -f "$src" ]; then 712 + cp "$src" "${INITRAMFS_DIR}/lib64/dri/" 713 + fi 714 + fi 715 + done 716 + 717 + GPU_SIZE=$(du -sh "${INITRAMFS_DIR}/lib64/dri" 2>/dev/null | cut -f1 || true) 718 + GALLIUM_SIZE=$([ -n "${GALLIUM:-}" ] && du -sh "$GALLIUM" 2>/dev/null | cut -f1 || echo "none") 719 + log " Mesa GPU: dri=${GPU_SIZE:-0} gallium=${GALLIUM_SIZE}" 720 + 721 + # xkb keyboard data (needed for Wayland keyboard layout) 722 + for xkb_dir in /usr/share/X11/xkb /usr/share/xkb; do 723 + if [ -d "$xkb_dir" ]; then 724 + mkdir -p "${INITRAMFS_DIR}/usr/share/X11" 725 + cp -aL "$xkb_dir" "${INITRAMFS_DIR}/usr/share/X11/xkb" 2>/dev/null || \ 726 + cp -rL "$xkb_dir" "${INITRAMFS_DIR}/usr/share/X11/xkb" 2>/dev/null || true 727 + log " xkb: $(du -sh "${INITRAMFS_DIR}/usr/share/X11/xkb" 2>/dev/null | cut -f1)" 728 + break 729 + fi 730 + done 731 + 732 + # Init: shell script that starts seatd + cage + ac-native (Wayland compositor mode) 733 + # Falls back to direct ac-native exec if cage is not available 734 + cp "${SCRIPT_DIR}/../initramfs/init" "${INITRAMFS_DIR}/init" 735 + chmod +x "${INITRAMFS_DIR}/init" 736 + log "Init: cage+Wayland boot script installed" 737 + 738 + # ── Resolve ALL transitive shared library dependencies ── 739 + # Individual binary copies above use ldd per-binary, but miss transitive deps 740 + # (e.g. libnl-genl-3.so needed by iw/wpa_supplicant, libcurl.so.4 needed by curl). 741 + # Run ldd on every ELF binary/lib in the initramfs and copy anything still missing. 742 + log "Resolving transitive shared library dependencies..." 743 + TRANS_COPIED=0 744 + for pass in 1 2; do 745 + # Find all ELF files (binaries + shared libs) in the initramfs 746 + NEEDS_COPY=0 747 + while IFS= read -r elf_file; do 748 + [ -f "$elf_file" ] || continue 749 + # Skip non-ELF files (scripts, data) 750 + file "$elf_file" 2>/dev/null | grep -q "ELF" || continue 751 + for lib in $(ldd "$elf_file" 2>/dev/null | grep -oP '/[^ ()]+\.so[^ ()]*'); do 752 + [ -f "$lib" ] || continue 753 + base="$(basename "$lib")" 754 + # Check if already in lib64/ 755 + if [ ! -f "${INITRAMFS_DIR}/lib64/${base}" ]; then 756 + cp "$lib" "${INITRAMFS_DIR}/lib64/" 2>/dev/null && NEEDS_COPY=1 && TRANS_COPIED=$((TRANS_COPIED + 1)) 757 + # Also create unversioned .so symlink 758 + novers="$(echo "$base" | sed 's/\.so\..*/\.so/')" 759 + [ "$novers" != "$base" ] && ln -sf "$base" "${INITRAMFS_DIR}/lib64/$novers" 2>/dev/null || true 760 + fi 761 + # All libs go into lib64/ only — no canonical-path copies. 762 + # /lib is a symlink to lib64/, so duplicating into /lib/x86_64-linux-gnu/ 763 + # would create a real directory that shadows the symlink and wastes ~84 MB. 764 + done 765 + done < <(find "${INITRAMFS_DIR}" -type f \( -name "*.so*" -o -path "*/bin/*" -o -path "*/sbin/*" -o -path "*/usr/sbin/*" -o -name "ac-native" \) 2>/dev/null) 766 + # Second pass catches deps-of-deps; break early if nothing new was found 767 + [ "${NEEDS_COPY}" -eq 0 ] && break 768 + done 769 + log "Transitive deps: ${TRANS_COPIED} additional libraries copied" 770 + 771 + # ── Deduplicate shared libraries ── 772 + # The build copies libs into lib64/ (primary) but transitive deps and ldd 773 + # also populated lib/x86_64-linux-gnu/ with identical copies (~84 MB waste). 774 + # Replace duplicates with symlinks; keep lib64/ as the single source of truth. 775 + if [ -d "${INITRAMFS_DIR}/lib/x86_64-linux-gnu" ]; then 776 + DUP_SAVED=0 777 + for dup in "${INITRAMFS_DIR}/lib/x86_64-linux-gnu/"*; do 778 + [ -f "$dup" ] || continue 779 + base="$(basename "$dup")" 780 + if [ -f "${INITRAMFS_DIR}/lib64/${base}" ]; then 781 + DUP_SIZE=$(wc -c < "$dup") 782 + rm "$dup" 783 + ln -sf "../../lib64/${base}" "$dup" 784 + DUP_SAVED=$((DUP_SAVED + DUP_SIZE)) 785 + fi 786 + done 787 + log "Deduplicated lib/x86_64-linux-gnu/: saved $((DUP_SAVED / 1048576)) MB" 788 + fi 789 + 790 + # Ensure /lib64 is on the dynamic linker search path 791 + mkdir -p "${INITRAMFS_DIR}/etc" 792 + echo "/lib64" > "${INITRAMFS_DIR}/etc/ld.so.conf" 793 + echo "/lib/x86_64-linux-gnu" >> "${INITRAMFS_DIR}/etc/ld.so.conf" 794 + 795 + # Ensure /lib/x86_64-linux-gnu exists (Ubuntu binaries like iw link against this path) 796 + mkdir -p "${INITRAMFS_DIR}/lib/x86_64-linux-gnu" 797 + # Symlink all lib64 .so files into the Ubuntu canonical path 798 + for lib in "${INITRAMFS_DIR}/lib64/"*.so*; do 799 + [ -f "$lib" ] || continue 800 + base="$(basename "$lib")" 801 + [ ! -e "${INITRAMFS_DIR}/lib/x86_64-linux-gnu/${base}" ] && \ 802 + ln -sf "../../lib64/${base}" "${INITRAMFS_DIR}/lib/x86_64-linux-gnu/${base}" 2>/dev/null || true 803 + done 804 + 805 + # Generate initramfs manifest (for build parity verification between local/oven) 806 + cd "${INITRAMFS_DIR}" 807 + find . -type f -o -type l | sort | while IFS= read -r f; do 808 + if [ -L "$f" ]; then 809 + printf "L %s -> %s\n" "$f" "$(readlink "$f")" 810 + else 811 + printf "F %s %s\n" "$(wc -c < "$f")" "$f" 812 + fi 813 + done > "${BUILD_DIR}/initramfs-manifest.txt" 814 + cp "${BUILD_DIR}/initramfs-manifest.txt" "${INITRAMFS_DIR}/manifest.txt" 815 + MANIFEST_LINES=$(wc -l < "${BUILD_DIR}/initramfs-manifest.txt") 816 + log "Manifest: ${MANIFEST_LINES} entries (${BUILD_DIR}/initramfs-manifest.txt)" 817 + 818 + # Create cpio + lz4 819 + INITRAMFS_CPIO="${BUILD_DIR}/initramfs.cpio" 820 + cd "${INITRAMFS_DIR}" 821 + find . -print0 | cpio --null -ov --format=newc > "${INITRAMFS_CPIO}" 2>/dev/null 822 + lz4 -l -9 -f "${INITRAMFS_CPIO}" "${INITRAMFS_CPIO}.lz4" 823 + CPIO_SIZE=$(wc -c < "${INITRAMFS_CPIO}.lz4") 824 + log "Initramfs: ${CPIO_SIZE} bytes (LZ4)" 825 + 826 + # ============================================================ 827 + # Step 4: Build kernel with embedded initramfs 828 + # ============================================================ 829 + KERNEL_SRC="${BUILD_DIR}/linux-${KERNEL_VERSION}" 830 + VMLINUZ="${BUILD_DIR}/vmlinuz" 831 + 832 + if [ "${SKIP_KERNEL}" -eq 0 ] || [ ! -f "${VMLINUZ}" ]; then 833 + log "Building kernel ${KERNEL_VERSION} with embedded initramfs..." 834 + 835 + # Download kernel source if needed 836 + KERNEL_TAR="${BUILD_DIR}/linux-${KERNEL_VERSION}.tar.xz" 837 + if [ ! -f "${KERNEL_TAR}" ]; then 838 + log "Downloading kernel..." 839 + curl -L "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KERNEL_VERSION}.tar.xz" -o "${KERNEL_TAR}" 840 + fi 841 + if [ ! -d "${KERNEL_SRC}" ]; then 842 + log "Extracting kernel..." 843 + tar xf "${KERNEL_TAR}" -C "${BUILD_DIR}" 844 + fi 845 + 846 + # Apply config 847 + cp "${KERNEL_DIR}/config-minimal" "${KERNEL_SRC}/.config" 848 + 849 + # Ensure built-in firmware blobs exist (kernel embeds these via CONFIG_EXTRA_FIRMWARE) 850 + FIRMWARE_ABS="$(cd "${BUILD_DIR}" && pwd)/firmware" 851 + mkdir -p "${FIRMWARE_ABS}" 852 + for fw in iwlwifi-9260-th-b0-jf-b0-46.ucode iwlwifi-cc-a0-77.ucode iwlwifi-QuZ-a0-hr-b0-77.ucode iwlwifi-QuZ-a0-jf-b0-77.ucode; do 853 + if [ ! -f "${FIRMWARE_ABS}/${fw}" ]; then 854 + if [ -f "/lib/firmware/${fw}" ]; then 855 + cp "/lib/firmware/${fw}" "${FIRMWARE_ABS}/${fw}" 856 + elif [ -f "/lib/firmware/${fw}.zst" ]; then 857 + zstd -d "/lib/firmware/${fw}.zst" -o "${FIRMWARE_ABS}/${fw}" 2>/dev/null 858 + elif [ -f "/lib/firmware/${fw}.xz" ]; then 859 + xz -dk "/lib/firmware/${fw}.xz" -c > "${FIRMWARE_ABS}/${fw}" 2>/dev/null 860 + else 861 + log "WARNING: firmware ${fw} not found in /lib/firmware/" 862 + fi 863 + fi 864 + done 865 + 866 + # Fix firmware dir to match actual build location (config-minimal has a hardcoded path) 867 + sed -i "s|^CONFIG_EXTRA_FIRMWARE_DIR=.*|CONFIG_EXTRA_FIRMWARE_DIR=\"${FIRMWARE_ABS}\"|" "${KERNEL_SRC}/.config" 868 + 869 + # Set initramfs source path 870 + INITRAMFS_ABS="$(cd "${BUILD_DIR}" && pwd)/initramfs.cpio.lz4" 871 + echo "CONFIG_INITRAMFS_SOURCE=\"${INITRAMFS_ABS}\"" >> "${KERNEL_SRC}/.config" 872 + 873 + # Finalize config 874 + cd "${KERNEL_SRC}" 875 + make olddefconfig 876 + 877 + # Build 878 + JOBS="${JOBS:-$(nproc)}" 879 + log "Compiling kernel (${JOBS} jobs)..." 880 + make -j"${JOBS}" bzImage 881 + 882 + # Copy output 883 + BZIMAGE="${KERNEL_SRC}/arch/x86/boot/bzImage" 884 + if [ -f "${BZIMAGE}" ]; then 885 + cp "${BZIMAGE}" "${VMLINUZ}" 886 + else 887 + err "bzImage not found!" 888 + exit 1 889 + fi 890 + else 891 + log "Skipping kernel build (using existing vmlinuz)" 892 + fi 893 + 894 + KERNEL_SIZE=$(wc -c < "${VMLINUZ}") 895 + log "Kernel: $(( KERNEL_SIZE / 1024 / 1024 ))MB (${KERNEL_SIZE} bytes)" 896 + 897 + # ============================================================ 898 + # Step 5: Flash to USB (if --flash specified) 899 + # ============================================================ 900 + if [ -n "${FLASH_DEV}" ]; then 901 + if [ ! -b "${FLASH_DEV}" ]; then 902 + err "${FLASH_DEV} is not a block device" 903 + exit 1 904 + fi 905 + 906 + log "Flashing to ${FLASH_DEV}..." 907 + warn "This will ERASE ALL DATA on ${FLASH_DEV}" 908 + 909 + # Create GPT + EFI System Partition 910 + sfdisk "${FLASH_DEV}" << 'PART_EOF' 911 + label: gpt 912 + type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, size=512M 913 + PART_EOF 914 + sync 915 + 916 + # Wait for partition 917 + PART="${FLASH_DEV}1" 918 + for i in $(seq 1 20); do 919 + [ -b "${PART}" ] && break 920 + sleep 0.3 921 + done 922 + 923 + if [ ! -b "${PART}" ]; then 924 + err "Partition ${PART} not found" 925 + exit 1 926 + fi 927 + 928 + # Format FAT32 929 + mkfs.vfat -F 32 -n "AC-NATIVE" "${PART}" 930 + 931 + # Copy kernel + splash chainloader using mtools (no mount needed) 932 + mmd -i "${PART}" ::EFI 933 + mmd -i "${PART}" ::EFI/BOOT 934 + SPLASH_EFI="${NATIVE_DIR}/bootloader/splash.efi" 935 + if [ -f "${SPLASH_EFI}" ]; then 936 + log "Using splash chainloader (${SPLASH_EFI})" 937 + mcopy -i "${PART}" "${SPLASH_EFI}" ::EFI/BOOT/BOOTX64.EFI 938 + mcopy -i "${PART}" "${VMLINUZ}" ::EFI/BOOT/KERNEL.EFI 939 + else 940 + log "No splash chainloader found, using kernel directly" 941 + mcopy -i "${PART}" "${VMLINUZ}" ::EFI/BOOT/BOOTX64.EFI 942 + fi 943 + 944 + # Write config.json with handle + optional per-char colors from handle-colors API 945 + HANDLE_CLEAN="${HANDLE#@}" 946 + [ -z "${HANDLE_CLEAN}" ] && HANDLE_CLEAN="jeffrey" 947 + CONFIG_TMP=$(mktemp) 948 + COLORS_JSON="" 949 + if [ -n "${HANDLE_CLEAN}" ]; then 950 + HANDLE_URI="$(printf '%s' "${HANDLE_CLEAN}" | jq -sRr @uri)" 951 + COLORS_RESP="$(curl -fsSL --connect-timeout 5 --max-time 12 \ 952 + "${HANDLE_COLORS_API}?handle=${HANDLE_URI}" 2>/dev/null || true)" 953 + if [ -n "${COLORS_RESP}" ]; then 954 + COLORS_JSON="$(printf '%s' "${COLORS_RESP}" | jq -c '.colors // empty' 2>/dev/null || true)" 955 + [ "${COLORS_JSON}" = "null" ] && COLORS_JSON="" 956 + fi 957 + fi 958 + 959 + if [ -n "${COLORS_JSON}" ] && printf '%s' "${COLORS_JSON}" | jq -e 'type == "array"' >/dev/null 2>&1; then 960 + jq -cn --arg handle "${HANDLE_CLEAN}" --argjson colors "${COLORS_JSON}" \ 961 + '{handle:$handle, colors:$colors}' > "${CONFIG_TMP}" 962 + COLOR_COUNT="$(printf '%s' "${COLORS_JSON}" | jq 'length')" 963 + log "Fetched ${COLOR_COUNT} boot colors for @${HANDLE_CLEAN}" 964 + else 965 + jq -cn --arg handle "${HANDLE_CLEAN}" '{handle:$handle}' > "${CONFIG_TMP}" 966 + warn "No handle colors for @${HANDLE_CLEAN} (using fallback rainbow title)" 967 + fi 968 + 969 + mcopy -i "${PART}" "${CONFIG_TMP}" ::config.json 970 + rm -f "${CONFIG_TMP}" 971 + log "Wrote config.json (handle: ${HANDLE_CLEAN})" 972 + 973 + sync 974 + log "Flashed! Kernel at EFI/BOOT/BOOTX64.EFI on ${FLASH_DEV}" 975 + log "Remove USB and boot from it (UEFI, Secure Boot OFF)." 976 + else 977 + log "" 978 + log "=== Build complete ===" 979 + log "Kernel: ${VMLINUZ}" 980 + log "" 981 + log "To flash to USB:" 982 + log " $0 --flash /dev/sdX" 983 + log "" 984 + log "Or manually with mtools:" 985 + log " sfdisk /dev/sdX < <(echo 'label: gpt'; echo 'type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, size=512M')" 986 + log " mkfs.vfat -F 32 -n AC-NATIVE /dev/sdX1" 987 + log " mmd -i /dev/sdX1 ::EFI ::EFI/BOOT" 988 + log " mcopy -i /dev/sdX1 ${VMLINUZ} ::EFI/BOOT/BOOTX64.EFI" 989 + fi