180 lines
7.0 KiB
Bash
Executable File
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 "============================================"
|