diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 709d08d..a965275 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -205,19 +205,19 @@ Runs the FastAPI app and Nginx; serves the built PWA static files. starting point, managed by a systemd unit: ```ini -# /etc/systemd/system/chatapp.service +# /etc/systemd/system/ds-chat.service [Unit] Description=Chat service app server After=network.target [Service] -User=chatapp -WorkingDirectory=/srv/chatapp -EnvironmentFile=/etc/chatapp/env -ExecStart=/srv/chatapp/venv/bin/gunicorn app.main:app \ +User=ds-chat +WorkingDirectory=/srv/ds-chat +EnvironmentFile=/etc/ds-chat/env +ExecStart=/srv/ds-chat/venv/bin/gunicorn app.main:app \ -k uvicorn.workers.UvicornWorker \ --workers 4 \ - --bind unix:/run/chatapp/chatapp.sock + --bind unix:/run/ds-chat/ds-chat.sock Restart=on-failure [Install] @@ -233,16 +233,16 @@ server { listen 443 ssl; server_name chat.example.com; - root /srv/chatapp/frontend/dist; + root /srv/ds-chat/frontend/dist; try_files $uri /index.html; location /api/ { - proxy_pass http://unix:/run/chatapp/chatapp.sock; + proxy_pass http://unix:/run/ds-chat/ds-chat.sock; proxy_set_header Host $host; } location /ws/ { - proxy_pass http://unix:/run/chatapp/chatapp.sock; + 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"; @@ -252,12 +252,12 @@ server { ``` - Secrets (database URL pointing at the data server's private IP, Redis URL, - VAPID keys, session secret) live in `/etc/chatapp/env`, loaded via + VAPID keys, session secret) live in `/etc/ds-chat/env`, loaded via `EnvironmentFile=`, never committed to the repository. - Deploy process: `git pull`, install/update dependencies, `alembic upgrade - head`, build the frontend, `systemctl restart chatapp`, `nginx -s reload` if + head`, build the frontend, `systemctl restart ds-chat`, `nginx -s reload` if the Nginx config changed. -- Logs: `journalctl -u chatapp`, rotated by systemd/journald defaults; add +- Logs: `journalctl -u ds-chat`, rotated by systemd/journald defaults; add `logrotate` if the app also writes its own log files. ## 10. Security considerations diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 3b37d5c..3a27d6e 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,4 +1,4 @@ -# Deploying KeepItTalking +# Deploying DS Chat Two Debian 13 servers, no containers, matching [ARCHITECTURE.md §9](ARCHITECTURE.md#9-deployment-architecture--two-linux-servers-no-docker): @@ -49,8 +49,8 @@ sudo apt install -y postgresql redis-server **PostgreSQL** — create the role and database: ```bash -sudo -u postgres psql -c "CREATE ROLE chatapp WITH LOGIN PASSWORD '';" -sudo -u postgres psql -c "CREATE DATABASE chatapp OWNER chatapp;" +sudo -u postgres psql -c "CREATE ROLE ds_chat WITH LOGIN PASSWORD '';" +sudo -u postgres psql -c "CREATE DATABASE ds_chat OWNER ds_chat;" ``` Bind it to the private interface only (find the exact config path with @@ -67,7 +67,7 @@ enough (a single `/32`) that it won't collide with Debian's default `127.0.0.1`/`::1`-only entries, so appending is fine: ```bash -echo "host chatapp chatapp /32 scram-sha-256" \ +echo "host ds_chat ds_chat /32 scram-sha-256" \ | sudo tee -a /etc/postgresql/17/main/pg_hba.conf sudo systemctl restart postgresql ``` @@ -95,35 +95,35 @@ sudo ufw enable install steps (copy it to `/usr/local/bin/`, cron entry). Off-box shipping is left as a placeholder in that script — see §8 below. -## 3. App server: Python, Node.js, the `chatapp` user, and the app itself +## 3. App server: Python, Node.js, the `ds-chat` user, and the app itself ```bash sudo apt update sudo apt install -y python3 python3-venv nodejs npm git ``` -### 3a. The `chatapp` system user and directory +### 3a. The `ds-chat` system user and directory ```bash -sudo useradd --system --shell /usr/sbin/nologin --home-dir /srv/chatapp --create-home chatapp -sudo chown chatapp:chatapp /srv/chatapp -sudo -u chatapp mkdir -p /srv/chatapp/uploads +sudo useradd --system --shell /usr/sbin/nologin --home-dir /srv/ds-chat --create-home ds-chat +sudo chown ds-chat:ds-chat /srv/ds-chat +sudo -u ds-chat mkdir -p /srv/ds-chat/uploads ``` ### 3b. Clone the repo (deploy key, not a personal token) ```bash -sudo -u chatapp mkdir -p /srv/chatapp/.ssh -sudo -u chatapp ssh-keygen -t ed25519 -f /srv/chatapp/.ssh/id_ed25519 -N "" -sudo cat /srv/chatapp/.ssh/id_ed25519.pub +sudo -u ds-chat mkdir -p /srv/ds-chat/.ssh +sudo -u ds-chat ssh-keygen -t ed25519 -f /srv/ds-chat/.ssh/id_ed25519 -N "" +sudo cat /srv/ds-chat/.ssh/id_ed25519.pub ``` Add that public key as a **read-only deploy key** on the Gitea repo (Settings → Deploy Keys), then: ```bash -sudo -u chatapp ssh-keyscan git.darksingularity.org >> /srv/chatapp/.ssh/known_hosts -sudo -u chatapp git clone git@git.darksingularity.org:DarkSingularity/KeepItTalking.git /srv/chatapp +sudo -u ds-chat ssh-keyscan git.darksingularity.org >> /srv/ds-chat/.ssh/known_hosts +sudo -u ds-chat git clone git@git.darksingularity.org:DarkSingularity/ds-chat.git /srv/ds-chat ``` (If your Gitea's SSH is on a non-default port, adjust the clone URL and @@ -132,16 +132,16 @@ sudo -u chatapp git clone git@git.darksingularity.org:DarkSingularity/KeepItTalk ### 3c. Backend: venv, env file, migrations, first admin ```bash -sudo -u chatapp python3 -m venv /srv/chatapp/backend/.venv -sudo -u chatapp /srv/chatapp/backend/.venv/bin/pip install -e /srv/chatapp/backend +sudo -u ds-chat python3 -m venv /srv/ds-chat/backend/.venv +sudo -u ds-chat /srv/ds-chat/backend/.venv/bin/pip install -e /srv/ds-chat/backend ``` ```bash -sudo mkdir -p /etc/chatapp -sudo cp /srv/chatapp/deploy/chatapp.env.example /etc/chatapp/env -sudo chown root:chatapp /etc/chatapp/env -sudo chmod 0640 /etc/chatapp/env -sudo -e /etc/chatapp/env # fill in DATABASE_URL, REDIS_URL, SESSION_SECRET (see below) +sudo mkdir -p /etc/ds-chat +sudo cp /srv/ds-chat/deploy/ds-chat.env.example /etc/ds-chat/env +sudo chown root:ds-chat /etc/ds-chat/env +sudo chmod 0640 /etc/ds-chat/env +sudo -e /etc/ds-chat/env # fill in DATABASE_URL, REDIS_URL, SESSION_SECRET (see below) ``` Generate `SESSION_SECRET`: @@ -150,23 +150,23 @@ Generate `SESSION_SECRET`: python3 -c "import secrets; print(secrets.token_urlsafe(32))" ``` -Run migrations and create the first admin account (as `chatapp`, with the +Run migrations and create the first admin account (as `ds-chat`, with the env file sourced so `DATABASE_URL` is set): ```bash -sudo -u chatapp bash -c 'set -a; source /etc/chatapp/env; set +a; \ - cd /srv/chatapp/backend && .venv/bin/alembic upgrade head' +sudo -u ds-chat bash -c 'set -a; source /etc/ds-chat/env; set +a; \ + cd /srv/ds-chat/backend && .venv/bin/alembic upgrade head' -sudo -u chatapp bash -c 'set -a; source /etc/chatapp/env; set +a; \ - cd /srv/chatapp/backend && .venv/bin/python -m app.cli create-user "" --admin' +sudo -u ds-chat bash -c 'set -a; source /etc/ds-chat/env; set +a; \ + cd /srv/ds-chat/backend && .venv/bin/python -m app.cli create-user "" --admin' ``` Optional: push notifications. Skipped silently if `VAPID_PUBLIC_KEY`/ -`VAPID_PRIVATE_KEY` are left unset in `/etc/chatapp/env`. To enable: +`VAPID_PRIVATE_KEY` are left unset in `/etc/ds-chat/env`. To enable: ```bash -sudo -u chatapp /srv/chatapp/backend/.venv/bin/python -m app.cli generate-vapid-keys -# paste the three printed lines into /etc/chatapp/env +sudo -u ds-chat /srv/ds-chat/backend/.venv/bin/python -m app.cli generate-vapid-keys +# paste the three printed lines into /etc/ds-chat/env ``` Optional: outgoing email (admin-invited signups, room membership notifications). @@ -182,16 +182,16 @@ admin sets it up. Manager forward the whole domain to one port with no custom path routing. ```bash -sudo -u chatapp bash -c 'cd /srv/chatapp/frontend && npm ci && npm run build' +sudo -u ds-chat bash -c 'cd /srv/ds-chat/frontend && npm ci && npm run build' ``` ### 3e. systemd unit ```bash -sudo cp /srv/chatapp/deploy/systemd/chatapp.service /etc/systemd/system/ +sudo cp /srv/ds-chat/deploy/systemd/ds-chat.service /etc/systemd/system/ sudo systemctl daemon-reload -sudo systemctl enable --now chatapp -sudo systemctl status chatapp --no-pager +sudo systemctl enable --now ds-chat +sudo systemctl status ds-chat --no-pager ``` Confirm it's actually up before continuing: @@ -200,13 +200,13 @@ Confirm it's actually up before continuing: curl -s http://127.0.0.1:8000/api/health # expect {"status":"ok"} ``` -### 3f. Let `chatapp` restart its own service (needed for `deploy/upgrade.sh`) +### 3f. Let `ds-chat` restart its own service (needed for `deploy/upgrade.sh`) ```bash -echo 'chatapp ALL=(root) NOPASSWD: /usr/bin/systemctl restart chatapp, /usr/bin/systemctl status chatapp' \ - | sudo tee /etc/sudoers.d/chatapp -sudo chmod 0440 /etc/sudoers.d/chatapp -sudo visudo -cf /etc/sudoers.d/chatapp # validates syntax before it's live +echo 'ds-chat ALL=(root) NOPASSWD: /usr/bin/systemctl restart ds-chat, /usr/bin/systemctl status ds-chat' \ + | sudo tee /etc/sudoers.d/ds-chat +sudo chmod 0440 /etc/sudoers.d/ds-chat +sudo visudo -cf /etc/sudoers.d/ds-chat # validates syntax before it's live ``` ### 3g. Firewall @@ -222,7 +222,7 @@ sudo ufw enable If NPM reaches this box over the same private network the data server uses, bind gunicorn to that private IP instead of `0.0.0.0` in -`deploy/systemd/chatapp.service` for defense in depth on top of the +`deploy/systemd/ds-chat.service` for defense in depth on top of the firewall rule (edit `--bind`, then `daemon-reload` + `restart`). ## 4. Configuring Nginx Proxy Manager @@ -251,17 +251,17 @@ This is config in NPM's own UI/database, not a file this repo ships: - Open `https://chat.example.com` in a browser, log in with the admin account from §3c, create a room, send a message, confirm it appears live (WebSocket working). -- `sudo journalctl -u chatapp -f` on the app server while doing the above — +- `sudo journalctl -u ds-chat -f` on the app server while doing the above — should show request logs, no tracebacks. ## 6. Upgrades ```bash -sudo -u chatapp /srv/chatapp/deploy/upgrade.sh +sudo -u ds-chat /srv/ds-chat/deploy/upgrade.sh ``` Pulls latest `main`, reinstalls backend deps, runs `alembic upgrade head`, -rebuilds the frontend, restarts `chatapp`, and curls `/api/health` to +rebuilds the frontend, restarts `ds-chat`, and curls `/api/health` to confirm it came back up. Fails loudly (`set -euo pipefail`) and stops before restarting anything if an earlier step — most importantly a failed migration — errors out, so a bad deploy doesn't take down the previously @@ -281,30 +281,30 @@ in practice, downgrades written and tested by hand if one is ever needed). ## 7. Backups `deploy/backup-postgres.sh` (installed in §2) runs nightly via cron, -producing a gzipped `pg_dump` in `/var/backups/chatapp/` with 14-day local +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 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** -server's disk (`/srv/chatapp/uploads`, created in §3a) — a separate machine +server's disk (`/srv/ds-chat/uploads`, created in §3a) — a separate machine from the data server this script runs on — and currently have no backup mechanism at all. Whatever off-box destination you pick above, include -`/srv/chatapp/uploads` in it too (e.g. a second `rsync` line run from the +`/srv/ds-chat/uploads` in it too (e.g. a second `rsync` line run from the app server). **Test a restore** (against a scratch database, never directly onto -`chatapp`): +`ds_chat`): ```bash -sudo -u postgres createdb chatapp_restore_test -gunzip -c /var/backups/chatapp/chatapp-.sql.gz | sudo -u postgres psql chatapp_restore_test -sudo -u postgres dropdb chatapp_restore_test +sudo -u postgres createdb ds_chat_restore_test +gunzip -c /var/backups/ds-chat/ds-chat-.sql.gz | sudo -u postgres psql ds_chat_restore_test +sudo -u postgres dropdb ds_chat_restore_test ``` ## 8. Troubleshooting -- **`chatapp` service won't start**: `sudo journalctl -u chatapp -n 50`. - Common causes: `/etc/chatapp/env` missing/malformed (gunicorn workers +- **`ds-chat` service won't start**: `sudo journalctl -u ds-chat -n 50`. + Common causes: `/etc/ds-chat/env` missing/malformed (gunicorn workers crash-loop on `pydantic-settings` validation errors), or Postgres/Redis unreachable (check the data-server firewall rules in §2 actually match the app server's real private IP). @@ -313,7 +313,7 @@ sudo -u postgres dropdb chatapp_restore_test (isolates "app is down" from "NPM can't reach it") — then check §3g's `ufw` rule matches NPM's actual source IP. - **Migration fails mid-`upgrade.sh`**: the script stops before restarting - `chatapp`, so the previous (still-migrated-to-its-old-schema) code keeps + `ds-chat`, so the previous (still-migrated-to-its-old-schema) code keeps running. Fix the migration, re-run the script. - **Chat works but disconnects after ~a minute of inactivity, then reconnects**: expected under the current design (§4's NPM timeout note) — @@ -334,7 +334,7 @@ scope decisions" for the full detail on each): re-validated per delivery (DNS-rebinding gap). - Backup off-box shipping is a placeholder — decide a destination and fill in `deploy/backup-postgres.sh`. -- Uploaded chat images (`/srv/chatapp/uploads` on the app server) have no +- Uploaded chat images (`/srv/ds-chat/uploads` on the 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 Send) leak an orphaned file on disk — no cleanup job for this yet. Not a diff --git a/README.md b/README.md index 1f5721b..d2d762d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# KeepItTalking +# DS Chat A web-based team chat service (Mattermost-style, no threaded conversations), invite-only. See [ARCHITECTURE.md](ARCHITECTURE.md) for the full system design @@ -25,8 +25,8 @@ prioritized. ```bash # 1. Postgres (see backend/README.md for details) -docker run -d --name chatapp-postgres \ - -e POSTGRES_USER=chatapp -e POSTGRES_PASSWORD=chatapp -e POSTGRES_DB=chatapp \ +docker run -d --name ds-chat-postgres \ + -e POSTGRES_USER=ds_chat -e POSTGRES_PASSWORD=ds_chat -e POSTGRES_DB=ds_chat \ -p 5432:5432 postgres:16-alpine # 2. Backend diff --git a/backend/.env.example b/backend/.env.example index 81ebd38..1809c40 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,4 @@ -DATABASE_URL=postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp +DATABASE_URL=postgresql+asyncpg://ds_chat:ds_chat@localhost:5432/ds_chat SESSION_SECRET=change-me-to-a-long-random-string SESSION_HTTPS_ONLY=false REDIS_URL=redis://localhost:6379/0 diff --git a/backend/README.md b/backend/README.md index 6b6dc61..062e661 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,4 +1,4 @@ -# KeepItTalking backend (Phase 1 + 2 + 4 + 5 + 6 + 7 + 8, image uploads, file attachments, admin-configurable upload size limits, emoji & reactions, user profiles, site invites & email, password reset) +# DS Chat backend (Phase 1 + 2 + 4 + 5 + 6 + 7 + 8, image uploads, file attachments, admin-configurable upload size limits, emoji & reactions, user profiles, site invites & email, password reset) FastAPI + SQLAlchemy 2.0 (async) + PostgreSQL + Redis. Implements auth, room CRUD (open and private), room roles (owner/admin/member) and direct @@ -24,15 +24,15 @@ Accounts are created by an operator on the app server — see step 4 below. Any local Postgres 14+ works. The quickest option is a container: ```bash -docker run -d --name chatapp-postgres \ - -e POSTGRES_USER=chatapp -e POSTGRES_PASSWORD=chatapp -e POSTGRES_DB=chatapp \ +docker run -d --name ds-chat-postgres \ + -e POSTGRES_USER=ds_chat -e POSTGRES_PASSWORD=ds_chat -e POSTGRES_DB=ds_chat \ -p 5432:5432 postgres:16-alpine ``` Then create the test database (used by the test suite, kept separate from dev data): ```bash -docker exec chatapp-postgres psql -U chatapp -d chatapp -c "CREATE DATABASE chatapp_test;" +docker exec ds-chat-postgres psql -U ds_chat -d ds_chat -c "CREATE DATABASE ds_chat_test;" ``` (Docker here is purely a local-dev convenience for standing up Postgres quickly — @@ -44,7 +44,7 @@ Used for cross-instance WebSocket fan-out and presence (see the section below). Required — there's no in-memory fallback. ```bash -docker run -d --name chatapp-redis -p 6379:6379 redis:7-alpine +docker run -d --name ds-chat-redis -p 6379:6379 redis:7-alpine ``` ### 3. Python environment @@ -99,13 +99,13 @@ connected to the other, purely via Redis. ### 8. Run tests -Tests run against a real Postgres database (`chatapp_test` by default — native +Tests run against a real Postgres database (`ds_chat_test` by default — native `ENUM`/`UUID` types aren't faithfully reproduced by SQLite) and a real Redis (db 15 by default, kept separate from dev use of db 0), with each test wrapped in a transaction that's rolled back afterward: ```bash -DATABASE_URL=postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp_test .venv/bin/pytest +DATABASE_URL=postgresql+asyncpg://ds_chat:ds_chat@localhost:5432/ds_chat_test .venv/bin/pytest ``` ## Layout @@ -247,7 +247,7 @@ call sites rather than duplicated). **Outgoing webhooks / event subscriptions** (`POST /api/rooms/{id}/event-subscriptions`, room-admin managed; room-scoped or global via `room_id=null`): fires an HMAC-SHA256-signed POST -(`X-KeepItTalking-Signature: sha256=...`) on `message.created`/ +(`X-DS-Chat-Signature: sha256=...`) on `message.created`/ `message.updated`, delivered via a backgrounded `asyncio.create_task` (`app/services/webhook_delivery.py`) — safe to background here, unlike the Phase 4 push lesson, since there's no DB session involved, just the diff --git a/backend/alembic.ini b/backend/alembic.ini index 7152cdd..d180567 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -86,7 +86,7 @@ path_separator = os # database URL. This is consumed by the user-maintained env.py script only. # other means of configuring database URLs may be customized within the env.py # file. -sqlalchemy.url = postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp +sqlalchemy.url = postgresql+asyncpg://chatapp:chatapp@localhost:5432/ds_chat [post_write_hooks] diff --git a/backend/app/main.py b/backend/app/main.py index 31f81e0..31522a9 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -18,8 +18,8 @@ from app.ws.connection_manager import ConnectionManager from app.ws.presence import Presence # backend/app/main.py -> backend/ -> repo root -- matches both the local -# monorepo layout and the production layout (/srv/chatapp/backend, -# /srv/chatapp/frontend/dist), which is the same relative shape. +# monorepo layout and the production layout (/srv/ds-chat/backend, +# /srv/ds-chat/frontend/dist), which is the same relative shape. FRONTEND_DIST = pathlib.Path(__file__).resolve().parent.parent.parent / "frontend" / "dist" @@ -55,7 +55,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: def create_app() -> FastAPI: - app = FastAPI(title="KeepItTalking", lifespan=lifespan) + app = FastAPI(title="DS Chat", lifespan=lifespan) app.add_middleware( SessionMiddleware, diff --git a/backend/app/services/email_service.py b/backend/app/services/email_service.py index d5963c8..ab77f42 100644 --- a/backend/app/services/email_service.py +++ b/backend/app/services/email_service.py @@ -58,6 +58,6 @@ async def send_test_email(db: AsyncSession, to_address: str) -> None: await _deliver( cfg, to_address, - "KeepItTalking test email", - "This is a test email from KeepItTalking to confirm your SMTP settings are working.", + "DS Chat test email", + "This is a test email from DS Chat to confirm your SMTP settings are working.", ) diff --git a/backend/app/services/password_service.py b/backend/app/services/password_service.py index e976464..91608bd 100644 --- a/backend/app/services/password_service.py +++ b/backend/app/services/password_service.py @@ -44,7 +44,7 @@ async def request_password_reset(db: AsyncSession, email: str, base_url: str) -> await send_email( db, email, - "Reset your KeepItTalking password", + "Reset your DS Chat password", f"Someone requested a password reset for this account.\n\n" f"Reset it here:\n{reset_link}\n\n" f"This link expires in 15 minutes. If you didn't request this, " diff --git a/backend/app/services/room_service.py b/backend/app/services/room_service.py index 3719abb..5436b3f 100644 --- a/backend/app/services/room_service.py +++ b/backend/app/services/room_service.py @@ -140,7 +140,7 @@ async def add_member( db, target.email, f"You've been added to #{room.name}", - f"You've been added to the #{room.name} room on KeepItTalking.\n\n" + f"You've been added to the #{room.name} room on DS Chat.\n\n" f"Open the app: {base_url.rstrip('/')}", ) diff --git a/backend/app/services/site_invite_service.py b/backend/app/services/site_invite_service.py index 03964ca..9b0262f 100644 --- a/backend/app/services/site_invite_service.py +++ b/backend/app/services/site_invite_service.py @@ -43,8 +43,8 @@ async def create_site_invite( await send_email( db, email, - "You're invited to join KeepItTalking", - f"You've been invited to join KeepItTalking by {actor.username}.\n\n" + "You're invited to join DS Chat", + f"You've been invited to join DS Chat by {actor.username}.\n\n" f"Set up your account here:\n{signup_link}\n\n" f"This link expires in 7 days.", ) diff --git a/backend/app/services/webhook_delivery.py b/backend/app/services/webhook_delivery.py index 925e4d3..99acdc4 100644 --- a/backend/app/services/webhook_delivery.py +++ b/backend/app/services/webhook_delivery.py @@ -32,7 +32,7 @@ async def deliver_event(subscription: EventSubscription, event_type: str, payloa content=body, headers={ "Content-Type": "application/json", - "X-KeepItTalking-Signature": f"sha256={signature}", + "X-DS-Chat-Signature": f"sha256={signature}", }, ) except httpx.HTTPError: diff --git a/backend/app/storage.py b/backend/app/storage.py index c0d176e..e85d217 100644 --- a/backend/app/storage.py +++ b/backend/app/storage.py @@ -6,7 +6,7 @@ from PIL import Image, UnidentifiedImageError # backend/app/storage.py -> backend/ -> repo root -- same # resolve-relative-to-file convention FRONTEND_DIST uses in app/main.py, so -# this lands in the right place in both local dev and the /srv/chatapp +# this lands in the right place in both local dev and the /srv/ds-chat # production layout with zero new config. UPLOADS_DIR = pathlib.Path(__file__).resolve().parent.parent.parent / "uploads" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index d3b2be0..47c1019 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "chatapp" +name = "ds-chat" version = "0.1.0" -description = "KeepItTalking chat service backend" +description = "DS Chat backend service" requires-python = ">=3.11" dependencies = [ "fastapi>=0.115", @@ -25,7 +25,7 @@ dependencies = [ ] [project.scripts] -chatapp-create-user = "app.cli:main" +ds-chat-create-user = "app.cli:main" [project.optional-dependencies] dev = [ diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index a8a146b..54ab6f5 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -3,7 +3,7 @@ import os from pathlib import Path os.environ.setdefault( - "DATABASE_URL", "postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp_test" + "DATABASE_URL", "postgresql+asyncpg://chatapp:chatapp@localhost:5432/ds_chat_test" ) os.environ.setdefault("SESSION_SECRET", "test-secret") os.environ.setdefault("SESSION_HTTPS_ONLY", "false") diff --git a/backend/tests/test_push.py b/backend/tests/test_push.py index 8b7c79f..1c48ec7 100644 --- a/backend/tests/test_push.py +++ b/backend/tests/test_push.py @@ -31,7 +31,7 @@ async def test_subscribe_creates_row(client, db_session): resp = await client.post("/api/push/subscribe", json=payload) assert resp.status_code == 204 - # The chatapp_test database is shared across the whole suite and the + # The ds_chat_test database is shared across the whole suite and the # ws_client-based tests below intentionally don't roll back (see # conftest.ws_client), so a unique endpoint keeps this test independent # of leftover rows from those instead of asserting on the total count. diff --git a/backend/tests/test_webhooks.py b/backend/tests/test_webhooks.py index 466eb8c..a487c96 100644 --- a/backend/tests/test_webhooks.py +++ b/backend/tests/test_webhooks.py @@ -158,7 +158,7 @@ async def test_outgoing_webhook_delivers_signed_payload(client, db_session, monk assert len(posts) == 1 body = posts[0]["content"] expected_signature = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() - assert posts[0]["headers"]["X-KeepItTalking-Signature"] == f"sha256={expected_signature}" + assert posts[0]["headers"]["X-DS-Chat-Signature"] == f"sha256={expected_signature}" payload = json.loads(body) assert payload["event"] == "message.created" assert payload["data"]["content"] == "ping" diff --git a/deploy/backup-postgres.sh b/deploy/backup-postgres.sh index a4d6e09..4a794d0 100755 --- a/deploy/backup-postgres.sh +++ b/deploy/backup-postgres.sh @@ -1,27 +1,27 @@ #!/usr/bin/env bash -# Nightly Postgres backup for the KeepItTalking data server. +# Nightly Postgres backup for the DS Chat data server. # # Install (as root, on the data server): -# sudo cp deploy/backup-postgres.sh /usr/local/bin/chatapp-backup-postgres.sh -# sudo chmod 0700 /usr/local/bin/chatapp-backup-postgres.sh +# sudo cp deploy/backup-postgres.sh /usr/local/bin/ds-chat-backup-postgres.sh +# sudo chmod 0700 /usr/local/bin/ds-chat-backup-postgres.sh # sudo crontab -e # # add: -# 0 3 * * * /usr/local/bin/chatapp-backup-postgres.sh +# 0 3 * * * /usr/local/bin/ds-chat-backup-postgres.sh # # See ../DEPLOYMENT.md for the full data-server setup this fits into. # # Covers Postgres only. Uploaded chat images live on the app server's disk -# (/srv/chatapp/uploads, see app/storage.py), not here -- see DEPLOYMENT.md +# (/srv/ds-chat/uploads, see app/storage.py), not here -- see DEPLOYMENT.md # §7 for that gap. set -euo pipefail -DB_NAME="chatapp" -DB_USER="chatapp" -BACKUP_DIR="/var/backups/chatapp" +DB_NAME="ds_chat" +DB_USER="ds_chat" +BACKUP_DIR="/var/backups/ds-chat" RETENTION_DAYS=14 TIMESTAMP="$(date +%F-%H%M%S)" -DEST="${BACKUP_DIR}/chatapp-${TIMESTAMP}.sql.gz" +DEST="${BACKUP_DIR}/ds-chat-${TIMESTAMP}.sql.gz" mkdir -p "$BACKUP_DIR" @@ -35,7 +35,7 @@ echo "Backed up ${DB_NAME} to ${DEST}" # Local rotation -- keep RETENTION_DAYS days on this box regardless of # whether off-box shipping (below) is configured yet. -find "$BACKUP_DIR" -name 'chatapp-*.sql.gz' -mtime "+${RETENTION_DAYS}" -delete +find "$BACKUP_DIR" -name 'ds-chat-*.sql.gz' -mtime "+${RETENTION_DAYS}" -delete # --- Off-box shipping ------------------------------------------------- # Not configured yet -- destination wasn't decided as of this script being @@ -44,9 +44,9 @@ find "$BACKUP_DIR" -name 'chatapp-*.sql.gz' -mtime "+${RETENTION_DAYS}" -delete # # rsync (to a second host reachable by the data server, e.g. over the same # private network / a WireGuard tunnel used for anything else): -# rsync -a "$DEST" backup-user@backup-host:/path/to/chatapp-backups/ +# rsync -a "$DEST" backup-user@backup-host:/path/to/ds-chat-backups/ # # S3-compatible object storage (needs `aws configure` or rclone set up # separately first): -# aws s3 cp "$DEST" s3://your-bucket/chatapp-backups/ -# # or: rclone copy "$DEST" remote:chatapp-backups/ +# aws s3 cp "$DEST" s3://your-bucket/ds-chat-backups/ +# # or: rclone copy "$DEST" remote:ds-chat-backups/ diff --git a/deploy/chatapp.env.example b/deploy/ds-chat.env.example similarity index 76% rename from deploy/chatapp.env.example rename to deploy/ds-chat.env.example index 01952c8..ac90a1c 100644 --- a/deploy/chatapp.env.example +++ b/deploy/ds-chat.env.example @@ -1,15 +1,15 @@ -# /etc/chatapp/env (production) +# /etc/ds-chat/env (production) # # This file is loaded by systemd's EnvironmentFile= (see -# deploy/systemd/chatapp.service) directly into the app process's +# deploy/systemd/ds-chat.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 +# sudo mkdir -p /etc/ds-chat +# sudo cp deploy/ds-chat.env.example /etc/ds-chat/env +# sudo chown root:ds-chat /etc/ds-chat/env +# sudo chmod 0640 /etc/ds-chat/env # # then edit in the real values below # # See ../DEPLOYMENT.md for how each value is generated. @@ -17,7 +17,7 @@ # 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@:5432/chatapp +DATABASE_URL=postgresql+asyncpg://ds_chat:REPLACE_ME@:5432/ds_chat # Generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))" SESSION_SECRET=REPLACE_ME diff --git a/deploy/systemd/chatapp.service b/deploy/systemd/ds-chat.service similarity index 81% rename from deploy/systemd/chatapp.service rename to deploy/systemd/ds-chat.service index 6ff30b4..d627c93 100644 --- a/deploy/systemd/chatapp.service +++ b/deploy/systemd/ds-chat.service @@ -1,8 +1,8 @@ -# /etc/systemd/system/chatapp.service +# /etc/systemd/system/ds-chat.service # -# Install: sudo cp deploy/systemd/chatapp.service /etc/systemd/system/ +# Install: sudo cp deploy/systemd/ds-chat.service /etc/systemd/system/ # sudo systemctl daemon-reload -# sudo systemctl enable --now chatapp +# sudo systemctl enable --now ds-chat # # See ../../DEPLOYMENT.md for the full app-server setup this fits into. # TLS termination and public-facing reverse proxying are handled by an @@ -10,17 +10,17 @@ # unit just needs to be reachable on the TCP port below. [Unit] -Description=KeepItTalking chat service app server +Description=DS Chat app server After=network.target [Service] # No Type= override -- defaults to "simple", which is correct here since # gunicorn runs in the foreground (no --daemon flag below) and doesn't send # systemd's sd_notify readiness protocol. -User=chatapp -Group=chatapp -WorkingDirectory=/srv/chatapp/backend -EnvironmentFile=/etc/chatapp/env +User=ds-chat +Group=ds-chat +WorkingDirectory=/srv/ds-chat/backend +EnvironmentFile=/etc/ds-chat/env Environment=PYTHONUNBUFFERED=1 # 0.0.0.0 because Nginx Proxy Manager runs on a separate host -- the actual @@ -28,7 +28,7 @@ Environment=PYTHONUNBUFFERED=1 # port to NPM's IP specifically, not the bind address. If NPM reaches this # box over a private network interface, bind to that private IP instead # for defense in depth (belt-and-suspenders on top of the firewall rule). -ExecStart=/srv/chatapp/backend/.venv/bin/gunicorn app.main:app \ +ExecStart=/srv/ds-chat/backend/.venv/bin/gunicorn app.main:app \ -k uvicorn.workers.UvicornWorker \ --workers 4 \ --bind 0.0.0.0:8000 \ diff --git a/deploy/upgrade.sh b/deploy/upgrade.sh index 682e6f7..ff6b9b5 100755 --- a/deploy/upgrade.sh +++ b/deploy/upgrade.sh @@ -1,20 +1,20 @@ #!/usr/bin/env bash -# Day-2 deploy/upgrade script for the KeepItTalking app server. Run by hand -# over SSH as the `chatapp` user (or via sudo -u chatapp): +# Day-2 deploy/upgrade script for the DS Chat app server. Run by hand +# over SSH as the `ds-chat` user (or via sudo -u ds-chat): # -# sudo -u chatapp /srv/chatapp/deploy/upgrade.sh +# sudo -u ds-chat /srv/ds-chat/deploy/upgrade.sh # # Fails loudly and stops before touching the running service if any step # fails -- the previous deploy keeps running rather than being torn down # mid-upgrade. See ../DEPLOYMENT.md for what each step assumes is already -# in place (venv, /etc/chatapp/env, the systemd unit, Node.js). +# in place (venv, /etc/ds-chat/env, the systemd unit, Node.js). set -euo pipefail -REPO_DIR="/srv/chatapp" +REPO_DIR="/srv/ds-chat" BACKEND_DIR="${REPO_DIR}/backend" FRONTEND_DIR="${REPO_DIR}/frontend" -ENV_FILE="/etc/chatapp/env" +ENV_FILE="/etc/ds-chat/env" echo "==> Pulling latest code" cd "$REPO_DIR" @@ -28,7 +28,7 @@ echo "==> Running database migrations" # alembic reads DATABASE_URL from the environment (backend/alembic/env.py), # so the env file has to actually be sourced into this shell first -- it's # not read automatically just because systemd's EnvironmentFile= points at -# it (that only applies to the chatapp.service process, not this script). +# it (that only applies to the ds-chat.service process, not this script). set -a # shellcheck disable=SC1090 source "$ENV_FILE" @@ -40,20 +40,20 @@ cd "$FRONTEND_DIR" npm ci --silent npm run build --silent -echo "==> Restarting chatapp" +echo "==> Restarting ds-chat" # Active WebSocket connections drop here and reconnect automatically within # a few seconds (frontend/src/ws/useChatSocket.ts's exponential-backoff # reconnect) -- expected, not a bug, and not worth a blue-green setup for. -sudo systemctl restart chatapp +sudo systemctl restart ds-chat echo "==> Verifying" sleep 2 if curl -sf http://127.0.0.1:8000/api/health >/dev/null; then echo "Health check OK" else - echo "Health check FAILED -- check: sudo journalctl -u chatapp -n 50" >&2 + echo "Health check FAILED -- check: sudo journalctl -u ds-chat -n 50" >&2 exit 1 fi -sudo systemctl status chatapp --no-pager -l | head -10 +sudo systemctl status ds-chat --no-pager -l | head -10 -echo "==> Done. journalctl -u chatapp -f to watch logs." +echo "==> Done. journalctl -u ds-chat -f to watch logs." diff --git a/frontend/README.md b/frontend/README.md index 705d4d5..390e1ee 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,4 @@ -# KeepItTalking frontend (Phase 1) +# DS Chat frontend (Phase 1) React + Vite PWA. Login, room list, and chat views wired to the backend's REST API and `/ws/chat` WebSocket endpoint. See [`../README.md`](../README.md) diff --git a/frontend/index.html b/frontend/index.html index 716e607..e5e5e38 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - KeepItTalking + DS Chat
diff --git a/frontend/src/components/TopBar.tsx b/frontend/src/components/TopBar.tsx index 68e7b66..f88650d 100644 --- a/frontend/src/components/TopBar.tsx +++ b/frontend/src/components/TopBar.tsx @@ -46,7 +46,7 @@ export function TopBar() {
- KeepItTalking + DS Chat
diff --git a/frontend/src/pages/ForgotPasswordPage.tsx b/frontend/src/pages/ForgotPasswordPage.tsx index b069de0..3372a0d 100644 --- a/frontend/src/pages/ForgotPasswordPage.tsx +++ b/frontend/src/pages/ForgotPasswordPage.tsx @@ -32,7 +32,7 @@ export function ForgotPasswordPage() {
- KeepItTalking + DS Chat
{sent ? ( diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 8f69d87..1a6719e 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -34,7 +34,7 @@ export function LoginPage() {
- KeepItTalking + DS Chat

This is an invite-only site. Ask an admin for an account.

diff --git a/frontend/src/pages/ResetPasswordPage.tsx b/frontend/src/pages/ResetPasswordPage.tsx index 492e13b..9c56043 100644 --- a/frontend/src/pages/ResetPasswordPage.tsx +++ b/frontend/src/pages/ResetPasswordPage.tsx @@ -59,7 +59,7 @@ export function ResetPasswordPage() {
- KeepItTalking + DS Chat
{checking &&

Checking your reset link…

} diff --git a/frontend/src/pages/SignupPage.tsx b/frontend/src/pages/SignupPage.tsx index 4b2dfc9..c517682 100644 --- a/frontend/src/pages/SignupPage.tsx +++ b/frontend/src/pages/SignupPage.tsx @@ -57,7 +57,7 @@ export function SignupPage() {
- KeepItTalking + DS Chat
{checking &&

Checking your invite…

} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index a225d5f..22c2037 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -23,8 +23,8 @@ export default defineConfig({ }, registerType: 'autoUpdate', manifest: { - name: 'KeepItTalking', - short_name: 'Talking', + name: 'DS Chat', + short_name: 'DS Chat', start_url: '/', display: 'standalone', background_color: '#07080f', // --ds-void