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.
This commit is contained in:
1 parent
a664cf68f5
commit
913a57f8a6
146 files changed
+5258
-354
No files matched your search
@@ -0,0 +1,7 @@
|
||||
PENPOT_SECRET_KEY=CHANGE_ME
|
||||
PENPOT_DATABASE_PASSWORD=CHANGE_ME
|
||||
|
||||
# Also set on the live machine
|
||||
PENPOT_SMTP_PASSWORD=CHANGE_ME
|
||||
PENPOT_OIDC_CLIENT_ID=CHANGE_ME
|
||||
PENPOT_OIDC_CLIENT_SECRET=CHANGE_ME
|
||||
@@ -0,0 +1,183 @@
|
||||
# Penpot — VPS (ROOTLESS PODMAN). Ported from the home server 2026-08-12.
|
||||
#
|
||||
# First started 2026-09-21. Role + database created on the SHARED postgres, and the
|
||||
# same lines added to PostgreSQL/initdb/00-roles-and-databases.sql.template (note the
|
||||
# .template suffix -- the plain .sql path this header used to cite does not exist).
|
||||
# Managed by: systemctl --user enable --now podman-penpot.service
|
||||
#
|
||||
# Image tags are PINNED and watchtower is disabled on purpose. Penpot runs schema
|
||||
# migrations on boot and has shipped breaking major upgrades (1.x -> 2.x needed manual
|
||||
# steps), so it is a bad candidate for unattended updates. Bump the tags deliberately,
|
||||
# and back up the assets volume WITH the database -- a pg_dump alone will not restore.
|
||||
#
|
||||
# tty/stdin_open are false on every service here. A TTY makes tools in an entrypoint
|
||||
# think they are interactive; usesend hung silently on exactly that (2026-09-19).
|
||||
#
|
||||
# NOTE: the home version used postgres:15-alpine with --data-checksums. The shared
|
||||
# instance is postgres 17 without checksums. Penpot does not care, and since there is
|
||||
# no existing data this is not a migration — but if you ever DO need checksums, they
|
||||
# can only be set at initdb time for the whole cluster.
|
||||
#
|
||||
# Redis stays local to this stack: Penpot uses it for transient session and
|
||||
# notification state only, so it is not worth centralising.
|
||||
#
|
||||
# Rootless adaptations from the home version:
|
||||
# - Dropped its private postgres:15-alpine; uses the shared instance (AGENTS.md §2).
|
||||
# - PENPOT_DATABASE_URI repointed from penpot-db to the shared postgresql.
|
||||
# - :Z on the assets bind mount. It is shared between frontend and backend, so it
|
||||
# uses LOWERCASE :z -- uppercase would give each container a private MCS category
|
||||
# and the second to start would lock the first out (AGENTS.md §6).
|
||||
# - /etc/localtime mounts removed in favour of TZ (AGENTS.md §8).
|
||||
#
|
||||
# Public sign-up is off (disable-registration). Accounts come from Authentik SSO
|
||||
# instead: enable-oidc-registration creates a profile on first login.
|
||||
#
|
||||
# NPM: proxy penpot.gbrown.org -> http://penpot-frontend:8080
|
||||
#
|
||||
# MCP (added 2026-09-23): `enable-mcp` on the FRONTEND makes its entrypoint write
|
||||
# /mcp/stream, /mcp/sse and /mcp/ws locations proxying to penpot-mcp:4401 (HTTP) and
|
||||
# :4402 (plugin websocket), so NPM needs no extra config. The flag is also on the
|
||||
# backend, matching upstream, where it gates per-user MCP keys (Account > Integrations).
|
||||
# Claude Code connects to https://penpot.gbrown.org/mcp/stream?userToken=<key>.
|
||||
|
||||
networks:
|
||||
nginx-bridge:
|
||||
external: true
|
||||
|
||||
services:
|
||||
penpot-frontend:
|
||||
image: docker.io/penpotapp/frontend:2.17.2
|
||||
container_name: penpot-frontend
|
||||
hostname: penpot-frontend
|
||||
domainname: penpot.gbrown.org
|
||||
networks: ['nginx-bridge']
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
- PENPOT_FLAGS=disable-registration disable-login-with-password enable-login-with-oidc enable-oidc-registration enable-smtp enable-mcp
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "false"
|
||||
volumes:
|
||||
- ./volumes/assets:/opt/data/assets:z
|
||||
depends_on:
|
||||
- penpot-backend
|
||||
- penpot-exporter
|
||||
- penpot-mcp
|
||||
tty: false
|
||||
stdin_open: false
|
||||
restart: unless-stopped
|
||||
|
||||
penpot-backend:
|
||||
image: docker.io/penpotapp/backend:2.17.2
|
||||
container_name: penpot-backend
|
||||
hostname: penpot-backend
|
||||
domainname: penpot.gbrown.org
|
||||
networks: ['nginx-bridge']
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
# Heap deliberately UNCAPPED (Gib's call, 2026-09-21): the JVM takes ~25% of
|
||||
# host RAM, measuring ~2.3GB resident. Box has 16GB and the priority is
|
||||
# responsiveness, not footprint. If it gets tight, Penpot moves to the home
|
||||
# server rather than getting squeezed here. Set JVM_OPTS=-Xmx<n> to cap.
|
||||
- PENPOT_FLAGS=disable-registration disable-login-with-password enable-login-with-oidc enable-oidc-registration enable-smtp enable-prepl-server enable-mcp
|
||||
- PENPOT_PUBLIC_URI=https://penpot.gbrown.org
|
||||
# ── SMTP via the UseSend stack on this same host ──────────────────────
|
||||
# The proxy ADVERTISES "AUTH LOGIN PLAIN" before STARTTLS but actually refuses
|
||||
# it ("538 Must issue a STARTTLS command first"), so TLS is mandatory. It is
|
||||
# reached by the smtp.usesend.gbrown.org network alias rather than its
|
||||
# container name so the wildcard cert's hostname check passes.
|
||||
# Username is the proxy's SMTP_AUTH_USERNAME; password is a UseSend API key.
|
||||
# Max message size is 10MB.
|
||||
- PENPOT_SMTP_HOST=smtp.usesend.gbrown.org
|
||||
- PENPOT_SMTP_PORT=587
|
||||
- PENPOT_SMTP_USERNAME=usesend
|
||||
- PENPOT_SMTP_PASSWORD=${PENPOT_SMTP_PASSWORD:?set this in .env -- a UseSend API key}
|
||||
- PENPOT_SMTP_TLS=true
|
||||
- PENPOT_SMTP_SSL=false
|
||||
- PENPOT_SMTP_DEFAULT_FROM=Penpot <[email protected]>
|
||||
- PENPOT_SMTP_DEFAULT_REPLY_TO=Penpot <[email protected]>
|
||||
- PENPOT_SECRET_KEY=${PENPOT_SECRET_KEY}
|
||||
# Shared postgres, not a private container
|
||||
- PENPOT_DATABASE_URI=postgresql://postgresql/penpot
|
||||
- PENPOT_DATABASE_USERNAME=penpot
|
||||
- PENPOT_DATABASE_PASSWORD=${PENPOT_DATABASE_PASSWORD}
|
||||
# Penpot defaults to min-size=60 AND max-size=60 -- it eagerly opens 60
|
||||
# connections and never releases them. On the SHARED postgres (max_connections
|
||||
# = 100) that silently ate 60% of the cluster's slots and other apps started
|
||||
# getting "remaining connection slots are reserved for SUPERUSER". Undocumented
|
||||
# in Penpot's config guide; the numbers are visible in its startup log line
|
||||
# `app.db - initialize connection pool ... min-size=60, max-size=60`.
|
||||
- PENPOT_DATABASE_MIN_POOL_SIZE=4
|
||||
- PENPOT_DATABASE_MAX_POOL_SIZE=12
|
||||
- PENPOT_REDIS_URI=redis://penpot-redis/0
|
||||
- PENPOT_OBJECTS_STORAGE_BACKEND=fs
|
||||
- PENPOT_OBJECTS_STORAGE_FS_DIRECTORY=/opt/data/assets
|
||||
- PENPOT_TELEMETRY_ENABLED=false
|
||||
# ── SSO via Authentik (app slug "penpot", any Authentik user) ────────
|
||||
# Login is SSO-only (disable-login-with-password). Existing profiles match
|
||||
# by email, and enable-oidc-registration creates a profile on first SSO
|
||||
# login, while disable-registration keeps the public sign-up form off.
|
||||
# Authority must keep its trailing slash; Penpot appends the discovery path.
|
||||
- PENPOT_OIDC_BASE_URI=https://auth.gbrown.org/application/o/penpot/
|
||||
- PENPOT_OIDC_CLIENT_ID=${PENPOT_OIDC_CLIENT_ID:?set this in .env}
|
||||
- PENPOT_OIDC_CLIENT_SECRET=${PENPOT_OIDC_CLIENT_SECRET:?set this in .env}
|
||||
- PENPOT_OIDC_SCOPES=openid profile email
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "false"
|
||||
volumes:
|
||||
- ./volumes/assets:/opt/data/assets:z
|
||||
depends_on:
|
||||
- penpot-redis
|
||||
tty: false
|
||||
stdin_open: false
|
||||
restart: unless-stopped
|
||||
|
||||
penpot-mcp:
|
||||
# Upstream image defaults: `node index.js --multi-user`, listening on 0.0.0.0.
|
||||
# No volumes and no published ports; only the frontend nginx talks to it.
|
||||
image: docker.io/penpotapp/mcp:2.17.2
|
||||
container_name: penpot-mcp
|
||||
hostname: penpot-mcp
|
||||
domainname: penpot.gbrown.org
|
||||
networks: ['nginx-bridge']
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "false"
|
||||
tty: false
|
||||
stdin_open: false
|
||||
restart: unless-stopped
|
||||
|
||||
penpot-exporter:
|
||||
image: docker.io/penpotapp/exporter:2.17.2
|
||||
container_name: penpot-exporter
|
||||
hostname: penpot-exporter
|
||||
domainname: penpot.gbrown.org
|
||||
networks: ['nginx-bridge']
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
- PENPOT_PUBLIC_URI=http://penpot-frontend:8080
|
||||
# REQUIRED and missing from the original port: the exporter validates a
|
||||
# :secret-key at boot and crash-loops without it. Must match the backend.
|
||||
- PENPOT_SECRET_KEY=${PENPOT_SECRET_KEY}
|
||||
- PENPOT_REDIS_URI=redis://penpot-redis/0
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "false"
|
||||
depends_on:
|
||||
- penpot-redis
|
||||
tty: false
|
||||
stdin_open: false
|
||||
restart: unless-stopped
|
||||
|
||||
penpot-redis:
|
||||
image: docker.io/library/redis:7-alpine
|
||||
container_name: penpot-redis
|
||||
hostname: penpot-redis
|
||||
domainname: penpot.gbrown.org
|
||||
networks: ['nginx-bridge']
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "false"
|
||||
tty: false
|
||||
stdin_open: false
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/home/gib/Media
|
||||
Description=Podman Compose: Penpot
|
||||
After=network-online.target podman.socket podman-postgresql.service
|
||||
Wants=network-online.target podman-postgresql.service
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=3
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=%h/Server/Penpot
|
||||
ExecStart=/usr/bin/podman compose up -d
|
||||
ExecStop=/usr/bin/podman compose down
|
||||
RemainAfterExit=yes
|
||||
TimeoutStartSec=900
|
||||
TimeoutStopSec=90
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
Reference in new issue
Block a user