#!/usr/bin/env bash # # setup-space.sh: create the single space and its first admin on the # docker-compose.yml stack. Wraps scripts/setup.mjs (the same code as # `pnpm setup`) and runs it in a container on the stack's network, so the # host needs only Docker: no Node, no pnpm, no node_modules. # # Usage: scripts/setup-space.sh [command] [setup.mjs flags] # scripts/setup-space.sh interactive: space, then admin # scripts/setup-space.sh doctor check config and DB connectivity # scripts/setup-space.sh --yes \ # --admin-email you@example.org --admin-password '...' # # Reads from .env: SERVICE_ROLE_KEY, ANON_KEY, NEXT_PUBLIC_APP_URL, # NEXT_PUBLIC_SITE_NAME and NEXT_PUBLIC_SINGLE_TENANT_SPACE_SLUG (used as the # default space name and slug), and any SETUP_* values. Flags override these. # The stack must already be running. If the database has no schema yet (a fresh # volume), scripts/schema.sql is applied first. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ENV_FILE="$ROOT/.env" NETWORK="hackerspace_net" IMAGE="hackerspace-setup:local" command -v docker >/dev/null || { echo "error: docker is required" >&2; exit 1; } [[ -f "$ENV_FILE" ]] || { echo "error: $ENV_FILE not found; run scripts/generate-env.sh first" >&2; exit 1; } docker network inspect "$NETWORK" >/dev/null 2>&1 \ || { echo "error: network $NETWORK not found; start the stack with 'docker compose up -d'" >&2; exit 1; } # Read single keys instead of sourcing the file, so nothing in it is executed. env_get() { grep -E "^$1=" "$ENV_FILE" | tail -n1 | cut -d= -f2- || true; } SERVICE_KEY="$(env_get SERVICE_ROLE_KEY)" ANON="$(env_get ANON_KEY)" [[ -n "$SERVICE_KEY" && -n "$ANON" ]] \ || { echo "error: SERVICE_ROLE_KEY or ANON_KEY is empty in .env" >&2; exit 1; } export NEXT_PUBLIC_SUPABASE_URL="http://kong:8000" export NEXT_PUBLIC_SUPABASE_ANON_KEY="$ANON" export SUPABASE_SERVICE_ROLE_KEY="$SERVICE_KEY" export NEXT_PUBLIC_APP_URL="$(env_get NEXT_PUBLIC_APP_URL)" for name in SETUP_SPACE_NAME SETUP_SPACE_SLUG SETUP_SPACE_CITY \ SETUP_ADMIN_EMAIL SETUP_ADMIN_PASSWORD SETUP_ADMIN_NAME; do export "$name=$(env_get "$name")" done [[ -n "$SETUP_SPACE_NAME" ]] || export SETUP_SPACE_NAME="$(env_get NEXT_PUBLIC_SITE_NAME)" [[ -n "$SETUP_SPACE_SLUG" ]] || export SETUP_SPACE_SLUG="$(env_get NEXT_PUBLIC_SINGLE_TENANT_SPACE_SLUG)" dc() { docker compose -f "$ROOT/docker-compose.yml" "$@"; } if [[ "$(dc exec -T db psql -U postgres -d postgres -Atc "select to_regclass('public.spaces') is not null" 2>/dev/null)" != "t" ]]; then echo "No schema found; applying scripts/schema.sql" dc exec -T db psql -v ON_ERROR_STOP=1 -q -U postgres -d postgres < "$ROOT/scripts/schema.sql" >/dev/null # PostgREST caches the schema; restart so it sees the new tables. dc restart rest >/dev/null until [[ "$(dc ps rest --format '{{.State}}')" == "running" ]]; do sleep 1; done sleep 3 fi # The Dockerfile deps stage is Node plus installed node_modules; the scripts # directory is mounted so edits to setup.mjs apply without a rebuild. docker build -q --target deps -t "$IMAGE" "$ROOT" >/dev/null tty_flags=() [[ -t 0 && -t 1 ]] && tty_flags=(-it) # `-e NAME` without a value forwards the variable, so secrets stay off the # docker command line. exec docker run --rm "${tty_flags[@]}" --network "$NETWORK" \ -v "$ROOT/scripts:/app/scripts:ro" \ -e NEXT_PUBLIC_SUPABASE_URL -e NEXT_PUBLIC_SUPABASE_ANON_KEY \ -e SUPABASE_SERVICE_ROLE_KEY -e NEXT_PUBLIC_APP_URL \ -e SETUP_SPACE_NAME -e SETUP_SPACE_SLUG -e SETUP_SPACE_CITY \ -e SETUP_ADMIN_EMAIL -e SETUP_ADMIN_PASSWORD -e SETUP_ADMIN_NAME \ "$IMAGE" node scripts/setup.mjs "$@"