diff --git a/run_migrations.sh b/run_migrations.sh new file mode 100755 index 0000000..103b74c --- /dev/null +++ b/run_migrations.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# run_migrations.sh — Tracksolid DB Migration Runner +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# Applies any pending numbered SQL migration files in order. +# Safe to run on every deployment — skips already-applied migrations. +# +# Usage: +# ./run_migrations.sh # auto-detect container + DB +# ./run_migrations.sh --dry-run # show what would run, don't apply +# +# Called by Coolify post-deployment command: +# bash /app/run_migrations.sh +# +# How it tracks applied migrations: +# Creates tracksolid.schema_migrations table on first run. +# Records the filename of every successfully applied migration. +# Skips any file already recorded in that table. +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +set -euo pipefail + +# ── Config ──────────────────────────────────────────────────────────────────── +DB_NAME="${DB_NAME:-tracksolid_db}" +DB_USER="${DB_USER:-postgres}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DRY_RUN=false + +# ── Argument parsing ────────────────────────────────────────────────────────── +for arg in "$@"; do + case $arg in + --dry-run) DRY_RUN=true ;; + esac +done + +# ── Resolve TimescaleDB container ───────────────────────────────────────────── +TS_DB=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) +if [[ -z "$TS_DB" ]]; then + echo "ERROR: no running timescale_db container found." >&2 + exit 1 +fi +echo "Using container: $TS_DB" + +# ── Helper: run SQL against the DB ─────────────────────────────────────────── +run_sql() { + docker exec -i "$TS_DB" psql -U "$DB_USER" -d "$DB_NAME" "$@" +} + +# ── Ensure migration tracking table exists ─────────────────────────────────── +run_sql -c " + CREATE TABLE IF NOT EXISTS tracksolid.schema_migrations ( + filename TEXT PRIMARY KEY, + applied_at TIMESTAMPTZ NOT NULL DEFAULT now() + ); +" > /dev/null + +# ── Find and apply pending migrations ──────────────────────────────────────── +MIGRATION_FILES=$(find "$SCRIPT_DIR" -maxdepth 1 -name '[0-9][0-9]_*.sql' | sort) + +if [[ -z "$MIGRATION_FILES" ]]; then + echo "No migration files found in $SCRIPT_DIR" + exit 0 +fi + +APPLIED=0 +SKIPPED=0 + +for filepath in $MIGRATION_FILES; do + filename=$(basename "$filepath") + + # Check if already applied + already_applied=$(run_sql -t -c \ + "SELECT COUNT(*) FROM tracksolid.schema_migrations WHERE filename = '$filename';" \ + 2>/dev/null | tr -d '[:space:]') + + if [[ "$already_applied" == "1" ]]; then + echo " SKIP $filename (already applied)" + ((SKIPPED++)) || true + continue + fi + + if [[ "$DRY_RUN" == "true" ]]; then + echo " PENDING $filename (would apply)" + continue + fi + + echo " APPLY $filename ..." + if run_sql < "$filepath"; then + # Record successful application + run_sql -c \ + "INSERT INTO tracksolid.schema_migrations (filename) VALUES ('$filename');" \ + > /dev/null + echo " OK $filename" + ((APPLIED++)) || true + else + echo " FAIL $filename — aborting migration run" >&2 + exit 1 + fi +done + +echo "" +echo "Migrations complete: $APPLIED applied, $SKIPPED skipped."