#!/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."