@@ -58,7 +58,9 @@ GBL_ADMIN_REMOTE_DIR ?= /opt/data/pgdump
5858GBL_ADMIN_DUMP_GLOB ?= pgdump-geoportal_production-*.sql.gz
5959GBL_ADMIN_LOCAL_DIR ?= tmp
6060GBL_ADMIN_SQL_GLOB ?= pgdump-geoportal_production-*.sql
61+ GBL_ADMIN_RETAIN_DBS ?= 2
6162GBL_ADMIN_IMPORT_CONFLICT ?= update
63+ GBL_ADMIN_RETIRE_MISSING ?= false
6264GBL_ADMIN_DISTRIBUTIONS_BATCH_SIZE ?= 2000
6365KAMAL_APP_ROLE ?= web
6466KAMAL_PYTHON ?= /opt/venv/bin/python
@@ -415,7 +417,7 @@ gbl-admin-db-unzip: ## Decompress latest GBL Admin dump
415417 gunzip -c " $$ LOCAL_GZ" > " $$ LOCAL_SQL" || { echo " ERROR: gunzip failed (check disk space)." ; exit 1; }; \
416418 echo " Decompressed SQL: $$ LOCAL_SQL"
417419
418- # Restore production GBL Admin dump to local ParadeDB. Uses .sql if present, otherwise streams from . gz (no extra disk) .
420+ # Restore production GBL Admin dump to local ParadeDB. Uses the newest local .sql or .sql. gz dump .
419421gbl-admin-db-restore : # # Restore GBL Admin dump to local ParadeDB
420422 @echo " Restoring production GBL Admin SQL into local ParadeDB..."
421423 @if ! command -v docker > /dev/null 2>&1 ; then \
@@ -427,19 +429,30 @@ gbl-admin-db-restore: ## Restore GBL Admin dump to local ParadeDB
427429 echo " Start it with: docker compose up -d paradedb" ; \
428430 exit 1; \
429431 fi
430- @LOCAL_SQL =$$(ls -1t " $( GBL_ADMIN_LOCAL_DIR ) "/ $( GBL_ADMIN_SQL_GLOB ) 2>/dev/null | head -n 1 ) ; \
431- LOCAL_GZ= $$( ls -1t "$(GBL_ADMIN_LOCAL_DIR ) "/$(GBL_ADMIN_DUMP_GLOB ) 2>/dev/null | head -n 1 ) ; \
432- if [ -n " $$ LOCAL_SQL " ]; then \
433- SOURCE= " $$ LOCAL_SQL " ; \
434- DUMP_DATE= $$( basename "$$LOCAL_SQL" | sed -E 's/^pgdump-geoportal_production-([0-9]{8}) \. sql $$ / \1 / ' ) ; \
435- elif [ -n " $$ LOCAL_GZ " ] ; then \
436- SOURCE= " $$ LOCAL_GZ " ; \
437- DUMP_DATE= $$( basename "$$LOCAL_GZ" | sed -E 's/^pgdump-geoportal_production-([0-9]{8}) \. sql \. gz $$ / \1 / ' ); \
438- else \
432+ @SOURCE =$$( \
433+ for file in " $( GBL_ADMIN_LOCAL_DIR ) " / $( GBL_ADMIN_SQL_GLOB ) " $( GBL_ADMIN_LOCAL_DIR) " /$( GBL_ADMIN_DUMP_GLOB) ; do \
434+ [ -f " $$ file " ] || continue ; \
435+ MTIME= $$( stat -f %m "$$file" 2>/dev/null || stat -c %Y "$$file" 2>/dev/null ) ; \
436+ [ -n " $$ MTIME " ] || continue ; \
437+ printf " %s\t%s\n " " $$ MTIME " " $$ file " ; \
438+ done | sort -nr | head -n 1 | cut -f2- \
439+ ); \
440+ if [ -z " $$ SOURCE " ] ; then \
439441 echo " ERROR: No dump found in $( GBL_ADMIN_LOCAL_DIR) (need $( GBL_ADMIN_SQL_GLOB) or $( GBL_ADMIN_DUMP_GLOB) )." ; \
440442 echo " Run 'make gbl-admin-db-download' first." ; \
441443 exit 1; \
442444 fi ; \
445+ case " $$ SOURCE" in \
446+ * .sql) \
447+ RESTORE_MODE=" sql" ; \
448+ DUMP_DATE=$$(basename "$$SOURCE" | sed -E 's/^pgdump-geoportal_production-([0-9]{8}) \. sql$$ /\1 /' ) ;; \
449+ * .sql.gz) \
450+ RESTORE_MODE=" gz" ; \
451+ DUMP_DATE=$$(basename "$$SOURCE" | sed -E 's/^pgdump-geoportal_production-([0-9]{8}) \. sql\. gz$$ /\1 /' ) ;; \
452+ * ) \
453+ echo " ERROR: Unrecognized dump filename: $$ SOURCE" ; \
454+ exit 1 ;; \
455+ esac ; \
443456 if ! echo " $$ DUMP_DATE" | grep -Eq ' ^[0-9]{8}$$' ; then \
444457 echo " ERROR: Could not parse dump date from $$ SOURCE." ; \
445458 exit 1; \
@@ -450,12 +463,13 @@ gbl-admin-db-restore: ## Restore GBL Admin dump to local ParadeDB
450463 docker compose exec -T paradedb psql -U postgres -d postgres -c " SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$$ DB_NAME' AND pid <> pg_backend_pid();" || true ; \
451464 docker compose exec -T paradedb psql -U postgres -d postgres -c " DROP DATABASE IF EXISTS \" $$ DB_NAME\" ;" ; \
452465 docker compose exec -T paradedb psql -U postgres -d postgres -c " CREATE DATABASE \" $$ DB_NAME\" OWNER postgres;" ; \
453- if [ -n " $$ LOCAL_SQL" ]; then \
454- echo " Restoring from decompressed SQL: $$ LOCAL_SQL" ; \
455- cat " $$ LOCAL_SQL" | docker compose exec -T paradedb psql -U postgres -d " $$ DB_NAME" ; \
466+ echo " Selected newest local dump: $$ SOURCE" ; \
467+ if [ " $$ RESTORE_MODE" = " sql" ]; then \
468+ echo " Restoring from decompressed SQL: $$ SOURCE" ; \
469+ cat " $$ SOURCE" | docker compose exec -T paradedb psql -U postgres -d " $$ DB_NAME" ; \
456470 else \
457- echo " Streaming from compressed dump: $$ LOCAL_GZ (no extra disk used)" ; \
458- gunzip -c " $$ LOCAL_GZ " | docker compose exec -T paradedb psql -U postgres -d " $$ DB_NAME" ; \
471+ echo " Streaming from compressed dump: $$ SOURCE (no extra disk used)" ; \
472+ gunzip -c " $$ SOURCE " | docker compose exec -T paradedb psql -U postgres -d " $$ DB_NAME" ; \
459473 fi ; \
460474 echo " Restore complete." ; \
461475 echo " Dump used: $$ SOURCE" ; \
@@ -475,7 +489,22 @@ gbl-admin-db-restore: ## Restore GBL Admin dump to local ParadeDB
475489 -e DB_PORT=" 5432" \
476490 -e DB_USER=" postgres" \
477491 -e DB_PASSWORD=" $$ DB_PASSWORD" \
478- api bash -lc ' cd /app/backend && python db/migrations/bridge_old_production.py --create-view'
492+ api bash -lc ' cd /app/backend && python db/migrations/bridge_old_production.py --create-view' ; \
493+ if [ " $( GBL_ADMIN_RETAIN_DBS) " -lt 1 ]; then \
494+ echo " ERROR: GBL_ADMIN_RETAIN_DBS must be at least 1." ; \
495+ exit 1; \
496+ fi ; \
497+ PRUNE_DBS=$$(docker compose exec -T paradedb psql -U postgres -d postgres -Atc "WITH ranked AS ( SELECT datname, ROW_NUMBER( ) OVER ( ORDER BY CASE WHEN datname = ' $$DB_NAME' THEN 0 ELSE 1 END, datname DESC ) AS rn FROM pg_database WHERE datname LIKE ' geoportal_production_%' ) SELECT datname FROM ranked WHERE rn > $(GBL_ADMIN_RETAIN_DBS ) ; " ); \
498+ if [ -n " $$ PRUNE_DBS" ]; then \
499+ echo " Pruning older restored GBL Admin databases (retaining $( GBL_ADMIN_RETAIN_DBS) )..." ; \
500+ for PRUNE_DB in $$ PRUNE_DBS; do \
501+ echo " Dropping old restored DB: $$ PRUNE_DB" ; \
502+ docker compose exec -T paradedb psql -U postgres -d postgres -c " SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$$ PRUNE_DB' AND pid <> pg_backend_pid();" || true ; \
503+ docker compose exec -T paradedb psql -U postgres -d postgres -c " DROP DATABASE IF EXISTS \" $$ PRUNE_DB\" ;" ; \
504+ done ; \
505+ else \
506+ echo " No older restored GBL Admin databases to prune." ; \
507+ fi
479508
480509# End-to-end: download latest dump and restore (streams from .gz; no decompression to disk)
481510gbl-admin-db-sync : gbl-admin-db-download gbl-admin-db-restore # # Download + restore GBL Admin dump
@@ -510,6 +539,12 @@ gbl-admin-db-import-resources: ## Import resources from GBL Admin bridge
510539 echo " ERROR: Could not read POSTGRES_PASSWORD from paradedb container." ; \
511540 exit 1; \
512541 fi ; \
542+ IMPORT_FLAGS=" --conflict $( GBL_ADMIN_IMPORT_CONFLICT) --verify" ; \
543+ case " $( GBL_ADMIN_RETIRE_MISSING) " in \
544+ 1| true| TRUE| yes| YES) \
545+ IMPORT_FLAGS=" $$ IMPORT_FLAGS --retire-missing" ; \
546+ echo " Missing resources will be marked retired after import." ;; \
547+ esac ; \
513548 echo " OLD_DB_NAME=$$ RESOLVED_OLD_DB_NAME" ; \
514549 docker compose exec -T \
515550 -e OLD_DB_NAME=" $$ RESOLVED_OLD_DB_NAME" \
@@ -518,7 +553,7 @@ gbl-admin-db-import-resources: ## Import resources from GBL Admin bridge
518553 -e DB_PORT=" 5432" \
519554 -e DB_USER=" postgres" \
520555 -e DB_PASSWORD=" $$ DB_PASSWORD" \
521- api bash -lc ' cd /app/backend && python db/migrations/import_from_old_production.py --conflict $(GBL_ADMIN_IMPORT_CONFLICT) --verify '
556+ api bash -lc " cd /app/backend && python db/migrations/import_from_old_production.py $$ IMPORT_FLAGS "
522557
523558# Populate resource_distributions from legacy document_distributions.
524559# Uses the latest restored geoportal_production_* DB if OLD_DB_NAME is unset.
@@ -589,7 +624,13 @@ populate-data-dictionaries: ## Populate data dictionaries from legacy tables
589624 api bash -lc ' cd /app/backend && python db/migrations/migrate_resource_data_dictionaries.py'
590625
591626# Full GBL Admin import pipeline after restore.
592- gbl-admin-db-import-all : gbl-admin-db-add-latest-btaa-fields gbl-admin-db-import-resources populate-distributions populate-data-dictionaries populate-relationships reindex # # Full GBL Admin import pipeline
627+ gbl-admin-db-import-all : # # Full GBL Admin import pipeline
628+ @$(MAKE ) gbl-admin-db-add-latest-btaa-fields
629+ @$(MAKE ) gbl-admin-db-import-resources GBL_ADMIN_RETIRE_MISSING=true
630+ @$(MAKE ) populate-distributions
631+ @$(MAKE ) populate-data-dictionaries
632+ @$(MAKE ) populate-relationships
633+ @$(MAKE ) reindex
593634 @echo " GBL Admin full import pipeline complete!"
594635
595636# Search indexing tasks
0 commit comments