#!/usr/bin/env bash set -Eeuo pipefail SOURCE=$(realpath "${1:?checkout directory required}") IMAGE=${2:?verified integration image required} PORT=${PREVIEW_PORT:-18136} [[ "$IMAGE" =~ ^waterfall-wiki-tests:[0-9a-f]{40}$ ]] || exit 2 [[ "$PORT" =~ ^[0-9]+$ ]] && (( PORT >= 18136 && PORT <= 65535 )) || exit 2 command -v rsync >/dev/null command -v ss >/dev/null docker image inspect "$IMAGE" >/dev/null ROOT=/opt/prompt-preview exec 7>/var/lock/prompt-preview-init.lock flock -w 30 7 if [ -s "$ROOT/fingerprint" ]; then echo 'Already initialized; use sync.sh, or perform an explicit manual refresh' >&2; exit 1; fi if ss -H -ltn "sport = :$PORT" | grep -q .; then echo "Preview port $PORT is occupied; choose another PREVIEW_PORT" >&2; exit 1; fi # The prebuilt tooling image must match dependency, startup and schema inputs. expected=$(node "$SOURCE/scripts/preview/fingerprint.mjs" "$SOURCE") actual=$(docker run --rm --entrypoint node \ --mount "type=bind,src=$SOURCE/scripts/preview/fingerprint.mjs,dst=/tmp/preview-fingerprint.mjs,readonly" \ "$IMAGE" /tmp/preview-fingerprint.mjs /repo) [[ "$actual" == "$expected" ]] || { echo 'Tooling image does not match preview dependencies/configuration/schema' >&2; exit 1; } install -d -m 0700 "$ROOT" install -d -m 0755 -o 1000 -g 1000 "$ROOT/source" "$ROOT/status" # Create independent preview credentials only once, never copy production secrets. if [ ! -s "$ROOT/env" ]; then ROOT="$ROOT" IMAGE="$IMAGE" PORT="$PORT" python3 - <<'PY' import os,secrets,pathlib p=pathlib.Path(os.environ['ROOT'])/'env' with p.open('x') as f: os.chmod(p,0o600) f.write('PREVIEW_TOOL_IMAGE='+os.environ['IMAGE']+'\nPREVIEW_PORT='+os.environ['PORT']+'\n') for key in ['POSTGRES_PASSWORD','S3_ACCESS_KEY','S3_SECRET_KEY']:f.write(key+'='+secrets.token_hex(24)+'\n') PY fi rsync -a --delete --chown=1000:1000 \ --exclude='.git' --exclude='node_modules' --exclude='.next' --exclude='dist' \ --exclude='*.tsbuildinfo' --exclude='.env' --exclude='.env.*' --exclude='.gitea' \ "$SOURCE/" "$ROOT/source/" install -m 0644 "$SOURCE/scripts/preview/compose.yml" "$ROOT/compose.yml" compose(){ docker compose -p prompt-preview --env-file "$ROOT/env" -f "$ROOT/compose.yml" "$@"; } compose up -d --no-build --no-recreate --wait --wait-timeout 120 postgres objects compose run --rm --no-deps prepare-tools compose run --rm --no-deps auth-admin compose run --rm --no-deps migrate # The dependency volume is seeded from the already-verified image, no npm install. compose up -d --no-build --no-recreate --no-deps waterfall-wiki printf '%s\n' "$expected" > "$ROOT/fingerprint" for attempt in {1..90}; do if curl -fsS --max-time 5 "http://127.0.0.1:$PORT/api/health" >/dev/null; then echo "Preview initialized on port $PORT; pnpm dev remains running" exit 0 fi sleep 2 done compose logs --tail 60 waterfall-wiki exit 1