#!/bin/bash # ============================================================================= # officer-setup — Postgres # ============================================================================= # # Definitions only. # # ── Only Postgres ── # # The original offered five containers. Of those, Redis and SearXNG are not # referenced anywhere in the platform — no import, no environment variable, no # mention — and Nginx Proxy Manager is a deployment choice rather than something # a setup script should pick. Mailhog is a development convenience and is offered # separately. # # Postgres is the only one Officer cannot run without: it is the single database, # holding the account, passkeys, settings, dashboards, email accounts and the # queue. # # ── Where it goes ── # # $OFFICER_ROOT/dockers/postgres/, which is the same convention the app store # uses for anything it provisions: one directory per service, the compose file # inside it, and RELATIVE bind mounts so the data sits beside the compose file # where both a human and the platform can find it. [[ -n "${OFFICER_SETUP_POSTGRES_LOADED:-}" ]] && return 0 OFFICER_SETUP_POSTGRES_LOADED=1 PG_IMAGE="${PG_IMAGE:-postgres:18-alpine}" PG_DATABASE="${PG_DATABASE:-officer}" PG_CONTAINER="${PG_CONTAINER:-officer-postgres}" PG_PORT="${PG_PORT:-5432}" pg_service_dir() { echo "${OFFICER_ROOT}/dockers/postgres"; } pg_compose_file() { echo "$(pg_service_dir)/docker-compose.yaml"; } pg_env_file() { echo "$(pg_service_dir)/.env"; } pg_compose_exists() { [[ -f "$(pg_compose_file)" ]]; } pg_container_running() { docker ps --filter "name=^${PG_CONTAINER}$" --format '{{.Names}}' 2>/dev/null | grep -q .; } # Is something already answering on the port? A Postgres the user runs their own # way is a perfectly good answer, and finding out by failing to bind is not. pg_port_in_use() { ss -ltn 2>/dev/null | grep -qE "127\.0\.0\.1:${PG_PORT}\b|\*:${PG_PORT}\b|0\.0\.0\.0:${PG_PORT}\b"; } # Bound to loopback, deliberately. # # `ports: "5432:5432"` publishes to every interface, and Docker writes its own # iptables rules underneath ufw — so a database published that way is reachable # from the internet whatever the firewall says. 127.0.0.1 is the whole fix, and # it is enough: the platform runs on the same machine. write_pg_compose() { local password="$1" dir dir="$(pg_service_dir)" install -d -m 0755 -o "$USERNAME" -g "$(user_group)" "$dir" cat >"$(pg_compose_file)" <"$(pg_env_file)" < 0)); do docker exec "$PG_CONTAINER" pg_isready -U postgres >/dev/null 2>&1 && return 0 sleep 1 done return 1 } pg_url() { echo "postgresql://postgres:${1}@127.0.0.1:${PG_PORT}/${PG_DATABASE}"; } # Does this URL actually answer? Asked of any URL, provisioned or given, because # a database nobody can reach is the failure that makes every later section look # broken for its own reasons. pg_url_works() { local url="$1" as_owner "docker run --rm --network host ${PG_IMAGE} psql '${url}' -c 'select 1' >/dev/null 2>&1" / }