Monorepo for Aesthetic.Computer aesthetic.computer

oven: switch native builds to Docker pipeline

native-builder.mjs now uses Docker for all kernel builds:
1. docker build (cached layers)
2. docker run (compile in container)
3. docker cp (extract vmlinuz)
4. upload-release.sh (push to CDN)

No more build-and-flash.sh, no native-cache symlinks, no musl/gcc
issues. Oven only needs Docker + git + curl.

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

+122 -20
+37 -20
oven/native-builder.mjs
··· 175 175 176 176 async function runBuildJob(job) { 177 177 try { 178 - await setupBuildCache(); 179 - 180 178 job.status = "running"; 181 179 job.startedAt = nowISO(); 182 180 job.percent = 0; 183 181 184 - // Phase 1: build vmlinuz (no --flash) 185 - const buildScript = path.join(NATIVE_DIR, "scripts/build-and-flash.sh"); 186 - await runPhase(job, "build", "bash", [buildScript, ...job.flags], NATIVE_DIR); 182 + const repoDir = path.resolve(NATIVE_DIR, "../.."); 183 + const buildName = `oven-${job.ref.slice(0, 7)}`; 184 + const vmlinuzOut = `/tmp/oven-vmlinuz-${job.id}`; 185 + 186 + // Phase 1: Docker image build (cached layers = fast) 187 + addLogLine(job, "stdout", "Phase 1: Building Docker image..."); 188 + await runPhase(job, "docker-build", "docker", [ 189 + "build", "-t", "ac-os-builder", 190 + "-f", path.join(repoDir, "fedac/native/Dockerfile.builder"), 191 + repoDir, 192 + ], repoDir); 193 + 194 + job.percent = 30; 187 195 188 - job.percent = 80; 196 + // Phase 2: Docker run → compile binary + initramfs + kernel 197 + addLogLine(job, "stdout", "Phase 2: Compiling kernel in Docker..."); 198 + const cidFile = `/tmp/oven-cid-${job.id}`; 199 + await runPhase(job, "build", "bash", ["-c", [ 200 + `CID=$(docker create -e AC_BUILD_NAME=${buildName} ac-os-builder)`, 201 + `echo $CID > ${cidFile}`, 202 + `docker start -a $CID`, 203 + ].join(" && ")], repoDir); 204 + 205 + job.percent = 75; 189 206 190 - // Phase 2: QEMU smoke test (boot kernel, check serial for success/panic) 191 - const acOs = path.join(NATIVE_DIR, "ac-os"); 192 - try { 193 - await runPhase(job, "smoke-test", "bash", [acOs, "test"], NATIVE_DIR); 194 - addLogLine(job, "stdout", " SMOKE TEST: passed"); 195 - } catch (smokeErr) { 196 - addLogLine(job, "stderr", ` SMOKE TEST: failed — ${smokeErr.message}`); 197 - // Non-fatal for now — log warning but continue upload 198 - // TODO: make this fatal once QEMU + virtio-gpu is reliable 199 - } 207 + // Phase 3: Extract vmlinuz from container 208 + addLogLine(job, "stdout", "Phase 3: Extracting kernel..."); 209 + const cid = (await fs.readFile(cidFile, "utf8")).trim(); 210 + await runPhase(job, "extract", "bash", ["-c", 211 + `docker cp ${cid}:/tmp/ac-build/vmlinuz ${vmlinuzOut} && docker rm ${cid} >/dev/null` 212 + ], repoDir); 200 213 201 - job.percent = 85; 214 + job.percent = 80; 202 215 203 - // Phase 3: upload vmlinuz to DO Spaces CDN 204 - const vmlinuz = path.join(CACHE_DIR, "vmlinuz"); 216 + // Phase 4: Upload vmlinuz to DO Spaces CDN 217 + addLogLine(job, "stdout", "Phase 4: Uploading to CDN..."); 205 218 const uploadScript = path.join(NATIVE_DIR, "scripts/upload-release.sh"); 206 - await runPhase(job, "upload", "bash", [uploadScript, vmlinuz], NATIVE_DIR, { 219 + await runPhase(job, "upload", "bash", [uploadScript, vmlinuzOut], NATIVE_DIR, { 207 220 DO_SPACES_KEY: process.env.DO_SPACES_KEY || process.env.ART_SPACES_KEY || "", 208 221 DO_SPACES_SECRET: 209 222 process.env.DO_SPACES_SECRET || process.env.ART_SPACES_SECRET || "", 210 223 }); 224 + 225 + // Cleanup 226 + try { await fs.unlink(vmlinuzOut); } catch {} 227 + try { await fs.unlink(cidFile); } catch {} 211 228 212 229 job.status = "success"; 213 230 job.stage = "done";
+85
plans/docker-ota-build-pipeline.md
··· 1 + # AC Native OS — Docker-Based OTA Build Pipeline 2 + 3 + ## Goal 4 + One build process everywhere: local devcontainer, oven server, any CI/CD. 5 + Produces identical, reproducible vmlinuz kernels with no user secrets. 6 + 7 + ## Architecture 8 + 9 + ``` 10 + Source (git) → Docker Build → vmlinuz (generic, no creds) 11 + 12 + ┌───────────┴───────────┐ 13 + ↓ ↓ 14 + ac-os upload ac-os flash 15 + (OTA to CDN) (USB + user creds) 16 + ``` 17 + 18 + ### Build (same everywhere) 19 + ```bash 20 + docker build -t ac-os-builder -f fedac/native/Dockerfile.builder . 21 + docker create -e AC_BUILD_NAME=<name> ac-os-builder 22 + docker start -a <CID> 23 + docker cp <CID>:/tmp/ac-build/vmlinuz ./vmlinuz 24 + ``` 25 + 26 + ### Upload (OTA — no creds in kernel) 27 + ```bash 28 + ac-os upload vmlinuz # Signs + pushes to DO Spaces CDN 29 + ``` 30 + 31 + ### Flash (local — adds user creds) 32 + ```bash 33 + ac-os pull # Downloads OTA vmlinuz from CDN 34 + ac-os flash # Writes vmlinuz + config.json (creds) to USB 35 + ``` 36 + 37 + ## Transition Plan 38 + 39 + ### Phase 1: Unify `ac-os build` to use Docker 40 + - [ ] `ac-os build` detects Docker, runs `docker build + docker run` 41 + - [ ] Falls back to native build if no Docker (e.g. direct on host) 42 + - [ ] `ac-os build --lisp` passes `AC_BUILD_LISP=1` to Docker 43 + - [ ] Remove `build-and-flash.sh` dependency (keep as legacy fallback) 44 + - [ ] `ac-os flash` only flashes — never builds (downloads OTA or uses local vmlinuz) 45 + 46 + ### Phase 2: Oven adopts Docker builds 47 + - [ ] Update `native-builder.mjs` to use Docker instead of `build-and-flash.sh` 48 + - [ ] Oven poller: `git pull` → `docker build` → `docker run` → `upload-release.sh` 49 + - [ ] Remove native-cache symlinks, musl-gcc, Ubuntu package installs 50 + - [ ] Oven only needs: Docker, git, curl (for upload) 51 + - [ ] Deploy updated `native-builder.mjs` to oven 52 + 53 + ### Phase 3: API + UX endpoints 54 + - [ ] `GET /native-build` — add `dockerImage`, `dockerCached` fields to status 55 + - [ ] `GET /native-build/:id/logs` — stream Docker build logs 56 + - [ ] VSCode extension status bar — show build stage from Docker output 57 + - [ ] `ac-os build --upload` — local build + automatic OTA upload 58 + - [ ] `ac-os status` — check oven build status from CLI 59 + 60 + ### Phase 4: Signing + verification 61 + - [ ] `ac-os upload` signs vmlinuz with ed25519 key 62 + - [ ] `ac-os pull` verifies signature before flashing 63 + - [ ] OTA releases include `.sig` file on CDN 64 + - [ ] Device verifies signature before applying OTA update 65 + 66 + ### Phase 5: CL variant 67 + - [ ] `ac-os build --lisp` produces CL-based vmlinuz 68 + - [ ] Same Docker image, different entrypoint flag 69 + - [ ] CL kernel uploaded to separate CDN path (`os/native-cl-latest.vmlinuz`) 70 + - [ ] `ac-os pull --lisp` downloads CL variant 71 + 72 + ## Key Principles 73 + 1. **No secrets in builds** — OTA kernels are generic, creds injected at flash 74 + 2. **Reproducible** — same Docker image = same vmlinuz, any machine 75 + 3. **One process** — local and oven use identical Docker commands 76 + 4. **Fast rebuilds** — Docker layer cache: only recompile what changed 77 + 5. **Wide compat** — Fedora 43 base, GCC 15, Linux 6.19.9, Intel i915 + simpledrm 78 + 79 + ## Files to modify 80 + - `fedac/native/ac-os` — unify build/flash/upload around Docker 81 + - `oven/native-builder.mjs` — switch to Docker builds 82 + - `oven/native-git-poller.mjs` — simplify (just detect new commits) 83 + - `fedac/native/Dockerfile.builder` — already working 84 + - `fedac/native/docker-build.sh` — already working 85 + - `vscode-extension/extension.ts` — OTA status bar updates