250 lines
7.6 KiB
Bash
Executable File
250 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
IMAGE_NAME="${IMAGE_NAME:-paperclip-onboard-smoke}"
|
|
HOST_PORT="${HOST_PORT:-3131}"
|
|
PAPERCLIPAI_VERSION="${PAPERCLIPAI_VERSION:-latest}"
|
|
DATA_DIR="${DATA_DIR:-$REPO_ROOT/data/docker-onboard-smoke}"
|
|
HOST_UID="${HOST_UID:-$(id -u)}"
|
|
PAPERCLIP_DEPLOYMENT_MODE="${PAPERCLIP_DEPLOYMENT_MODE:-authenticated}"
|
|
PAPERCLIP_DEPLOYMENT_EXPOSURE="${PAPERCLIP_DEPLOYMENT_EXPOSURE:-private}"
|
|
PAPERCLIP_PUBLIC_URL="${PAPERCLIP_PUBLIC_URL:-http://localhost:${HOST_PORT}}"
|
|
SMOKE_AUTO_BOOTSTRAP="${SMOKE_AUTO_BOOTSTRAP:-true}"
|
|
SMOKE_ADMIN_NAME="${SMOKE_ADMIN_NAME:-Smoke Admin}"
|
|
SMOKE_ADMIN_EMAIL="${SMOKE_ADMIN_EMAIL:-smoke-admin@paperclip.local}"
|
|
SMOKE_ADMIN_PASSWORD="${SMOKE_ADMIN_PASSWORD:-paperclip-smoke-password}"
|
|
CONTAINER_NAME="${IMAGE_NAME//[^a-zA-Z0-9_.-]/-}"
|
|
LOG_PID=""
|
|
COOKIE_JAR=""
|
|
TMP_DIR=""
|
|
|
|
mkdir -p "$DATA_DIR"
|
|
|
|
cleanup() {
|
|
if [[ -n "$LOG_PID" ]]; then
|
|
kill "$LOG_PID" >/dev/null 2>&1 || true
|
|
fi
|
|
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
if [[ -n "$TMP_DIR" && -d "$TMP_DIR" ]]; then
|
|
rm -rf "$TMP_DIR"
|
|
fi
|
|
}
|
|
|
|
trap cleanup EXIT INT TERM
|
|
|
|
wait_for_http() {
|
|
local url="$1"
|
|
local attempts="${2:-60}"
|
|
local sleep_seconds="${3:-1}"
|
|
local i
|
|
for ((i = 1; i <= attempts; i += 1)); do
|
|
if curl -fsS "$url" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep "$sleep_seconds"
|
|
done
|
|
return 1
|
|
}
|
|
|
|
generate_bootstrap_invite_url() {
|
|
local bootstrap_output
|
|
local bootstrap_status
|
|
if bootstrap_output="$(
|
|
docker exec \
|
|
-e PAPERCLIP_DEPLOYMENT_MODE="$PAPERCLIP_DEPLOYMENT_MODE" \
|
|
-e PAPERCLIP_DEPLOYMENT_EXPOSURE="$PAPERCLIP_DEPLOYMENT_EXPOSURE" \
|
|
-e PAPERCLIP_PUBLIC_URL="$PAPERCLIP_PUBLIC_URL" \
|
|
-e PAPERCLIP_HOME="/paperclip" \
|
|
"$CONTAINER_NAME" bash -lc \
|
|
'timeout 20s npx --yes "paperclipai@${PAPERCLIPAI_VERSION}" auth bootstrap-ceo --data-dir "$PAPERCLIP_HOME" --base-url "$PAPERCLIP_PUBLIC_URL"' \
|
|
2>&1
|
|
)"; then
|
|
bootstrap_status=0
|
|
else
|
|
bootstrap_status=$?
|
|
fi
|
|
|
|
if [[ $bootstrap_status -ne 0 && $bootstrap_status -ne 124 ]]; then
|
|
echo "Smoke bootstrap failed: could not run bootstrap-ceo inside container" >&2
|
|
printf '%s\n' "$bootstrap_output" >&2
|
|
return 1
|
|
fi
|
|
|
|
local invite_url
|
|
invite_url="$(
|
|
printf '%s\n' "$bootstrap_output" \
|
|
| grep -o 'https\?://[^[:space:]]*/invite/pcp_bootstrap_[[:alnum:]]*' \
|
|
| tail -n 1
|
|
)"
|
|
|
|
if [[ -z "$invite_url" ]]; then
|
|
echo "Smoke bootstrap failed: bootstrap-ceo did not print an invite URL" >&2
|
|
printf '%s\n' "$bootstrap_output" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [[ $bootstrap_status -eq 124 ]]; then
|
|
echo " Smoke bootstrap: bootstrap-ceo timed out after printing invite URL; continuing" >&2
|
|
fi
|
|
|
|
printf '%s\n' "$invite_url"
|
|
}
|
|
|
|
post_json_with_cookies() {
|
|
local url="$1"
|
|
local body="$2"
|
|
local output_file="$3"
|
|
curl -sS \
|
|
-o "$output_file" \
|
|
-w "%{http_code}" \
|
|
-c "$COOKIE_JAR" \
|
|
-b "$COOKIE_JAR" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Origin: $PAPERCLIP_PUBLIC_URL" \
|
|
-X POST \
|
|
"$url" \
|
|
--data "$body"
|
|
}
|
|
|
|
get_with_cookies() {
|
|
local url="$1"
|
|
curl -fsS \
|
|
-c "$COOKIE_JAR" \
|
|
-b "$COOKIE_JAR" \
|
|
-H "Accept: application/json" \
|
|
"$url"
|
|
}
|
|
|
|
sign_up_or_sign_in() {
|
|
local signup_response="$TMP_DIR/signup.json"
|
|
local signup_status
|
|
signup_status="$(post_json_with_cookies \
|
|
"$PAPERCLIP_PUBLIC_URL/api/auth/sign-up/email" \
|
|
"{\"name\":\"$SMOKE_ADMIN_NAME\",\"email\":\"$SMOKE_ADMIN_EMAIL\",\"password\":\"$SMOKE_ADMIN_PASSWORD\"}" \
|
|
"$signup_response")"
|
|
if [[ "$signup_status" =~ ^2 ]]; then
|
|
echo " Smoke bootstrap: created admin user $SMOKE_ADMIN_EMAIL"
|
|
return 0
|
|
fi
|
|
|
|
local signin_response="$TMP_DIR/signin.json"
|
|
local signin_status
|
|
signin_status="$(post_json_with_cookies \
|
|
"$PAPERCLIP_PUBLIC_URL/api/auth/sign-in/email" \
|
|
"{\"email\":\"$SMOKE_ADMIN_EMAIL\",\"password\":\"$SMOKE_ADMIN_PASSWORD\"}" \
|
|
"$signin_response")"
|
|
if [[ "$signin_status" =~ ^2 ]]; then
|
|
echo " Smoke bootstrap: signed in existing admin user $SMOKE_ADMIN_EMAIL"
|
|
return 0
|
|
fi
|
|
|
|
echo "Smoke bootstrap failed: could not sign up or sign in admin user" >&2
|
|
echo "Sign-up response:" >&2
|
|
cat "$signup_response" >&2 || true
|
|
echo >&2
|
|
echo "Sign-in response:" >&2
|
|
cat "$signin_response" >&2 || true
|
|
echo >&2
|
|
return 1
|
|
}
|
|
|
|
auto_bootstrap_authenticated_smoke() {
|
|
local health_url="$PAPERCLIP_PUBLIC_URL/api/health"
|
|
local health_json
|
|
health_json="$(curl -fsS "$health_url")"
|
|
if [[ "$health_json" != *'"deploymentMode":"authenticated"'* ]]; then
|
|
return 0
|
|
fi
|
|
|
|
sign_up_or_sign_in
|
|
|
|
if [[ "$health_json" == *'"bootstrapStatus":"ready"'* ]]; then
|
|
echo " Smoke bootstrap: instance already ready"
|
|
else
|
|
local invite_url
|
|
invite_url="$(generate_bootstrap_invite_url)"
|
|
echo " Smoke bootstrap: generated bootstrap invite via auth bootstrap-ceo"
|
|
|
|
local invite_token="${invite_url##*/}"
|
|
local accept_response="$TMP_DIR/accept.json"
|
|
local accept_status
|
|
accept_status="$(post_json_with_cookies \
|
|
"$PAPERCLIP_PUBLIC_URL/api/invites/$invite_token/accept" \
|
|
'{"requestType":"human"}' \
|
|
"$accept_response")"
|
|
if [[ ! "$accept_status" =~ ^2 ]]; then
|
|
echo "Smoke bootstrap failed: bootstrap invite acceptance returned HTTP $accept_status" >&2
|
|
cat "$accept_response" >&2 || true
|
|
echo >&2
|
|
return 1
|
|
fi
|
|
echo " Smoke bootstrap: accepted bootstrap invite"
|
|
fi
|
|
|
|
local session_json
|
|
session_json="$(get_with_cookies "$PAPERCLIP_PUBLIC_URL/api/auth/get-session")"
|
|
if [[ "$session_json" != *'"userId"'* ]]; then
|
|
echo "Smoke bootstrap failed: no authenticated session after bootstrap" >&2
|
|
echo "$session_json" >&2
|
|
return 1
|
|
fi
|
|
|
|
local companies_json
|
|
companies_json="$(get_with_cookies "$PAPERCLIP_PUBLIC_URL/api/companies")"
|
|
if [[ "${companies_json:0:1}" != "[" ]]; then
|
|
echo "Smoke bootstrap failed: board companies endpoint did not return JSON array" >&2
|
|
echo "$companies_json" >&2
|
|
return 1
|
|
fi
|
|
|
|
echo " Smoke bootstrap: board session verified"
|
|
echo " Smoke admin credentials: $SMOKE_ADMIN_EMAIL / $SMOKE_ADMIN_PASSWORD"
|
|
}
|
|
|
|
echo "==> Building onboard smoke image"
|
|
docker build \
|
|
--build-arg PAPERCLIPAI_VERSION="$PAPERCLIPAI_VERSION" \
|
|
--build-arg HOST_UID="$HOST_UID" \
|
|
-f "$REPO_ROOT/Dockerfile.onboard-smoke" \
|
|
-t "$IMAGE_NAME" \
|
|
"$REPO_ROOT"
|
|
|
|
echo "==> Running onboard smoke container"
|
|
echo " UI should be reachable at: http://localhost:$HOST_PORT"
|
|
echo " Public URL: $PAPERCLIP_PUBLIC_URL"
|
|
echo " Smoke auto-bootstrap: $SMOKE_AUTO_BOOTSTRAP"
|
|
echo " Data dir: $DATA_DIR"
|
|
echo " Deployment: $PAPERCLIP_DEPLOYMENT_MODE/$PAPERCLIP_DEPLOYMENT_EXPOSURE"
|
|
echo " Live output: onboard banner and server logs stream in this terminal (Ctrl+C to stop)"
|
|
|
|
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
|
|
docker run -d --rm \
|
|
--name "$CONTAINER_NAME" \
|
|
-p "$HOST_PORT:3100" \
|
|
-e HOST=0.0.0.0 \
|
|
-e PORT=3100 \
|
|
-e PAPERCLIP_DEPLOYMENT_MODE="$PAPERCLIP_DEPLOYMENT_MODE" \
|
|
-e PAPERCLIP_DEPLOYMENT_EXPOSURE="$PAPERCLIP_DEPLOYMENT_EXPOSURE" \
|
|
-e PAPERCLIP_PUBLIC_URL="$PAPERCLIP_PUBLIC_URL" \
|
|
-v "$DATA_DIR:/paperclip" \
|
|
"$IMAGE_NAME" >/dev/null
|
|
|
|
docker logs -f "$CONTAINER_NAME" &
|
|
LOG_PID=$!
|
|
|
|
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/paperclip-onboard-smoke.XXXXXX")"
|
|
COOKIE_JAR="$TMP_DIR/cookies.txt"
|
|
|
|
if ! wait_for_http "$PAPERCLIP_PUBLIC_URL/api/health" 90 1; then
|
|
echo "Smoke bootstrap failed: server did not become ready at $PAPERCLIP_PUBLIC_URL/api/health" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$SMOKE_AUTO_BOOTSTRAP" == "true" && "$PAPERCLIP_DEPLOYMENT_MODE" == "authenticated" ]]; then
|
|
auto_bootstrap_authenticated_smoke
|
|
fi
|
|
|
|
wait "$LOG_PID"
|