#!/bin/sh # pb-init.sh — idempotent PocketBase collection bootstrap # # Creates all collections required by libnovel. Safe to re-run: POST returns # 400/422 when a collection already exists; both are treated as success. # # Required env vars (with defaults): # POCKETBASE_URL http://pocketbase:8090 # POCKETBASE_ADMIN_EMAIL admin@libnovel.local # POCKETBASE_ADMIN_PASSWORD changeme123 set -e PB_URL="${POCKETBASE_URL:-http://pocketbase:8090}" PB_EMAIL="${POCKETBASE_ADMIN_EMAIL:-admin@libnovel.local}" PB_PASSWORD="${POCKETBASE_ADMIN_PASSWORD:-changeme123}" log() { echo "[pb-init] $*"; } # ─── 1. Wait for PocketBase to be ready ────────────────────────────────────── log "waiting for PocketBase at $PB_URL ..." until wget -qO- "$PB_URL/api/health" > /dev/null 2>&1; do sleep 2 done log "PocketBase is up" # ─── 2. Authenticate and obtain a superuser token ──────────────────────────── log "authenticating as $PB_EMAIL ..." AUTH_RESPONSE=$(wget -qO- \ --header="Content-Type: application/json" \ --post-data="{\"identity\":\"$PB_EMAIL\",\"password\":\"$PB_PASSWORD\"}" \ "$PB_URL/api/collections/_superusers/auth-with-password") TOKEN=$(echo "$AUTH_RESPONSE" | sed 's/.*"token":"\([^"]*\)".*/\1/') if [ -z "$TOKEN" ] || [ "$TOKEN" = "$AUTH_RESPONSE" ]; then log "ERROR: failed to obtain auth token. Response: $AUTH_RESPONSE" exit 1 fi log "auth token obtained" # ─── 3. Helper: create a collection (ignores 400/422 already-exists) ───────── create_collection() { NAME="$1" BODY="$2" STATUS=$(wget -qSO- \ --header="Content-Type: application/json" \ --header="Authorization: $TOKEN" \ --post-data="$BODY" \ "$PB_URL/api/collections" 2>&1 | grep "HTTP/" | tail -1 | awk '{print $2}') case "$STATUS" in 200|201) log "created collection: $NAME" ;; 400|422) log "collection already exists (skipped): $NAME" ;; *) log "WARNING: unexpected status $STATUS for collection: $NAME" ;; esac } # ─── 4. Create collections ──────────────────────────────────────────────────── create_collection "books" '{ "name": "books", "type": "base", "fields": [ {"name": "slug", "type": "text", "required": true}, {"name": "title", "type": "text", "required": true}, {"name": "author", "type": "text"}, {"name": "cover", "type": "text"}, {"name": "status", "type": "text"}, {"name": "genres", "type": "json"}, {"name": "summary", "type": "text"}, {"name": "total_chapters", "type": "number"}, {"name": "source_url", "type": "text"}, {"name": "ranking", "type": "number"}, {"name": "meta_updated", "type": "date"} ] }' create_collection "chapters_idx" '{ "name": "chapters_idx", "type": "base", "fields": [ {"name": "slug", "type": "text", "required": true}, {"name": "number", "type": "number", "required": true}, {"name": "title", "type": "text"}, {"name": "date_label", "type": "text"} ] }' create_collection "ranking" '{ "name": "ranking", "type": "base", "fields": [ {"name": "rank", "type": "number", "required": true}, {"name": "slug", "type": "text", "required": true}, {"name": "title", "type": "text"}, {"name": "author", "type": "text"}, {"name": "cover", "type": "text"}, {"name": "status", "type": "text"}, {"name": "genres", "type": "json"}, {"name": "source_url", "type": "text"}, {"name": "updated", "type": "date"} ] }' create_collection "progress" '{ "name": "progress", "type": "base", "fields": [ {"name": "session_id", "type": "text", "required": true}, {"name": "slug", "type": "text", "required": true}, {"name": "chapter", "type": "number"}, {"name": "updated", "type": "date"} ] }' create_collection "audio_cache" '{ "name": "audio_cache", "type": "base", "fields": [ {"name": "cache_key", "type": "text", "required": true}, {"name": "filename", "type": "text"}, {"name": "updated", "type": "date"} ] }' create_collection "app_users" '{ "name": "app_users", "type": "base", "fields": [ {"name": "username", "type": "text", "required": true}, {"name": "password_hash", "type": "text", "required": true}, {"name": "role", "type": "text"}, {"name": "created", "type": "date"} ] }' log "all collections ready"