#!/usr/bin/env bash set -euo pipefail umask 077 ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" IMAGE="${CONDUCTOR_TEST_IMAGE:-conductor:slice8-test}" PROJECT="conductor-release-test-${RANDOM}" PORT="${CONDUCTOR_TEST_PORT:-3202}" work="$(mktemp -d)" env_file="${work}/.env" backup_dir="${work}/backups" compose=(docker compose -p "$PROJECT" --env-file "$env_file" -f "$ROOT/compose.production.yml") cleanup() { "${compose[@]}" down -v --remove-orphans >/dev/null 2>&1 || true; rm -rf -- "$work"; } trap cleanup EXIT if [[ "${1:-}" != "--skip-build" ]]; then docker build -f "$ROOT/Dockerfile.production" -t "$IMAGE" "$ROOT"; fi if docker run --rm "$IMAGE" >/dev/null 2>&1; then echo "Production image started without required keys." >&2; exit 1; fi cat > "$env_file" </dev/null curl --fail --silent --show-error "http://127.0.0.1:${PORT}/" | grep -qi '' curl --fail --silent --show-error "http://127.0.0.1:${PORT}/apps/test/details" | grep -qi '' api_status="$(curl --silent --output /dev/null --write-out '%{http_code}' "http://127.0.0.1:${PORT}/api/not-a-route")" [[ "$api_status" == 404 ]] || { echo "Unknown API routes must return 404, not the SPA." >&2; exit 1; } "${compose[@]}" exec -T conductor node -e 'const D=require("better-sqlite3");const d=new D("/data/conductor.db");d.prepare("INSERT INTO projects(name,description,project_json) VALUES(?,?,?)").run("release-restore-marker","test","{}");d.close()' COMPOSE_PROJECT_NAME="$PROJECT" "$ROOT/scripts/production/backup-conductor.sh" --compose-file "$ROOT/compose.production.yml" --env-file "$env_file" --backup-dir "$backup_dir" --retention-days 1 >/dev/null archive="$(find "$backup_dir" -name 'conductor_*.tar.gz' -type f | head -n1)" "${compose[@]}" exec -T conductor node -e 'const D=require("better-sqlite3");const d=new D("/data/conductor.db");d.prepare("DELETE FROM projects WHERE name=?").run("release-restore-marker");d.close()' COMPOSE_PROJECT_NAME="$PROJECT" "$ROOT/scripts/production/restore-conductor.sh" --compose-file "$ROOT/compose.production.yml" --env-file "$env_file" --backup-dir "$backup_dir" --backup-file "$archive" --skip-safety-backup --force >/dev/null count="$("${compose[@]}" exec -T conductor node -e 'const D=require("better-sqlite3");const d=new D("/data/conductor.db",{readonly:true});process.stdout.write(String(d.prepare("SELECT count(*) FROM projects WHERE name=?").pluck().get("release-restore-marker")));d.close()')" [[ "$count" == 1 ]] || { echo "Restore marker was not recovered." >&2; exit 1; } docker inspect "$("${compose[@]}" ps -q conductor)" --format '{{.Config.User}} {{.HostConfig.ReadonlyRootfs}}' | grep -q '^conductor true$' echo '{"productionValidation":"passed"}'