book: scaffold + ch00 (execution trail as spine) — evidence exp 8-31, round 3
This commit is contained in:
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# ---- OpenCode agent server (background, best-effort) ----
|
||||
# Start `opencode serve` inside the same container so the deployed tac-app can
|
||||
# reach it on :4096, in the SAME working directory (/app) — sharing
|
||||
# opencode.json, the skill library and .opencode/. The browser talks to it via
|
||||
# the app's OPENCODE_BASE_URL; --cors must allow the app's own origin.
|
||||
#
|
||||
# opencode MUST NOT gate app startup. It used to: the entrypoint blocked on an
|
||||
# unbounded probe, so when opencode's HTTP layer accepted the TCP connection
|
||||
# but never answered (slow MCP cold-start) the curl hung forever, the container
|
||||
# never listened on :3000, Coolify's healthcheck failed, and the site stayed
|
||||
# down until `next start` was started manually in the terminal.
|
||||
OPENCODE_PORT="${OPENCODE_PORT:-4096}"
|
||||
OPENCODE_HOSTNAME="${OPENCODE_HOSTNAME:-0.0.0.0}"
|
||||
|
||||
cors_origins=""
|
||||
cors_args=""
|
||||
add_cors() {
|
||||
for existing in $cors_origins; do
|
||||
[ "$existing" = "$1" ] && return
|
||||
done
|
||||
cors_origins="$cors_origins $1"
|
||||
cors_args="$cors_args --cors $1"
|
||||
}
|
||||
if [ -n "${OPENCODE_CORS:-}" ]; then
|
||||
for origin in $(echo "$OPENCODE_CORS" | tr ',' ' '); do
|
||||
[ -n "$origin" ] && add_cors "$origin"
|
||||
done
|
||||
else
|
||||
for origin in "http://localhost:3000" "https://localhost:3000" \
|
||||
"${BETTER_AUTH_URL:-}" "${APP_URL:-}"; do
|
||||
[ -n "$origin" ] && add_cors "$origin"
|
||||
done
|
||||
fi
|
||||
|
||||
echo "> Starting opencode serve on :$OPENCODE_PORT (auto-restart; log: /tmp/opencode-serve.log) ..."
|
||||
(
|
||||
while :; do
|
||||
# shellcheck disable=SC2086 # intentional word splitting for --cors flags
|
||||
opencode serve --hostname "$OPENCODE_HOSTNAME" --port "$OPENCODE_PORT" $cors_args \
|
||||
|| echo "> opencode serve exited ($?) — restarting in 2s ..."
|
||||
sleep 2
|
||||
done
|
||||
) >/tmp/opencode-serve.log 2>&1 &
|
||||
OPENCODE_PID=$!
|
||||
|
||||
# Best-effort readiness probe: bounded (10s) and every curl capped with
|
||||
# --max-time, so a half-open listen can never stall the container again. If
|
||||
# opencode is slow or down, the app still starts — agent features just degrade.
|
||||
i=0
|
||||
while [ "$i" -lt 10 ]; do
|
||||
if curl -sS --max-time 2 -o /dev/null "http://127.0.0.1:$OPENCODE_PORT/"; then
|
||||
echo "> opencode serve ready on :$OPENCODE_PORT (pid $OPENCODE_PID)"
|
||||
break
|
||||
fi
|
||||
i=$((i + 1))
|
||||
sleep 1
|
||||
done
|
||||
if [ "$i" -ge 10 ]; then
|
||||
echo "> WARNING: opencode serve not ready after 10s — continuing anyway (tail -f /tmp/opencode-serve.log)"
|
||||
fi
|
||||
|
||||
# ---- Experiments submodule (git lineage) ----
|
||||
# The `experiments` submodule lives in the ephemeral container layer — it is
|
||||
# re-created at runtime by `trace.sh init` and is wiped on every redeploy. Ensure
|
||||
# it exists on each boot so /rd/graph and trace.sh work right after a deploy.
|
||||
# Idempotent (validates/creates against $GIT_REPO_URL) and best-effort: never
|
||||
# gate app startup.
|
||||
if [ -n "${GIT_REPO_URL:-}" ] && [ -n "${GIT_USER:-}" ]; then
|
||||
if [ -f /app/tac-qlib/skills/tac-qlib-custom/lib/git_exp.sh ]; then
|
||||
(
|
||||
cd /app
|
||||
GIT_REPO_URL="$GIT_REPO_URL" GIT_USER="$GIT_USER" GIT_PASS="${GIT_PASS:-}" \
|
||||
bash tac-qlib/skills/tac-qlib-custom/lib/git_exp.sh ensure_repo >/dev/null 2>&1 \
|
||||
&& GIT_REPO_URL="$GIT_REPO_URL" GIT_USER="$GIT_USER" GIT_PASS="${GIT_PASS:-}" \
|
||||
bash tac-qlib/skills/tac-qlib-custom/lib/git_exp.sh ensure_base main >/dev/null 2>&1
|
||||
) || echo "> WARNING: could not ensure experiments submodule — run trace.sh init in the container"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Default: serve HTTP with `next start` (production mode).
|
||||
if [ "${SERVER_TLS:-false}" != "true" ]; then
|
||||
exec node node_modules/next/dist/bin/next start tac-app
|
||||
fi
|
||||
|
||||
# ---- HTTPS mode (self-signed certificate) ----
|
||||
# Set SERVER_TLS=true to serve the app over HTTPS. A self-signed cert is
|
||||
# generated on first start and kept under TLS_DIR; override TLS_KEY / TLS_CERT
|
||||
# to mount your own certificates.
|
||||
TLS_HOST="${TLS_HOST:-localhost}"
|
||||
TLS_DIR="${TLS_DIR:-/tmp/tls}"
|
||||
TLS_KEY="${TLS_KEY:-$TLS_DIR/key.pem}"
|
||||
TLS_CERT="${TLS_CERT:-$TLS_DIR/cert.pem}"
|
||||
|
||||
if [ ! -s "$TLS_KEY" ] || [ ! -s "$TLS_CERT" ]; then
|
||||
echo "> Generating self-signed certificate for $TLS_HOST ..."
|
||||
echo "> (Browsers will warn ERR_CERT_AUTHORITY_INVALID. For a trusted cert, generate one with"
|
||||
echo "> mkcert on the host and mount it via TLS_KEY/TLS_CERT.)"
|
||||
mkdir -p "$TLS_DIR"
|
||||
openssl req -x509 -newkey rsa:2048 -nodes \
|
||||
-keyout "$TLS_KEY" -out "$TLS_CERT" -days 825 \
|
||||
-subj "/CN=$TLS_HOST" \
|
||||
-addext "subjectAltName=DNS:localhost,DNS:$TLS_HOST,IP:127.0.0.1" \
|
||||
>/dev/null 2>&1
|
||||
fi
|
||||
|
||||
export TLS_KEY TLS_CERT TLS_HOST
|
||||
exec node /app/tls-server.cjs
|
||||
Reference in New Issue
Block a user