Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env bash
# Drive a second harness machine in Docker — the one thing a single laptop cannot test.
#
# The desktop app reaches another of your machines through the backend relay
# (`src/lib/remoteRelay.ts`): frames leave the local daemon, cross the cloud, and are handled by the
# daemon on the far side. Every interesting behaviour on that path — creating an agent, moving one
# onto a grid, reading back which grid it landed on — is code that never runs when the app and the
# agent share a computer. This rig gives you the far side.
#
# bash cli/scripts/remote-machine.sh build # bundle THIS tree + build the image
# bash cli/scripts/remote-machine.sh up # start the box
# bash cli/scripts/remote-machine.sh login # SSO (interactive — opens a URL you approve)
# bash cli/scripts/remote-machine.sh link # let this Mac's relay reach the box
# bash cli/scripts/remote-machine.sh verify # what each remote agent is ACTUALLY running on
# bash cli/scripts/remote-machine.sh sh # a shell inside the box
# bash cli/scripts/remote-machine.sh down # stop + remove the container (keeps the session)
# bash cli/scripts/remote-machine.sh destroy # ...and throw the session away
#
# `verify` is the instrument the rig exists for. It prints, per agent, BOTH the environment of the
# live engine process (read from /proc, which is the only thing that decides where inference goes)
# AND what the daemon reports about it. Those two disagreeing is a real class of bug — the daemon can
# be right about a process and still describe it wrongly to the app — so the rig refuses to collapse
# them into one number.
set -euo pipefail
ADAPTER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # the cli package
RIG_DIR="$ADAPTER_DIR/docker/remote-machine"
RIG_ENV="$RIG_DIR/.rig.env" # gitignored; holds the pinned computer id, nothing secret
IMAGE="${HARNESS_RIG_IMAGE:-harness-remote-machine}"
CONTAINER="${HARNESS_RIG_CONTAINER:-harness-remote-machine}"
VOLUME="${HARNESS_RIG_VOLUME:-harness-remote-machine-state}"
# The Mac's own CLI — the side that has to be told how to reach the box.
HOST_HARNESS="${HARNESS_BIN:-$HOME/.local/bin/harness}"
# The SSO redirect lands on the HOST's browser but must reach a listener inside the box, so the port
# cannot be the ephemeral one `harness login` would otherwise pick — it has to be known in advance to
# be published. Pinned here and honoured by ADAPTER_LOGIN_CALLBACK_PORT on the far side.
LOGIN_PORT="${HARNESS_RIG_LOGIN_PORT:-46789}"
# The box's Codex state, kept on the HOST so it survives `up` — which recreates the container, and
# `.codex` is not on the state volume, so an engine signed in inside the box was signed out again by
# the next rebuild. Deliberately outside this repo: auth.json holds a live refresh token.
# Its own directory rather than a bind of the Mac's `~/.codex`: two Codex processes writing one
# config.toml, one history.jsonl and the same sqlite files over a bind mount is how those files get
# corrupted, and the Mac's project trust names paths that do not exist in here.
# Seed it once with the account you want the box to use:
# mkdir -p ~/.harness-rig/codex && cp ~/.codex/auth.json ~/.harness-rig/codex/
RIG_CODEX_HOME="${HARNESS_RIG_CODEX_HOME:-$HOME/.harness-rig/codex}"
die() { echo "error: $*" >&2; exit 1; }
note() { echo ">> $*"; }
require_docker() {
command -v docker >/dev/null 2>&1 || die "docker not found"
docker info >/dev/null 2>&1 || die "the Docker daemon is not running — start Docker Desktop first"
}
running() { [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo false)" = "true" ]; }
require_running() { running || die "the box is not up — run: bash cli/scripts/remote-machine.sh up"; }
# The container's identity, pinned OUTSIDE the volume.
#
# A machine is bound to its computer id on the backend, so an id that regenerates does not reconnect —
# it mints a SECOND machine on your account and leaves the first as a tombstone. The volume normally
# carries the id, but `destroy` (or any stray `docker volume rm`) takes it with it, and the cost of
# that lands on a real account. Keeping it here means even a wiped volume comes back as the same
# machine.
ensure_computer_id() {
if [ -f "$RIG_ENV" ]; then
# shellcheck source=/dev/null
. "$RIG_ENV"
fi
if [ -z "${ADAPTER_COMPUTER_ID:-}" ]; then
# A UUID, because the backend validates the shape and rejects anything else with
# "Invalid computer id" — a readable prefix like `docker-...` does not survive registration.
ADAPTER_COMPUTER_ID="$(node -e 'process.stdout.write(require("crypto").randomUUID())')"
mkdir -p "$RIG_DIR"
printf 'ADAPTER_COMPUTER_ID=%s\n' "$ADAPTER_COMPUTER_ID" > "$RIG_ENV"
note "minted a computer id for the box: $ADAPTER_COMPUTER_ID (kept in docker/remote-machine/.rig.env)"
fi
export ADAPTER_COMPUTER_ID
}
cmd_build() {
require_docker
# Bundle from the working tree, labelled exactly as a local install is, so `harness version` on the
# box and on this Mac read identically for the same code. See scripts/lib/build-label.sh.
# shellcheck source=lib/build-label.sh
. "$ADAPTER_DIR/scripts/lib/build-label.sh"
local label
label="$(harness_build_label "$ADAPTER_DIR")"
note "bundling ${label}…"
( cd "$ADAPTER_DIR" && ADAPTER_VERSION="$label" npm run bundle )
[ -f "$ADAPTER_DIR/dist/cli.js" ] || die "dist/cli.js missing after the bundle step"
note "building image ${IMAGE}…" # braces required: bash swallows the following UTF-8 byte into the name
docker build \
-f "$RIG_DIR/Dockerfile" \
-t "$IMAGE" \
"$ADAPTER_DIR"
note "built $IMAGE ($label)"
}
cmd_up() {
require_docker
ensure_computer_id
docker image inspect "$IMAGE" >/dev/null 2>&1 \
|| die "image $IMAGE not built — run: bash cli/scripts/remote-machine.sh build"
if running; then note "already up"; cmd_status; return; fi
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
docker volume create "$VOLUME" >/dev/null
# Created empty if it is not there: an unseeded box still starts, and Codex in it asks to sign in
# exactly as it would on a fresh computer.
mkdir -p "$RIG_CODEX_HOME" && chmod 700 "$RIG_CODEX_HOME"
note "starting ${CONTAINER}…"
docker run -d \
--name "$CONTAINER" \
--hostname harness-remote-box \
-e ADAPTER_COMPUTER_ID="$ADAPTER_COMPUTER_ID" \
-e ADAPTER_LOGIN_CALLBACK_PORT="$LOGIN_PORT" \
-p "127.0.0.1:${LOGIN_PORT}:${LOGIN_PORT}" \
-v "$VOLUME:/home/node/.harness" \
-v "$RIG_CODEX_HOME:/home/node/.codex" \
"$IMAGE" >/dev/null
sleep 1
docker logs "$CONTAINER" 2>&1 | sed 's/^/ /'
}
signed_in() {
docker exec "$CONTAINER" harness auth status --json 2>/dev/null | grep -q '"loggedIn":[[:space:]]*true'
}
cmd_login() {
require_running
if signed_in; then
note "the box is already signed in"
else
# `--json` rather than the human flow, and deliberately so: this is the same NDJSON stream the
# desktop app drives (`lib/auth/cli_login.dart`), so it needs no TTY and works the same whether a
# person or a script is watching. The browser step itself cannot be automated — SSO is a person
# approving something — so the URL is surfaced as loudly as possible and then we wait.
# Bridge the published port to the callback's loopback bind, for this sign-in only.
#
# `harness login` listens on 127.0.0.1:$LOGIN_PORT INSIDE the box — correct everywhere, and
# unreachable from here, because Docker forwards a published port to the container's eth0 address
# rather than its loopback. socat listens on that eth0 address (NOT 0.0.0.0: the CLI already owns
# the same port on loopback, and this way the two cannot collide) and hands the connection across.
# It exits with the sign-in; nothing is left listening.
note "bridging the callback port inside the box…"
docker exec -d "$CONTAINER" sh -c \
"socat TCP-LISTEN:${LOGIN_PORT},bind=\$(hostname -i),fork,reuseaddr TCP:127.0.0.1:${LOGIN_PORT}"
note "signing the box in — approve the URL below, then come back"
docker exec -i "$CONTAINER" harness login --json | node -e '
const rl = require("readline").createInterface({ input: process.stdin })
rl.on("line", (line) => {
let e; try { e = JSON.parse(line) } catch { return }
if (e.type === "authorize_url") {
console.log("")
console.log(" ┌─ approve the box in your browser ─────────────────────────────")
console.log(" │ " + e.url)
console.log(" └───────────────────────────────────────────────────────────────")
console.log("")
return
}
if (e.type === "result") {
if (e.status === "success") console.log(e.alreadySignedIn ? " already signed in" : " signed in")
else { console.error(` sign-in failed: ${e.code} ${e.message || ""}`); process.exitCode = 1 }
}
})'
docker exec "$CONTAINER" pkill -f "socat TCP-LISTEN:${LOGIN_PORT}" >/dev/null 2>&1 || true
signed_in || die "the box is still not signed in"
fi
note "starting the adapter on the box…"
docker exec "$CONTAINER" harness start || true
cmd_status
}
# The box's own machine id, straight from the backend's answer rather than anything we guessed.
box_machine_id() {
docker exec "$CONTAINER" harness machines --json 2>/dev/null \
| node -e '
let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{
for (const line of s.split("\n")) {
if (!line.trim()) continue
try { const m = JSON.parse(line); if (m.current) { process.stdout.write(String(m.machineId||"")); return } } catch {}
}
})'
}
cmd_link() {
require_running
[ -x "$HOST_HARNESS" ] || die "this Mac's harness CLI not found at $HOST_HARNESS (set HARNESS_BIN)"
local id
id="$(box_machine_id)"
[ -n "$id" ] || die "the box has no machine id yet — run: bash cli/scripts/remote-machine.sh login"
note "box machine id: $id"
# A fresh password per run, held only in this shell and passed on stdin to BOTH sides. It is never
# written to disk and never appears in argv — `ps` is world-readable, and a rig that taught the
# habit of putting credentials on a command line would be teaching the wrong habit.
local password
password="$(node -e 'process.stdout.write(require("crypto").randomBytes(24).toString("base64url"))')"
note "setting the box's remote password…"
printf '%s\n' "$password" | docker exec -i "$CONTAINER" harness remote-password set --stdin
note "connecting this Mac to it…"
printf '%s\n' "$password" | "$HOST_HARNESS" link connect "$id" --stdin
note "linked — the box should now appear as a machine in the desktop app"
}
cmd_status() {
require_docker
if ! running; then echo "box: not running"; return; fi
echo "box: running ($CONTAINER)"
docker exec "$CONTAINER" harness status 2>&1 | sed 's/^/ /' || true
if [ -x "$HOST_HARNESS" ]; then
echo "this Mac's linked machines:"
"$HOST_HARNESS" link list 2>&1 | sed 's/^/ /' || true
fi
}
# What is each remote agent ACTUALLY running on, and does the daemon agree?
cmd_verify() {
require_running
docker exec "$CONTAINER" node -e '
const fs = require("fs")
const path = require("path")
const dataDir = process.env.ADAPTER_DATA_DIR || "/home/node/.harness/data"
const registryPath = path.join(dataDir, "registry.json")
// Only the variables that decide where inference goes. The rest of a process environment is the
// user'"'"'s business and this rig has no reason to print it.
const INTERESTING = ["ANTHROPIC_BASE_URL", "ANTHROPIC_MODEL", "OPENAI_BASE_URL", "GROK_MODELS_BASE_URL", "COPILOT_PROVIDER_BASE_URL"]
function processEnv(pid) {
try {
const out = {}
for (const entry of fs.readFileSync(`/proc/${pid}/environ`, "utf8").split("\0")) {
const eq = entry.indexOf("=")
if (eq > 0 && INTERESTING.includes(entry.slice(0, eq))) out[entry.slice(0, eq)] = entry.slice(eq + 1)
}
return out
} catch { return null }
}
let agents = []
try {
const raw = JSON.parse(fs.readFileSync(registryPath, "utf8"))
agents = Array.isArray(raw) ? raw : (raw.agents ? (Array.isArray(raw.agents) ? raw.agents : Object.values(raw.agents)) : [])
} catch (err) {
console.log(`no registry at ${registryPath} (${err.code || err.message}) — has an agent been created on this box yet?`)
process.exit(0)
}
if (!agents.length) { console.log("the box has no agents yet — create one on this machine from the app"); process.exit(0) }
for (const a of agents) {
const pid = a.processIdentity && a.processIdentity.pid
const env = pid ? processEnv(pid) : null
console.log(`agent ${String(a.agentId).slice(0, 8)} engine=${a.engine} pid=${pid ?? "-"} cwd=${a.cwd ?? "-"}`)
// The process is the truth: an environment is fixed at exec, so this is where the requests go.
if (env === null) console.log(" process env : UNREADABLE (process gone?)")
else if (!Object.keys(env).length) console.log(" process env : no endpoint override — the engine is on its OWN login")
else for (const [k, v] of Object.entries(env)) console.log(` process env : ${k}=${v}`)
// What the daemon would tell the app. Printed separately ON PURPOSE — see the header of this file.
console.log(` daemon says : grid=${JSON.stringify(a.grid ?? null)}`)
}
' 2>&1
}
cmd_sh() { require_running; docker exec -it "$CONTAINER" bash; }
cmd_down() {
require_docker
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
note "container removed (volume $VOLUME kept — the box keeps its session and id)"
}
cmd_destroy() {
require_docker
local id=""
running && id="$(box_machine_id || true)"
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
docker volume rm "$VOLUME" >/dev/null 2>&1 || true
rm -f "$RIG_ENV"
note "container, volume and pinned id removed"
# The machine record lives on the ACCOUNT, and nothing local can remove it. Say so plainly rather
# than leaving a tombstone the user finds weeks later in their machine list.
if [ -n "$id" ]; then
echo " the machine record is still on your account. Remove it with:"
echo " harness machines delete $id"
else
echo " if the box was ever signed in, remove its machine record with:"
echo " harness machines # find it"
echo " harness machines delete <id>"
fi
}
case "${1:-}" in
build) cmd_build ;;
up) cmd_up ;;
login) cmd_login ;;
link) cmd_link ;;
status) cmd_status ;;
verify) cmd_verify ;;
sh) cmd_sh ;;
down) cmd_down ;;
destroy) cmd_destroy ;;
*) sed -n '2,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 1 ;;
esac
37a068556This file is pinned to the selected release. Download the original to open it in your design tool.