Private
Public Access
Fix stale deployment docs found during a pre-launch review
DEPLOYMENT.md: two spots (§7 backups, §9 known gaps) said "images" when they should say "images and file attachments" -- generic file attachments (#13) share the exact same no-backup-coverage and orphaned-upload gaps as images, but the wording was never updated when that feature shipped. ARCHITECTURE.md §9.2 and the tech-stack table described a materially different, outdated architecture: nginx running on the app server, reverse-proxying to gunicorn over a Unix socket. The actual setup (which DEPLOYMENT.md already correctly documents) has no nginx on the app server at all -- gunicorn binds a TCP port directly, and TLS/reverse-proxying is handled by an external, pre-existing Nginx Proxy Manager instance. Rewrote both to match reality. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+20
-39
@@ -26,7 +26,7 @@ database and Redis; the other runs the application and serves the frontend.
|
|||||||
| Cross-instance broadcast | Redis (pub/sub) | Lets multiple app server processes fan out messages to all connected clients |
|
| Cross-instance broadcast | Redis (pub/sub) | Lets multiple app server processes fan out messages to all connected clients |
|
||||||
| Push notifications | pywebpush + VAPID | Standard Web Push, works on Android and iOS 16.4+ (PWA must be installed to home screen on iOS) |
|
| Push notifications | pywebpush + VAPID | Standard Web Push, works on Android and iOS 16.4+ (PWA must be installed to home screen on iOS) |
|
||||||
| Frontend | React + Vite, vite-plugin-pwa | Generates the manifest and service worker for install + push |
|
| Frontend | React + Vite, vite-plugin-pwa | Generates the manifest and service worker for install + push |
|
||||||
| Reverse proxy / TLS | Nginx + Let's Encrypt (certbot) | Terminates TLS, serves static assets, proxies REST + WebSocket traffic |
|
| Reverse proxy / TLS | External Nginx Proxy Manager (pre-existing infra, not deployed by this project) | Terminates TLS, forwards REST + WebSocket traffic to the app server's one port. The app itself serves the built static frontend directly — no separate static-asset server needed |
|
||||||
| Process management | systemd | No Docker — native to the target Linux distro, no extra install |
|
| Process management | systemd | No Docker — native to the target Linux distro, no extra install |
|
||||||
| Auth | Session cookies (httpOnly, secure) | Simplest to carry through a WebSocket handshake automatically |
|
| Auth | Session cookies (httpOnly, secure) | Simplest to carry through a WebSocket handshake automatically |
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ database and Redis; the other runs the application and serves the frontend.
|
|||||||
PWA client (browser + service worker)
|
PWA client (browser + service worker)
|
||||||
| REST + WebSocket
|
| REST + WebSocket
|
||||||
v
|
v
|
||||||
FastAPI app server (N instances behind Nginx)
|
FastAPI app server (behind an external reverse proxy / TLS terminator)
|
||||||
| | |
|
| | |
|
||||||
v v v
|
v v v
|
||||||
PostgreSQL Redis pub/sub Push service (pywebpush)
|
PostgreSQL Redis pub/sub Push service (pywebpush)
|
||||||
@@ -198,67 +198,48 @@ Runs PostgreSQL and Redis.
|
|||||||
|
|
||||||
### 9.2 App server
|
### 9.2 App server
|
||||||
|
|
||||||
Runs the FastAPI app and Nginx; serves the built PWA static files.
|
Runs the FastAPI app, which also serves the built PWA static files directly
|
||||||
|
(`backend/app/main.py` mounts `frontend/dist` alongside `/api` and `/ws`) —
|
||||||
|
no separate web server runs on this box.
|
||||||
|
|
||||||
- Python virtualenv, application installed via `pip install -e .` or similar.
|
- Python virtualenv, application installed via `pip install -e .` or similar.
|
||||||
- App run via Gunicorn with Uvicorn workers, one process per CPU core as a
|
- App run via Gunicorn with Uvicorn workers, one process per CPU core as a
|
||||||
starting point, managed by a systemd unit:
|
starting point, managed by a systemd unit binding a plain TCP port:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# /etc/systemd/system/ds-chat.service
|
# /etc/systemd/system/ds-chat.service
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Chat service app server
|
Description=DS Chat app server
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=ds-chat
|
User=ds-chat
|
||||||
WorkingDirectory=/srv/ds-chat
|
WorkingDirectory=/srv/ds-chat/backend
|
||||||
EnvironmentFile=/etc/ds-chat/env
|
EnvironmentFile=/etc/ds-chat/env
|
||||||
ExecStart=/srv/ds-chat/venv/bin/gunicorn app.main:app \
|
ExecStart=/srv/ds-chat/backend/.venv/bin/gunicorn app.main:app \
|
||||||
-k uvicorn.workers.UvicornWorker \
|
-k uvicorn.workers.UvicornWorker \
|
||||||
--workers 4 \
|
--workers 4 \
|
||||||
--bind unix:/run/ds-chat/ds-chat.sock
|
--bind 0.0.0.0:8000
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
- Nginx terminates TLS (certbot-managed certificate), serves the built frontend
|
- TLS termination and public-facing reverse proxying are **not** handled on
|
||||||
assets directly, and reverse-proxies API and WebSocket traffic to the Unix
|
this box — they're handled by an existing, separate Nginx Proxy Manager
|
||||||
socket:
|
(NPM) instance elsewhere in the infrastructure, configured to forward
|
||||||
|
`chat.example.com` to this server's port 8000 with WebSocket support
|
||||||
```nginx
|
enabled (required — without it `/ws/chat` can't upgrade). A firewall rule
|
||||||
server {
|
(`ufw`) restricts port 8000 to NPM's address only — see `DEPLOYMENT.md`
|
||||||
listen 443 ssl;
|
§3g/§4 for the exact commands and NPM configuration steps.
|
||||||
server_name chat.example.com;
|
|
||||||
|
|
||||||
root /srv/ds-chat/frontend/dist;
|
|
||||||
try_files $uri /index.html;
|
|
||||||
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://unix:/run/ds-chat/ds-chat.sock;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /ws/ {
|
|
||||||
proxy_pass http://unix:/run/ds-chat/ds-chat.sock;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Secrets (database URL pointing at the data server's private IP, Redis URL,
|
- Secrets (database URL pointing at the data server's private IP, Redis URL,
|
||||||
VAPID keys, session secret) live in `/etc/ds-chat/env`, loaded via
|
VAPID keys, session secret) live in `/etc/ds-chat/env`, loaded via
|
||||||
`EnvironmentFile=`, never committed to the repository.
|
`EnvironmentFile=`, never committed to the repository.
|
||||||
- Deploy process: `git pull`, install/update dependencies, `alembic upgrade
|
- Deploy process: `git pull`, install/update dependencies, `alembic upgrade
|
||||||
head`, build the frontend, `systemctl restart ds-chat`, `nginx -s reload` if
|
head`, build the frontend, `systemctl restart ds-chat` — automated end to
|
||||||
the Nginx config changed.
|
end by `deploy/upgrade.sh`.
|
||||||
- Logs: `journalctl -u ds-chat`, rotated by systemd/journald defaults; add
|
- Logs: `journalctl -u ds-chat`, rotated by systemd/journald defaults.
|
||||||
`logrotate` if the app also writes its own log files.
|
|
||||||
|
|
||||||
## 10. Security considerations
|
## 10. Security considerations
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -285,12 +285,12 @@ producing a gzipped `pg_dump` in `/var/backups/ds-chat/` with 14-day local
|
|||||||
rotation. Off-box shipping is a placeholder in that script (commented-out
|
rotation. Off-box shipping is a placeholder in that script (commented-out
|
||||||
rsync/S3 examples) — decide where those need to go and fill it in.
|
rsync/S3 examples) — decide where those need to go and fill it in.
|
||||||
|
|
||||||
That script covers Postgres only. Uploaded chat images live on the **app**
|
That script covers Postgres only. Uploaded chat images and file attachments
|
||||||
server's disk (`/srv/ds-chat/uploads`, created in §3a) — a separate machine
|
both live on the **app** server's disk (`/srv/ds-chat/uploads`, created in
|
||||||
from the data server this script runs on — and currently have no backup
|
§3a) — a separate machine from the data server this script runs on — and
|
||||||
mechanism at all. Whatever off-box destination you pick above, include
|
currently have no backup mechanism at all. Whatever off-box destination you
|
||||||
`/srv/ds-chat/uploads` in it too (e.g. a second `rsync` line run from the
|
pick above, include `/srv/ds-chat/uploads` in it too (e.g. a second `rsync`
|
||||||
app server).
|
line run from the app server).
|
||||||
|
|
||||||
**Test a restore** (against a scratch database, never directly onto
|
**Test a restore** (against a scratch database, never directly onto
|
||||||
`ds_chat`):
|
`ds_chat`):
|
||||||
@@ -334,12 +334,12 @@ scope decisions" for the full detail on each):
|
|||||||
re-validated per delivery (DNS-rebinding gap).
|
re-validated per delivery (DNS-rebinding gap).
|
||||||
- Backup off-box shipping is a placeholder — decide a destination and fill
|
- Backup off-box shipping is a placeholder — decide a destination and fill
|
||||||
in `deploy/backup-postgres.sh`.
|
in `deploy/backup-postgres.sh`.
|
||||||
- Uploaded chat images (`/srv/ds-chat/uploads` on the app server) have no
|
- Uploaded chat images and file attachments (`/srv/ds-chat/uploads` on the
|
||||||
backup coverage at all yet, on-box or off — see §7.
|
app server) have no backup coverage at all yet, on-box or off — see §7.
|
||||||
- Uploaded-but-never-sent images (a user attaches a file, then never hits
|
- Uploaded-but-never-sent images or files (a user attaches one, then never
|
||||||
Send) leak an orphaned file on disk — no cleanup job for this yet. Not a
|
hits Send) leak an orphaned file on disk — no cleanup job for this yet.
|
||||||
security issue (still gated by room membership to view), just an eventual
|
Not a security issue (still gated by room membership to view), just an
|
||||||
disk-space housekeeping item.
|
eventual disk-space housekeeping item.
|
||||||
|
|
||||||
None of these are new to this phase — deploying doesn't change any of them,
|
None of these are new to this phase — deploying doesn't change any of them,
|
||||||
just makes them reachable from the internet instead of localhost, which is
|
just makes them reachable from the internet instead of localhost, which is
|
||||||
|
|||||||
Reference in New Issue
Block a user