From ab32433ea6e8f8ca62021569d957b561bb749ab8 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Wed, 11 Nov 2026 10:19:49 +0000 Subject: [PATCH] dev2: REMOTE_BUILD=c0mpute builds site images on a c0mpute builder deploy-app.sh gets an opt-in remote build: with REMOTE_BUILD=c0mpute in deploy.env it git-archives each build service's context at the deployed sha, passes the Dockerfile, target and build args compose resolved (the exported public NEXT_PUBLIC_*-style values), runs `c0mpute build --load -t ` on the builders in C0MPUTE_BUILDERS, then the usual standby / up / health / rollback. A failed remote build stops before the running stack is touched (REMOTE_BUILD_FALLBACK=local builds here instead). dev2-site writes the variables from sites.d remote_build / remote_builders / remote_build_fallback. On for c0mpute.com and threatcrush.com, building on vienna (node AmaKBdpB), to keep build scratch off dev2's full disk. Co-Authored-By: Claude Opus 5.5 --- dev2/dev2-site | 6 +++ dev2/sites.d/c0mpute.com.json | 7 ++++ dev2/sites.d/threatcrush.com.json | 4 ++ dev2/templates/deploy-app.sh | 62 ++++++++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 dev2/sites.d/c0mpute.com.json diff --git a/dev2/dev2-site b/dev2/dev2-site index bc92ee5..07215d2 100755 --- a/dev2/dev2-site +++ b/dev2/dev2-site @@ -1229,6 +1229,12 @@ def cmd_provision(args): repo_url = f"git@{key_alias}:{s['repo']}.git" if private else f"https://github.com/{s['repo']}.git" deploy_env = f"SITE={s['site']}\nREPO={repo_url}\nBRANCH={branch}\nAPP_PORT={s['port']}\nBUILD_SERVICES=\"{build_services}\"\nHEALTH_PATH={hp}\nIMAGE_ONLY={'1' if image_only else '0'}\n" if s.get("standby_port"): deploy_env += f"STANDBY_PORT={s['standby_port']}\n" + # sites.d "remote_build": "c0mpute" builds the image on a c0mpute builder (deploy-app.sh + # remote_build), "remote_builders" names them (peer ids), "remote_build_fallback": "local" + # builds here when the remote build fails. + if s.get("remote_build"): deploy_env += f"REMOTE_BUILD={s['remote_build']}\n" + if s.get("remote_builders"): deploy_env += f"C0MPUTE_BUILDERS={','.join(s['remote_builders'])}\n" + if s.get("remote_build_fallback"): deploy_env += f"REMOTE_BUILD_FALLBACK={s['remote_build_fallback']}\n" # Secrets come from the logicsrc team vault on every deploy (deploy-app.sh # sync_env_from_vault): sites.d "vault" names it, else --prod; "vault": "" # turns it off. env_remove keys are never taken from the vault. diff --git a/dev2/sites.d/c0mpute.com.json b/dev2/sites.d/c0mpute.com.json new file mode 100644 index 0000000..8c84f1e --- /dev/null +++ b/dev2/sites.d/c0mpute.com.json @@ -0,0 +1,7 @@ +{ + "remote_build": "c0mpute", + "remote_builders": [ + "12D3KooWK9tFibPXoRih5r66qatAQL59vj5SWD53PJTLAmaKBdpB" + ], + "remote_note": "image built on vienna (c0mpute container.run, DIP 0033), not on dev2" +} diff --git a/dev2/sites.d/threatcrush.com.json b/dev2/sites.d/threatcrush.com.json index 9ac796c..50746a0 100644 --- a/dev2/sites.d/threatcrush.com.json +++ b/dev2/sites.d/threatcrush.com.json @@ -26,5 +26,9 @@ "STOPFORUMSPAM_PASSWORD", "STOPFORUMSPAM_USERNAME", "THREATCRUSH_AGENT_KEY" + ], + "remote_build": "c0mpute", + "remote_builders": [ + "12D3KooWK9tFibPXoRih5r66qatAQL59vj5SWD53PJTLAmaKBdpB" ] } diff --git a/dev2/templates/deploy-app.sh b/dev2/templates/deploy-app.sh index 325c402..6d671cc 100755 --- a/dev2/templates/deploy-app.sh +++ b/dev2/templates/deploy-app.sh @@ -173,6 +173,55 @@ health() { return 1 } +# REMOTE_BUILD=c0mpute (deploy.env, from sites.d "remote_build"): build each service's +# image on a c0mpute builder instead of on this box, whose disk the build scratch (Rust +# target/, Next caches, BuildKit cache) keeps filling. The builder gets a plain tarball of +# the revision (git archive of the service's build context), the Dockerfile and target +# compose names, and the build args compose resolved (the public NEXT_PUBLIC_*-style values +# exported below, never app.env secrets). `c0mpute build --load -t ` loads the +# result and tags it as the compose image; the rest of the deploy (standby, `up`, health, +# rollback) is unchanged. Builders: C0MPUTE_BUILDERS (deploy.env, sites.d +# "remote_builders": peer ids of our own nodes), else [containers] builders in the +# deploy user's c0mpute config. A failed remote build stops the deploy before anything +# running is touched, unless REMOTE_BUILD_FALLBACK=local builds here instead. +remote_build() { + local c0 cfg svc ctx sub df target image tgz rc kv + local -a bargs + c0=$(command -v c0mpute || true) + [ -n "$c0" ] || c0=/usr/local/bin/c0mpute + [ -x "$c0" ] || { log "REMOTE_BUILD=c0mpute but no c0mpute CLI on this box"; return 1; } + command -v jq >/dev/null || { log "REMOTE_BUILD=c0mpute needs jq"; return 1; } + [ -z "${C0MPUTE_BUILDERS:-}" ] || export C0MPUTE_BUILDERS + cfg=$(compose config --format json) || return 1 + for svc in $BUILD_SERVICES; do + ctx=$(jq -r --arg s "$svc" '.services[$s].build.context // empty' <<<"$cfg") + df=$(jq -r --arg s "$svc" '.services[$s].build.dockerfile // "Dockerfile"' <<<"$cfg") + target=$(jq -r --arg s "$svc" '.services[$s].build.target // empty' <<<"$cfg") + image=$(jq -r --arg s "$svc" '.services[$s].image // empty' <<<"$cfg") + if [ -z "$ctx" ] || [ -z "$image" ]; then + log "Service $svc has no build context or image name; cannot build it remotely"; return 1 + fi + case $ctx in + "$APP_DIR") sub="" ;; + "$APP_DIR"/*) sub=${ctx#"$APP_DIR"/} ;; + *) log "Service $svc builds from $ctx, outside the checkout; cannot build it remotely"; return 1 ;; + esac + case $df in "$ctx"/*) df=${df#"$ctx"/} ;; esac + bargs=() + while IFS= read -r kv; do + [ -n "$kv" ] && bargs+=(--build-arg "$kv") + done < <(jq -r --arg s "$svc" '.services[$s].build.args // {} | to_entries[] | select(.value != null) | "\(.key)=\(.value)"' <<<"$cfg") + tgz=$(mktemp "$ROOT/.remote-ctx.XXXXXX") + git -C "$APP_DIR" archive --format=tar.gz -o "$tgz" "$SHA${sub:+:$sub}" || { rm -f "$tgz"; return 1; } + log "Remote build of $svc on c0mpute ($(du -h "$tgz" | cut -f1) context, $df${target:+, target $target}, ${#bargs[@]} build-arg flags)" + rc=0 + "$c0" build "$tgz" -f "$df" ${target:+--target "$target"} "${bargs[@]}" \ + --timeout-secs "${REMOTE_BUILD_TIMEOUT:-3600}" --load -t "$image" || rc=$? + rm -f "$tgz" + [ "$rc" = 0 ] || { log "Remote build of $svc failed (exit $rc)"; return 1; } + done +} + case "${1:-}" in --status) compose ps @@ -251,8 +300,17 @@ while IFS= read -r line || [ -n "$line" ]; do done < "$ROOT/app.env" # A failed build exits here (set -e) before anything running is touched: `compose # build` only moves the :latest tags when it succeeds. -# shellcheck disable=SC2086 -compose build $BUILD_SERVICES || die "build of $SHA failed; the running stack was not touched" +if [ "${REMOTE_BUILD:-}" = c0mpute ]; then + if ! remote_build; then + [ "${REMOTE_BUILD_FALLBACK:-}" = local ] || die "remote build of $SHA failed; the running stack was not touched" + log "Remote build failed; REMOTE_BUILD_FALLBACK=local, building here" + # shellcheck disable=SC2086 + compose build $BUILD_SERVICES || die "build of $SHA failed; the running stack was not touched" + fi +else + # shellcheck disable=SC2086 + compose build $BUILD_SERVICES || die "build of $SHA failed; the running stack was not touched" +fi # If the compose network's subnet changed (the kit moved sites to explicit # 10.200.x.0/24 subnets), `up -d` recreates the network in place and Docker's