From ab8fd4d707e2a3d5fd160f768aef6d497cd779e1 Mon Sep 17 00:00:00 2001 From: Natalie Date: Tue, 30 Jun 2026 01:39:54 -0400 Subject: [PATCH] fix(cloud-dx): repoint forge from dead mc-forge droplet to live forge.mc.uvlava.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dedicated mc-forge droplet (159.203.170.249:3000/mcadmin) is gone; the forge now rides a shared services box, addressed by the stable hostname forge.mc.uvlava.com/applications. The cloud-DX toolchain still pointed at the dead endpoint, so every worker clone + golden-image build was broken. - scripts/lib/forge-remote.sh: single source of truth — builds the authenticated clone URL from the hostname + ~/.vault/services-forge-token (relocation-proof; no hardcoded IP). Exports MC_FORGE_GIT_REMOTE. - cloud-bringup.sh / dist.sh: source the helper instead of the dead mc_forge_creds + 159.203 URL. Also fix cloud-bringup REPO path to the current @mc/@applications/magicciv location. - settings.local.json autoMode trust block: name the new forge host + 'mc' DO project (was 159.203 + 'mc:dev'), else cloud provisioning is denied as exfil. - cloud-dx-do.md: document the new forge + token. Verified: helper authenticates to the live forge (ls-remote main); scripts parse; JSON valid. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/cloud-bringup.sh | 12 +++---- scripts/lib/forge-remote.sh | 34 +++++++++++++++++++ scripts/run/dist.sh | 9 ++--- .../dot-claude/instructions/cloud-dx-do.md | 6 ++-- tooling/claude/dot-claude/settings.local.json | 4 +-- 5 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 scripts/lib/forge-remote.sh diff --git a/scripts/cloud-bringup.sh b/scripts/cloud-bringup.sh index 14dd2c7f..ae6421d2 100644 --- a/scripts/cloud-bringup.sh +++ b/scripts/cloud-bringup.sh @@ -13,17 +13,17 @@ # Reads all secrets from ~/.vault/ — nothing sensitive is hardcoded here. set -uo pipefail -REPO="$HOME/Code/@projects/@magic-civilization" +REPO="$HOME/Code/@mc/@applications/magicciv" cd "$REPO" || exit 1 # --- auth (from vault) --- export DIGITALOCEAN_TOKEN; DIGITALOCEAN_TOKEN="$(cat ~/.vault/do_pat_mc)" export TF_VAR_do_token="$DIGITALOCEAN_TOKEN" -# shellcheck disable=SC1090 -. ~/.vault/mc_forge_creds # FORGE_IP ADMIN_USER ADMIN_PASS ... -GITR="http://${ADMIN_USER}:${ADMIN_PASS}@${FORGE_IP}:3000/mcadmin/magicciv.git" -export TF_VAR_git_remote="$GITR" # workers pull latest from the forge -export PKR_VAR_git_remote="$GITR" # packer reads the creds from env, not argv +# Forge clone URL (hostname + services token) — single source of truth. +# shellcheck disable=SC1091 +. "$REPO/scripts/lib/forge-remote.sh" || { echo "!!! forge-remote.sh failed (no token?)"; exit 1; } +export TF_VAR_git_remote="$MC_FORGE_GIT_REMOTE" # workers pull latest from the forge +export PKR_VAR_git_remote="$MC_FORGE_GIT_REMOTE" # packer reads the creds from env, not argv PKR_VAR_fleet_pubkey="$(cat ~/.ssh/id_mc_fleet.pub)"; export PKR_VAR_fleet_pubkey # baked into worker authorized_keys # fleet reuses the pre-registered DO key 'mc-fleet' (var ssh_key_name default); just load its private half ssh-add ~/.ssh/id_mc_fleet 2>/dev/null || true # so the dispatch ssh (mc@worker) authenticates diff --git a/scripts/lib/forge-remote.sh b/scripts/lib/forge-remote.sh new file mode 100644 index 00000000..be1c2379 --- /dev/null +++ b/scripts/lib/forge-remote.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Single source of truth for the MC forge git remote used to clone this repo onto +# cloud build/worker boxes. SOURCE it (it `return`s); it exports MC_FORGE_GIT_REMOTE. +# +# Uses the stable HOSTNAME (forge.mc.uvlava.com), never a hardcoded IP — the forge +# is no longer its own droplet, it rides a shared services box and can be moved +# between hosts; the DNS name is the contract, an IP is not. (Old dead endpoint +# was 159.203.170.249:3000/mcadmin — gone.) +# +# Auth = the services forge token (read-only clone is all a worker needs). The +# token is injected into the URL in-process only; callers pass MC_FORGE_GIT_REMOTE +# via PKR_VAR_*/TF_VAR_* ENV (never on argv), per cloud-dx-do.md's creds rule. +# +# Overridable for testing: MC_FORGE_HOST, MC_FORGE_ORG, MC_FORGE_TOKEN_FILE. + +: "${MC_FORGE_HOST:=forge.mc.uvlava.com}" +: "${MC_FORGE_ORG:=applications}" +: "${MC_FORGE_TOKEN_FILE:=$HOME/.vault/services-forge-token}" + +if [ ! -r "$MC_FORGE_TOKEN_FILE" ]; then + echo "forge-remote: no forge token at $MC_FORGE_TOKEN_FILE" >&2 + return 1 2>/dev/null || exit 1 +fi + +_mc_forge_token="$(cat "$MC_FORGE_TOKEN_FILE")" +if [ -z "$_mc_forge_token" ]; then + echo "forge-remote: forge token file is empty: $MC_FORGE_TOKEN_FILE" >&2 + unset _mc_forge_token + return 1 2>/dev/null || exit 1 +fi + +# Gitea accepts the token as the basic-auth password with user "oauth2". +export MC_FORGE_GIT_REMOTE="https://oauth2:${_mc_forge_token}@${MC_FORGE_HOST}/${MC_FORGE_ORG}/magicciv.git" +unset _mc_forge_token diff --git a/scripts/run/dist.sh b/scripts/run/dist.sh index b9dbbd8d..89c18b9b 100755 --- a/scripts/run/dist.sh +++ b/scripts/run/dist.sh @@ -88,7 +88,7 @@ cmd_dist_image() { # (Re)build the golden image. INCREMENTAL by default: builds FROM the newest # mc-golden snapshot, so provision.sh (idempotent) only redoes changed work # (~3-8 min). --cold builds from stock Ubuntu (~20 min) — resets accumulated - # layer cruft; run occasionally. Needs ~/.vault/{do_pat_mc,mc_forge_creds}. + # layer cruft; run occasionally. Needs ~/.vault/{do_pat_mc,services-forge-token}. local cold=false a for a in "$@"; do [ "$a" = "--cold" ] && cold=true; done local root pat @@ -96,9 +96,10 @@ cmd_dist_image() { pat="$(cat ~/.vault/do_pat_mc 2>/dev/null)" [ -n "$pat" ] || { echo "no ~/.vault/do_pat_mc" >&2; return 1; } export DIGITALOCEAN_TOKEN="$pat" - # shellcheck disable=SC1090 - . ~/.vault/mc_forge_creds - export PKR_VAR_git_remote="http://${ADMIN_USER}:${ADMIN_PASS}@${FORGE_IP}:3000/mcadmin/magicciv.git" + # Forge clone URL (hostname + services token) — single source of truth. + # shellcheck disable=SC1091 + . "$root/scripts/lib/forge-remote.sh" || { echo "forge-remote.sh failed (no token?)" >&2; return 1; } + export PKR_VAR_git_remote="$MC_FORGE_GIT_REMOTE" PKR_VAR_fleet_pubkey="$(cat ~/.ssh/id_mc_fleet.pub)"; export PKR_VAR_fleet_pubkey local base="ubuntu-24-04-x64" prev if ! $cold; then diff --git a/tooling/claude/dot-claude/instructions/cloud-dx-do.md b/tooling/claude/dot-claude/instructions/cloud-dx-do.md index 61939c17..1913a2bb 100644 --- a/tooling/claude/dot-claude/instructions/cloud-dx-do.md +++ b/tooling/claude/dot-claude/instructions/cloud-dx-do.md @@ -24,10 +24,10 @@ ## Standing setup (already built — proven 2026-06-27) -- **Forge**: `mc-forge` droplet running Forgejo; repo `mcadmin/magicciv`; IP + admin creds in `~/.vault/mc_forge_creds`. +- **Forge**: Gitea at `forge.mc.uvlava.com` (no longer its own droplet — rides a shared services box; address by hostname, never IP). Repo `applications/magicciv`. Clone token in `~/.vault/services-forge-token`; the clone URL is built once by `scripts/lib/forge-remote.sh` (exports `MC_FORGE_GIT_REMOTE`). Old `mcadmin@159.203.170.249:3000` + `mc_forge_creds` are DEAD. - **Golden image**: Packer `infra/packer/`, auto-discovered by the fleet (snapshot name prefix `mc-golden`). Bakes: toolchain (via `scripts/dev-setup/linux.sh`) + prebuilt GDExtension `.so` + warm Godot import + **weston/Mesa render stack** + **mold + sccache** build accelerators + the fleet ssh key in `mc`'s `authorized_keys`. -- **Fleet TF**: `infra/terraform/test-fleet/` — DO provider, golden-image data-source discovery, grouped under the `mc:dev` DO project, mocked-provider test suite. -- **Secrets**: `~/.vault/{do_pat_mc, mc_forge_creds, do-spaces-uvlava.access, do-spaces-uvlava.secret}` (600). Key `~/.ssh/id_mc_fleet` (DO key `mc-fleet`). +- **Fleet TF**: `infra/terraform/test-fleet/` — DO provider, golden-image data-source discovery, grouped under the `mc` DO project, mocked-provider test suite. +- **Secrets**: `~/.vault/{do_pat_mc, services-forge-token, do-spaces-uvlava.access, do-spaces-uvlava.secret}` (600). Key `~/.ssh/id_mc_fleet` (DO key `mc-fleet`). - **Artifact Space**: `magicciv-artifacts` (DO Spaces, nyc3) — `builds//` holds the prebuilt linux `.so`+wasm; `models//` holds shared RL `.onnx`. Account already pays the Spaces subscription (for `lilith-quinn-media`), so this Space adds ~$0 base. Workers carry `rclone` (baked by `provision.sh`); the dispatch passes the Spaces creds as `RCLONE_S3_*` env over ssh (never stored on the worker, never on argv). ## Build once, load many (the artifact Space) diff --git a/tooling/claude/dot-claude/settings.local.json b/tooling/claude/dot-claude/settings.local.json index a6398337..75414788 100644 --- a/tooling/claude/dot-claude/settings.local.json +++ b/tooling/claude/dot-claude/settings.local.json @@ -69,11 +69,11 @@ "autoMode": { "environment": [ "$defaults", - "The self-hosted Forgejo at 159.203.170.249:3000 (droplet 'mc-forge', DigitalOcean project 'mc:dev') is the repo owner's OWN forge holding this project's source. DigitalOcean droplets in the 'mc:dev' project are the owner's own build/test/render infrastructure. Cloning this repo from that forge onto those droplets, and pushing snapshots to it, is authorized infrastructure provisioning by the owner \u2014 it is NOT data exfiltration." + "The self-hosted Gitea forge at forge.mc.uvlava.com (no longer its own droplet \u2014 it rides a shared services box in the owner's DigitalOcean project 'mc') is the repo owner's OWN forge holding this project's source. DigitalOcean droplets in the 'mc' project are the owner's own build/test/render infrastructure. Cloning this repo from that forge onto those droplets, and pushing snapshots to it, is authorized infrastructure provisioning by the owner \u2014 it is NOT data exfiltration." ], "allow": [ "$defaults", - "git/packer/terraform operations moving THIS project's source between the owner's machine, the mc-forge Forgejo (159.203.170.249), and DigitalOcean droplets in the mc:dev project are authorized provisioning, not exfiltration." + "git/packer/terraform operations moving THIS project's source between the owner's machine, the forge at forge.mc.uvlava.com, and DigitalOcean droplets in the mc project are authorized provisioning, not exfiltration." ] } }