Private
Public Access
Deployment artifacts for the two-server architecture from ARCHITECTURE.md §9, grounded in verified Debian 13 (trixie) package facts (Python 3.13, PostgreSQL 17, Node.js 20, redis-server 8.0, certbot 4.0, ufw -- confirmed rather than guessed) rather than a generic "modern Linux" guide: deploy/systemd/chatapp.service, deploy/chatapp.env.example, deploy/backup-postgres.sh, deploy/upgrade.sh, and DEPLOYMENT.md as the actual numbered runbook. Revised mid-implementation once the user clarified the app sits behind an existing, separate Nginx Proxy Manager rather than local Nginx+certbot: dropped the local Nginx config entirely, gunicorn now binds a TCP port instead of a Unix socket, and app/main.py gained a static-file mount + SPA fallback route so gunicorn alone serves the built frontend, /api, and /ws on one port -- what lets NPM's simple one-upstream-per-domain mode work with zero custom path routing. Path-traversal-guarded (full_path comes straight from the URL) and cache-header-differentiated (far-future immutable on Vite's content-hashed assets, no-cache on index.html/sw.js/ manifest so a deploy actually propagates instead of leaving clients on a stale service worker) -- verified locally against a real gunicorn process serving a real frontend build, not just eyeballed. Two real gaps found and fixed alongside the docs, not just noted: gunicorn wasn't a dependency anywhere despite being the whole app-server design, and there was no WebSocket reconnect logic on the client -- a reverse proxy's idle-connection timeout (NPM's or otherwise) would have silently killed a quiet chat connection with nothing to recover it. Added exponential-backoff reconnect to useChatSocket.ts, verified by hand (killed and restarted the local dev backend mid-session, confirmed auto-reconnect and that a message sends successfully afterward with no page reload). Every command in DEPLOYMENT.md that could be verified locally, was: the exact systemd ExecStart line run against local dev Postgres/Redis with clean SIGTERM shutdown, the static-file serving behavior against a real build, both shell scripts syntax-checked. What couldn't be verified from this sandbox (actual Debian 13 hardware, Nginx Proxy Manager itself) is flagged explicitly in the plan rather than claimed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
38 lines
1.5 KiB
Bash
38 lines
1.5 KiB
Bash
# /etc/chatapp/env (production)
|
|
#
|
|
# This file is loaded by systemd's EnvironmentFile= (see
|
|
# deploy/systemd/chatapp.service) directly into the app process's
|
|
# environment -- it is NOT a dotenv file Python reads from a working
|
|
# directory, and it must never be committed to the repository.
|
|
#
|
|
# Install:
|
|
# sudo mkdir -p /etc/chatapp
|
|
# sudo cp deploy/chatapp.env.example /etc/chatapp/env
|
|
# sudo chown root:chatapp /etc/chatapp/env
|
|
# sudo chmod 0640 /etc/chatapp/env
|
|
# # then edit in the real values below
|
|
#
|
|
# See ../DEPLOYMENT.md for how each value is generated.
|
|
|
|
# Points at the data server's PRIVATE address -- never the public one.
|
|
# The role/password here are whatever you created on the data server in
|
|
# DEPLOYMENT.md step 2.
|
|
DATABASE_URL=postgresql+asyncpg://chatapp:REPLACE_ME@<DATA_SERVER_PRIVATE_IP>:5432/chatapp
|
|
|
|
# Generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
SESSION_SECRET=REPLACE_ME
|
|
|
|
# true in production -- cookies are only sent over HTTPS. The local dev
|
|
# default (backend/.env.example) is false because dev runs over plain HTTP.
|
|
SESSION_HTTPS_ONLY=true
|
|
|
|
# Matches the requirepass set in /etc/redis/redis.conf on the data server
|
|
# (see DEPLOYMENT.md step 2). Same private-address rule as DATABASE_URL.
|
|
REDIS_URL=redis://:REPLACE_ME@<DATA_SERVER_PRIVATE_IP>:6379/0
|
|
|
|
# Optional: push notifications are silently skipped if these are unset.
|
|
# Generate with: .venv/bin/python -m app.cli generate-vapid-keys
|
|
VAPID_PUBLIC_KEY=
|
|
VAPID_PRIVATE_KEY=
|
|
VAPID_SUBJECT=mailto:you@example.com
|