diff --git a/fedac/native/ac-os b/fedac/native/ac-os index f2d89c55d3..afd5470960 100755 --- a/fedac/native/ac-os +++ b/fedac/native/ac-os @@ -1458,14 +1458,14 @@ exec qemu-system-x86_64 \ build) log "Building NixOS image..." cd "${NIXOS_DIR}" - nix build .#usb-image --print-out-paths + AC_NIX_NATIVE_SRC="${SCRIPT_DIR}" nix build .#usb-image --impure --print-out-paths log "NixOS image built" ;; flash) require_login log "Building + flashing NixOS image to USB..." cd "${NIXOS_DIR}" - IMAGE_PATH=$(nix build .#usb-image --print-out-paths --no-link) + IMAGE_PATH=$(AC_NIX_NATIVE_SRC="${SCRIPT_DIR}" nix build .#usb-image --impure --print-out-paths --no-link) ISO=$(find "${IMAGE_PATH}" -name '*.iso' -type f | head -1) if [ -z "${ISO}" ]; then err "No ISO found in nix build output" @@ -1482,7 +1482,7 @@ exec qemu-system-x86_64 \ require_clean log "Building + uploading NixOS image..." cd "${NIXOS_DIR}" - IMAGE_PATH=$(nix build .#usb-image --print-out-paths --no-link) + IMAGE_PATH=$(AC_NIX_NATIVE_SRC="${SCRIPT_DIR}" nix build .#usb-image --impure --print-out-paths --no-link) ISO=$(find "${IMAGE_PATH}" -name '*.iso' -type f | head -1) if [ -z "${ISO}" ]; then err "No ISO found in nix build output" diff --git a/fedac/nixos/configuration.nix b/fedac/nixos/configuration.nix index de9c966d99..acb8b98652 100644 --- a/fedac/nixos/configuration.nix +++ b/fedac/nixos/configuration.nix @@ -1,7 +1,7 @@ -{ config, pkgs, lib, self ? null, gitHash ? "unknown", version ? "dev", ... }: +{ config, pkgs, lib, self ? null, gitHash ? "unknown", version ? "dev", nativeSrc, ... }: let - ac-native = pkgs.callPackage ./packages/ac-native { inherit gitHash version; }; + ac-native = pkgs.callPackage ./packages/ac-native { inherit gitHash version nativeSrc; }; in { imports = [ diff --git a/fedac/nixos/flake.nix b/fedac/nixos/flake.nix index 052bcab9d7..26dcf4ddcb 100644 --- a/fedac/nixos/flake.nix +++ b/fedac/nixos/flake.nix @@ -15,12 +15,21 @@ pkgs = import nixpkgs { inherit system; }; version = builtins.substring 0 8 (self.lastModifiedDate or "unknown"); gitHash = self.shortRev or "dirty"; + nativeSrcPath = builtins.getEnv "AC_NIX_NATIVE_SRC"; + nativeSrc = + if nativeSrcPath != "" then + builtins.path { + path = nativeSrcPath; + name = "ac-native-source"; + } + else + throw "AC_NIX_NATIVE_SRC is required for fedac/nixos builds; run nix with --impure and point it at fedac/native."; in { # The ac-native binary as a standalone package packages.${system} = { ac-native = pkgs.callPackage ./packages/ac-native { - inherit gitHash version; + inherit gitHash version nativeSrc; }; # Bootable ISO image (no KVM needed to build) @@ -28,7 +37,7 @@ inherit system; modules = [ ./configuration.nix ]; format = "iso"; - specialArgs = { inherit self gitHash version; }; + specialArgs = { inherit self gitHash version nativeSrc; }; }; default = self.packages.${system}.usb-image; @@ -38,7 +47,7 @@ nixosConfigurations.ac-native-os = nixpkgs.lib.nixosSystem { inherit system; modules = [ ./configuration.nix ]; - specialArgs = { inherit self gitHash version; }; + specialArgs = { inherit self gitHash version nativeSrc; }; }; }; } diff --git a/fedac/nixos/modules/kiosk.nix b/fedac/nixos/modules/kiosk.nix index 0e2fddf35e..623bbd78ee 100644 --- a/fedac/nixos/modules/kiosk.nix +++ b/fedac/nixos/modules/kiosk.nix @@ -1,7 +1,7 @@ -{ config, pkgs, lib, gitHash ? "unknown", version ? "dev", ... }: +{ config, pkgs, lib, gitHash ? "unknown", version ? "dev", nativeSrc, ... }: let - ac-native = pkgs.callPackage ../packages/ac-native { inherit gitHash version; }; + ac-native = pkgs.callPackage ../packages/ac-native { inherit gitHash version nativeSrc; }; in { # seatd for unprivileged GPU/input access diff --git a/fedac/nixos/packages/ac-native/default.nix b/fedac/nixos/packages/ac-native/default.nix index 8aa7976c29..54e1afc559 100644 --- a/fedac/nixos/packages/ac-native/default.nix +++ b/fedac/nixos/packages/ac-native/default.nix @@ -2,6 +2,7 @@ , libdrm, alsa-lib, flite, openssl, curl , wayland, wayland-protocols, wayland-scanner , ffmpeg +, nativeSrc , gitHash ? "unknown", version ? "dev" }: @@ -15,7 +16,7 @@ stdenv.mkDerivation { pname = "ac-native"; inherit version; - src = ../../../native; + src = nativeSrc; nativeBuildInputs = [ pkg-config diff --git a/oven/deploy.sh b/oven/deploy.sh index 21c51df65c..2c151336be 100755 --- a/oven/deploy.sh +++ b/oven/deploy.sh @@ -95,6 +95,26 @@ ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" bash -s <<'NIX_E else curl -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm 2>&1 | tail -10 fi + NIX_BIN="" + for candidate in \ + /nix/var/nix/profiles/default/bin/nix \ + /root/.nix-profile/bin/nix \ + /home/oven/.nix-profile/bin/nix; do + if [ -x "$candidate" ]; then + NIX_BIN="$candidate" + break + fi + done + if [ -n "$NIX_BIN" ]; then + ln -sf "$NIX_BIN" /usr/local/bin/nix + NIX_GC_BIN="$(dirname "$NIX_BIN")/nix-collect-garbage" + if [ -x "$NIX_GC_BIN" ]; then + ln -sf "$NIX_GC_BIN" /usr/local/bin/nix-collect-garbage + fi + echo "Nix binary: $NIX_BIN" + else + echo "WARNING: nix binary not found after install" + fi # Enable flakes mkdir -p /etc/nix grep -q 'experimental-features' /etc/nix/nix.conf 2>/dev/null || \ @@ -253,11 +273,13 @@ if grep -q '^OVEN_VERSION=' .env 2>/dev/null; then else echo 'OVEN_VERSION=$GIT_VERSION' >> .env fi +install -m 0644 $REMOTE_DIR/infra/oven.service /etc/systemd/system/oven.service # Rewrite systemd override from scratch so stale directives do not survive deploys. mkdir -p /etc/systemd/system/oven.service.d cat > /etc/systemd/system/oven.service.d/override.conf < { - const proc = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'ignore'] }); + const proc = spawn(cmd, args, { + cwd, + env: env ? { ...process.env, ...env } : process.env, + stdio: ['ignore', 'pipe', 'ignore'], + }); let out = ''; proc.stdout.on('data', d => out += d); proc.on('close', () => resolve(out.trim())); @@ -29,6 +33,20 @@ const NATIVE_BUILD_COLLECTION = const NATIVE_DIR = process.env.NATIVE_DIR || "/opt/oven/native-git/fedac/native"; const NATIVE_BRANCH = process.env.NATIVE_GIT_BRANCH || "main"; +const NIX_BIN_CANDIDATES = [ + process.env.NIX_BIN || "", + "/usr/local/bin/nix", + "/nix/var/nix/profiles/default/bin/nix", + "/home/oven/.nix-profile/bin/nix", + "/root/.nix-profile/bin/nix", +]; +const NIX_GC_CANDIDATES = [ + process.env.NIX_GC_BIN || "", + "/usr/local/bin/nix-collect-garbage", + "/nix/var/nix/profiles/default/bin/nix-collect-garbage", + "/home/oven/.nix-profile/bin/nix-collect-garbage", + "/root/.nix-profile/bin/nix-collect-garbage", +]; // Kernel build cache: symlinked from fedac/native/build so kernel object // files survive rsync --delete between commits (5-10x faster warm builds). @@ -52,6 +70,10 @@ function nowISO() { return new Date().toISOString(); } +function uniqueNonEmpty(items) { + return [...new Set((items || []).filter(Boolean))]; +} + function toDateOrNull(v) { if (!v) return null; const d = new Date(v); @@ -101,6 +123,7 @@ async function persistNativeBuildRecord(job) { commitMsg: job.commitMsg || null, flags: Array.isArray(job.flags) ? job.flags : [], changedPaths: job.changedPaths || "", + variant: job.variant || "c", createdAt: toDateOrNull(job.createdAt), startedAt, updatedAt: toDateOrNull(job.updatedAt), @@ -123,6 +146,18 @@ function stripAnsi(s) { return String(s || "").replace(/\u001b\[[0-9;]*m/g, ""); } +async function resolveBinary(cmd, candidates = [], cwd = NATIVE_DIR) { + const fromPath = await runSync("bash", ["-lc", `command -v ${cmd} || true`], cwd); + if (fromPath) return fromPath.split("\n").pop().trim(); + for (const candidate of uniqueNonEmpty(candidates)) { + try { + await fs.access(candidate); + return candidate; + } catch {} + } + return ""; +} + function addLogLine(job, stream, line) { const clean = stripAnsi(line).replace(/\r/g, "").trimEnd(); if (!clean) return; @@ -511,43 +546,81 @@ async function runBuildJob(job) { if (buildNix) { const nixosDir = path.resolve(NATIVE_DIR, "../nixos"); const nixUploadDir = `/tmp/oven-nix-upload-${job.id}`; + const nixBin = await resolveBinary("nix", NIX_BIN_CANDIDATES, nixosDir); + if (!nixBin) { + throw new Error( + "Nix binary not found on oven host. Checked PATH and: " + + uniqueNonEmpty(NIX_BIN_CANDIDATES).join(", "), + ); + } + const nixGcBin = await resolveBinary( + "nix-collect-garbage", + [ + path.join(path.dirname(nixBin), "nix-collect-garbage"), + ...NIX_GC_CANDIDATES, + ], + nixosDir, + ); + const nixEnv = { + NIX_CONFIG: "experimental-features = nix-command flakes", + AC_NIX_NATIVE_SRC: NATIVE_DIR, + PATH: uniqueNonEmpty([ + path.dirname(nixBin), + nixGcBin ? path.dirname(nixGcBin) : "", + process.env.PATH || "", + ]).join(":"), + }; + addLogLine(job, "stdout", `Phase N: using nix at ${nixBin}`); // Preflight: garbage collect old nix store entries addLogLine(job, "stdout", "Phase N: NixOS — cleaning Nix store..."); - try { - await runPhase(job, "nix-gc", "bash", ["-c", - "nix-collect-garbage --delete-older-than 3d 2>&1 | tail -3 || true" - ], nixosDir, { NIX_CONFIG: "experimental-features = nix-command flakes" }); - } catch {} + if (nixGcBin) { + try { + await runPhase( + job, + "nix-gc", + nixGcBin, + ["--delete-older-than", "3d"], + nixosDir, + nixEnv, + ); + } catch {} + } else { + addLogLine(job, "stdout", "Phase N: skipping Nix GC — nix-collect-garbage not found"); + } addLogLine(job, "stdout", "Phase N: NixOS — building image with Nix..."); job.stage = "nix-build"; job.percent = Math.max(job.percent, 60); if (progressCallback) progressCallback(makeSnapshot(job)); + // fedac/nixos reads AC_NIX_NATIVE_SRC from the host env to import fedac/native. // Build the NixOS ISO image - await runPhase(job, "nix-build", "nix", [ + await runPhase(job, "nix-build", nixBin, [ "build", ".#usb-image", + "--impure", "--no-link", "--print-out-paths", - ], nixosDir, { NIX_CONFIG: "experimental-features = nix-command flakes" }); + ], nixosDir, nixEnv); job.percent = Math.max(job.percent, 85); // Extract ISO path from nix build output - const nixOutResult = await new Promise((resolve, reject) => { - const { execSync } = require("child_process"); - try { - const out = execSync( - "nix build .#usb-image --no-link --print-out-paths", - { cwd: nixosDir, env: { ...process.env, NIX_CONFIG: "experimental-features = nix-command flakes" }, encoding: "utf-8" } - ).trim(); - resolve(out); - } catch (e) { reject(e); } - }); + const nixOutResult = await runSync( + nixBin, + ["build", ".#usb-image", "--impure", "--no-link", "--print-out-paths"], + nixosDir, + nixEnv, + ); + if (!nixOutResult) { + throw new Error("NixOS build finished without returning an output path"); + } // Find the ISO in the output directory - const { execSync } = require("child_process"); - const isoPath = execSync(`find ${nixOutResult} -name '*.iso' -type f | head -1`, { encoding: "utf-8" }).trim(); + const isoPath = await runSync( + "bash", + ["-lc", "find \"$1\" -name '*.iso' -type f | head -1", "_", nixOutResult], + nixosDir, + ); if (!isoPath) { throw new Error("NixOS build produced no ISO file");