#!/bin/bash # Momentry Release Package — Deploy Script # Usage: SCHEMA=public bash deploy.sh [--force] set -euo pipefail DIR="$(cd "$(dirname "$0")" && pwd)" UUID=$(basename "$DIR") PG_BIN="${PG_BIN:-/Users/accusys/pgsql/18.3/bin}" DB_NAME="${DB_NAME:-momentry}" DB_USER="${DB_USER:-accusys}" SCHEMA="${SCHEMA:-dev}" DEMO_DIR="${DEMO_DIR:-/Users/accusys/momentry/var/sftpgo/data/demo}" OUTPUT_DIR="${OUTPUT_DIR:-/Users/accusys/momentry/output_dev}" P="${SCHEMA}." # Parse --force flag FORCE=false for arg in "$@"; do [ "$arg" = "--force" ] && FORCE=true done echo "=== Momentry Package Deploy ===" echo "UUID: $UUID" echo "Time: $(date '+%Y-%m-%d %H:%M:%S')" echo "" # 0. Version & build compatibility check echo "[0/9] Checking system version and build..." PKG_VER=$(python3 -c "import json; f=json.load(open('$DIR/file_info.json')); print(f.get('momentry_version','?'))") PKG_BUILD=$(python3 -c "import json; f=json.load(open('$DIR/file_info.json')); print(f.get('momentry_build','?'))") SRV=$(curl -sf http://localhost:3003/health | python3 -c " import json,sys d=json.load(sys.stdin) print(d.get('version','unknown'), d.get('build_git_hash','unknown')) " 2>/dev/null || echo "down down") SRV_VER=$(echo "$SRV" | cut -d' ' -f1) SRV_BUILD=$(echo "$SRV" | cut -d' ' -f2) if [ "$SRV_VER" = "down" ]; then echo " ⚠️ Cannot reach server at localhost:3003, skipping version check" elif [ "$SRV_VER" != "$PKG_VER" ]; then echo " ❌ Version mismatch:" echo " Package Server" echo " Version: $PKG_VER $SRV_VER" echo "" echo " Please obtain the matching system upgrade package." exit 1 elif [ "$SRV_BUILD" != "unknown" ] && [ "$SRV_BUILD" != "$PKG_BUILD" ]; then echo " ⚠️ Build mismatch (package=$PKG_BUILD, server=$SRV_BUILD) — version ok, continuing" else echo " ✅ Server v$SRV_VER (build $SRV_BUILD) matches package" fi # 1. Verify package integrity echo "[1/9] Verifying package..." REQUIRED_FILES=("data.sql" "file_info.json") MISSING=0 for f in "${REQUIRED_FILES[@]}"; do if [ ! -f "$DIR/$f" ]; then echo " ❌ Missing: $f" MISSING=1 fi done if [ $MISSING -eq 1 ]; then echo "ERROR: Package incomplete" exit 1 fi echo " ✅ Package verified" # 1b. Check for existing data before pre-clean EXISTING=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM ${P}videos WHERE file_uuid='$UUID'" 2>/dev/null || echo "0") if [ "$EXISTING" -gt 0 ] && [ "$FORCE" = false ]; then echo "" echo " ⚠️ Existing data found for UUID $UUID ($EXISTING videos table rows)" echo " This will DELETE all existing data for this UUID and re-import." echo " Continue? [y/N] " read -r CONFIRM if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then echo " ❌ Aborted by user" exit 1 fi elif [ "$EXISTING" -gt 0 ] && [ "$FORCE" = true ]; then echo " ⚠️ Existing data will be overwritten (--force)" fi # 2. Pre-clean: clear all data for this file (avoids PK/FK conflicts on COPY) echo "[2/9] Pre-cleaning existing data for this file..." "$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" <<-EOSQL > /dev/null 2>&1 DELETE FROM ${P}tkg_edges WHERE file_uuid = '$UUID'; DELETE FROM ${P}tkg_nodes WHERE file_uuid = '$UUID'; DELETE FROM ${P}identity_bindings WHERE identity_id IN ( SELECT id FROM ${P}identities WHERE file_uuid = '$UUID' OR file_uuid IS NULL OR name LIKE 'PERSON_${UUID:0:8}%' ); DELETE FROM ${P}identities WHERE file_uuid = '$UUID'; -- Also delete global identities that will be re-imported (TMDB + merged + user_defined) DELETE FROM ${P}identities WHERE file_uuid IS NULL AND source IN ('tmdb', 'merged', 'user_defined'); DELETE FROM ${P}face_detections WHERE file_uuid = '$UUID'; DELETE FROM ${P}chunk_vectors WHERE uuid = '$UUID'; DELETE FROM ${P}chunk WHERE file_uuid = '$UUID'; DELETE FROM ${P}videos WHERE file_uuid = '$UUID'; -- Drop legacy constraint that conflicts with global identity re-import DO \$\$ BEGIN IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'identities_name_key') THEN ALTER TABLE ${P}identities DROP CONSTRAINT identities_name_key; END IF; END \$\$; -- Ensure content_hash column exists (added in v1.0.0 V4.2) ALTER TABLE ${P}videos ADD COLUMN IF NOT EXISTS content_hash TEXT; EOSQL echo " ✅ Cleared existing data for $UUID (migration: content_hash column ensured)" # 3. Import each table file in dependency order (FK constraints) echo "[3/9] Normalizing schema prefix and importing DB data..." # Normalize COPY schema prefix in per-table files and data.sql if [ "${SCHEMA}" != "dev" ]; then for f in "$DIR"/sql/dev_*.sql; do sed -i '' "s/COPY dev\./COPY ${SCHEMA}./g" "$f" done # Also normalize data.sql if present (full dump format) if [ -f "$DIR/data.sql" ]; then sed -i '' "s/COPY dev\./COPY ${SCHEMA}./g" "$DIR/data.sql" sed -i '' "s/Schema: dev/Schema: ${SCHEMA}/g" "$DIR/data.sql" fi echo " Schema prefix normalized: dev. → ${SCHEMA}." fi IMPORT_ORDER=( "sql/dev_videos.sql" "sql/dev_chunk.sql" "sql/dev_chunk_vectors.sql" "sql/dev_face_detections.sql" "sql/dev_identities.sql" "sql/dev_identity_bindings.sql" "sql/dev_tkg_nodes.sql" "sql/dev_tkg_edges.sql" ) for tbl_sql in "${IMPORT_ORDER[@]}"; do tbl=$(basename "$tbl_sql" .sql) echo " Importing $tbl..." "$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -f "$DIR/$tbl_sql" 2>&1 | tail -1 done echo " ✅ Data imported" # 4. Copy video to demo dir (only this package's video, not scanning others) VIDEO_FILE=$(ls "$DIR"/*.mp4 "$DIR"/*.mov "$DIR"/*.avi "$DIR"/*.mkv 2>/dev/null | head -1) if [ -n "$VIDEO_FILE" ]; then VIDEO_NAME=$(basename "$VIDEO_FILE") DEST="$DEMO_DIR/$VIDEO_NAME" if [ ! -f "$DEST" ]; then cp "$VIDEO_FILE" "$DEST" echo "[4/9] Video copied: $VIDEO_NAME → $DEMO_DIR" else echo "[4/9] Video already in demo dir, skipping" fi else echo "[4/9] No video file in package, skipping" fi # 5. Set video status to completed (package is fully processed) echo "[5/9] Setting deployment status..." "$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -c "UPDATE ${P}videos SET status = 'completed' WHERE file_uuid = '$UUID'" > /dev/null 2>&1 echo " ✅ Status set to 'completed'" # 6. Copy output files echo "[6/9] Copying output files..." COPIED=0 for f in "$DIR"/*.json "$DIR"/*.sqlite "$DIR"/*.sqlite; do if [ -f "$f" ]; then FNAME=$(basename "$f") if [ "$FNAME" != "file_info.json" ] && [ "$FNAME" != "package.json" ]; then cp "$f" "$OUTPUT_DIR/$FNAME" COPIED=$((COPIED + 1)) fi fi done echo " ✅ $COPIED files copied to $OUTPUT_DIR" # 7. Verify deployment echo "[7/9] Installing vec0.dylib..." if [ -f "$DIR/vec0.dylib" ]; then cp "$DIR/vec0.dylib" "/tmp/vec0.dylib" echo " ✅ vec0.dylib installed to /tmp/" else echo " ⚠️ vec0.dylib not found in package" fi # 8. Verify deployment echo "[8/9] Verifying deployment..." CHUNKS=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM ${P}chunk WHERE file_uuid='$UUID'" 2>/dev/null || echo "?") FACES=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM ${P}face_detections WHERE file_uuid='$UUID'" 2>/dev/null || echo "?") IDENTS=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM ${P}identities WHERE file_uuid='$UUID'" 2>/dev/null || echo "?") TKG_NODES=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM ${P}tkg_nodes WHERE file_uuid='$UUID'" 2>/dev/null || echo "?") TKG_EDGES=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM ${P}tkg_edges WHERE file_uuid='$UUID'" 2>/dev/null || echo "?") echo "" echo "=== Deploy Complete ===" echo " UUID: $UUID" echo " Chunks: $CHUNKS" echo " Faces: $FACES" echo " Identities: $IDENTS" echo " TKG nodes: $TKG_NODES" echo " TKG edges: $TKG_EDGES" echo " Output: $OUTPUT_DIR/" echo "" echo "Package is self-contained — no further processing needed." echo "" echo "Offline report:" echo " python3 scripts/render_offline_report.py $OUTPUT_DIR/$UUID.sqlite"