From b1b1f7627849c5ed4787dc3611c81f7689eb4013 Mon Sep 17 00:00:00 2001 From: Lewis John McGibbney Date: Sun, 11 Oct 2026 09:11:57 -0700 Subject: [PATCH] feat: preload the pgAdmin PostGIS server Register the tagbase database when pgAdmin starts, using the configured POSTGRES_PASSWORD, so operators no longer add the server by hand. --- docker-compose.publish.yml | 51 +++++++++++++++++++++++++++++++++++ docker-compose.yml | 33 +++++++++++++++++++++++ services/pgadmin/servers.json | 14 ++++++++++ wiki/Installation.md | 9 ++++--- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 services/pgadmin/servers.json diff --git a/docker-compose.publish.yml b/docker-compose.publish.yml index 4e93abf7..3ba948df 100644 --- a/docker-compose.publish.yml +++ b/docker-compose.publish.yml @@ -103,13 +103,41 @@ services: - acme-www:/var/www/acme pgadmin4: + configs: + - source: pgadmin-servers + target: /pgadmin4/servers.json + - source: pgadmin-pgpass + target: /run/secrets/pgpass + uid: "5050" + gid: "0" + mode: 0400 depends_on: postgis: condition: service_healthy + # The image copies PGPASS_FILE only while pgadmin4.db is first created. + # Its /var/lib/pgadmin volume survives recreate, so refresh .pgpass here. + entrypoint: + - /bin/sh + - -c + - | + set -eu + if [ -n "$${PGPASS_FILE:-}" ] && [ -f "$$PGPASS_FILE" ]; then + user_dir=$$(printf '%s' "$${PGADMIN_DEFAULT_EMAIL}" | sed 's/@/_/g') + mkdir -p "/var/lib/pgadmin/storage/$${user_dir}" + cp "$$PGPASS_FILE" "/var/lib/pgadmin/storage/$${user_dir}/.pgpass" + chmod 600 "/var/lib/pgadmin/storage/$${user_dir}/.pgpass" + fi + exec /entrypoint.sh environment: - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-admin@example.com} - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD:?set PGADMIN_DEFAULT_PASSWORD} - PGADMIN_LISTEN_PORT=5434 + # The image compares this value to the string True. + - PGADMIN_REPLACE_SERVERS_ON_STARTUP=True + - PGADMIN_SERVER_JSON_FILE=/pgadmin4/servers.json + - PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False + # Server mode copies this to the login user's storage as .pgpass. + - PGPASS_FILE=/run/secrets/pgpass expose: - 5434 hostname: pgadmin4 @@ -291,3 +319,26 @@ volumes: staging-data: dbbackups: tagbase-logs: + +configs: + # Keep in sync with services/pgadmin/servers.json. Inline content avoids a + # host bind, which Compose OCI artifacts cannot include. + pgadmin-servers: + content: | + { + "Servers": { + "1": { + "Name": "tagbase", + "Group": "Servers", + "Host": "postgis", + "Port": 5432, + "MaintenanceDB": "tagbase", + "Username": "tagbase", + "SSLMode": "prefer", + "PassFile": ".pgpass" + } + } + } + pgadmin-pgpass: + content: | + postgis:5432:*:tagbase:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} diff --git a/docker-compose.yml b/docker-compose.yml index 396147a7..81aef610 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -115,13 +115,39 @@ services: - nginx-certs:/etc/nginx/certs - acme-www:/var/www/acme pgadmin4: + configs: + - source: pgadmin-pgpass + target: /run/secrets/pgpass + uid: "5050" + gid: "0" + mode: 0400 depends_on: postgis: condition: service_healthy + # The image copies PGPASS_FILE only while pgadmin4.db is first created. + # Its /var/lib/pgadmin volume survives recreate, so refresh .pgpass here. + entrypoint: + - /bin/sh + - -c + - | + set -eu + if [ -n "$${PGPASS_FILE:-}" ] && [ -f "$$PGPASS_FILE" ]; then + user_dir=$$(printf '%s' "$${PGADMIN_DEFAULT_EMAIL}" | sed 's/@/_/g') + mkdir -p "/var/lib/pgadmin/storage/$${user_dir}" + cp "$$PGPASS_FILE" "/var/lib/pgadmin/storage/$${user_dir}/.pgpass" + chmod 600 "/var/lib/pgadmin/storage/$${user_dir}/.pgpass" + fi + exec /entrypoint.sh environment: - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-admin@example.com} - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD:-tagbase} - PGADMIN_LISTEN_PORT=5434 + # The image compares this value to the string True. + - PGADMIN_REPLACE_SERVERS_ON_STARTUP=True + - PGADMIN_SERVER_JSON_FILE=/pgadmin4/servers.json + - PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False + # Server mode copies this to the login user's storage as .pgpass. + - PGPASS_FILE=/run/secrets/pgpass expose: - 5434 hostname: pgadmin4 @@ -135,6 +161,8 @@ services: networks: - internal-network restart: unless-stopped + volumes: + - ./services/pgadmin/servers.json:/pgadmin4/servers.json:ro # postgres: # build: # context: ./services/postgres @@ -345,3 +373,8 @@ volumes: nginx-certs: acme-www: lego-data: + +configs: + pgadmin-pgpass: + content: | + postgis:5432:*:tagbase:${POSTGRES_PASSWORD:-tagbase} diff --git a/services/pgadmin/servers.json b/services/pgadmin/servers.json new file mode 100644 index 00000000..47101c1e --- /dev/null +++ b/services/pgadmin/servers.json @@ -0,0 +1,14 @@ +{ + "Servers": { + "1": { + "Name": "tagbase", + "Group": "Servers", + "Host": "postgis", + "Port": 5432, + "MaintenanceDB": "tagbase", + "Username": "tagbase", + "SSLMode": "prefer", + "PassFile": ".pgpass" + } + } +} diff --git a/wiki/Installation.md b/wiki/Installation.md index 1a5b276c..49c59019 100644 --- a/wiki/Installation.md +++ b/wiki/Installation.md @@ -129,13 +129,16 @@ All browser routes need **nginx basic auth** (`NGINX_USER` / `NGINX_PASS`). Graf **OpenAPI** is the ingest contract (GET/POST, `file` / `ftp` / `http` / `https`, `.txt` / archives). Start at [https://localhost/docs](https://localhost/docs). -**pgAdmin:** [https://localhost/pgadmin4/](https://localhost/pgadmin4/) — nginx auth first, then `PGADMIN_DEFAULT_EMAIL` / `PGADMIN_DEFAULT_PASSWORD`. Register a server: +**pgAdmin:** [https://localhost/pgadmin4/](https://localhost/pgadmin4/) — nginx auth first, then `PGADMIN_DEFAULT_EMAIL` / `PGADMIN_DEFAULT_PASSWORD`. The saved `tagbase` connection is already filled in: -- Host: **`postgis`** (Docker DNS name, not `postgres` and not `localhost`) +- Host: `postgis` (Docker DNS name on the Compose network) - Port: `5432` - Database: `tagbase` - Username: `tagbase` -- Password: the same `POSTGRES_PASSWORD` + +Expand **tagbase** in the object explorer to connect. The password is the configured `POSTGRES_PASSWORD`. + +Keep `:`, `\`, and line breaks out of `POSTGRES_PASSWORD`. The preloaded connection reads that password from a [pgpass](https://www.postgresql.org/docs/current/libpq-pgpass.html) file, which treats those characters as syntax. **Materialized views** (once, after you have ingested tags you want in “application ready” form): run [`tagbase_materialized_views.sql`](https://github.com/tagbase/tagbase-server/blob/main/services/postgis/tagbase_materialized_views.sql) in the pgAdmin query tool. Do not re-run that script on every ingest.