fix: give Workspace Chat its own public hostname
Some checks failed
Deploy homelab (CT 116) / Deploy to CT 116 (push) Failing after 7s
CI / Lint, type-check, test (push) Failing after 20m19s

This commit is contained in:
varutasu 2026-10-30 19:38:03 +00:00
parent ff01a5f097
commit 32f76abc9f
6 changed files with 35 additions and 10 deletions

View file

@ -178,6 +178,6 @@ CHAT_TICKET_SECRET="CHANGE_ME_USE_A_RANDOM_32_BYTE_SECRET"
# Internal collab chat listener port (distinct from Hocuspocus/Yjs on 1234)
CHAT_PORT=1235
# Public WSS endpoint, baked into the web bundle at build time. Rebuild web after changing.
NEXT_PUBLIC_CHAT_SERVER_URL="wss://chat.stillwell.cloud"
NEXT_PUBLIC_CHAT_SERVER_URL="wss://echodo-chat.stillwell.cloud"
COLLAB_PORT=1234

View file

@ -12,7 +12,7 @@
# Domains (Dokploy UI or CT 100 Traefik):
# web → echodo.stillwell.cloud (port 3000)
# collab → collab.echodo.stillwell.cloud (port 1234)
# chat → chat.stillwell.cloud (collab service, CHAT_PORT default 1235)
# chat → echodo-chat.stillwell.cloud (collab service, CHAT_PORT default 1235)
# opencode (optional) → opencode.stillwell.cloud (port 4096)
#
# Build (required on ≤8 GB hosts — parallel export OOMs with exit 255):

View file

@ -1,10 +1,16 @@
# Workspace Chat — Current Progress
Updated: 2026-10-29 (homelab go-live + acceptance).
Updated: 2026-10-30 (dedicated Chat hostname migration pending rebuild).
## Status
## Hostname correction — pending deployment
Chat runtime is live on CT 116; operator reports **14/14 acceptance checks passed**. Operator-confirmed CT 102 `echodo` ledger: **24 entries through `0023`**, with required columns/table verified and all `chat_*` tables owned by `echodo`. An earlier copy of this document said `0022–0024`; no `0024` migration exists in this checkout, so that statement is not treated as verified.
`chat.stillwell.cloud` belongs to OpenMausBot on CT 117, not Workspace Chat. The operator reports routing to that app has been restored and `echodo-chat.stillwell.cloud` is already an alias on the Workspace Chat Traefik rule.
The Workspace Chat endpoint is now **`wss://echodo-chat.stillwell.cloud`** (socket path `/chat`). Set `NEXT_PUBLIC_CHAT_SERVER_URL` to that value in CT 116's `/opt/submatter/.env`, rebuild the web image, and recreate web as documented in `docs/deployment-production.md`. Repo examples now use only the dedicated hostname. Deployment and browser verification of the new endpoint are not yet confirmed; a push alone will not update the host's `.env`.
## Previous go-live evidence (before hostname correction)
The following records the previous deployment, not verification of the new hostname. Operator reported **14/14 acceptance checks passed**. Operator-confirmed CT 102 `echodo` ledger: **24 entries through `0023`**, with required columns/table verified and all `chat_*` tables owned by `echodo`. An earlier copy of this document said `0022–0024`; no `0024` migration exists in this checkout, so that statement is not treated as verified.
The deployment evidence below was supplied by the operator and is present in this workspace copy. It was not independently rerun in this reconciliation session; read-only access through PVE stopped at unknown SSH host-key verification.

View file

