Files
xamxam/scripts/migrate.sh
Pontoporeia a88e5562f8 fix(config): auto-route test.db locally, posterg.db on production
- config.php: getDatabasePath() detects php built-in CLI server
  (php_sapi_name() === 'cli-server') and routes to test.db; all
  other SAPIs (nginx/fpm) get posterg.db. DB_ENV env-var still
  overrides either way.

- migrate.sh: auto-initialise the target DB from storage/schema.sql
  when the file is absent or has no tables yet. Existing DBs with
  data are left completely untouched (table_count check, no re-run
  of schema on populated DB). Idempotent: safe to run repeatedly.

- justfile: serve still calls migrate (which now handles init too),
  no DB_ENV prefix needed since sapi detection handles routing.
2026-04-01 15:55:12 +02:00

129 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Apply pending SQL migrations to one or both SQLite databases.
# Usage:
# scripts/migrate.sh # migrates both test.db and posterg.db
# scripts/migrate.sh test # migrates storage/test.db only
# scripts/migrate.sh prod # migrates storage/posterg.db only
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MIGRATIONS_DIR="$REPO_ROOT/storage/migrations"
TEST_DB="$REPO_ROOT/storage/test.db"
PROD_DB="$REPO_ROOT/storage/posterg.db"
# ---------------------------------------------------------------------------
# Check whether a migration's effects are already present in the DB so that
# legacy databases (created before the migrations table existed) can be
# bootstrapped correctly without re-running non-idempotent SQL.
# ---------------------------------------------------------------------------
already_applied_structurally() {
local db="$1"
local name="$2"
case "$name" in
001_rename_keywords_to_tags.sql)
# Effect: table 'tags' and 'thesis_tags' exist
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('tags','thesis_tags');")
[ "$count" -eq 2 ]
;;
002_add_visibility.sql)
# Effect: access_types seed rows (always safe to re-run with OR IGNORE, but mark done if rows exist)
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM access_types WHERE id IN (1,2,3);" 2>/dev/null || echo 0)
[ "$count" -eq 3 ]
;;
003_seed_license_types.sql)
# Effect: at least one row in license_types
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM license_types;" 2>/dev/null || echo 0)
[ "$count" -gt 0 ]
;;
004_jury_roles.sql)
# Effect: 'role' column on thesis_supervisors
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM pragma_table_info('thesis_supervisors') WHERE name='role';")
[ "$count" -eq 1 ]
;;
005_add_banner.sql)
# Effect: 'banner_path' column on theses
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM pragma_table_info('theses') WHERE name='banner_path';")
[ "$count" -eq 1 ]
;;
006_add_composite_index.sql)
# Effect: index idx_theses_pub_year exists (CREATE INDEX IF NOT EXISTS — safe to re-run anyway)
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_theses_pub_year';")
[ "$count" -eq 1 ]
;;
*)
# Unknown migration — assume not applied
return 1
;;
esac
}
migrate_db() {
local db="$1"
local label="$2"
# Auto-create from schema only when the file is absent or truly empty (no tables)
local table_count
table_count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table';" 2>/dev/null || echo 0)
if [ "$table_count" -eq 0 ]; then
echo " [$label] initialising from schema…"
sqlite3 "$db" < "$REPO_ROOT/storage/schema.sql"
echo " [$label] schema applied."
fi
# Ensure tracking table exists
sqlite3 "$db" "CREATE TABLE IF NOT EXISTS schema_migrations (
name TEXT PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);"
local applied=0
local seeded=0
local skipped=0
for migration in "$MIGRATIONS_DIR"/*.sql; do
name="$(basename "$migration")"
already=$(sqlite3 "$db" "SELECT COUNT(*) FROM schema_migrations WHERE name='$name';")
if [ "$already" -eq 1 ]; then
skipped=$((skipped + 1))
continue
fi
# Not in tracking table — check if it was already applied before we started tracking
if already_applied_structurally "$db" "$name"; then
sqlite3 "$db" "INSERT OR IGNORE INTO schema_migrations (name) VALUES ('$name');"
seeded=$((seeded + 1))
echo " [$label] seeded $name (already applied)"
continue
fi
echo " [$label] applying $name"
if sqlite3 "$db" < "$migration"; then
sqlite3 "$db" "INSERT OR IGNORE INTO schema_migrations (name) VALUES ('$name');"
applied=$((applied + 1))
else
echo " [$label] ERROR applying $name — aborting" >&2
exit 1
fi
done
echo " [$label] done — $applied applied, $seeded seeded, $skipped already up-to-date"
}
TARGET="${1:-both}"
case "$TARGET" in
test) migrate_db "$TEST_DB" "test.db" ;;
prod) migrate_db "$PROD_DB" "posterg.db" ;;
both)
migrate_db "$TEST_DB" "test.db"
migrate_db "$PROD_DB" "posterg.db"
;;
*)
echo "Usage: $0 [test|prod|both]" >&2
exit 1
;;
esac