Files
Gabriel Brown 913a57f8a6 Snapshot both servers: the VPS catalog moves to server/vps, the home server joins as server/home
The catalog in server/containers had drifted from the VPS. Eleven compose
files and twelve units no longer matched what runs, and two running
services, Gbrown_Agent and BillTracker, were missing. server/vps is now a
copy of the live files, and panama-server reads from it.

server/home holds the home server's services in the same shape. They still
run under Docker, so the podman units there are drafts for a later move, and
nothing installs from that tree. PostHog, Sentry and Plausible are checkouts
of upstream repositories. Only their local files are kept, next to an
UPSTREAM file naming the repository and the exact commit.

Every secret in both trees is a ${VAR} reference named in .env.example, or
CHANGE_ME in files that are not compose files. The live servers still hold
some of those values inline, so a live file can differ from its copy on
those lines alone. Existing .env.example files keep their comments, with new
keys appended at the end.

The ignore rules and the secrets contract now cover both trees, plus the
volume/ and volumes/ directories the home server uses.
2026-10-06 10:52:47 -04:00

81 lines
4.0 KiB
YAML

networks:
nginx-bridge:
external: true
services:
postgresql:
# pgvector image = stock postgres:17 plus the `vector` extension compiled in. Same
# PostgreSQL 17.10 and same data directory format, so this is a drop-in swap with no
# dump/restore needed.
#
# ⚠️ THE TAG MUST STAY -trixie. This is not cosmetic.
#
# The plain `pgvector/pgvector:pg17` tag is built on Debian 12 (bookworm, glibc 2.36).
# The stock `postgres:17` this cluster was created under is Debian 13 (trixie,
# glibc 2.41). glibc supplies the collation used to order every text index, and it is
# NOT guaranteed stable across versions. Starting on bookworm made all 14 databases
# report:
# WARNING: database "npm" has a collation version mismatch
# DETAIL: created using collation version 2.41, but the OS provides version 2.36
# An index built under one collation and read under another can silently return wrong
# results — missed rows in range scans, duplicate values slipping past unique
# constraints. It does not error; it just quietly answers incorrectly.
#
# Using the -trixie build keeps glibc at 2.41, matching how the data was written, so
# no REINDEX is required. If you ever must move to a different base, the correct
# procedure is: REINDEX DATABASE <each>, then ALTER DATABASE <each> REFRESH COLLATION
# VERSION — not simply silencing the warning.
#
# The extension is AVAILABLE but not enabled anywhere by default. To use it in a
# database, enable it per-database (it is not cluster-wide):
# podman exec postgresql psql -U npm -d <dbname> -c 'CREATE EXTENSION vector;'
#
# ⚠️ Do NOT let watchtower update this to a different pg major. It is already in
# WATCHTOWER_DISABLE_CONTAINERS, which is what keeps that from happening.
image: pgvector/pgvector:pg17-trixie
container_name: postgresql
# Rootless: maps host uid 1000 (gib) -> container uid 999 (postgres), so ./pg_data
# stays gib-owned on the host. Postgres' entrypoint supports running as non-root
# provided the data dir ownership matches, which this guarantees.
userns_mode: "keep-id:uid=999,gid=999"
hostname: postgresql
domainname: pg.gbrown.org
networks:
- nginx-bridge
# ⚠️ WIREGUARD ADDRESS ONLY. This was previously '5432:5432', which bound the
# database to every interface including the public one — the only thing preventing
# the internet from reaching a Postgres auth prompt was firewalld plus the Hetzner
# cloud firewall. Two independent firewall rules were the sole barrier in front of
# all 14 databases.
#
# Nothing needs the public binding: every consumer (authentik, infisical, adminer,
# the payload/convex app stacks, gitea) resolves `postgresql` over the nginx-bridge
# container network and never touches the published port at all. This mapping now
# exists purely so you can point a desktop client at it across the tunnel.
#
# Same pattern as watchtower's API and authentik's 9000 — see AGENTS.md §5.
ports: ['192.168.2.2:5432:5432']
env_file: .env
environment:
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
- TZ=America/New_York
labels:
com.centurylinklabs.watchtower.enable: "true"
volumes:
- ./pg_data:/var/lib/postgresql/data:Z
# Runs ONLY when pg_data is empty (fresh install). Recreates every role and
# database this VPS needs, so the environment can be rebuilt from scratch without
# hand-creating them. Does nothing to an existing database — safe to leave mounted.
# The committed file is 00-roles-and-databases.sql.TEMPLATE with placeholder
# passwords; copy it to .sql and fill in real values from each service's .env.
- ./initdb:/docker-entrypoint-initdb.d:Z
tty: true
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s