@ -65,7 +65,7 @@ docker compose -f docker/docker-compose.prod.yml \
| `AUTH_AUTHENTIK_*` | | Authentik OIDC app `echodo` |
| `COLLAB_SERVER_URL` | `wss://collab.echodo.stillwell.cloud` | Server-side |
| `NEXT_PUBLIC_COLLAB_SERVER_URL` | `wss://collab.echodo.stillwell.cloud` | **Build arg** — requires image rebuild |
| `NEXT_PUBLIC_CHAT_SERVER_URL` | `wss://chat.stillwell.cloud` | **Build arg** — requires web image rebuild; provision DNS/TLS first |
| `NEXT_PUBLIC_CHAT_SERVER_URL` | `wss://echodo-chat.stillwell.cloud` | **Build arg** — requires web image rebuild; provision DNS/TLS first |
| `CHAT_TICKET_SECRET` | Generate with `openssl rand -base64 32` | Same runtime secret for web and collab; at least 32 characters; never a build arg |
| `CHAT_PORT` | `1235` | Second listener inside collab; cannot equal Hocuspocus port `1234` |
| `OMNIROUTE_BASE_URL` | `https://omniroute.stillwell.cloud/v1` | Web + worker |
@ -82,7 +82,16 @@ Changing any `NEXT_PUBLIC_*` value requires `bash scripts/deploy-prod.sh build`,
Chat runs inside the existing `collab` service on a **separate listener** from Hocuspocus. The worker publishes metadata-only Redis hints; Postgres history and mutation catch-up remain authoritative. Team Chat does not invoke AI agents, and object contexts are not enabled by this deployment.
1. Set the Chat variables above in `/opt/submatter/.env`. Keep the signing secret server-side; Compose requires a nonempty value, and the application requires at least 32 characters. A missing/short secret disables the listener and makes the dual-listener health check fail.
2. Provision `chat.stillwell.cloud` DNS and TLS on CT 100 (canonical browser endpoint). The proxy example also retains `chat.echodo.stillwell.cloud`; a single-level wildcard certificate does not cover that nested hostname. Apply the `echodo-chat` router/service from [`traefik-ct100.example.yml`](../proxmox/ct116/traefik-ct100.example.yml). Traefik supports WebSocket upgrades without a separate service process. If `CHAT_PORT` changes, update the proxy backend port too. Restrict host backend ports to the trusted proxy network.
2. Provision `echodo-chat.stillwell.cloud` DNS and TLS on CT 100 (canonical browser endpoint). `chat.stillwell.cloud` belongs to another app: never include it in the Workspace Chat router. Apply the `echodo-chat` router/service from [`traefik-ct100.example.yml`](../proxmox/ct116/traefik-ct100.example.yml). Traefik supports WebSocket upgrades without a separate service process. If `CHAT_PORT` changes, update the proxy backend port too. Restrict host backend ports to the trusted proxy network.
For the hostname migration, set `NEXT_PUBLIC_CHAT_SERVER_URL=wss://echodo-chat.stillwell.cloud` in CT 116's `/opt/submatter/.env`, then run from `/opt/submatter`:
```bash
docker compose --project-directory /opt/submatter -f docker/docker-compose.prod.yml -f docker/docker-compose.prod.standalone.yml build web
docker compose --project-directory /opt/submatter -f docker/docker-compose.prod.yml -f docker/docker-compose.prod.standalone.yml up -d --no-deps web
```
A git push or container restart alone does not change the baked browser URL. Test Workspace Chat inside `https://echodo.stillwell.cloud`; its WebSocket should connect to `wss://echodo-chat.stillwell.cloud/chat`. The WSS endpoint is not a separate chat UI.
3. Before rolling out, verify the database target is CT 102 **`echodo`** and inspect `drizzle.__drizzle_migrations`. The operator reports 24 ledger entries through `0023`, verified Chat schema, and ownership of all `chat_*` tables transferred to `echodo`. Do not reapply these migrations; inspect the ledger for any future pending entries. Compare the ledger with `packages/database/migrations/meta/_journal.json`; apply only pending journaled migrations using `pnpm --filter @tasks/database db:migrate` in a checkout with the real `DATABASE_URL` exported securely. Do not print the URL, use `db:push`, or replay historical migrations manually. Take a backup first.
4. Verify `0022` added `chat_messages.send_intent_fingerprint` and positive `revision`; verify `0023` added `chat_message_mutations`, its tenant/channel/message foreign keys and indexes, and `chat_channels.next_mutation_sequence`. Do not infer applied schema from TypeScript files. The historical `0017` correction is already local; do not replay it on an initialized database.
5. Validate Compose on the deployment host without printing resolved secrets:

View file

@ -26,7 +26,7 @@ http:
certResolver: letsencrypt
echodo-chat:
rule: Host(`chat.stillwell.cloud`) || Host(`chat.echodo.stillwell.cloud`)
rule: Host(`echodo-chat.stillwell.cloud`)
entryPoints: [websecure]
service: echodo-chat
tls:

View file

@ -27,6 +27,16 @@ test("web receives the Chat URL at build time, without baking the signing secret
assert.ok(turbo.tasks.build.env.includes("NEXT_PUBLIC_*"));
});
test("Workspace Chat uses its dedicated hostname, not the other app's hostname", () => {
assert.match(
read(".env.example"),
/^NEXT_PUBLIC_CHAT_SERVER_URL="wss:\/\/echodo-chat\.stillwell\.cloud"$/m,
);
const proxy = read("proxmox/ct116/traefik-ct100.example.yml");
assert.ok(proxy.includes("Host(`echodo-chat.stillwell.cloud`)"));
assert.ok(!proxy.includes("Host(`chat.stillwell.cloud`)"));
});
test("web and collab receive the same required runtime signing secret", () => {
for (const service of [web, collab]) {
assert.match(
@ -57,8 +67,8 @@ test("standalone publication matches the configurable Chat listener", () => {
test("Traefik example routes public Chat TLS to the second collab listener", () => {
const proxy = read("proxmox/ct116/traefik-ct100.example.yml");
assert.match(proxy, /Host\(`chat\.echodo\.stillwell\.cloud`\)/);
assert.match(proxy, /Host\(`chat\.stillwell\.cloud`\)/);
assert.match(proxy, /Host\(`echodo-chat\.stillwell\.cloud`\)/);
assert.doesNotMatch(proxy, /Host\(`chat\.stillwell\.cloud`\)/);
assert.match(proxy, /service: echodo-chat/);
assert.match(proxy, /url: "http:\/\/192\.168\.68\.116:1235"/);
});