Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

fix: unblock oven nix iso upload

+127 -105
+13 -6
fedac/native/scripts/upload-release.sh
··· 60 60 BASE_URL="https://${DO_SPACES_BUCKET}.${DO_SPACES_REGION}.digitaloceanspaces.com" 61 61 62 62 # Build version string from git. Dirty/conflicted uploads are blocked by default. 63 - GIT_HASH=$(git -C "$SCRIPT_DIR" rev-parse --short HEAD 2>/dev/null || echo "unknown") 64 - CONFLICT_FILES=$(git -C "$SCRIPT_DIR" diff --name-only --diff-filter=U 2>/dev/null || true) 63 + GIT_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true) 64 + GIT_CWD="$SCRIPT_DIR" 65 + GIT_NATIVE_PATHS=() 66 + if [ -n "$GIT_ROOT" ]; then 67 + GIT_CWD="$GIT_ROOT" 68 + GIT_NATIVE_PATHS=(fedac/native fedac/nixos) 69 + fi 70 + GIT_HASH=$(git -C "$GIT_CWD" rev-parse --short HEAD 2>/dev/null || echo "unknown") 71 + CONFLICT_FILES=$(git -C "$GIT_CWD" diff --name-only --diff-filter=U -- "${GIT_NATIVE_PATHS[@]}" 2>/dev/null || true) 65 72 if [ -n "$CONFLICT_FILES" ]; then 66 73 echo "Error: refusing upload with unresolved merge conflicts:" >&2 67 74 echo "$CONFLICT_FILES" >&2 68 75 exit 1 69 76 fi 70 77 DIRTY_TRACKED=0 71 - if ! git -C "$SCRIPT_DIR" diff --quiet HEAD 2>/dev/null; then 78 + if ! git -C "$GIT_CWD" diff --quiet HEAD -- "${GIT_NATIVE_PATHS[@]}" 2>/dev/null; then 72 79 DIRTY_TRACKED=1 73 80 fi 74 81 if [ "$DIRTY_TRACKED" -eq 1 ] && [ "${ALLOW_DIRTY_UPLOAD:-0}" != "1" ]; then 75 - echo "Error: refusing dirty upload. Commit/stash/reset native changes first." >&2 76 - git -C "$SCRIPT_DIR" status --porcelain --untracked-files=no -- fedac/native 2>/dev/null >&2 || true 82 + echo "Error: refusing dirty upload. Commit/stash/reset fedac/native or fedac/nixos changes first." >&2 83 + git -C "$GIT_CWD" status --porcelain --untracked-files=no -- "${GIT_NATIVE_PATHS[@]}" 2>/dev/null >&2 || true 77 84 echo "Override only for emergencies: ALLOW_DIRTY_UPLOAD=1 ./scripts/upload-release.sh ..." >&2 78 85 exit 1 79 86 fi ··· 180 187 || echo '{"releases":[]}' > "$RELEASES_JSON" 181 188 182 189 # Append new entry (keep last 50) 183 - COMMIT_MSG=$(git -C "$SCRIPT_DIR" log -1 --format="%s" 2>/dev/null || echo "") 190 + COMMIT_MSG=$(git -C "$GIT_CWD" log -1 --format="%s" 2>/dev/null || echo "") 184 191 BUILD_HANDLE="${AC_HANDLE:-}" 185 192 186 193 python3 - "$RELEASES_JSON" "$FULL_VERSION" "$SHA256" "$SIZE" "$GIT_HASH" "$BUILD_TS" "$BUILD_NAME" "$CHANNEL_PREFIX" "$COMMIT_MSG" "$BUILD_HANDLE" <<'PYEOF'
+4
oven/deploy.sh
··· 116 116 else 117 117 echo "WARNING: nix binary not found after install" 118 118 fi 119 + if id -u oven >/dev/null 2>&1; then 120 + mkdir -p /home/oven/.cache/nix 121 + chown -R oven:oven /home/oven/.cache 122 + fi 119 123 # Enable flakes 120 124 mkdir -p /etc/nix 121 125 grep -q 'experimental-features' /etc/nix/nix.conf 2>/dev/null || \
+110 -99
oven/native-builder.mjs
··· 331 331 332 332 const repoDir = path.resolve(NATIVE_DIR, "../.."); 333 333 334 - // Preflight: hard-sync build repo and refuse conflicted/dirty native trees. 334 + // Determine variant: "c" (default), "cl", "nix", "both", or "all" 335 + const variant = job.variant || "c"; 336 + const buildC = variant === "c" || variant === "both" || variant === "all"; 337 + const buildCL = variant === "cl" || variant === "both" || variant === "all"; 338 + const buildNix = variant === "nix" || variant === "all"; 339 + const needsDockerBuild = buildC || buildCL; 340 + 341 + // Preflight: hard-sync build repo and refuse conflicted/dirty native/Nix trees. 335 342 addLogLine(job, "stdout", "Preflight: syncing native git checkout..."); 336 343 await runPhase(job, "preflight-sync", "bash", ["-lc", [ 337 344 "set -euo pipefail", ··· 340 347 `if git rev-parse --verify origin/${NATIVE_BRANCH} >/dev/null 2>&1; then`, 341 348 ` git reset --hard origin/${NATIVE_BRANCH} --quiet`, 342 349 "fi", 343 - "git clean -fdq -- fedac/native", 350 + "git clean -fdq -- fedac/native fedac/nixos", 344 351 ].join("\n")], repoDir); 345 352 346 353 const syncedRef = await runSync("git", ["rev-parse", "HEAD"], repoDir); ··· 348 355 349 356 const trackedDirty = await runSync( 350 357 "git", 351 - ["status", "--porcelain", "--untracked-files=no", "--", "fedac/native"], 358 + ["status", "--porcelain", "--untracked-files=no", "--", "fedac/native", "fedac/nixos"], 352 359 repoDir, 353 360 ); 354 361 if (trackedDirty) { 355 362 throw new Error( 356 - `Refusing native build: fedac/native tree is dirty after sync:\n${trackedDirty}`, 363 + `Refusing native build: fedac/native or fedac/nixos tree is dirty after sync:\n${trackedDirty}`, 357 364 ); 358 365 } 359 366 360 367 const unresolved = await runSync( 361 368 "git", 362 - ["diff", "--name-only", "--diff-filter=U", "--", "fedac/native"], 369 + ["diff", "--name-only", "--diff-filter=U", "--", "fedac/native", "fedac/nixos"], 363 370 repoDir, 364 371 ); 365 372 if (unresolved) { ··· 412 419 job.commitMsg = commitMsg; 413 420 const vmlinuzOut = `/tmp/oven-vmlinuz-${job.id}`; 414 421 415 - // Pre-build: aggressive prune to avoid disk-full failures (60GB droplet fills fast) 416 - addLogLine(job, "stdout", "Pre-build: Pruning Docker artifacts..."); 417 - try { 418 - await runPhase(job, "prune", "bash", ["-c", 419 - "docker container prune -f && docker image prune -af --filter until=2h && docker builder prune -af --filter until=30m && docker volume prune -f", 420 - ], repoDir); 421 - // Check free space — abort early if less than 10GB free 422 - const dfOut = await runSync("bash", ["-c", "df --output=avail / | tail -1"], repoDir); 423 - const availKB = parseInt(dfOut, 10) || 0; 424 - const availGB = availKB / 1048576; 425 - addLogLine(job, "stdout", ` Disk: ${availGB.toFixed(1)}GB free`); 426 - if (availGB < 10) { 427 - addLogLine(job, "stderr", ` WARNING: Only ${availGB.toFixed(1)}GB free — running full prune...`); 428 - await runPhase(job, "emergency-prune", "bash", ["-c", 429 - "docker system prune -af --volumes", 422 + // Pre-build: prune Docker only when a Docker-backed variant is needed. 423 + if (needsDockerBuild) { 424 + addLogLine(job, "stdout", "Pre-build: Pruning Docker artifacts..."); 425 + try { 426 + await runPhase(job, "prune", "bash", ["-c", 427 + "docker container prune -f && docker image prune -af --filter until=2h && docker builder prune -af --filter until=30m && docker volume prune -f", 430 428 ], repoDir); 431 - } 432 - } catch { addLogLine(job, "stdout", " Prune skipped (non-fatal)"); } 429 + const dfOut = await runSync("bash", ["-c", "df --output=avail / | tail -1"], repoDir); 430 + const availKB = parseInt(dfOut, 10) || 0; 431 + const availGB = availKB / 1048576; 432 + addLogLine(job, "stdout", ` Disk: ${availGB.toFixed(1)}GB free`); 433 + if (availGB < 10) { 434 + addLogLine(job, "stderr", ` WARNING: Only ${availGB.toFixed(1)}GB free — running full prune...`); 435 + await runPhase(job, "emergency-prune", "bash", ["-c", 436 + "docker system prune -af --volumes", 437 + ], repoDir); 438 + } 439 + } catch { addLogLine(job, "stdout", " Prune skipped (non-fatal)"); } 433 440 434 - // Phase 1: Docker image build (cached layers = fast) 435 - addLogLine(job, "stdout", "Phase 1: Building Docker image..."); 436 - await runPhase(job, "docker-build", "docker", [ 437 - "build", "-t", "ac-os-builder", 438 - "-f", path.join(repoDir, "fedac/native/Dockerfile.builder"), 439 - repoDir, 440 - ], repoDir); 441 + // Phase 1: Docker image build (cached layers = fast) 442 + addLogLine(job, "stdout", "Phase 1: Building Docker image..."); 443 + await runPhase(job, "docker-build", "docker", [ 444 + "build", "-t", "ac-os-builder", 445 + "-f", path.join(repoDir, "fedac/native/Dockerfile.builder"), 446 + repoDir, 447 + ], repoDir); 441 448 442 - job.percent = 30; 449 + job.percent = 30; 450 + } 443 451 444 - // Determine variant: "c" (default), "cl", "nix", "both", or "all" 445 - const variant = job.variant || "c"; 446 - const buildC = variant === "c" || variant === "both" || variant === "all"; 447 - const buildCL = variant === "cl" || variant === "both" || variant === "all"; 448 - const buildNix = variant === "nix" || variant === "all"; 449 452 const uploadScript = path.join(NATIVE_DIR, "scripts/upload-release.sh"); 450 453 const uploadEnv = { 451 454 DO_SPACES_KEY: process.env.DO_SPACES_KEY || process.env.ART_SPACES_KEY || "", ··· 545 548 // ── NixOS variant: build directly on host with nix (no Docker) ── 546 549 if (buildNix) { 547 550 const nixosDir = path.resolve(NATIVE_DIR, "../nixos"); 551 + const nixHomeDir = `/tmp/oven-nix-home-${job.id}`; 548 552 const nixUploadDir = `/tmp/oven-nix-upload-${job.id}`; 549 553 const nixBin = await resolveBinary("nix", NIX_BIN_CANDIDATES, nixosDir); 550 554 if (!nixBin) { ··· 561 565 ], 562 566 nixosDir, 563 567 ); 568 + await fs.mkdir(path.join(nixHomeDir, ".cache", "nix"), { recursive: true }); 564 569 const nixEnv = { 565 - NIX_CONFIG: "experimental-features = nix-command flakes", 570 + HOME: nixHomeDir, 571 + XDG_CACHE_HOME: path.join(nixHomeDir, ".cache"), 572 + NIX_CONFIG: "experimental-features = nix-command flakes\nwarn-dirty = false", 566 573 AC_NIX_NATIVE_SRC: NATIVE_DIR, 567 574 PATH: uniqueNonEmpty([ 568 575 path.dirname(nixBin), ··· 572 579 }; 573 580 addLogLine(job, "stdout", `Phase N: using nix at ${nixBin}`); 574 581 575 - // Preflight: garbage collect old nix store entries 576 - addLogLine(job, "stdout", "Phase N: NixOS — cleaning Nix store..."); 577 - if (nixGcBin) { 578 - try { 579 - await runPhase( 580 - job, 581 - "nix-gc", 582 - nixGcBin, 583 - ["--delete-older-than", "3d"], 584 - nixosDir, 585 - nixEnv, 586 - ); 587 - } catch {} 588 - } else { 589 - addLogLine(job, "stdout", "Phase N: skipping Nix GC — nix-collect-garbage not found"); 590 - } 582 + try { 583 + // Preflight: garbage collect old nix store entries 584 + addLogLine(job, "stdout", "Phase N: NixOS — cleaning Nix store..."); 585 + if (nixGcBin) { 586 + try { 587 + await runPhase( 588 + job, 589 + "nix-gc", 590 + nixGcBin, 591 + ["--delete-older-than", "3d"], 592 + nixosDir, 593 + nixEnv, 594 + ); 595 + } catch {} 596 + } else { 597 + addLogLine(job, "stdout", "Phase N: skipping Nix GC — nix-collect-garbage not found"); 598 + } 591 599 592 - addLogLine(job, "stdout", "Phase N: NixOS — building image with Nix..."); 593 - job.stage = "nix-build"; 594 - job.percent = Math.max(job.percent, 60); 595 - if (progressCallback) progressCallback(makeSnapshot(job)); 600 + addLogLine(job, "stdout", "Phase N: NixOS — building image with Nix..."); 601 + job.stage = "nix-build"; 602 + job.percent = Math.max(job.percent, 60); 603 + if (progressCallback) progressCallback(makeSnapshot(job)); 596 604 597 - // fedac/nixos reads AC_NIX_NATIVE_SRC from the host env to import fedac/native. 598 - // Build the NixOS ISO image 599 - await runPhase(job, "nix-build", nixBin, [ 600 - "build", ".#usb-image", 601 - "--impure", 602 - "--no-link", "--print-out-paths", 603 - ], nixosDir, nixEnv); 605 + // fedac/nixos reads AC_NIX_NATIVE_SRC from the host env to import fedac/native. 606 + // Build the NixOS ISO image 607 + await runPhase(job, "nix-build", nixBin, [ 608 + "build", ".#usb-image", 609 + "--impure", 610 + "--no-link", "--print-out-paths", 611 + ], nixosDir, nixEnv); 604 612 605 - job.percent = Math.max(job.percent, 85); 613 + job.percent = Math.max(job.percent, 85); 606 614 607 - // Extract ISO path from nix build output 608 - const nixOutResult = await runSync( 609 - nixBin, 610 - ["build", ".#usb-image", "--impure", "--no-link", "--print-out-paths"], 611 - nixosDir, 612 - nixEnv, 613 - ); 614 - if (!nixOutResult) { 615 - throw new Error("NixOS build finished without returning an output path"); 616 - } 615 + // Extract ISO path from nix build output 616 + const nixOutResult = await runSync( 617 + nixBin, 618 + ["build", ".#usb-image", "--impure", "--no-link", "--print-out-paths"], 619 + nixosDir, 620 + nixEnv, 621 + ); 622 + if (!nixOutResult) { 623 + throw new Error("NixOS build finished without returning an output path"); 624 + } 617 625 618 - // Find the ISO in the output directory 619 - const isoPath = await runSync( 620 - "bash", 621 - ["-lc", "find \"$1\" -name '*.iso' -type f | head -1", "_", nixOutResult], 622 - nixosDir, 623 - ); 626 + // Find the ISO in the output directory 627 + const isoPath = await runSync( 628 + "bash", 629 + ["-lc", "find \"$1\" -name '*.iso' -type f | head -1", "_", nixOutResult], 630 + nixosDir, 631 + ); 624 632 625 - if (!isoPath) { 626 - throw new Error("NixOS build produced no ISO file"); 627 - } 633 + if (!isoPath) { 634 + throw new Error("NixOS build produced no ISO file"); 635 + } 628 636 629 - addLogLine(job, "stdout", `NixOS image: ${isoPath}`); 637 + addLogLine(job, "stdout", `NixOS image: ${isoPath}`); 630 638 631 - // Copy to upload directory 632 - await fs.mkdir(nixUploadDir, { recursive: true }); 633 - const nixIsoUpload = path.join(nixUploadDir, "ac-os-nixos.iso"); 634 - await fs.copyFile(isoPath, nixIsoUpload); 639 + // Copy to upload directory 640 + await fs.mkdir(nixUploadDir, { recursive: true }); 641 + const nixIsoUpload = path.join(nixUploadDir, "ac-os-nixos.iso"); 642 + await fs.copyFile(isoPath, nixIsoUpload); 635 643 636 - job.stage = "nix-upload"; 637 - job.percent = Math.max(job.percent, 90); 644 + job.stage = "nix-upload"; 645 + job.percent = Math.max(job.percent, 90); 638 646 639 - // Upload with nix- channel prefix 640 - await runPhase(job, "nix-upload", "bash", [ 641 - uploadScript, "--iso", nixIsoUpload, 642 - ], NATIVE_DIR, { 643 - ...uploadEnv, 644 - OTA_CHANNEL: "nix", 645 - }); 647 + // Upload with nix- channel prefix 648 + await runPhase(job, "nix-upload", "bash", [ 649 + uploadScript, "--iso", nixIsoUpload, 650 + ], NATIVE_DIR, { 651 + ...uploadEnv, 652 + OTA_CHANNEL: "nix", 653 + }); 646 654 647 - try { await fs.rm(nixUploadDir, { recursive: true }); } catch {} 648 - addLogLine(job, "stdout", "NixOS variant uploaded successfully"); 655 + addLogLine(job, "stdout", "NixOS variant uploaded successfully"); 656 + } finally { 657 + try { await fs.rm(nixUploadDir, { recursive: true }); } catch {} 658 + try { await fs.rm(nixHomeDir, { recursive: true }); } catch {} 659 + } 649 660 } 650 661 651 662 job.status = "success";