Files
openwork/.devcontainer/test-server-on-daytona.sh

180 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# test-server-on-daytona.sh — one-command Daytona Den server sandbox.
#
# Usage:
# bash .devcontainer/test-server-on-daytona.sh [branch-or-commit]
# bash .devcontainer/test-server-on-daytona.sh [branch-or-commit] --force-install
# bash .devcontainer/test-server-on-daytona.sh [branch-or-commit] --seed
#
# Creates a Daytona sandbox, starts MySQL + Den API + Den Web,
# waits for health checks, and prints public preview URLs.
REF=""
FORCE_INSTALL=0
RUN_SEED=0
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Pid + random suffix so parallel invocations (multiple features/worktrees)
# never collide on the second-granularity timestamp.
SANDBOX="openwork-server-$(date +%Y%m%d-%H%M%S)-$$-$(od -An -N2 -tx2 /dev/urandom | tr -d ' ')"
DAYTONA_SERVER_SNAPSHOT="${DAYTONA_SERVER_SNAPSHOT:-openwork-server}"
DAYTONA_TARGET="${DAYTONA_TARGET:-us}"
DEN_API_PORT="${DEN_API_PORT:-8788}"
DEN_WEB_PORT="${DEN_WEB_PORT:-3005}"
MAX_WAIT="${DAYTONA_SERVER_MAX_WAIT:-240}"
DEN_GENERATED_ARTIFACT_VIEWS_ENABLED="${DEN_GENERATED_ARTIFACT_VIEWS_ENABLED:-}"
if [ -z "$DEN_GENERATED_ARTIFACT_VIEWS_ENABLED" ]; then
if [ "${OPENWORK_EVAL_GENERATED_ARTIFACT_VIEWS_E2E_TEST:-0}" = "1" ]; then
DEN_GENERATED_ARTIFACT_VIEWS_ENABLED="true"
else
DEN_GENERATED_ARTIFACT_VIEWS_ENABLED="false"
fi
fi
case "$DEN_GENERATED_ARTIFACT_VIEWS_ENABLED" in
true|false) ;;
*) echo "ERROR: DEN_GENERATED_ARTIFACT_VIEWS_ENABLED must be true or false" >&2; exit 1 ;;
esac
while [ "$#" -gt 0 ]; do
case "$1" in
--force-install)
FORCE_INSTALL=1
;;
--seed)
RUN_SEED=1
;;
--snapshot)
shift
DAYTONA_SERVER_SNAPSHOT="${1:?missing snapshot name}"
;;
--name)
shift
SANDBOX="${1:?missing sandbox name}"
;;
--help|-h)
sed -n '1,13p' "$0"
exit 0
;;
--*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
if [ -n "$REF" ]; then
echo "Unexpected extra argument: $1" >&2
exit 1
fi
REF="$1"
;;
esac
shift
done
if [ -z "$REF" ]; then
REF="$(git branch --show-current 2>/dev/null || true)"
REF="${REF:-$(git rev-parse HEAD)}"
fi
# The ref is interpolated into a remote shell assignment below, so anything
# outside git's safe alphabet (or a leading dash) is remote code execution.
case "$REF" in
""|-*|*[!A-Za-z0-9._/-]*)
echo "ERROR: unsafe ref '$REF' — only letters, digits and . _ / - are allowed." >&2
exit 1
;;
esac
snapshot_id() {
daytona snapshot list -f json | node -e 'const name = process.argv[1]; let input = ""; process.stdin.on("data", (chunk) => input += chunk); process.stdin.on("end", () => { const snapshot = JSON.parse(input).find((item) => item.name === name); if (snapshot) process.stdout.write(snapshot.id || snapshot.name); });' "$1"
}
SNAPSHOT_ID="$(snapshot_id "$DAYTONA_SERVER_SNAPSHOT")"
CREATE_ARGS=(--name "$SANDBOX" --auto-stop 60 --public --target "$DAYTONA_TARGET")
if [ -n "$SNAPSHOT_ID" ]; then
echo "==> Using Daytona server snapshot: $DAYTONA_SERVER_SNAPSHOT"
CREATE_ARGS+=(--snapshot "$SNAPSHOT_ID")
else
echo "==> Daytona server snapshot '$DAYTONA_SERVER_SNAPSHOT' not found; building from Dockerfile."
CREATE_ARGS+=(--dockerfile "$ROOT_DIR/.devcontainer/Dockerfile.daytona-server" --cpu 4 --memory 8 --disk 10)
fi
echo "==> Creating server sandbox: $SANDBOX"
echo " Ref: $REF"
daytona create "${CREATE_ARGS[@]}"
echo "==> Waiting for sandbox exec readiness..."
exec_ready=0
for _ in $(seq 1 60); do
if daytona exec "$SANDBOX" -- "bash -lc 'true'" >/dev/null 2>&1; then
exec_ready=1
break
fi
sleep 5
done
if [ "$exec_ready" -ne 1 ]; then
echo "ERROR: sandbox did not become exec-ready" >&2
exit 1
fi
DEN_WEB_URL="$(daytona preview-url "$SANDBOX" -p "$DEN_WEB_PORT" --expires 86400 2>/dev/null | grep -v "^time=")"
DEN_API_URL="$(daytona preview-url "$SANDBOX" -p "$DEN_API_PORT" --expires 86400 2>/dev/null | grep -v "^time=")"
# These exact URLs become the Den's public identity (OAuth issuer + MCP
# resource). Every `daytona preview-url` call signs a fresh hostname, so a
# caller that re-derives them later gets a *different* host and RFC 9728
# validating MCP clients (opencode) refuse the mismatched resource. Hand the
# baked URLs to the caller through a trusted runner-side file instead; this
# write happens on the runner from daytona CLI output only, so sandbox (ref
# controlled) output can never influence it.
if [ -n "${OPENWORK_DEN_URLS_FILE:-}" ]; then
printf 'DEN_WEB_URL=%s\nDEN_API_URL=%s\n' \
"$DEN_WEB_URL" "$DEN_API_URL" > "$OPENWORK_DEN_URLS_FILE"
fi
echo "==> Checking out $REF..."
daytona exec "$SANDBOX" -- "bash -lc 'set -euo pipefail; cd /workspace; REF=\"$REF\"; FORCE_INSTALL=\"$FORCE_INSTALL\"; git reset --hard HEAD; if git fetch origin \"\$REF\"; then git checkout --detach FETCH_HEAD; else git fetch origin dev --depth 50 || true; git checkout \"\$REF\"; fi; git rev-parse --short HEAD; if [ \"\$FORCE_INSTALL\" = 1 ]; then rm -f .openwork-daytona/pnpm-lock.sha256 .openwork-daytona/den-web-build.tree .openwork-daytona/den-api-assets.tree; fi'"
echo "==> Uploading server start script..."
START_SCRIPT_B64="$(base64 < "$ROOT_DIR/.devcontainer/start-daytona-server.sh" | tr -d '\n')"
daytona exec "$SANDBOX" -- "bash -lc 'set -euo pipefail; cd /workspace; mkdir -p .devcontainer; printf %s $START_SCRIPT_B64 | base64 -d > .devcontainer/start-daytona-server.sh; chmod +x .devcontainer/start-daytona-server.sh'"
echo "==> Starting OpenWork Den server stack..."
BOOTSTRAP_ADMIN_EMAILS_B64="$(printf %s "${DEN_BOOTSTRAP_ADMIN_EMAILS:-}" | base64 | tr -d '\n')"
daytona exec "$SANDBOX" -- "bash -lc 'set -euo pipefail; cd /workspace; DEN_BOOTSTRAP_ADMIN_EMAILS=\"\$(printf %s $BOOTSTRAP_ADMIN_EMAILS_B64 | base64 -d)\" DEN_GENERATED_ARTIFACT_VIEWS_ENABLED=\"$DEN_GENERATED_ARTIFACT_VIEWS_ENABLED\" DEN_WEB_PUBLIC_URL=\"$DEN_WEB_URL\" DEN_API_PUBLIC_URL=\"$DEN_API_URL\" DEN_WEB_PORT=$DEN_WEB_PORT DEN_API_PORT=$DEN_API_PORT RUN_SEED=$RUN_SEED bash .devcontainer/start-daytona-server.sh'"
echo "==> Waiting for public Den Web health (up to ${MAX_WAIT}s)..."
elapsed=0
while [ "$elapsed" -lt "$MAX_WAIT" ]; do
if curl -sf "$DEN_WEB_URL/api/den/health" >/dev/null 2>&1; then
echo " Den Web ready after ${elapsed}s."
break
fi
sleep 5
elapsed=$((elapsed + 5))
printf " %ds...\r" "$elapsed"
done
if [ "$elapsed" -ge "$MAX_WAIT" ]; then
echo ""
echo "WARNING: Den Web did not become ready within ${MAX_WAIT}s." >&2
echo "Check logs:" >&2
echo " daytona exec $SANDBOX -- 'tail -120 /tmp/den-api.log'" >&2
echo " daytona exec $SANDBOX -- 'tail -120 /tmp/den-web.log'" >&2
exit 1
fi
echo ""
echo "============================================"
echo " Server sandbox ready: $SANDBOX"
echo ""
echo " Den Web: $DEN_WEB_URL"
echo " Den API: $DEN_API_URL"
echo ""
echo " Start Electron against this server:"
echo " bash .devcontainer/test-on-daytona.sh $REF --den-base-url $DEN_WEB_URL --den-api-base-url $DEN_API_URL"
echo ""
echo " Cleanup:"
echo " daytona delete $SANDBOX"
echo "============================================"