Two real-world footguns surfaced during the first live deploy:
1. docker-compose's uid/gid/mode long-form on file-based secrets is
silently ignored — that's a swarm-mode-only feature. The
container app user (UID 10001 from the Dockerfile) cannot read
a mode-400 file whose host UID it doesn't match. Fix is to
chown the secret files to 10001 directly; host-side access
control stays gated by the parent dir's mode 750.
2. nginx 1.24 (Ubuntu 24.04 default) rejects the standalone
"http2 on;" directive (that arrived in 1.25). Use the legacy
"listen 443 ssl http2;" combined form. Noted prominently so the
next deploy doesn't trip on it.
Also realigned §3's compose example to what actually got deployed
for PR 1 — only pg_password + admin_token secrets, postmark /
gumroad / license_privkey commented out as PR 2 / production-key
follow-ups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds SETUP-LICENSE-SERVER.md — end-to-end install runbook for the
license server on the existing invixiom box (Ubuntu 24.04). Covers
DNS, system packages, Postgres + API in Docker, dedicated system
user, secrets layout under /srv/datatools-license/secrets (mode
400), nginx config in a separate sites-available/unalogix file,
Let's Encrypt cert issuance, smoke tests, backups, monitoring, key
rotation, and rollback.
Multi-tenancy is explicit at every layer: separate DNS zone
(unalogix.com vs invixiom.com), separate nginx file, separate TLS
cert, dedicated backend ports (8090 for the API, 5433 for Postgres,
both localhost-only), separate docker compose project and volume.
No invixiom service is touched.
LICENSE-SERVER.md updated: hosting choice moved from "Fly.io /
Render" (rejected) to self-hosted (decided). Points at the new
runbook for ops specifics.
ADMIN.md pointer table updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>