serverless-ircd 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +34 -2
- package/.github/workflows/deploy-aws.yml +1 -2
- package/.github/workflows/deploy-cf-tcp.yml +1 -5
- package/.github/workflows/deploy-cf.yml +1 -2
- package/CHANGELOG.md +435 -0
- package/README.md +206 -27
- package/apps/aws-stack/README.md +37 -3
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +106 -13
- package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
- package/apps/aws-stack/tests/stack.test.ts +49 -3
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
- package/apps/aws-stack/tests/vitest.setup.ts +18 -0
- package/apps/aws-stack/vitest.config.ts +3 -0
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/config-loader.ts +6 -7
- package/apps/cf-tcp-container/src/container-server.ts +3 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
- package/apps/cf-tcp-container/wrangler.toml +1 -1
- package/apps/cf-worker/package.json +2 -1
- package/apps/cf-worker/vitest.config.ts +1 -1
- package/apps/cf-worker/wrangler.toml +5 -3
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +6 -6
- package/apps/local-cli/src/main.ts +20 -0
- package/apps/local-cli/src/server.ts +138 -28
- package/apps/local-cli/tests/e2e.test.ts +113 -29
- package/apps/local-cli/tests/tcp.test.ts +71 -19
- package/apps/local-cli/tests/ws-subprotocol.test.ts +262 -0
- package/biome.json +1 -0
- package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
- package/docs/ADR-002-location-of-authority.md +82 -0
- package/docs/ADR-003-durable-object-sharding.md +110 -0
- package/docs/ADR-004-dynamodb-schema.md +96 -0
- package/docs/ADR-005-wss-only-transport-v1.md +83 -0
- package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
- package/docs/ADR-Index.md +15 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1275 -0
- package/docs/AWS-TCP-Deployment.md +410 -0
- package/docs/Cloudflare-Deployment-Guide.md +719 -0
- package/docs/Cloudflare-TCP-Deployment.md +510 -0
- package/docs/Home.md +15 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanExtensions.md +812 -0
- package/docs/PlanIRCv3Websocket.md +503 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +69 -0
- package/packages/aws-adapter/src/handlers/connect.ts +36 -5
- package/packages/aws-adapter/src/handlers/default.ts +66 -5
- package/packages/aws-adapter/src/handlers/index.ts +41 -2
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +18 -0
- package/packages/aws-adapter/src/index.ts +2 -0
- package/packages/aws-adapter/src/serialize.ts +40 -2
- package/packages/aws-adapter/src/stats.ts +80 -0
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
- package/packages/aws-adapter/tests/account-store.test.ts +4 -7
- package/packages/aws-adapter/tests/aws-harness.ts +5 -2
- package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
- package/packages/aws-adapter/tests/aws-runtime.test.ts +140 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
- package/packages/aws-adapter/tests/connect.test.ts +100 -4
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
- package/packages/aws-adapter/tests/global-setup.ts +55 -30
- package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
- package/packages/aws-adapter/tests/handlers.test.ts +238 -4
- package/packages/aws-adapter/tests/message-store.test.ts +6 -5
- package/packages/aws-adapter/tests/nlb-stream.test.ts +62 -8
- package/packages/aws-adapter/tests/stats.test.ts +317 -0
- package/packages/cf-adapter/package.json +6 -1
- package/packages/cf-adapter/src/cf-runtime.ts +66 -1
- package/packages/cf-adapter/src/channel-do.ts +2 -2
- package/packages/cf-adapter/src/connection-do.ts +185 -54
- package/packages/cf-adapter/src/env.ts +25 -6
- package/packages/cf-adapter/src/index.ts +2 -0
- package/packages/cf-adapter/src/registry-do.ts +22 -3
- package/packages/cf-adapter/src/serialize.ts +25 -4
- package/packages/cf-adapter/src/sharding.ts +1 -2
- package/packages/cf-adapter/src/stats.ts +65 -0
- package/packages/cf-adapter/tests/cf-harness.ts +1 -1
- package/packages/cf-adapter/tests/cf-integration.test.ts +4 -4
- package/packages/cf-adapter/tests/cf-runtime.test.ts +307 -2
- package/packages/cf-adapter/tests/channel-do.test.ts +119 -2
- package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
- package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +2 -2
- package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +2 -2
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
- package/packages/cf-adapter/tests/connection-do-ws-spec-contract.test.ts +289 -0
- package/packages/cf-adapter/tests/connection-do-ws-subprotocol.test.ts +184 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +27 -2
- package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +108 -4
- package/packages/cf-adapter/tests/serialize.test.ts +308 -0
- package/packages/cf-adapter/tests/sharding.test.ts +1 -1
- package/packages/cf-adapter/tests/stats.test.ts +120 -0
- package/packages/cf-adapter/tests/worker/main.ts +7 -7
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +2 -2
- package/packages/cf-adapter/tests/worker/stubs/registry-stub.ts +8 -2
- package/packages/cf-adapter/vitest.config.ts +1 -1
- package/packages/cf-adapter/wrangler.test.toml +7 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +39 -0
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +259 -0
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/admission.ts +16 -15
- package/packages/irc-core/src/caps/capabilities.ts +38 -4
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/caps/sts.ts +84 -0
- package/packages/irc-core/src/commands/account-notify.ts +53 -0
- package/packages/irc-core/src/commands/away.ts +9 -3
- package/packages/irc-core/src/commands/cap.ts +23 -5
- package/packages/irc-core/src/commands/chathistory.ts +40 -11
- package/packages/irc-core/src/commands/index.ts +42 -0
- package/packages/irc-core/src/commands/invite.ts +2 -4
- package/packages/irc-core/src/commands/isupport.ts +59 -2
- package/packages/irc-core/src/commands/kick.ts +2 -4
- package/packages/irc-core/src/commands/kill.ts +127 -0
- package/packages/irc-core/src/commands/list.ts +1 -1
- package/packages/irc-core/src/commands/lusers.ts +204 -0
- package/packages/irc-core/src/commands/mode.ts +12 -9
- package/packages/irc-core/src/commands/monitor.ts +327 -0
- package/packages/irc-core/src/commands/multiline.ts +256 -0
- package/packages/irc-core/src/commands/names.ts +3 -5
- package/packages/irc-core/src/commands/part.ts +2 -4
- package/packages/irc-core/src/commands/pre-away.ts +112 -0
- package/packages/irc-core/src/commands/privmsg.ts +19 -12
- package/packages/irc-core/src/commands/read-marker.ts +108 -0
- package/packages/irc-core/src/commands/registration.ts +8 -0
- package/packages/irc-core/src/commands/rehash.ts +119 -0
- package/packages/irc-core/src/commands/sasl.ts +24 -1
- package/packages/irc-core/src/commands/setname.ts +109 -0
- package/packages/irc-core/src/commands/stats.ts +152 -0
- package/packages/irc-core/src/commands/tagmsg.ts +71 -1
- package/packages/irc-core/src/commands/topic.ts +2 -4
- package/packages/irc-core/src/commands/trace.ts +137 -0
- package/packages/irc-core/src/commands/wallops.ts +118 -0
- package/packages/irc-core/src/commands/whois.ts +5 -0
- package/packages/irc-core/src/config.ts +72 -10
- package/packages/irc-core/src/effects.ts +41 -1
- package/packages/irc-core/src/index.ts +2 -0
- package/packages/irc-core/src/ports.ts +568 -0
- package/packages/irc-core/src/protocol/index.ts +14 -0
- package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
- package/packages/irc-core/src/protocol/numerics.ts +57 -11
- package/packages/irc-core/src/protocol/outbound.ts +36 -4
- package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
- package/packages/irc-core/src/state/connection.ts +32 -1
- package/packages/irc-core/src/types.ts +120 -1
- package/packages/irc-core/src/ws-framing.ts +132 -0
- package/packages/irc-core/src/ws-subprotocol.ts +66 -0
- package/packages/irc-core/stryker.commands.conf.json +1 -2
- package/packages/irc-core/tests/admission.test.ts +18 -0
- package/packages/irc-core/tests/away-store.test.ts +73 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
- package/packages/irc-core/tests/caps/sts.test.ts +123 -0
- package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
- package/packages/irc-core/tests/commands/away.test.ts +85 -2
- package/packages/irc-core/tests/commands/cap.test.ts +165 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
- package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
- package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
- package/packages/irc-core/tests/commands/kill.test.ts +243 -0
- package/packages/irc-core/tests/commands/lusers.test.ts +368 -0
- package/packages/irc-core/tests/commands/mode.test.ts +129 -0
- package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
- package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
- package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
- package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
- package/packages/irc-core/tests/commands/registration.test.ts +64 -0
- package/packages/irc-core/tests/commands/rehash.test.ts +171 -0
- package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
- package/packages/irc-core/tests/commands/setname.test.ts +225 -0
- package/packages/irc-core/tests/commands/stats.test.ts +294 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
- package/packages/irc-core/tests/commands/trace.test.ts +282 -0
- package/packages/irc-core/tests/commands/wallops.test.ts +231 -0
- package/packages/irc-core/tests/commands/whois.test.ts +34 -0
- package/packages/irc-core/tests/config.test.ts +103 -13
- package/packages/irc-core/tests/dropped-s2s-and-obsolete-verbs.test.ts +90 -0
- package/packages/irc-core/tests/effects.test.ts +14 -0
- package/packages/irc-core/tests/labeled-response.test.ts +181 -0
- package/packages/irc-core/tests/message-tags.test.ts +114 -0
- package/packages/irc-core/tests/monitor-store.test.ts +200 -0
- package/packages/irc-core/tests/numerics.test.ts +90 -0
- package/packages/irc-core/tests/outbound.test.ts +51 -0
- package/packages/irc-core/tests/ports.test.ts +22 -0
- package/packages/irc-core/tests/raw-modules.d.ts +11 -0
- package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
- package/packages/irc-core/tests/standard-replies.test.ts +412 -0
- package/packages/irc-core/tests/stats-store.test.ts +222 -0
- package/packages/irc-core/tests/types.test.ts +27 -0
- package/packages/irc-core/tests/ws-framing.test.ts +213 -0
- package/packages/irc-core/tests/ws-subprotocol.test.ts +111 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +699 -19
- package/packages/irc-server/src/dispatch.ts +109 -16
- package/packages/irc-server/src/routing.ts +3 -0
- package/packages/irc-server/src/runtime.ts +31 -0
- package/packages/irc-server/src/transport.ts +10 -7
- package/packages/irc-server/tests/actor.test.ts +2523 -42
- package/packages/irc-server/tests/dispatch.test.ts +300 -2
- package/packages/irc-server/tests/raw-modules.d.ts +11 -0
- package/packages/irc-server/tests/routing.test.ts +1 -0
- package/packages/irc-server/tests/runtime.test.ts +7 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/index.ts +6 -0
- package/packages/irc-test-support/src/scenarios.ts +9 -1
- package/packages/irc-test-support/src/test-config.ts +54 -0
- package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +1 -1
- package/packages/irc-test-support/tests/test-config.test.ts +51 -0
- package/pnpm-workspace.yaml +1 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
- package/tools/load-test/package.json +33 -0
- package/tools/load-test/src/client.ts +351 -0
- package/tools/load-test/src/config.ts +313 -0
- package/tools/load-test/src/harness.ts +116 -0
- package/tools/load-test/src/main.ts +120 -0
- package/tools/load-test/src/metrics.ts +168 -0
- package/tools/load-test/src/report.ts +106 -0
- package/tools/load-test/tests/client.test.ts +212 -0
- package/tools/load-test/tests/config.test.ts +152 -0
- package/tools/load-test/tests/framing.test.ts +37 -0
- package/tools/load-test/tests/harness.test.ts +165 -0
- package/tools/load-test/tests/metrics.test.ts +174 -0
- package/tools/load-test/tests/report.test.ts +161 -0
- package/tools/load-test/tests/smoke.test.ts +67 -0
- package/tools/load-test/tsconfig.build.json +12 -0
- package/tools/load-test/tsconfig.test.json +10 -0
- package/tools/load-test/vitest.config.ts +29 -0
- package/tools/package.json +6 -1
- package/tools/seed-cf-accounts.ts +4 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/src/forwarder.ts +57 -9
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +34 -1
- package/tools/tcp-ws-forwarder/tests/framing.test.ts +65 -1
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# Cloudflare TCP+TLS Deployment Guide (`irc+tls` :6697)
|
|
2
|
+
|
|
3
|
+
End-to-end guide for deploying the **raw TCP+TLS** transport of
|
|
4
|
+
ServerlessIRCd to Cloudflare (Phase 7 of `PLAN.md`, TICKET-055). This is
|
|
5
|
+
the RFC 1459/2812-compliant path that older clients, bots, and bridges
|
|
6
|
+
use. The default **wss** transport is documented separately in
|
|
7
|
+
[`Cloudflare-Deployment-Guide.md`](./Cloudflare-Deployment-Guide.md);
|
|
8
|
+
the two are independent and a deployment can enable either or both.
|
|
9
|
+
|
|
10
|
+
> **Read first:** [`ADR-009`](./ADR-009-dual-transport-wss-and-irc-tls.md)
|
|
11
|
+
> for *why* a second transport exists and the per-platform mapping.
|
|
12
|
+
> This doc covers the *how*.
|
|
13
|
+
|
|
14
|
+
**Acceptance criterion (TICKET-058):** a new contributor can deploy
|
|
15
|
+
their own TCP+TLS staging instance following only this doc.
|
|
16
|
+
|
|
17
|
+
Cross-reference: `apps/cf-tcp-container/` (origin),
|
|
18
|
+
`apps/cf-tcp-container/wrangler.toml` (container config),
|
|
19
|
+
`apps/cf-tcp-container/terraform/spectrum.tf` (Spectrum TLS config),
|
|
20
|
+
`.github/workflows/deploy-cf-tcp.yml` (CI), TICKET-054 (mTLS), ADR-006
|
|
21
|
+
(SASL mechanism scope).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. What gets deployed
|
|
26
|
+
|
|
27
|
+
The wss path is pure-serverless (Worker + Durable Objects). The TCP path
|
|
28
|
+
**cannot** be — Cloudflare Workers cannot accept raw TCP. So the TCP+TLS
|
|
29
|
+
path adds two stateful pieces in front of the shared IRC core:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
IRC client (irc+tls :6697)
|
|
33
|
+
│
|
|
34
|
+
│ TLS handshake (client ⟷ Cloudflare)
|
|
35
|
+
▼
|
|
36
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
37
|
+
│ Cloudflare Spectrum (Enterprise) │
|
|
38
|
+
│ • terminates TLS on :6697 │
|
|
39
|
+
│ • forwards plaintext TCP to the origin │
|
|
40
|
+
│ • PROXY protocol v1 carries the real client IP │
|
|
41
|
+
└──────────────────────────┬───────────────────────────────────┘
|
|
42
|
+
│ plaintext TCP (:6667)
|
|
43
|
+
▼
|
|
44
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
45
|
+
│ Cloudflare Container (apps/cf-tcp-container) │
|
|
46
|
+
│ • node:24 process, EXPOSE 6667 │
|
|
47
|
+
│ • ConnectionActor + InMemoryRuntime + TcpByteStream- │
|
|
48
|
+
│ Transport (the shared transport seam, TICKET-053) │
|
|
49
|
+
│ • persistence snapshot at /data/state.json │
|
|
50
|
+
│ (channel topology survives restart; live connections │
|
|
51
|
+
│ do not — clients reconnect) │
|
|
52
|
+
└──────────────────────────────────────────────────────────────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The **parser → reducer → dispatch pipeline is shared verbatim** with the
|
|
56
|
+
wss path. Only the line-framing transport differs: a `TcpByteStreamTransport`
|
|
57
|
+
reassembles `\r\n`-terminated IRC lines across arbitrary TCP chunks
|
|
58
|
+
(partial-line buffering), whereas the wss Worker uses one-message-per-frame.
|
|
59
|
+
|
|
60
|
+
Two artifacts are deployed and configured:
|
|
61
|
+
|
|
62
|
+
| Artifact | Tool | What it does |
|
|
63
|
+
|---------------------------------------|----------------|------------------------------------------------|
|
|
64
|
+
| `sirc-tcp-origin` Cloudflare Container| `wrangler` | Builds + runs the IRC core container image. |
|
|
65
|
+
| `cloudflare_spectrum_application` | `terraform` | TLS termination on :6697 + origin forwarding. |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 2. Prerequisites
|
|
70
|
+
|
|
71
|
+
| Requirement | Detail |
|
|
72
|
+
|--------------------------|---------------------------------------------------------------------|
|
|
73
|
+
| Node.js | ≥ 24 (`engines.node` in root `package.json`). |
|
|
74
|
+
| pnpm | 11.x (`packageManager: pnpm@11.17.0`). |
|
|
75
|
+
| `wrangler` CLI | v4.112+ (comes from `apps/cf-tcp-container/devDependencies`). |
|
|
76
|
+
| `terraform` CLI | ≥ 1.5 (for the Spectrum step). Install separately. |
|
|
77
|
+
| **Cloudflare Enterprise**| **Spectrum is an Enterprise-tier feature.** Required for the TCP path. |
|
|
78
|
+
| Containers beta access | Cloudflare Containers is in beta; request access in the dashboard. |
|
|
79
|
+
| A Cloudflare zone | The public hostname (e.g. `irc.example.com`) must live in a zone you control. |
|
|
80
|
+
| Git checkout | Clean working tree on `main` for production deploys. |
|
|
81
|
+
|
|
82
|
+
> The wss path runs on the Workers Free/Paid plan. The TCP path does
|
|
83
|
+
> **not** — Spectrum mandates Enterprise. If you do not have Enterprise,
|
|
84
|
+
> deploy the wss path instead (see `Cloudflare-Deployment-Guide.md`).
|
|
85
|
+
|
|
86
|
+
Confirm the local environment:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
node --version # v24.x or newer
|
|
90
|
+
pnpm --version # 11.x
|
|
91
|
+
terraform version # >= 1.5
|
|
92
|
+
pnpm install # from repo root
|
|
93
|
+
pnpm build # builds irc-core, irc-server, in-memory-runtime, cf-tcp-container
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 3. First-time setup
|
|
99
|
+
|
|
100
|
+
Done once per Cloudflare account + zone.
|
|
101
|
+
|
|
102
|
+
### 3.1 API token
|
|
103
|
+
|
|
104
|
+
Same token shape as the wss path works, but it additionally needs
|
|
105
|
+
Spectrum edit rights. Create a token at
|
|
106
|
+
<https://dash.cloudflare.com/profile/api-tokens> with:
|
|
107
|
+
|
|
108
|
+
- `Account · Workers Scripts · Edit` (Containers deploy)
|
|
109
|
+
- `Zone · Spectrum · Edit` (terraform `cloudflare_spectrum_application`)
|
|
110
|
+
- `Zone · DNS · Edit` (Spectrum creates a CNAME)
|
|
111
|
+
|
|
112
|
+
Restrict to the target zone. Set as `CLOUDFLARE_API_TOKEN` locally and as
|
|
113
|
+
a GitHub Actions secret for CI.
|
|
114
|
+
|
|
115
|
+
### 3.2 Collect the IDs the deploy needs
|
|
116
|
+
|
|
117
|
+
| Secret / var | Where to find it |
|
|
118
|
+
|--------------------------|---------------------------------------------------------|
|
|
119
|
+
| `CLOUDFLARE_ACCOUNT_ID` | Dashboard sidebar (any zone → Overview, lower right). |
|
|
120
|
+
| `CF_ZONE_ID` | Dashboard sidebar of the target zone → Overview. |
|
|
121
|
+
| `CF_TCP_ORIGIN_ADDRESS` | The Container's origin hostname (printed after first `wrangler deploy`, see §5.1). |
|
|
122
|
+
|
|
123
|
+
### 3.3 Authenticate the CLI
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx wrangler login # interactive
|
|
127
|
+
# or non-interactive:
|
|
128
|
+
export CLOUDFLARE_API_TOKEN=...
|
|
129
|
+
export CLOUDFLARE_ACCOUNT_ID=...
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 4. Local development
|
|
135
|
+
|
|
136
|
+
Run the container origin locally without Spectrum — connect a plaintext
|
|
137
|
+
TCP IRC client directly to :6667. This exercises the exact
|
|
138
|
+
`ConnectionActor` + `TcpByteStreamTransport` pipeline.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Build the workspace + the container package.
|
|
142
|
+
pnpm build
|
|
143
|
+
|
|
144
|
+
# Run the origin on plaintext :6667 (default TCP_PORT).
|
|
145
|
+
pnpm --filter @serverless-ircd/cf-tcp-container start \
|
|
146
|
+
-- --env-file <(printf 'SERVER_NAME=irc.local\nTCP_PORT=6667\n')
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Connect a plaintext client (no TLS) to `localhost:6667`:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
/eval /connect localhost 6667 # WeeChat, no SSL
|
|
153
|
+
NICK alice
|
|
154
|
+
USER alice 0 * :Alice
|
|
155
|
+
JOIN #test
|
|
156
|
+
PRIVMSG #test :hello over plaintext TCP
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For a TLS-terminated local loop, put `stunnel` (or `openssl s_server`)
|
|
160
|
+
in front of :6667 and connect on :6697 — mirroring the Spectrum topology.
|
|
161
|
+
|
|
162
|
+
The unit + the TLS e2e suites live in `apps/cf-tcp-container/tests/`
|
|
163
|
+
(including `tls-e2e.test.ts`, which runs a real TLS handshake against the
|
|
164
|
+
origin). Run them with:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pnpm --filter @serverless-ircd/cf-tcp-container test
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 5. Deploy staging
|
|
173
|
+
|
|
174
|
+
Staging is what CI deploys on every push that touches
|
|
175
|
+
`apps/cf-tcp-container/**`. You can also deploy it manually.
|
|
176
|
+
|
|
177
|
+
### 5.1 Deploy the container origin
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# From the repo root:
|
|
181
|
+
pnpm deploy:cf-tcp:staging
|
|
182
|
+
# Equivalent to:
|
|
183
|
+
# pnpm build && pnpm --filter @serverless-ircd/cf-tcp-container deploy:staging
|
|
184
|
+
# → wrangler deploy --env staging (inside apps/cf-tcp-container)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This builds the container image from `apps/cf-tcp-container/Dockerfile`
|
|
188
|
+
and deploys the `sirc-tcp-origin-staging` Container. The first deploy
|
|
189
|
+
prints the **origin address** — copy it into `CF_TCP_ORIGIN_ADDRESS`
|
|
190
|
+
(used by the Spectrum step next).
|
|
191
|
+
|
|
192
|
+
### 5.2 Apply the Spectrum TLS config
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
cd apps/cf-tcp-container/terraform
|
|
196
|
+
terraform init
|
|
197
|
+
terraform apply \
|
|
198
|
+
-var zone_id=$CF_ZONE_ID \
|
|
199
|
+
-var origin_address=$CF_TCP_ORIGIN_ADDRESS \
|
|
200
|
+
-var hostname=irc-staging.example.com
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This creates the `cloudflare_spectrum_application.irc_tls` resource:
|
|
204
|
+
TLS on :6697 → plaintext TCP to the container origin on :6667.
|
|
205
|
+
|
|
206
|
+
### 5.3 Smoke test over TLS
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Minimal smoke: register + quit over the real TLS endpoint.
|
|
210
|
+
printf 'NICK smoke\r\nUSER smoke 0 * :Smoke\r\nQUIT :smoke\r\n' | \
|
|
211
|
+
timeout 10 openssl s_client -connect irc-staging.example.com:6697 -quiet | \
|
|
212
|
+
grep -q '001' && echo "SMOKE PASS" || echo "SMOKE FAIL"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
CI does the same — see `.github/workflows/deploy-cf-tcp.yml` step
|
|
216
|
+
"Smoke e2e (irc+tls)", gated on the `CF_TCP_SMOKE_HOST` repo variable.
|
|
217
|
+
|
|
218
|
+
### 5.4 Connect a real IRC client
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
/server add ircd irc-staging.example.com/6697
|
|
222
|
+
/set irc.server.ircd.ssl on
|
|
223
|
+
/connect ircd
|
|
224
|
+
/join #test
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 6. Deploy production
|
|
230
|
+
|
|
231
|
+
Production is the default `wrangler.toml` environment (no `--env` flag).
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# 1. Edit wrangler.toml [vars] for production identity.
|
|
235
|
+
# 2. Deploy the container:
|
|
236
|
+
pnpm deploy:cf-tcp:prod
|
|
237
|
+
# 3. Apply Spectrum config for the production hostname:
|
|
238
|
+
cd apps/cf-tcp-container/terraform
|
|
239
|
+
terraform apply -var zone_id=$CF_ZONE_ID \
|
|
240
|
+
-var origin_address=$CF_TCP_ORIGIN_ADDRESS \
|
|
241
|
+
-var hostname=irc.example.com
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
There is **no CI auto-deploy to production** — production deploys are
|
|
245
|
+
manual. Staging deploys on every push to `main` that touches the
|
|
246
|
+
container path.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 7. Configuration reference
|
|
251
|
+
|
|
252
|
+
### 7.1 Container — `apps/cf-tcp-container/wrangler.toml`
|
|
253
|
+
|
|
254
|
+
Top-level `[vars]` map directly to the container config loader
|
|
255
|
+
(`apps/cf-tcp-container/src/config-loader.ts`):
|
|
256
|
+
|
|
257
|
+
| Var | Purpose | Default |
|
|
258
|
+
|----------------------|------------------------------------------------------|--------------------------|
|
|
259
|
+
| `SERVER_NAME` | Server name in `001`/`005` numerics. **Required** — the container config loader fails fast when unset. | _No default; must be set._ |
|
|
260
|
+
| `NETWORK_NAME` | Network label in `005 NETWORK=…`. | `ExampleNet` |
|
|
261
|
+
| `SERVER_VERSION` | Version string in `002`. | (from package version) |
|
|
262
|
+
| `CREATED_AT` | Creation text/time in `003`. | build/deploy timestamp |
|
|
263
|
+
| `MOTD_LINES` | Message-of-the-day, `\n`-delimited. | welcome banner |
|
|
264
|
+
| `TCP_PORT` | Plaintext port the container listens on. | `6667` |
|
|
265
|
+
| `TCP_HOST` | Bind address (Spectrum forwards from the edge). | `0.0.0.0` |
|
|
266
|
+
| `PERSISTENCE_PATH` | Snapshot file path; enables channel-state survival. | `/data/state.json` |
|
|
267
|
+
| `SNAPSHOT_INTERVAL_MS`| Auto-snapshot cadence (ms). | `60000` |
|
|
268
|
+
| `OPER_USER` / `OPER_PASSWORD` | Oper credentials for `OPER`. | — |
|
|
269
|
+
| `SASL_ACCOUNTS` | `\n`-delimited `user:password` lines for SASL PLAIN. | — |
|
|
270
|
+
| `MAX_CHANNELS_PER_USER`, `MAX_TARGETS_PER_COMMAND`, `NICK_LEN`, `CHANNEL_LEN`, `TOPIC_LEN`, `MAX_LIST_ENTRIES`, `QUIT_MESSAGE` | Standard `ServerConfig` knobs (mirror the wss path). | shared defaults |
|
|
271
|
+
|
|
272
|
+
The container block itself:
|
|
273
|
+
|
|
274
|
+
```toml
|
|
275
|
+
[[containers]]
|
|
276
|
+
class_name = "IrcTcpOrigin"
|
|
277
|
+
image = "./Dockerfile"
|
|
278
|
+
instance_type = "standard-1"
|
|
279
|
+
max_instances = 3
|
|
280
|
+
sleep_after = "2h"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- `max_instances` caps horizontal scale; the `InMemoryRuntime` is
|
|
284
|
+
per-process, so connections on different instances do not share state.
|
|
285
|
+
Tune `max_instances` to your expected concurrency (one origin handles
|
|
286
|
+
thousands of idle connections thanks to the async TCP server).
|
|
287
|
+
- `sleep_after = "2h"` lets an idle origin scale to zero; the first new
|
|
288
|
+
connection cold-starts it. Persistence (§8) means channel topology is
|
|
289
|
+
not lost across sleep.
|
|
290
|
+
|
|
291
|
+
### 7.2 Spectrum — `apps/cf-tcp-container/terraform/spectrum.tf`
|
|
292
|
+
|
|
293
|
+
| Variable | Purpose | Default |
|
|
294
|
+
|-------------------|---------------------------------------------------|---------------------|
|
|
295
|
+
| `zone_id` | Cloudflare zone for the hostname. | *(required)* |
|
|
296
|
+
| `origin_address` | Hostname/IP of the container origin. | *(required)* |
|
|
297
|
+
| `origin_port` | Port the origin listens on (plaintext). | `6667` |
|
|
298
|
+
| `hostname` | Public hostname clients connect to. | `irc.example.com` |
|
|
299
|
+
| `spectrum_port` | Public port Spectrum listens on. | `6697` |
|
|
300
|
+
| `tls_mode` | `flexible` (TLS→CF, plain→origin) / `full` / `strict`. | `flexible` |
|
|
301
|
+
|
|
302
|
+
`ip_firewall = true` (Cloudflare's L3/L4 DDoS) and
|
|
303
|
+
`argo_smart_routing = true` are on by default. `proxy_protocol = "v1"`
|
|
304
|
+
forwards the **real client IP** to the origin so the container's
|
|
305
|
+
per-IP admission gate sees the actual source, not Spectrum's address.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 8. Persistence & state survival
|
|
310
|
+
|
|
311
|
+
The origin is a **stateful process** — a hard requirement that the wss
|
|
312
|
+
path (Durable Objects) does not have. Two layers of survival:
|
|
313
|
+
|
|
314
|
+
1. **Across chunks within a connection** — the `TcpByteStreamTransport`
|
|
315
|
+
buffers partial lines in memory; a chunk may split an IRC command at
|
|
316
|
+
an arbitrary byte boundary and the next chunk completes it. This is
|
|
317
|
+
all in-process; nothing to configure.
|
|
318
|
+
2. **Across origin restarts** (crash, deploy, scale-to-zero) — every
|
|
319
|
+
live TCP connection **drops**; clients must reconnect. But the
|
|
320
|
+
**channel topology** (topics, modes, ban masks) is snapshotted to
|
|
321
|
+
`PERSISTENCE_PATH` (`/data/state.json`, a `/data` volume in the
|
|
322
|
+
Dockerfile) on an interval (`SNAPSHOT_INTERVAL_MS`) and at graceful
|
|
323
|
+
shutdown, then restored on boot. Member rosters are **not** persisted
|
|
324
|
+
— they reference ConnIds that are stale after a restart, so a
|
|
325
|
+
reconnecting client rejoins an otherwise-empty channel.
|
|
326
|
+
|
|
327
|
+
> **Operational caveat:** the snapshot is per-instance. With
|
|
328
|
+
> `max_instances > 1`, each instance owns disjoint channels (no
|
|
329
|
+
> cross-instance state share). For a single coordinated channel space,
|
|
330
|
+
> keep `max_instances = 1` until the Durable-Object-backed persistent
|
|
331
|
+
> variant lands (ADR-009 "Negative", follow-up to this ticket).
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 9. mTLS (SASL EXTERNAL)
|
|
336
|
+
|
|
337
|
+
SASL `EXTERNAL` needs the client certificate to reach the application
|
|
338
|
+
layer (ADR-006, TICKET-054). On the TCP path this is the TLS handshake:
|
|
339
|
+
|
|
340
|
+
- **Flexible mode (default):** Spectrum terminates TLS; the origin sees
|
|
341
|
+
plaintext. The cert is consumed at the edge and the app only gets a
|
|
342
|
+
verified subject via Cloudflare's mTLS config — SASL `EXTERNAL` is
|
|
343
|
+
**not** available in flexible mode.
|
|
344
|
+
- **Full mode:** set `tls_mode = "full"` and configure the origin to
|
|
345
|
+
present its own certificate; configure Spectrum/API Shield mTLS with
|
|
346
|
+
your CA pool. The verified client-cert subject is then exposed to the
|
|
347
|
+
origin, feeding the `MtlsIdentityProvider` port → `AccountStore` →
|
|
348
|
+
`AUTHENTICATE EXTERNAL` succeeds (`903`) or fails (`904`).
|
|
349
|
+
|
|
350
|
+
Cross-reference: TICKET-054 (mTLS support), ADR-006 (SASL scope). The wss
|
|
351
|
+
path achieves the same via CF API Shield mTLS (see
|
|
352
|
+
`Cloudflare-Deployment-Guide.md`).
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## 10. Secrets
|
|
357
|
+
|
|
358
|
+
The container consumes **no Cloudflare secret bindings** for v1; oper
|
|
359
|
+
and SASL credentials ship as `[vars]` (`OPER_*`, `SASL_ACCOUNTS`). When
|
|
360
|
+
strong-secret handling lands, set them via:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
wrangler secret put OPER_PASSWORD --env staging
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
CI uses these **GitHub Actions secrets/variables** (configure under
|
|
367
|
+
Settings → Secrets and variables → Actions):
|
|
368
|
+
|
|
369
|
+
| Secret / var | Used by | Purpose |
|
|
370
|
+
|--------------------------|------------------------|--------------------------------------------|
|
|
371
|
+
| `CLOUDFLARE_API_TOKEN` | `deploy-cf-tcp.yml` | `wrangler deploy` + terraform (`TF_VAR_*`).|
|
|
372
|
+
| `CLOUDFLARE_ACCOUNT_ID` | `deploy-cf-tcp.yml` | Account context. |
|
|
373
|
+
| `CF_ZONE_ID` | `deploy-cf-tcp.yml` | `TF_VAR_zone_id`. |
|
|
374
|
+
| `CF_TCP_ORIGIN_ADDRESS` | `deploy-cf-tcp.yml` | `TF_VAR_origin_address`. |
|
|
375
|
+
| `CF_TCP_SMOKE_HOST` (var)| `deploy-cf-tcp.yml` | Host the TLS smoke step connects to. |
|
|
376
|
+
|
|
377
|
+
**Never** commit credentials to `wrangler.toml` or terraform `.tfvars`.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## 11. CI/CD
|
|
382
|
+
|
|
383
|
+
`.github/workflows/deploy-cf-tcp.yml` runs alongside `deploy-cf.yml`
|
|
384
|
+
(the wss path). The two are independent — a deployment can enable
|
|
385
|
+
either or both.
|
|
386
|
+
|
|
387
|
+
Triggers: push to `main` touching `apps/cf-tcp-container/**` or the
|
|
388
|
+
workflow itself, plus `workflow_dispatch`. Steps (in order):
|
|
389
|
+
|
|
390
|
+
1. Checkout → pnpm 11 + Node 24 → `pnpm install --frozen-lockfile`.
|
|
391
|
+
2. `pnpm build`, `pnpm typecheck`, `pnpm --filter cf-tcp-container test`.
|
|
392
|
+
3. `wrangler deploy --env staging` (container).
|
|
393
|
+
4. `terraform init && terraform apply -auto-approve` (Spectrum).
|
|
394
|
+
5. TLS smoke (`openssl s_client`), gated on `CF_TCP_SMOKE_HOST`.
|
|
395
|
+
|
|
396
|
+
Concurrency is serialized via `concurrency.group: cf-tcp-staging`.
|
|
397
|
+
|
|
398
|
+
There is **no auto-deploy to production**; production is manual
|
|
399
|
+
(`pnpm deploy:cf-tcp:prod` + `terraform apply` for the prod hostname).
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 12. Cost notes (versus the wss path)
|
|
404
|
+
|
|
405
|
+
The TCP path is materially more expensive to operate than the wss path
|
|
406
|
+
because of the stateful origin:
|
|
407
|
+
|
|
408
|
+
| Dimension | wss path (Workers + DO) | TCP path (Spectrum + Container) |
|
|
409
|
+
|------------------------|------------------------------------|--------------------------------------------|
|
|
410
|
+
| **Plan tier** | Workers Paid ($5/mo) suffices. | **Enterprise required** (Spectrum). |
|
|
411
|
+
| **Idle connections** | Hibernation → ~zero memory cost. | Origin holds every socket; `sleep_after` only helps when *all* connections are idle. |
|
|
412
|
+
| **Compute** | Per-request (WebSocket frames). | **GB-second** of running container time. |
|
|
413
|
+
| **State durability** | DO storage (managed). | Container volume snapshot (self-managed). |
|
|
414
|
+
| **DDoS** | Cloudflare built-in. | Spectrum `ip_firewall` + Argo. |
|
|
415
|
+
|
|
416
|
+
Recommendation: keep the wss path as the default and only enable the TCP
|
|
417
|
+
path when you specifically need RFC-compliant clients or mTLS-via-TLS.
|
|
418
|
+
Most modern clients (WeeChat, IRCCloud, TheLounge) work over wss.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## 13. Troubleshooting
|
|
423
|
+
|
|
424
|
+
### 13.1 `terraform apply` fails: `Spectrum app creation not permitted`
|
|
425
|
+
|
|
426
|
+
Spectrum is Enterprise-only. The API token's account/zone must be on an
|
|
427
|
+
Enterprise plan. Confirm in the dashboard (Network → Spectrum). If the
|
|
428
|
+
zone is not Enterprise, use the wss path.
|
|
429
|
+
|
|
430
|
+
### 13.2 Client connects to :6697 but gets no `001`
|
|
431
|
+
|
|
432
|
+
The TLS handshake completed (Spectrum is up) but the origin did not
|
|
433
|
+
respond. Causes:
|
|
434
|
+
|
|
435
|
+
- The container is scaled to zero (`sleep_after`) and the cold start is
|
|
436
|
+
slow — wait a few seconds and retry.
|
|
437
|
+
- `CF_TCP_ORIGIN_ADDRESS` points at the wrong origin. It must match the
|
|
438
|
+
address printed by `wrangler deploy` (§5.1).
|
|
439
|
+
- The origin crashed on boot. Check `wrangler tail` for the container
|
|
440
|
+
(or container logs in the dashboard). A common cause is an invalid
|
|
441
|
+
`MOTD_LINES` or a malformed `SASL_ACCOUNTS` line.
|
|
442
|
+
|
|
443
|
+
### 13.3 Channel topic/modes lost after a restart
|
|
444
|
+
|
|
445
|
+
`PERSISTENCE_PATH` is unset or the volume is not attached. Confirm
|
|
446
|
+
`PERSISTENCE_PATH=/data/state.json` in `[vars]` and that the `/data`
|
|
447
|
+
volume (`VOLUME ["/data"]` in the `Dockerfile`) is mounted on the
|
|
448
|
+
container platform. Without it the snapshot is written to ephemeral
|
|
449
|
+
storage and lost.
|
|
450
|
+
|
|
451
|
+
### 13.4 Client IP seen as Spectrum's address
|
|
452
|
+
|
|
453
|
+
`proxy_protocol = "v1"` must be set in `spectrum.tf` (it is by default).
|
|
454
|
+
If you overrode it, the origin's per-IP admission gate sees Spectrum's
|
|
455
|
+
egress IP for every client and will wrongly throttle/cap.
|
|
456
|
+
|
|
457
|
+
### 13.5 Messages split across chunks get lost
|
|
458
|
+
|
|
459
|
+
This is a transport-layer bug, not a config issue — the
|
|
460
|
+
`TcpByteStreamTransport` is contract-tested for chunk boundaries
|
|
461
|
+
(TICKET-053 / TICKET-057). If you see it, run
|
|
462
|
+
`pnpm --filter @serverless-ircd/cf-tcp-container test` and the shared
|
|
463
|
+
transport suite; a regression there will fail.
|
|
464
|
+
|
|
465
|
+
### 13.6 `wrangler deploy` fails: `Containers beta not enabled`
|
|
466
|
+
|
|
467
|
+
Cloudflare Containers is in beta. Request access in the dashboard
|
|
468
|
+
(Workers & Pages → Containers). Until granted, only the local Docker
|
|
469
|
+
path (§4) and the wss transport work.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 14. Quick reference
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
# One-time account setup
|
|
477
|
+
npx wrangler login # or set CLOUDFLARE_API_TOKEN
|
|
478
|
+
terraform version # >= 1.5
|
|
479
|
+
|
|
480
|
+
# Local dev (plaintext :6667, no Spectrum)
|
|
481
|
+
pnpm build
|
|
482
|
+
pnpm --filter @serverless-ircd/cf-tcp-container start
|
|
483
|
+
|
|
484
|
+
# Staging
|
|
485
|
+
pnpm deploy:cf-tcp:staging # container
|
|
486
|
+
cd apps/cf-tcp-container/terraform && \
|
|
487
|
+
terraform apply -var zone_id=$CF_ZONE_ID \
|
|
488
|
+
-var origin_address=$CF_TCP_ORIGIN_ADDRESS \
|
|
489
|
+
-var hostname=irc-staging.example.com # Spectrum
|
|
490
|
+
|
|
491
|
+
# Production (manual)
|
|
492
|
+
pnpm deploy:cf-tcp:prod
|
|
493
|
+
cd apps/cf-tcp-container/terraform && terraform apply ... # prod hostname
|
|
494
|
+
|
|
495
|
+
# Tests
|
|
496
|
+
pnpm --filter @serverless-ircd/cf-tcp-container test
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Key files:
|
|
500
|
+
|
|
501
|
+
| Path | What |
|
|
502
|
+
|-------------------------------------------------|--------------------------------------------|
|
|
503
|
+
| `apps/cf-tcp-container/src/main.ts` | Container entry point. |
|
|
504
|
+
| `apps/cf-tcp-container/src/container-server.ts` | TCP server + actor wiring + persistence. |
|
|
505
|
+
| `apps/cf-tcp-container/src/config-loader.ts` | Env → `ServerConfig` + container knobs. |
|
|
506
|
+
| `apps/cf-tcp-container/src/persistence.ts` | Channel-topology snapshot/restore. |
|
|
507
|
+
| `apps/cf-tcp-container/wrangler.toml` | Container deploy config. |
|
|
508
|
+
| `apps/cf-tcp-container/Dockerfile` | Image (node:24, `/data` volume, :6667). |
|
|
509
|
+
| `apps/cf-tcp-container/terraform/spectrum.tf` | Spectrum TLS :6697 → origin :6667. |
|
|
510
|
+
| `.github/workflows/deploy-cf-tcp.yml` | Staging deploy + Spectrum + smoke CI. |
|
package/docs/Home.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Welcome to the Wiki.
|
|
2
|
+
|
|
3
|
+
## Pages
|
|
4
|
+
|
|
5
|
+
- [AWS Adapter Architecture](AWS-Adapter-Architecture)
|
|
6
|
+
- [AWS Deployment Guide](AWS-Deployment)
|
|
7
|
+
- [AWS TCP+TLS Deployment Guide](AWS-TCP-Deployment)
|
|
8
|
+
- [Cloudflare Deployment Guide](Cloudflare-Deployment-Guide)
|
|
9
|
+
- [Cloudflare TCP+TLS Deployment Guide](Cloudflare-TCP-Deployment)
|
|
10
|
+
- [Observability](Observability)
|
|
11
|
+
- [IRCv3 WebSocket Extension Plan](PlanIRCv3Websocket)
|
|
12
|
+
- [Web Client Plan](PlanWebClient)
|
|
13
|
+
- [Extensions Plan](PlanExtensions)
|
|
14
|
+
- [Release Process](Release-Process)
|
|
15
|
+
- [ADR Index](ADR-Index)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Observability
|
|
2
|
+
|
|
3
|
+
The server ships a structured-logger port that every adapter uses to emit
|
|
4
|
+
JSON log lines carrying a per-request `traceId` and per-connection
|
|
5
|
+
`connectionId`. The canonical event names are the contract dashboards and
|
|
6
|
+
log queries are built against.
|
|
7
|
+
|
|
8
|
+
## Log contract
|
|
9
|
+
|
|
10
|
+
Every record is one physical line of JSON with at least:
|
|
11
|
+
|
|
12
|
+
| Field | Source |
|
|
13
|
+
|----------------|-------------------------------------------------|
|
|
14
|
+
| `ts` | ISO-8601 timestamp from the injected `Clock`. |
|
|
15
|
+
| `level` | `debug` / `info` / `warn` / `error`. |
|
|
16
|
+
| `msg` | Event name (see below). |
|
|
17
|
+
| `traceId` | Per-request id; fresh per inbound frame. |
|
|
18
|
+
| `connectionId` | The bound connection's id (`ConnId`). |
|
|
19
|
+
|
|
20
|
+
Additional structured fields appear per event. The `msg` catalogue:
|
|
21
|
+
|
|
22
|
+
| `msg` | Level | Emitted by | Notes |
|
|
23
|
+
|--------------------|-------|-------------------------|------------------------------------------------------|
|
|
24
|
+
| `frame.receive` | debug | `ConnectionActor` | One per parsed IRC line. Fields: `command`, `lines`. |
|
|
25
|
+
| `frame.parse-error`| warn | `ConnectionActor` | A line failed to parse. Fields: `line`, `token`. |
|
|
26
|
+
| `dispatch` | debug | `dispatch()` | Before interpreting an effect list. Fields: `effects` (array of tags), `histogram`. |
|
|
27
|
+
| `dispatch.error` | warn | `dispatch()` | A runtime method rejected. Fields: `effect`, `err`. |
|
|
28
|
+
|
|
29
|
+
Adapters may emit their own events (e.g. `local-cli listening`, `shutdown`)
|
|
30
|
+
following the same shape.
|
|
31
|
+
|
|
32
|
+
## Cloudflare Workers
|
|
33
|
+
|
|
34
|
+
The `apps/cf-worker/wrangler.toml` enables `[observability]` so every
|
|
35
|
+
`console.*` line emitted by the `ConsoleLogger` inside `ConnectionDO` is
|
|
36
|
+
captured by Workers Analytics.
|
|
37
|
+
|
|
38
|
+
- Tail the live feed: `pnpm --filter @serverless-ircd/cf-worker tail`
|
|
39
|
+
- Filter by connection: `connectionId = "<DO hex id>"`
|
|
40
|
+
- Filter by trace: `traceId = "<uuid>"`
|
|
41
|
+
- Errors only: `level = "error" or msg = "dispatch.error"`
|
|
42
|
+
|
|
43
|
+
Workers Analytics dashboards can be built on the Cloudflare dashboard
|
|
44
|
+
under **Workers & Pages → <Worker> → Observability**.
|
|
45
|
+
|
|
46
|
+
## AWS (Lambda + API Gateway WebSocket)
|
|
47
|
+
|
|
48
|
+
The Lambda handler writes to CloudWatch Logs via the global `console`.
|
|
49
|
+
Each invocation emits one JSON line per log record; the log group name
|
|
50
|
+
follows the standard `/aws/lambda/<function-name>` convention.
|
|
51
|
+
|
|
52
|
+
A starter CloudWatch dashboard is checked in at
|
|
53
|
+
`dashboards/cloudwatch-irc.json`. Provision it via:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
aws cloudwatch put-dashboard \
|
|
57
|
+
--dashboard-name ServerlessIRCd-staging \
|
|
58
|
+
--dashboard-body file://dashboards/cloudwatch-irc.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The dashboard variables (`API_ID`, `LAMBDA_NAME`, `LOG_GROUP_ARN`,
|
|
62
|
+
`AWS_REGION`) are templated by CloudWatch's dashboard variables feature —
|
|
63
|
+
set them once in the dashboard UI after import.
|
|
64
|
+
|
|
65
|
+
Sample CloudWatch Logs Insights query — count dispatched effects by tag
|
|
66
|
+
over the last hour:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
fields @timestamp, msg, fields.histogram
|
|
70
|
+
| filter msg = "dispatch"
|
|
71
|
+
| sort @timestamp desc
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Local CLI
|
|
75
|
+
|
|
76
|
+
The local-cli server uses the same `ConsoleLogger` so the structured
|
|
77
|
+
stream is visible while developing:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
{"ts":"2026-07-22T12:34:56.789Z","level":"debug","msg":"frame.receive","traceId":"…","connectionId":"…","command":"PRIVMSG","lines":1}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Filter with `jq`:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
pnpm --filter local-cli start | jq 'select(.msg=="dispatch.error")'
|
|
87
|
+
```
|