Updated
This commit is contained in:
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# generate-env.sh: copy .env.example to .env and fill in every secret.
|
||||
#
|
||||
# Fills POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY,
|
||||
# SECRET_KEY_BASE, SECRETS_ENCRYPTION_KEY and CRON_SECRET. ANON_KEY and
|
||||
# SERVICE_ROLE_KEY are HS256 JWTs signed with the generated JWT_SECRET.
|
||||
#
|
||||
# Usage: scripts/generate-env.sh [--output FILE] [--yes]
|
||||
# --output FILE write here instead of .env
|
||||
# --yes, --force skip the confirmation prompt (required when stdin is not a
|
||||
# terminal)
|
||||
#
|
||||
# Always asks before writing; the default answer is N.
|
||||
#
|
||||
# The default target is .env (git-ignored), not .env.example, because
|
||||
# .env.example is committed and must never hold real secrets.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TEMPLATE="$ROOT/.env.example"
|
||||
OUT="$ROOT/.env"
|
||||
FORCE=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--output) OUT="$2"; shift 2 ;;
|
||||
--yes|--force) FORCE=1; shift ;;
|
||||
-h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
||||
*) echo "error: unknown argument: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
command -v openssl >/dev/null || { echo "error: openssl is required" >&2; exit 1; }
|
||||
[[ -f "$TEMPLATE" ]] || { echo "error: $TEMPLATE not found" >&2; exit 1; }
|
||||
if [[ $FORCE -ne 1 ]]; then
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo "error: not a terminal; pass --yes to run non-interactively" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -e "$OUT" ]]; then
|
||||
echo "$OUT already exists. Continuing replaces it and rotates every secret."
|
||||
else
|
||||
echo "This will create $OUT with newly generated secrets."
|
||||
fi
|
||||
read -r -p "Continue? [y/N] " answer
|
||||
case "$answer" in
|
||||
y|Y|yes|YES) ;;
|
||||
*) echo "aborted"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
|
||||
|
||||
# sign_jwt ROLE SECRET -> HS256 JWT valid for 10 years
|
||||
sign_jwt() {
|
||||
local role="$1" secret="$2" iat header payload sig
|
||||
iat="$(date +%s)"
|
||||
header="$(printf '{"alg":"HS256","typ":"JWT"}' | b64url)"
|
||||
payload="$(printf '{"role":"%s","iss":"supabase","iat":%d,"exp":%d}' \
|
||||
"$role" "$iat" "$((iat + 315360000))" | b64url)"
|
||||
sig="$(printf '%s.%s' "$header" "$payload" \
|
||||
| openssl dgst -sha256 -hmac "$secret" -binary | b64url)"
|
||||
printf '%s.%s.%s' "$header" "$payload" "$sig"
|
||||
}
|
||||
|
||||
POSTGRES_PASSWORD="$(openssl rand -hex 24)"
|
||||
JWT_SECRET="$(openssl rand -hex 32)"
|
||||
ANON_KEY="$(sign_jwt anon "$JWT_SECRET")"
|
||||
SERVICE_ROLE_KEY="$(sign_jwt service_role "$JWT_SECRET")"
|
||||
SECRET_KEY_BASE="$(openssl rand -hex 32)"
|
||||
SECRETS_ENCRYPTION_KEY="$(openssl rand -hex 32)"
|
||||
CRON_SECRET="$(openssl rand -hex 32)"
|
||||
|
||||
umask 077
|
||||
cp "$TEMPLATE" "$OUT"
|
||||
for name in POSTGRES_PASSWORD JWT_SECRET ANON_KEY SERVICE_ROLE_KEY \
|
||||
SECRET_KEY_BASE SECRETS_ENCRYPTION_KEY CRON_SECRET; do
|
||||
# Values are hex or base64url, so they contain no sed-special characters.
|
||||
sed -i "s|^${name}=.*|${name}=${!name}|" "$OUT"
|
||||
done
|
||||
chmod 600 "$OUT"
|
||||
|
||||
echo "wrote $OUT"
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user