Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions backend/tests/test_kamal_deploy_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,12 @@ def test_kamal_cron_role_and_crontab_are_wired():
assert "cron" in dockerfile
assert "COPY config/crontab ./config/crontab" in dockerfile
assert "start_cron.sh" in dockerfile


def test_kamal_accessory_directories_preserve_live_production_binds():
base_config = _load_deploy_config("config/deploy.yml")
accessories = base_config["accessories"]

assert accessories["elasticsearch"]["directories"] == ["esdata:/usr/share/elasticsearch/data"]
assert accessories["postgres"]["directories"] == ["pgdata:/var/lib/postgresql/data"]
assert accessories["redis"]["directories"] == ["redisdata:/data"]
12 changes: 9 additions & 3 deletions config/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ accessories:
# only listen on localhost
port: "127.0.0.1:9200:9200"
directories:
- /var/lib/opengeometadata-api/elasticsearch:/usr/share/elasticsearch/data
# Keep this relative source name: Kamal resolves it to the existing
# /home/ewlarson/ogm-api-elasticsearch/esdata production bind.
- esdata:/usr/share/elasticsearch/data

postgres:
image: postgres:15
Expand All @@ -115,11 +117,15 @@ accessories:
files:
- config/init.sql:/docker-entrypoint-initdb.d/setup.sql
directories:
- /var/lib/opengeometadata-api/postgres:/var/lib/postgresql/data
# Keep this relative source name: Kamal resolves it to the existing
# /home/ewlarson/ogm-api-postgres/pgdata production bind.
- pgdata:/var/lib/postgresql/data

redis:
image: redis:7.2
roles: [web]
cmd: sh -lc 'redis-server --appendonly yes --protected-mode yes --bind 0.0.0.0'
directories:
- /var/lib/opengeometadata-api/redis:/data
# Keep this relative source name: Kamal resolves it to the existing
# /home/ewlarson/ogm-api-redis/redisdata production bind.
- redisdata:/data
37 changes: 23 additions & 14 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ elasticsearch:
- Single-node configuration
- 2GB heap size
- Security disabled (bound to localhost only)
- Data persisted at `/var/lib/opengeometadata-api/elasticsearch` on the host
- Kamal directory source `esdata`, currently resolved to
`/home/ewlarson/ogm-api-elasticsearch/esdata` on the production host

### PostgreSQL

Expand All @@ -237,7 +238,8 @@ postgres:
- Existing PostgreSQL 15 accessory used by the current production deployment
- Initialized via `config/init.sql`
- FAST vector embeddings are disabled in Kamal by default because the current production database image does not expose the `vector` extension
- Data persisted at `/var/lib/opengeometadata-api/postgres` on the host
- Kamal directory source `pgdata`, currently resolved to
`/home/ewlarson/ogm-api-postgres/pgdata` on the production host

### Redis

Expand All @@ -251,7 +253,8 @@ redis:
- Append-only file (AOF) persistence enabled
- Current production accessory runs without Redis AUTH; the app should omit
`REDIS_PASSWORD` unless/until the accessory is explicitly recreated with auth
- Data persisted at `/var/lib/opengeometadata-api/redis` on the host
- Kamal directory source `redisdata`, currently resolved to
`/home/ewlarson/ogm-api-redis/redisdata` on the production host

## Deployment Commands

Expand Down Expand Up @@ -495,8 +498,9 @@ PostgreSQL is running; copying its live data directory is not:
ssh ewlarson@ogm.geo4lib.app

# Create a restricted backup directory and a custom-format dump
ogm_backup_dir=/var/backups/opengeometadata-api
sudo install -d -m 700 -o "$(id -un)" -g "$(id -gn)" "$ogm_backup_dir"
umask 077
ogm_backup_dir="$HOME/ogm-api-backups"
install -d -m 700 "$ogm_backup_dir"
docker exec ogm-api-postgres \
pg_dump --format=custom -U ogm_api_user btaa_ogm_api \
> "$ogm_backup_dir/postgres-$(date -u +%Y%m%dT%H%M%SZ).dump"
Expand All @@ -514,15 +518,15 @@ record its identifier before a deployment or migration window.

```bash
# Copy a verified custom-format backup to the server
scp postgres-YYYYMMDDTHHMMSSZ.dump ewlarson@ogm.geo4lib.app:/var/backups/opengeometadata-api/
scp postgres-YYYYMMDDTHHMMSSZ.dump ewlarson@ogm.geo4lib.app:ogm-api-backups/

# SSH to server
ssh ewlarson@ogm.geo4lib.app

# Restore only during an approved recovery window
docker exec -i ogm-api-postgres \
pg_restore --clean --if-exists -U ogm_api_user -d btaa_ogm_api \
< /var/backups/opengeometadata-api/postgres-YYYYMMDDTHHMMSSZ.dump
< "$HOME/ogm-api-backups/postgres-YYYYMMDDTHHMMSSZ.dump"
```

## Elasticsearch Operations
Expand Down Expand Up @@ -678,15 +682,20 @@ For automated deployments via GitHub Actions or similar:

## Host Directories & Data Persistence

Kamal bind-mounts three persistent host directories. These paths are stable
production identity and must not change during a repository or image transfer:
Kamal bind-mounts three persistent host directories. The relative directory
sources in `config/deploy.yml` are stable production identity and must not
change during a repository or image transfer:

