Something went wrong. Try again.
[PRE-ALPHA] an open source web-based 2D/3D graphing calculator cartesium.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285#!/bin/shset -euusage() { cat >&2 <<'EOF'Usage: ./deploy.sh test|prodTargets: test test.example.org/ -> /path/to/cartesium-test:2222 prod example.org/ -> /path/to/cartesium-prod:3333Set CARTESIUM_DEPLOY_REMOTE to the SSH destination, for example: CARTESIUM_DEPLOY_REMOTE=root@your-host ./deploy.sh testThe deployment waits for AppView's filtered archive backfill to complete.Override the bounded wait when a first deployment needs longer: CARTESIUM_APPVIEW_VERIFY_TIMEOUT=7200 CARTESIUM_DEPLOY_REMOTE=... ./deploy.sh testEOF}TARGET=${1:-}REMOTE=${CARTESIUM_DEPLOY_REMOTE:?Set CARTESIUM_DEPLOY_REMOTE to the SSH destination}APPVIEW_VERIFY_TIMEOUT=${CARTESIUM_APPVIEW_VERIFY_TIMEOUT:-3600}APPVIEW_VERIFY_INTERVAL=${CARTESIUM_APPVIEW_VERIFY_INTERVAL:-15}case "$APPVIEW_VERIFY_TIMEOUT" in ''|*[!0-9]*) echo 'CARTESIUM_APPVIEW_VERIFY_TIMEOUT must be a positive number of seconds.' >&2 exit 2 ;;esaccase "$APPVIEW_VERIFY_INTERVAL" in ''|*[!0-9]*) echo 'CARTESIUM_APPVIEW_VERIFY_INTERVAL must be a positive number of seconds.' >&2 exit 2 ;;esacif [ "$APPVIEW_VERIFY_TIMEOUT" -lt 1 ] || [ "$APPVIEW_VERIFY_INTERVAL" -lt 1 ]; then echo 'CARTESIUM_APPVIEW_VERIFY_TIMEOUT and CARTESIUM_APPVIEW_VERIFY_INTERVAL must be positive.' >&2 exit 2ficase "$TARGET" in test) BASE_PATH= ORIGIN=https://test.example.org HOST_PORT=2222 REMOTE_DIR=/path/to/cartesium-test COMPOSE_PROJECT=cartesium-test ;; prod) BASE_PATH= ORIGIN=https://example.org HOST_PORT=3333 REMOTE_DIR=/path/to/cartesium-prod COMPOSE_PROJECT=cartesium-prod ;; *) usage exit 2 ;;esacREPO_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)APP_ROOT=$REPO_ROOT/apps/webecho "Preparing $REMOTE:$REMOTE_DIR..."ssh "$REMOTE" "install -d -m 0755 '$REMOTE_DIR'"rsync -avz "$APP_ROOT/.env.example" "$REMOTE:$REMOTE_DIR/"ssh "$REMOTE" \"test -f '$REMOTE_DIR/.env' || { echo 'Missing $REMOTE_DIR/.env.' >&2; echo 'Run: cd $REMOTE_DIR && cp .env.example .env, then edit it.' >&2; exit 1; }"echo "Building Cartesium for $TARGET ($ORIGIN)..."CARTESIUM_ORIGIN="$ORIGIN" CARTESIUM_BASE_PATH="$BASE_PATH" npm --prefix "$APP_ROOT" run buildrsync -avz "$APP_ROOT/build/" "$REMOTE:$REMOTE_DIR/build/"rsync -avz \"$APP_ROOT/Dockerfile" \"$APP_ROOT/compose.yaml" \"$APP_ROOT/.dockerignore" \"$REMOTE:$REMOTE_DIR/"rsync -avz --delete \--exclude node_modules \--exclude dist \--exclude .svelte-kit \"$APP_ROOT/appview/" \"$REMOTE:$REMOTE_DIR/appview/"if ! ssh "$REMOTE" \"cd '$REMOTE_DIR' && CARTESIUM_HOST_PORT='$HOST_PORT' docker compose -p '$COMPOSE_PROJECT' up --detach --build --remove-orphans --wait"; then echo "Deployment services did not become healthy. Inspect them with:" >&2 echo " ssh '$REMOTE' \"cd '$REMOTE_DIR' && docker compose -p '$COMPOSE_PROJECT' ps\"" >&2 echo " ssh '$REMOTE' \"cd '$REMOTE_DIR' && docker compose -p '$COMPOSE_PROJECT' logs --tail=100 web appview postgres\"" >&2 exit 1fiecho "Verifying AppView index state on $REMOTE:$REMOTE_DIR..."ssh "$REMOTE" sh -s -- "$REMOTE_DIR" "$COMPOSE_PROJECT" "$APPVIEW_VERIFY_TIMEOUT" "$APPVIEW_VERIFY_INTERVAL" <<'REMOTE_APPVIEW_VERIFY'set -euREMOTE_DIR=$1COMPOSE_PROJECT=$2VERIFY_TIMEOUT=$3VERIFY_INTERVAL=$4cd "$REMOTE_DIR"diagnostics() { echo 'Safe AppView diagnostics:' >&2 if docker compose -p "$COMPOSE_PROJECT" ps web appview postgres 2>/dev/null; then : fi echo "Inspect recent service output with: cd '$REMOTE_DIR' && docker compose -p '$COMPOSE_PROJECT' logs --tail=100 appview" >&2 echo "Inspect the database service with: cd '$REMOTE_DIR' && docker compose -p '$COMPOSE_PROJECT' logs --tail=100 postgres" >&2 echo "The verifier never prints .env, compose config, database URLs, or Jetstream credentials." >&2}probe_appview() { # Keep parsing inside the AppView image, where node is already available. The # probe emits only fixed, non-secret fields and suppresses Docker/error output. timeout 10 docker compose -p "$COMPOSE_PROJECT" exec -T appview node -e 'fetch("http://127.0.0.1:4100/readyz", { signal: AbortSignal.timeout(5000) }) .then(async (response) => { const text = await response.text(); let payload; try { payload = JSON.parse(text); } catch { process.exitCode = 2; return; } const index = payload && payload.index; if ( !index || typeof index !== "object" || typeof index.state !== "string" || typeof index.backfillComplete !== "boolean" ) { process.exitCode = 3; return; } const sequence = (value) => Number.isSafeInteger(value) && value >= 0 ? String(value) : "-"; const lastEventAt = typeof index.lastEventAt === "string" && index.lastEventAt.length > 0 ? index.lastEventAt.replace(/[\t\r\n]/g, "") || "-" : "-"; process.stdout.write([ String(response.status), index.state, index.backfillComplete ? "true" : "false", sequence(index.backfillCursor), sequence(index.backfillTarget), lastEventAt ].join("\t")); }) .catch(() => { process.exitCode = 4; });' 2>/dev/null}started_at=$(date +%s)last_cursor=last_target=last_state=probe_failures=0echo "AppView archive backfill may take a while on the first deployment; waiting up to ${VERIFY_TIMEOUT}s."while :; do now=$(date +%s) elapsed=$((now - started_at)) if [ "$elapsed" -ge "$VERIFY_TIMEOUT" ]; then echo "ERROR: AppView index verification timed out after ${VERIFY_TIMEOUT}s; the backfill may be stalled." >&2 echo "Last observed AppView state: ${last_state:-unavailable}, cursor ${last_cursor:--}, target ${last_target:--}." >&2 diagnostics exit 1 fi if probe=$(probe_appview); then probe_failures=0 else probe_failures=$((probe_failures + 1)) if [ "$probe_failures" -ge 3 ]; then echo 'ERROR: AppView /readyz is unreachable or returned an invalid response three times.' >&2 diagnostics exit 1 fi echo "AppView /readyz is not reachable yet (attempt ${probe_failures}/3); retrying in ${VERIFY_INTERVAL}s." >&2 sleep "$VERIFY_INTERVAL" continue fi # The probe has exactly six tab-separated, non-secret fields. Disable glob # expansion while splitting so a malformed response cannot match a filename. set -f old_ifs=$IFS IFS=' ' set -- $probe IFS=$old_ifs set +f if [ "$#" -ne 6 ]; then echo 'ERROR: AppView /readyz returned an invalid index response.' >&2 diagnostics exit 1 fi http_status=$1 state=$2 backfill_complete=$3 cursor=$4 target=$5 last_event_at=$6 case "$cursor" in ''|*[!0-9]*) echo 'ERROR: AppView /readyz returned an invalid backfill cursor.' >&2 diagnostics exit 1 ;; esac case "$target" in -) ;; ''|*[!0-9]*) echo 'ERROR: AppView /readyz returned an invalid backfill target.' >&2 diagnostics exit 1 ;; esac case "$http_status" in ''|*[!0-9]*) echo 'ERROR: AppView /readyz returned an invalid HTTP status.' >&2 diagnostics exit 1 ;; esac if [ "$http_status" -ne 200 ]; then if [ "$state" = degraded ]; then echo "ERROR: AppView reports degraded (HTTP ${http_status}; cursor ${cursor}, target ${target})." >&2 else echo "ERROR: AppView /readyz returned HTTP ${http_status} (state ${state})." >&2 fi echo "Last event timestamp: ${last_event_at}." >&2 diagnostics exit 1 fi case "$state:$backfill_complete" in degraded:*) echo "ERROR: AppView reports degraded (cursor ${cursor}, target ${target})." >&2 echo "Last event timestamp: ${last_event_at}." >&2 diagnostics exit 1 ;; complete:true) if [ "$target" = '-' ]; then echo 'ERROR: AppView reported complete without a pinned backfill target.' >&2 diagnostics exit 1 fi echo "AppView index verified complete (cursor ${cursor}, target ${target})." exit 0 ;; running:false) if [ "$state" != "$last_state" ] || [ "$cursor" != "$last_cursor" ] || [ "$target" != "$last_target" ]; then if [ "$target" = '-' ]; then echo "AppView backfill is running; Jetstream target is being planned (elapsed ${elapsed}s)." else echo "AppView backfill is running (cursor ${cursor}, target ${target}; elapsed ${elapsed}s)." fi fi last_state=$state last_cursor=$cursor last_target=$target ;; *) echo "ERROR: AppView returned inconsistent index state ${state}/${backfill_complete}." >&2 diagnostics exit 1 ;; esac sleep "$VERIFY_INTERVAL"doneREMOTE_APPVIEW_VERIFY