- `/var/lib/opengeometadata-api/elasticsearch`: Elasticsearch index data
- `/var/lib/opengeometadata-api/postgres`: PostgreSQL database files
- `/var/lib/opengeometadata-api/redis`: Redis append-only cache data
- `esdata` -> `/home/ewlarson/ogm-api-elasticsearch/esdata`: Elasticsearch index data
- `pgdata` -> `/home/ewlarson/ogm-api-postgres/pgdata`: PostgreSQL database files
- `redisdata` -> `/home/ewlarson/ogm-api-redis/redisdata`: Redis append-only cache data

These directories persist across deployments and container restarts. They are
not Docker named volumes; `docker volume` commands do not back them up.
not Docker named volumes; `docker volume` commands do not back them up. Kamal
derives the resolved host paths from the deployment user, service, accessory,
and relative source name. Do not replace the relative sources with new absolute
paths unless the data is deliberately migrated in a separate maintenance
window.

### Backing Up Volumes

Expand All @@ -695,7 +704,7 @@ not Docker named volumes; `docker volume` commands do not back them up.
ssh ewlarson@ogm.geo4lib.app

# Confirm the configured directories and mounts
sudo find /var/lib/opengeometadata-api -maxdepth 1 -type d -print
find "$HOME" -maxdepth 2 -type d -name 'ogm-api-*' -print
docker inspect ogm-api-postgres --format '{{json .Mounts}}'
```

Expand Down
12 changes: 9 additions & 3 deletions docs/repository_transfer.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,19 @@ The following values are intentionally frozen through the repository transfer:
| Registry and login owner | `ghcr.io`, `ewlarson` |
| Elasticsearch index | `opengeometadata_api` |
| PostgreSQL database | `btaa_ogm_api` |
| Elasticsearch volume | `/var/lib/opengeometadata-api/elasticsearch` |
| PostgreSQL volume | `/var/lib/opengeometadata-api/postgres` |
| Redis volume | `/var/lib/opengeometadata-api/redis` |
| Elasticsearch directory source | `esdata` (resolves to `/home/ewlarson/ogm-api-elasticsearch/esdata`) |
| PostgreSQL directory source | `pgdata` (resolves to `/home/ewlarson/ogm-api-postgres/pgdata`) |
| Redis directory source | `redisdata` (resolves to `/home/ewlarson/ogm-api-redis/redisdata`) |

The legacy PostgreSQL database name is persistent identity, not public project
branding. Rename it only in a separate, backed-up database migration.

The relative Kamal directory source names are also persistent identity. Kamal
expands them under the deployment user's home directory using the service and
accessory names. Replacing them with a different absolute host path would make
an accessory reboot mount a different data directory, even though an ordinary
repository transfer does not restart the existing containers.

Run the local invariant checks before and after every preparation commit:

```bash
Expand Down
19 changes: 19 additions & 0 deletions docs/repository_transfer_rehearsal.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,22 @@ Before the actual transfer:
6. rewrite a fresh mirror of the frozen GitHub repository and require
`make transfer-readiness-full` to pass in a checkout of the result; and
7. follow the transfer and rollback sequence in `repository_transfer.md`.

## Change-window preflight correction

The 2026-08-20 live preflight found that the running accessories use the
original Kamal-resolved bind paths under `/home/ewlarson/ogm-api-*`, while the
prepared configuration incorrectly named new `/var/lib/opengeometadata-api/*`
sources. No running container had adopted the new paths. The transfer was
paused before branch deletion or history rewriting, and the configuration was
corrected to preserve the original relative directory sources `esdata`,
`pgdata`, and `redisdata`.

The same preflight confirmed that OpenGeoMetadata permits Actions for all
repositories, standard hosted runners are enabled, no repository in the
organization occupies the BTAA fork network, and the personal GHCR package is
private, remains personal-account scoped, and is not linked to this repository.
A current logical PostgreSQL dump completed successfully, its permissions were
restricted to the deployment user, and `pg_restore --list` validated its
catalog. The private change record holds the backup identifier, byte size,
checksum, running image digest, and exact live mount inspection output.
6 changes: 3 additions & 3 deletions scripts/verify_transfer_readiness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ expect_exact_line config/deploy.yml "image: $expected_image" 'production image i
expect_exact_line config/deploy.yml ' host: ogm.geo4lib.app' 'production proxy hostname is unchanged'
expect_exact_line config/deploy.yml ' ELASTICSEARCH_INDEX: opengeometadata_api' 'Elasticsearch index is unchanged'
expect_exact_line config/deploy.yml ' POSTGRES_DB: btaa_ogm_api' 'PostgreSQL database is unchanged'
expect_exact_line config/deploy.yml ' - /var/lib/opengeometadata-api/elasticsearch:/usr/share/elasticsearch/data' 'Elasticsearch volume is unchanged'
expect_exact_line config/deploy.yml ' - /var/lib/opengeometadata-api/postgres:/var/lib/postgresql/data' 'PostgreSQL volume is unchanged'
expect_exact_line config/deploy.yml ' - /var/lib/opengeometadata-api/redis:/data' 'Redis volume is unchanged'
expect_exact_line config/deploy.yml ' - esdata:/usr/share/elasticsearch/data' 'Elasticsearch volume is unchanged'
expect_exact_line config/deploy.yml ' - pgdata:/var/lib/postgresql/data' 'PostgreSQL volume is unchanged'
expect_exact_line config/deploy.yml ' - redisdata:/data' 'Redis volume is unchanged'

for role in web worker cron; do
if grep -Eq "^ ${role}:$" config/deploy.yml; then
Expand Down
Loading