serverless-ircd 0.2.0 → 0.4.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.
Files changed (221) hide show
  1. package/.github/workflows/ci.yml +2 -2
  2. package/.github/workflows/deploy-aws.yml +1 -3
  3. package/.github/workflows/deploy-cf-tcp.yml +87 -0
  4. package/.github/workflows/deploy-cf.yml +1 -1
  5. package/.node-version +1 -0
  6. package/.nvmrc +1 -0
  7. package/CHANGELOG.md +258 -18
  8. package/README.md +72 -30
  9. package/apps/aws-stack/README.md +2 -2
  10. package/apps/aws-stack/bin/aws.ts +7 -0
  11. package/apps/aws-stack/package.json +4 -4
  12. package/apps/aws-stack/src/aws-stack.ts +118 -6
  13. package/apps/aws-stack/tests/stack.test.ts +98 -3
  14. package/apps/cf-tcp-container/Dockerfile +69 -0
  15. package/apps/cf-tcp-container/package.json +34 -0
  16. package/apps/cf-tcp-container/src/config-loader.ts +145 -0
  17. package/apps/cf-tcp-container/src/container-do.ts +38 -0
  18. package/apps/cf-tcp-container/src/container-server.ts +363 -0
  19. package/apps/cf-tcp-container/src/main.ts +77 -0
  20. package/apps/cf-tcp-container/src/persistence.ts +144 -0
  21. package/apps/cf-tcp-container/src/worker.ts +41 -0
  22. package/apps/cf-tcp-container/terraform/provider.tf +24 -0
  23. package/apps/cf-tcp-container/terraform/spectrum.tf +81 -0
  24. package/apps/cf-tcp-container/tests/config-loader.test.ts +217 -0
  25. package/apps/cf-tcp-container/tests/container-server.test.ts +465 -0
  26. package/apps/cf-tcp-container/tests/persistence.test.ts +227 -0
  27. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +275 -0
  28. package/apps/cf-tcp-container/tsconfig.build.json +17 -0
  29. package/apps/cf-tcp-container/tsconfig.test.json +15 -0
  30. package/apps/cf-tcp-container/vitest.config.ts +26 -0
  31. package/apps/cf-tcp-container/wrangler.toml +63 -0
  32. package/apps/cf-worker/package.json +1 -1
  33. package/apps/cf-worker/scripts/smoke.mjs +0 -0
  34. package/apps/cf-worker/src/worker.ts +8 -2
  35. package/apps/cf-worker/tests/smoke.test.ts +1 -0
  36. package/apps/cf-worker/wrangler.test.toml +11 -1
  37. package/apps/cf-worker/wrangler.toml +69 -19
  38. package/apps/local-cli/package.json +1 -1
  39. package/apps/local-cli/src/config-loader.ts +11 -0
  40. package/apps/local-cli/src/main.ts +1 -1
  41. package/apps/local-cli/src/server.ts +46 -3
  42. package/apps/local-cli/tests/e2e.test.ts +52 -0
  43. package/package.json +19 -16
  44. package/packages/aws-adapter/package.json +3 -3
  45. package/packages/aws-adapter/src/account-store.ts +121 -0
  46. package/packages/aws-adapter/src/admission.ts +74 -0
  47. package/packages/aws-adapter/src/aws-runtime.ts +124 -34
  48. package/packages/aws-adapter/src/cdk-table-defs.ts +5 -1
  49. package/packages/aws-adapter/src/config-loader.ts +62 -0
  50. package/packages/aws-adapter/src/dynamo-account-store.ts +95 -0
  51. package/packages/aws-adapter/src/handlers/connect.ts +64 -8
  52. package/packages/aws-adapter/src/handlers/default.ts +43 -2
  53. package/packages/aws-adapter/src/handlers/disconnect.ts +27 -8
  54. package/packages/aws-adapter/src/handlers/index.ts +120 -9
  55. package/packages/aws-adapter/src/handlers/nlb-stream.ts +481 -0
  56. package/packages/aws-adapter/src/handlers/ping-checker.ts +1 -1
  57. package/packages/aws-adapter/src/index.ts +13 -0
  58. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +182 -0
  59. package/packages/aws-adapter/tests/account-store.test.ts +279 -0
  60. package/packages/aws-adapter/tests/admission.test.ts +70 -0
  61. package/packages/aws-adapter/tests/aws-harness.ts +13 -1
  62. package/packages/aws-adapter/tests/config-loader.test.ts +75 -0
  63. package/packages/aws-adapter/tests/connect.test.ts +78 -0
  64. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +343 -0
  65. package/packages/aws-adapter/tests/gone-exception.test.ts +31 -26
  66. package/packages/aws-adapter/tests/handlers.test.ts +194 -47
  67. package/packages/aws-adapter/tests/nlb-stream.test.ts +478 -0
  68. package/packages/aws-adapter/tests/ping-checker.test.ts +34 -29
  69. package/packages/aws-adapter/tests/sweeper.test.ts +25 -18
  70. package/packages/aws-adapter/tests/transactions.test.ts +25 -20
  71. package/packages/cf-adapter/package.json +1 -1
  72. package/packages/cf-adapter/src/cf-runtime.ts +40 -22
  73. package/packages/cf-adapter/src/channel-do.ts +40 -1
  74. package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
  75. package/packages/cf-adapter/src/config-loader.ts +62 -0
  76. package/packages/cf-adapter/src/connection-do.ts +181 -31
  77. package/packages/cf-adapter/src/d1-account-store.ts +198 -0
  78. package/packages/cf-adapter/src/env.ts +58 -8
  79. package/packages/cf-adapter/src/index.ts +11 -9
  80. package/packages/cf-adapter/tests/cf-harness.ts +11 -1
  81. package/packages/cf-adapter/tests/cf-integration.test.ts +2 -1
  82. package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
  83. package/packages/cf-adapter/tests/config-loader.test.ts +22 -0
  84. package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +37 -0
  85. package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +52 -0
  86. package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +166 -0
  87. package/packages/cf-adapter/tests/connection-do.test.ts +40 -0
  88. package/packages/cf-adapter/tests/d1-account-store.test.ts +226 -0
  89. package/packages/cf-adapter/tests/raw-modules.d.ts +11 -0
  90. package/packages/cf-adapter/tests/worker/main.ts +9 -1
  91. package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
  92. package/packages/cf-adapter/wrangler.test.toml +18 -1
  93. package/packages/in-memory-runtime/package.json +1 -1
  94. package/packages/in-memory-runtime/src/in-memory-runtime.ts +14 -28
  95. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +19 -0
  96. package/packages/irc-core/package.json +8 -2
  97. package/packages/irc-core/scripts/generate-build-info.mjs +31 -0
  98. package/packages/irc-core/src/caps/capabilities.ts +23 -2
  99. package/packages/irc-core/src/case-fold.ts +64 -0
  100. package/packages/irc-core/src/cloak.ts +1 -1
  101. package/packages/irc-core/src/commands/cap.ts +8 -1
  102. package/packages/irc-core/src/commands/index.ts +11 -0
  103. package/packages/irc-core/src/commands/invite.ts +6 -9
  104. package/packages/irc-core/src/commands/ison.ts +61 -0
  105. package/packages/irc-core/src/commands/isupport.ts +1 -1
  106. package/packages/irc-core/src/commands/join.ts +4 -3
  107. package/packages/irc-core/src/commands/kick.ts +4 -11
  108. package/packages/irc-core/src/commands/list.ts +5 -4
  109. package/packages/irc-core/src/commands/mode.ts +5 -9
  110. package/packages/irc-core/src/commands/motd-lines.ts +127 -0
  111. package/packages/irc-core/src/commands/motd.ts +6 -110
  112. package/packages/irc-core/src/commands/oper.ts +151 -0
  113. package/packages/irc-core/src/commands/quit.ts +12 -0
  114. package/packages/irc-core/src/commands/registration.ts +26 -19
  115. package/packages/irc-core/src/commands/sasl.ts +72 -9
  116. package/packages/irc-core/src/commands/server-info.ts +129 -0
  117. package/packages/irc-core/src/commands/tagmsg.ts +205 -0
  118. package/packages/irc-core/src/commands/userhost.ts +84 -0
  119. package/packages/irc-core/src/commands/whowas.ts +113 -0
  120. package/packages/irc-core/src/config.ts +84 -3
  121. package/packages/irc-core/src/credential-hashing.ts +124 -0
  122. package/packages/irc-core/src/effects.ts +6 -29
  123. package/packages/irc-core/src/index.ts +3 -0
  124. package/packages/irc-core/src/ports.ts +240 -7
  125. package/packages/irc-core/src/protocol/numerics.ts +14 -8
  126. package/packages/irc-core/src/types.ts +60 -1
  127. package/packages/irc-core/tests/account-store.test.ts +131 -0
  128. package/packages/irc-core/tests/caps/capabilities.test.ts +4 -3
  129. package/packages/irc-core/tests/case-fold.test.ts +80 -0
  130. package/packages/irc-core/tests/commands/cap.test.ts +33 -1
  131. package/packages/irc-core/tests/commands/ison.test.ts +166 -0
  132. package/packages/irc-core/tests/commands/kick.test.ts +15 -0
  133. package/packages/irc-core/tests/commands/oper.test.ts +257 -0
  134. package/packages/irc-core/tests/commands/quit.test.ts +69 -2
  135. package/packages/irc-core/tests/commands/registration.test.ts +256 -19
  136. package/packages/irc-core/tests/commands/sasl.test.ts +118 -10
  137. package/packages/irc-core/tests/commands/server-info.test.ts +274 -0
  138. package/packages/irc-core/tests/commands/tagmsg.test.ts +662 -0
  139. package/packages/irc-core/tests/commands/userhost.test.ts +264 -0
  140. package/packages/irc-core/tests/commands/who.test.ts +22 -4
  141. package/packages/irc-core/tests/commands/whois.test.ts +8 -1
  142. package/packages/irc-core/tests/commands/whowas.test.ts +312 -0
  143. package/packages/irc-core/tests/config.test.ts +170 -1
  144. package/packages/irc-core/tests/credential-hashing.test.ts +170 -0
  145. package/packages/irc-core/tests/effects.test.ts +0 -27
  146. package/packages/irc-core/tests/nick-history-store.test.ts +162 -0
  147. package/packages/irc-core/tests/numerics.test.ts +12 -0
  148. package/packages/irc-core/tests/types.test.ts +35 -1
  149. package/packages/irc-core/tsconfig.build.json +1 -1
  150. package/packages/irc-core/tsconfig.test.json +1 -1
  151. package/packages/irc-server/package.json +1 -1
  152. package/packages/irc-server/src/actor.ts +182 -14
  153. package/packages/irc-server/src/dispatch.ts +0 -3
  154. package/packages/irc-server/src/index.ts +10 -2
  155. package/packages/irc-server/src/routing.ts +21 -0
  156. package/packages/irc-server/src/transport.ts +101 -0
  157. package/packages/irc-server/tests/actor.test.ts +617 -1
  158. package/packages/irc-server/tests/dispatch.test.ts +0 -17
  159. package/packages/irc-server/tests/routing.test.ts +7 -0
  160. package/packages/irc-server/tests/transport.test.ts +230 -0
  161. package/packages/irc-test-support/package.json +1 -1
  162. package/packages/irc-test-support/src/harness.ts +44 -9
  163. package/packages/irc-test-support/src/in-memory-harness.ts +78 -13
  164. package/packages/irc-test-support/src/index.ts +3 -0
  165. package/packages/irc-test-support/src/scenarios.ts +132 -2
  166. package/packages/irc-test-support/tests/in-memory-harness.test.ts +2 -1
  167. package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +23 -9
  168. package/pnpm-workspace.yaml +9 -0
  169. package/tools/ci-hardening/package.json +1 -1
  170. package/tools/package.json +4 -0
  171. package/tools/seed-aws-accounts.ts +79 -0
  172. package/tools/seed-cf-accounts.ts +104 -0
  173. package/tools/tcp-ws-forwarder/package.json +1 -1
  174. package/AGENTS.md +0 -5
  175. package/MOTD.txt +0 -3
  176. package/PLAN-FIXES.md +0 -358
  177. package/PLAN.md +0 -420
  178. package/dashboards/cloudwatch-irc.json +0 -139
  179. package/docs/AWS-Adapter-Architecture.md +0 -494
  180. package/docs/AWS-Deployment.md +0 -1107
  181. package/docs/Cloudflare-Deployment-Guide.md +0 -660
  182. package/docs/Home.md +0 -1
  183. package/docs/Observability.md +0 -87
  184. package/docs/PlanIRCv3Websocket.md +0 -489
  185. package/docs/PlanWebClient.md +0 -451
  186. package/docs/Release-Process.md +0 -440
  187. package/progress.md +0 -107
  188. package/tickets.md +0 -2485
  189. package/webircgateway/LICENSE +0 -201
  190. package/webircgateway/Makefile +0 -44
  191. package/webircgateway/README.md +0 -134
  192. package/webircgateway/config.conf.example +0 -135
  193. package/webircgateway/go.mod +0 -16
  194. package/webircgateway/go.sum +0 -89
  195. package/webircgateway/main.go +0 -118
  196. package/webircgateway/pkg/dnsbl/dnsbl.go +0 -121
  197. package/webircgateway/pkg/identd/identd.go +0 -86
  198. package/webircgateway/pkg/identd/rpcclient.go +0 -59
  199. package/webircgateway/pkg/irc/isupport.go +0 -56
  200. package/webircgateway/pkg/irc/message.go +0 -217
  201. package/webircgateway/pkg/irc/state.go +0 -79
  202. package/webircgateway/pkg/proxy/proxy.go +0 -129
  203. package/webircgateway/pkg/proxy/server.go +0 -237
  204. package/webircgateway/pkg/recaptcha/recaptcha.go +0 -59
  205. package/webircgateway/pkg/webircgateway/client.go +0 -741
  206. package/webircgateway/pkg/webircgateway/client_command_handlers.go +0 -495
  207. package/webircgateway/pkg/webircgateway/config.go +0 -385
  208. package/webircgateway/pkg/webircgateway/gateway.go +0 -278
  209. package/webircgateway/pkg/webircgateway/gateway_utils.go +0 -133
  210. package/webircgateway/pkg/webircgateway/hooks.go +0 -152
  211. package/webircgateway/pkg/webircgateway/letsencrypt.go +0 -41
  212. package/webircgateway/pkg/webircgateway/messagetags.go +0 -103
  213. package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +0 -206
  214. package/webircgateway/pkg/webircgateway/transport_sockjs.go +0 -107
  215. package/webircgateway/pkg/webircgateway/transport_tcp.go +0 -113
  216. package/webircgateway/pkg/webircgateway/transport_websocket.go +0 -126
  217. package/webircgateway/pkg/webircgateway/utils.go +0 -147
  218. package/webircgateway/plugins/example/plugin.go +0 -11
  219. package/webircgateway/plugins/stats/plugin.go +0 -52
  220. package/webircgateway/staticcheck.conf +0 -1
  221. package/webircgateway/webircgateway.svg +0 -3
@@ -1,660 +0,0 @@
1
- # Cloudflare Deployment Guide
2
-
3
- End-to-end guide for deploying ServerlessIRCd to Cloudflare Workers
4
- (Phase 3 of `PLAN.md`). Covers prerequisites, first-time account setup,
5
- local development, staging deploy, production deploy, configuration,
6
- secrets, sharding, CI/CD, cost, and troubleshooting.
7
-
8
- Cross-reference: `PLAN.md` §6.1 (CF mapping), `apps/cf-worker/wrangler.toml`,
9
- `packages/cf-adapter/`, `.github/workflows/deploy-cf.yml`. For the AWS
10
- equivalent, see `docs/deployment-aws.md` (TICKET-044).
11
-
12
- **Acceptance criterion (TICKET-038):** a new contributor can deploy their
13
- own staging instance following only this doc.
14
-
15
- ---
16
-
17
- ## 1. What gets deployed
18
-
19
- A single Cloudflare Worker (`serverless-ircd`) plus three Durable Object
20
- namespaces that hold all IRC state:
21
-
22
- ```
23
- ┌─────────────────────────────────────────────────────────────┐
24
- │ Worker: serverless-ircd (apps/cf-worker/src/worker.ts) │
25
- │ │
26
- │ fetch(request, env): │
27
- │ Upgrade: websocket ──▶ ConnectionDO.idFromName(uuid) │
28
- │ else ──▶ 200 human health string │
29
- └─────────────────────────┬───────────────────────────────────┘
30
- │ env.CONNECTION_DO.get(id).fetch()
31
-
32
- ┌─────────────────────────────────────────────────────────────┐
33
- │ ConnectionDO (one per WebSocket connection) │
34
- │ • hibernatable WS via HibernatingWebSocketHandler │
35
- │ • owns ConnectionState, drives ConnectionActor │
36
- │ • DO storage persists state across hibernation │
37
- │ • alarm() drives the PING / no-PONG disconnect sweep │
38
- └────┬───────────────────────────────┬────────────────────────┘
39
- │ stub() RPC │ stub() RPC
40
- ▼ ▼
41
- ┌──────────────────────┐ ┌─────────────────────────────────┐
42
- │ RegistryDO │ │ ChannelDO │
43
- │ (sharded by nick) │ │ (one per lowercased chan name) │
44
- │ • nick uniqueness │ │ • roster + modes + topic │
45
- │ • nick→connId map │ │ • fanout to members │
46
- └──────────────────────┘ └──────────────────────────────────┘
47
- ```
48
-
49
- The Worker itself is a thin edge: it allocates a server-generated
50
- `connectionId` (`crypto.randomUUID()`) and routes the upgrade to that
51
- connection's `ConnectionDO`. All IRC protocol logic lives in the shared
52
- `irc-core` reducers, run by `ConnectionActor` inside the DO. No state is
53
- held in the Worker process itself.
54
-
55
- The same artifacts serve both staging and production; the only difference
56
- is the `--env` flag and the `[env.staging]` block in `wrangler.toml`.
57
-
58
- ---
59
-
60
- ## 2. Prerequisites
61
-
62
- | Requirement | Version / detail |
63
- |-----------------------|--------------------------------------------------------|
64
- | Node.js | ≥ 20 (matches CI; `engines.node` in root `package.json`)|
65
- | pnpm | 9.x (`packageManager: pnpm@9.15.9` in root `package.json`)|
66
- | Cloudflare account | Free tier is enough for staging. Workers Paid plan |
67
- | | (USD $5/mo) is recommended for production — see §10. |
68
- | `wrangler` CLI | Comes from `apps/cf-worker/devDependencies`; no global install needed. |
69
- | Git checkout | Clean working tree on `main` for production deploys. |
70
-
71
- A **Workers Paid plan** is not strictly required for staging, but
72
- Durable Objects only run on the Paid plan in any meaningful production
73
- scenario. The free tier caps DO requests and disables hibernation
74
- budget headroom. See §10 (Cost).
75
-
76
- Confirm the local environment:
77
-
78
- ```bash
79
- node --version # v20.x or newer
80
- pnpm --version # 9.x
81
- pnpm install # from repo root, installs the whole workspace
82
- pnpm build # builds irc-core, irc-server, cf-adapter, cf-worker
83
- ```
84
-
85
- `pnpm build` must succeed before `wrangler deploy` — the Worker's
86
- `main = "src/worker.ts"` is transpiled by wrangler, but it imports
87
- compiled `dist/` from the workspace packages.
88
-
89
- ---
90
-
91
- ## 3. First-time Cloudflare setup
92
-
93
- These steps are done once per Cloudflare account.
94
-
95
- ### 3.1 Claim a workers.dev subdomain
96
-
97
- Every Worker is reachable at `<worker-name>.<subdomain>.workers.dev`.
98
- Claim your subdomain in the dashboard: **Workers & Pages → General →
99
- Subdomain** (or via `wrangler subdomain <name>`). This guide assumes
100
- your subdomain is `example`; substitute your own.
101
-
102
- If you intend to use a custom domain (`irc.example.com`), you can skip
103
- the workers.dev subdomain and add a **Route** + DNS record instead (see
104
- §8.4).
105
-
106
- ### 3.2 Create an API token
107
-
108
- The `wrangler` CLI needs a token with permission to deploy Workers and
109
- manage Durable Objects.
110
-
111
- 1. Go to <https://dash.cloudflare.com/profile/api-tokens> → **Create
112
- Token**.
113
- 2. Use the **"Edit Cloudflare Workers"** template, which grants:
114
- - `Account · Workers Scripts · Edit`
115
- - `Account · Durable Objects · Edit`
116
- - `Account · Worker Routes · Edit` (only if using custom domains)
117
- 3. Restrict to the target **Account Resources**.
118
- 4. Copy the token. You will set it as `CLOUDFLARE_API_TOKEN` (env var
119
- for local CLI; Actions secret for CI).
120
-
121
- Also grab your **Account ID** from the dashboard sidebar — set it as
122
- `CLOUDFLARE_ACCOUNT_ID` if your token doesn't carry an unambiguous
123
- account context.
124
-
125
- ### 3.3 Authenticate the CLI locally
126
-
127
- Two equivalent options:
128
-
129
- ```bash
130
- # Interactive (recommended for humans):
131
- npx wrangler login
132
-
133
- # Non-interactive (CI or ephemeral):
134
- export CLOUDFLARE_API_TOKEN=...
135
- export CLOUDFLARE_ACCOUNT_ID=...
136
- ```
137
-
138
- When `CLOUDFLARE_API_TOKEN` is set in the environment, `wrangler` uses
139
- it directly and skips the OAuth store. The deploy commands in
140
- `apps/cf-worker/package.json` (`deploy:staging`, `deploy:prod`) honor
141
- both.
142
-
143
- ---
144
-
145
- ## 4. Local development
146
-
147
- Before deploying, run the Worker locally with the real `workerd`
148
- runtime. This exercises the DOs, storage, and alarms against Miniflare
149
- without touching Cloudflare.
150
-
151
- From the repo root:
152
-
153
- ```bash
154
- pnpm build
155
- pnpm --filter @serverless-ircd/cf-worker dev
156
- # → boots wrangler dev on http://localhost:8787
157
- ```
158
-
159
- In another terminal, run the smoke e2e against the local URL (the
160
- script defaults to `ws://localhost:8787`):
161
-
162
- ```bash
163
- node apps/cf-worker/scripts/smoke.mjs
164
- ```
165
-
166
- Expected output (success):
167
-
168
- ```json
169
- {"ts":"...","level":"info","msg":"cf-worker smoke e2e passed","url":"ws://localhost:8787"}
170
- ```
171
-
172
- The smoke script (`scripts/smoke.mjs`) replays
173
- CONNECT → NICK/USER → JOIN #smoke → PRIVMSG → QUIT and asserts the
174
- server emits `001`, `376`, `353`, `366`, and closes the socket. It is
175
- the exact same script CI runs against the deployed staging URL.
176
-
177
- The unit + integration suites live in `packages/cf-adapter/tests/` and
178
- `apps/cf-worker/tests/`; both run under `@cloudflare/vitest-pool-workers`
179
- against real `workerd`, so behavior matches production. Run them with:
180
-
181
- ```bash
182
- pnpm --filter @serverless-ircd/cf-adapter test
183
- pnpm --filter @serverless-ircd/cf-worker test
184
- ```
185
-
186
- ---
187
-
188
- ## 5. Deploy staging
189
-
190
- Staging is the environment CI deploys on every push to `main`; you can
191
- also deploy it manually from a clean checkout.
192
-
193
- ### 5.1 Deploy from your machine
194
-
195
- ```bash
196
- # From the repo root:
197
- pnpm deploy:cf:staging
198
- # Equivalent to:
199
- # pnpm --filter @serverless-ircd/cf-worker deploy:staging
200
- # → wrangler deploy --env staging
201
- ```
202
-
203
- The first deploy of a given account will:
204
-
205
- 1. Create the Worker `serverless-ircd-staging`.
206
- 2. Apply the `[[env.staging.migrations]]` block (`tag = "v1"`,
207
- `new_classes = [ConnectionDO, RegistryDO, ChannelDO]`). This is what
208
- provisions the three DO namespaces.
209
- 3. Print the deployed URL, e.g.
210
- `https://serverless-ircd-staging.example.workers.dev`.
211
-
212
- If you see `Migration tag has already been applied`, the namespaces
213
- already exist; subsequent deploys just update the Worker code.
214
-
215
- ### 5.2 Verify with the smoke script
216
-
217
- ```bash
218
- node apps/cf-worker/scripts/smoke.mjs \
219
- --url wss://serverless-ircd-staging.example.workers.dev
220
- ```
221
-
222
- The same JSON `level: "info"` line on stdout means the full
223
- REGISTER/JOIN/PRIVMSG/QUIT flow completed against the deployed Worker.
224
-
225
- ### 5.3 Connect a real IRC client
226
-
227
- Point any RFC-compliant WebSocket-aware IRC client at the deployed URL.
228
- For WeeChat:
229
-
230
- ```
231
- /server add ircd serverless-ircd-staging.example.workers.dev/443
232
- /set irc.server.ircd.ssl on
233
- /connect ircd
234
- /join #test
235
- ```
236
-
237
- For a quick text-only check, the `apps/local-cli` and
238
- `tools/tcp-ws-forwarder` packages bridge a plain TCP IRC client to the
239
- Worker's WebSocket endpoint — see the README "Bridging raw TCP clients"
240
- section.
241
-
242
- ---
243
-
244
- ## 6. Deploy production
245
-
246
- Production is the default `wrangler.toml` environment (no `--env`
247
- flag). It deploys the Worker named `serverless-ircd` (no `-staging`
248
- suffix) with its own DO namespaces.
249
-
250
- ```bash
251
- # From the repo root:
252
- pnpm deploy:cf:prod
253
- ```
254
-
255
- Before your first production deploy, edit `apps/cf-worker/wrangler.toml`:
256
-
257
- 1. Set `[vars].SERVER_NAME` to the public hostname clients should see
258
- in numerics (`001`, `005`, etc.) — e.g. `irc.example.com`.
259
- 2. Set `[vars].NETWORK_NAME` to your network label.
260
- 3. Set `[vars].MOTD_LINES` to your message-of-the-day (newline-delimited).
261
- 4. (Optional) Add a custom-domain Route under `[[routes]]` — see §8.4.
262
-
263
- **First-time production migration:** the default `[[migrations]]` block
264
- applies on the first `deploy:cf:prod` and creates the production DO
265
- classes. This is one-way: once a DO namespace has data, you cannot
266
- rename or delete a class without an explicit migration entry (see
267
- §9.2).
268
-
269
- There is **no CI auto-deploy to production** — production deploys are
270
- always manual (`workflow_dispatch` only is intentionally not wired; see
271
- `.github/workflows/deploy-cf.yml`). Staging deploys on every push to
272
- `main`.
273
-
274
- ---
275
-
276
- ## 7. Configuration reference
277
-
278
- All deployment knobs live in `apps/cf-worker/wrangler.toml`. The file
279
- is the single source of truth for both environments.
280
-
281
- ### 7.1 Top-level / `[vars]`
282
-
283
- | Var | Purpose | Default |
284
- |-----------------|----------------------------------------------------|--------------------------------------|
285
- | `name` | Worker name. Staging overrides to `...-staging`. | `serverless-ircd` |
286
- | `main` | Worker entry. | `src/worker.ts` |
287
- | `compatibility_date` | Pins Workers runtime behavior. | `2024-11-01` |
288
- | `compatibility_flags` | `nodejs_compat` enables Node-style APIs. | `["nodejs_compat"]` |
289
- | `SERVER_NAME` | Server name sent in `001`/`005` numerics. | `irc.example.com` (override!) |
290
- | `NETWORK_NAME` | Network label in `005 NETWORK=…`. | `ServerlessIRCd` |
291
- | `MOTD_LINES` | Message-of-the-day, `\n`-delimited. | Welcome banner string. |
292
-
293
- These are read by `ConnectionDO` via `Env` (see
294
- `packages/cf-adapter/src/env.ts:20` and the `serverConfig()` method in
295
- `connection-do.ts:337`). They are **not** secrets — they ship in the
296
- bundle and are visible in the dashboard.
297
-
298
- ### 7.2 `[env.staging]`
299
-
300
- The staging block overrides `name` and `[vars]` only. The DO bindings
301
- and migrations must be redeclared per-environment (wrangler requires
302
- explicit per-env blocks — they do not inherit):
303
-
304
- ```toml
305
- [env.staging]
306
- name = "serverless-ircd-staging"
307
-
308
- [env.staging.vars]
309
- SERVER_NAME = "irc-staging.example.com"
310
- NETWORK_NAME = "ServerlessIRCd (staging)"
311
- MOTD_LINES = "..."
312
- ```
313
-
314
- ### 7.3 Durable Object bindings
315
-
316
- Three DO classes are exported from `src/worker.ts` (re-exported from
317
- `@serverless-ircd/cf-adapter`) and bound in `wrangler.toml`:
318
-
319
- | Binding name | Class | Owned state |
320
- |-------------------|----------------|--------------------------------------------|
321
- | `CONNECTION_DO` | `ConnectionDO` | Socket + ConnectionState + PING alarms. |
322
- | `REGISTRY_DO` | `RegistryDO` | Nick uniqueness, nick→connId map (sharded).|
323
- | `CHANNEL_DO` | `ChannelDO` | Per-channel roster, modes, topic, fanout. |
324
-
325
- The class names in `wrangler.toml`'s `class_name` field MUST match the
326
- re-exports at the top of `apps/cf-worker/src/worker.ts:29`.
327
-
328
- ### 7.4 Migrations
329
-
330
- ```toml
331
- [[migrations]]
332
- tag = "v1"
333
- new_classes = ["ConnectionDO", "RegistryDO", "ChannelDO"]
334
- ```
335
-
336
- `tag` is the migration id (string, monotonically tracked by wrangler).
337
- `new_classes` declares the DO classes on first deploy. To **rename** or
338
- **delete** a class after data exists, append a new `[[migrations]]`
339
- block with `tag = "v2"` (etc.) and a `renamed_classes` or `deleted_classes`
340
- entry. See <https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/>.
341
-
342
- Both the default and staging environments carry their own migration
343
- list. Keep them in sync unless you intentionally diverge.
344
-
345
- ### 7.5 Custom domain (optional)
346
-
347
- To serve the Worker on `irc.example.com` instead of the workers.dev
348
- subdomain, add a route and bind a Cloudflare-managed DNS record:
349
-
350
- ```toml
351
- [[routes]]
352
- pattern = "irc.example.com/*"
353
- zone_name = "example.com"
354
- custom_domain = false # true if you'd rather use a Worker Custom Domain
355
- ```
356
-
357
- Custom domains are optional for staging and recommended for production
358
- (many IRC clients expect a stable, owned hostname). DNS records are
359
- managed separately in the dashboard.
360
-
361
- ---
362
-
363
- ## 8. Secrets
364
-
365
- **As of v1 the Worker consumes no secret bindings.** Server password,
366
- SASL `AccountStore` credentials, and oper creds land in later tickets
367
- (TICKET-046 / TICKET-047). When they ship, secrets will be set via:
368
-
369
- ```bash
370
- wrangler secret put SERVER_PASSWORD --env staging
371
- # → prompts for the value; stores it in the Workers secret store,
372
- # NOT in wrangler.toml or the repo.
373
- ```
374
-
375
- Secrets are visible to the Worker as plain `env.SECRET_NAME` bindings,
376
- but their values are never written to disk, the bundle, or the
377
- dashboard's variable listing. To rotate, run `wrangler secret put`
378
- again; to remove, `wrangler secret delete <NAME>`.
379
-
380
- **Never** put credentials, API tokens, or password hashes in
381
- `wrangler.toml`, in `[vars]`, or in committed files. The
382
- `wrangler.toml` header enforces this with a comment block (see lines
383
- 15-20).
384
-
385
- The CI deploy uses two **GitHub Actions secrets**, not Workers
386
- secrets:
387
-
388
- | Secret | Used by | Purpose |
389
- |-------------------------|--------------------------|--------------------------------------|
390
- | `CLOUDFLARE_API_TOKEN` | `deploy-cf.yml` | Authenticates `wrangler deploy`. |
391
- | `CLOUDFLARE_ACCOUNT_ID` | `deploy-cf.yml` | Disambiguates account context. |
392
-
393
- And one **Actions variable**:
394
-
395
- | Variable | Purpose |
396
- |----------------|------------------------------------------------------------|
397
- | `CF_SMOKE_URL` | `wss://` URL the smoke e2e step hits. If unset, smoke is skipped with a warning. |
398
-
399
- Configure both under repo **Settings → Secrets and variables → Actions**.
400
-
401
- ---
402
-
403
- ## 9. Sharding
404
-
405
- ### 9.1 How nick registry sharding works
406
-
407
- The `RegistryDO` fleet is sharded so no single DO becomes a hot spot
408
- for popular nicknames. Shard assignment is:
409
-
410
- ```
411
- shard = fnv1a32(lowercased(nick)) mod N
412
- ```
413
-
414
- Properties (see `packages/cf-adapter/src/sharding.ts` for full rationale):
415
-
416
- - **Deterministic** — every Worker instance computes the same shard for
417
- a given nick with zero coordination.
418
- - **Case-insensitive** — `Alice`, `alice`, and `ALICE` collide in the
419
- same shard, so the single-shard uniqueness check covers all RFC 1459
420
- case variants.
421
- - **Synchronous & dependency-free** — runs on every nick reservation,
422
- so no `await` and no external hash library.
423
-
424
- The shard id (`s0`, `s1`, …, `s31`) becomes the `idFromName()` key for
425
- that nick's `RegistryDO`. Two clients racing on `Alice` both route to
426
- `RegistryDO s7`, where DO single-threadedness makes the uniqueness
427
- invariant **structural** rather than optimistic.
428
-
429
- ### 9.2 `REGISTRY_SHARDS`
430
-
431
- The default fleet size is **32**, defined as
432
- `DEFAULT_REGISTRY_SHARDS` in
433
- `packages/cf-adapter/src/sharding.ts:42`. This is the value used by
434
- the production `CfRuntime` (`cf-runtime.ts:91`).
435
-
436
- > **Planned knob:** `sharding.ts` documents a future `REGISTRY_SHARDS`
437
- > environment variable (see the doc comment at line 39). That override
438
- > path is **not yet wired** through `Env` / `ConnectionDO` — the shard
439
- > count is currently a code-level default. Once TICKET-047 (config
440
- > schema) ships, this will become a true env var. Until then, changing
441
- > the shard count requires a code edit in `cf-runtime.ts`.
442
-
443
- ### 9.3 Re-sharding caveat
444
-
445
- **Changing `REGISTRY_SHARDS` after the DOs hold data is a migration.**
446
- Existing nick→connId mappings live in the *old* shard layout; the new
447
- layout will not find them. Plan a migration window:
448
-
449
- 1. Drain live connections (announce + reconnect window), or accept that
450
- in-flight nick reservations during the cutover can briefly succeed
451
- against both layouts.
452
- 2. Iterate the old shard range, read each `nick→connId` entry, and write
453
- it to the shard computed under the new `N`.
454
- 3. Verify with a sample of known nicks before re-enrolling write traffic.
455
-
456
- There is **no in-repo re-sharding tool** today (post-v1 work). For v1
457
- deployments the recommendation is: pick `REGISTRY_SHARDS` once, and
458
- prefer over-provisioning (64 or 128) if you anticipate >10k concurrent
459
- users — the per-shard cost is negligible because hibernated DOs are
460
- free to hold.
461
-
462
- ---
463
-
464
- ## 10. Cost notes
465
-
466
- Cloudflare bills Durable Objects on three dimensions (figures below are
467
- the public list price as of 2024-11; verify current pricing at
468
- <https://developers.cloudflare.com/durable-objects/platform/pricing/>):
469
-
470
- | Dimension | Notes for ServerlessIRCd |
471
- |----------------------------|-------------------------------------------------------|
472
- | **Duration** (GB-second) | Hibernation (`HibernatingWebSocketHandler`) drops idle connections to ~zero memory while asleep. A 10k-user deployment with most connections idle is the entire reason this adapter exists. |
473
- | **Requests** | Each WebSocket frame in/out is a request. PRIVMSG to a 1k-member channel fans out via the ChannelDO; each member's `ConnectionDO.deliver()` is one request. |
474
- | **DO storage** (MB-month) | ConnectionState is small (a few KB). Channel roster / modes are small per channel. The Nicks registry is the only fleet that grows linearly with MAU. |
475
-
476
- Practical numbers:
477
-
478
- - **Workers Free plan**: sufficient to bring up staging, exercise the
479
- smoke e2e, and validate a handful of concurrent connections. Not
480
- suitable for production traffic (request caps, no reserved DO
481
- compute).
482
- - **Workers Paid plan** (USD $5/mo base + usage): enough headroom for a
483
- small production network. The dominant cost driver at scale is
484
- **fanout requests on hot channels**, not connection count — thanks to
485
- hibernation. Plan accordingly for channels with >1k members.
486
-
487
- Other billable surfaces (all optional for v1):
488
-
489
- - **Workers KV** — not used today; planned for MOTD / config offload.
490
- - **D1** — not used today; planned for account/SASL persistence.
491
- - **Workers Analytics** — free tier covers basic dashboards.
492
-
493
- See PLAN §10 (Risk / Cost) for the scaling ceilings and post-v1
494
- mitigations (batched fanout, per-channel send-list cache).
495
-
496
- ---
497
-
498
- ## 11. CI/CD
499
-
500
- The GitHub Actions workflow at `.github/workflows/deploy-cf.yml`
501
- deploys staging on every push to `main` and replays the smoke e2e. It
502
- is the canonical deploy path — manual `pnpm deploy:cf:staging` is for
503
- iteration only.
504
-
505
- Steps performed by the workflow (in order):
506
-
507
- 1. Checkout (frozen lockfile).
508
- 2. Install pnpm 9 + Node 20.
509
- 3. `pnpm install --frozen-lockfile`.
510
- 4. `pnpm build` — builds all workspace packages.
511
- 5. `pnpm typecheck` and `pnpm test` — gate the deploy.
512
- 6. `pnpm deploy:cf:staging` — `wrangler deploy --env staging`.
513
- 7. Resolve the smoke URL from the `CF_SMOKE_URL` repo variable. If
514
- unset, the smoke step is skipped with a warning.
515
- 8. Run `node scripts/smoke.mjs --url "$SMOKE_URL"`. Failure fails the
516
- build.
517
-
518
- Concurrency is serialized via `concurrency.group: cf-staging` so two
519
- deploys cannot race the same DO namespace.
520
-
521
- Required repo configuration (under Settings → Secrets and variables →
522
- Actions):
523
-
524
- - **Secret `CLOUDFLARE_API_TOKEN`** — required.
525
- - **Secret `CLOUDFLARE_ACCOUNT_ID`** — required if the token's account
526
- context is ambiguous.
527
- - **Variable `CF_SMOKE_URL`** — set to the staging Worker's `wss://`
528
- URL. Without it, the smoke step silently no-ops.
529
-
530
- There is **no auto-deploy to production**. Production deploys are
531
- intentionally manual (`pnpm deploy:cf:prod` from a clean checkout on
532
- `main`).
533
-
534
- ---
535
-
536
- ## 12. Troubleshooting
537
-
538
- ### 12.1 `wrangler deploy` fails with `Migration tag has already been applied`
539
-
540
- The on-disk migration list in `wrangler.toml` doesn't match the live
541
- Worker state. If you previously deployed `tag = "v1"` and then edited
542
- the `[[migrations]]` block, wrangler refuses to proceed.
543
-
544
- - If you intentionally changed the DO schema, append a new
545
- `[[migrations]]` block with `tag = "v2"` (and `renamed_classes` /
546
- `deleted_classes` as needed). Never edit an already-applied `tag`.
547
- - If the staging namespace has stale state from an aborted experiment,
548
- you can destroy and recreate it from the dashboard (Workers & Pages →
549
- Durable Objects → namespaces). **Production namespaces must never be
550
- deleted** — that data is gone permanently.
551
-
552
- ### 12.2 `Cannot find module '@serverless-ircd/cf-adapter'`
553
-
554
- The workspace packages weren't built. Run `pnpm build` from the repo
555
- root before `wrangler deploy`. The Worker entry imports compiled
556
- `dist/` from `irc-core`, `irc-server`, and `cf-adapter`.
557
-
558
- ### 12.3 Smoke e2e times out waiting for `001`
559
-
560
- The connection upgrade succeeded but registration didn't complete.
561
- Common causes:
562
-
563
- - The deployed Worker is reachable but the DO threw on the first frame.
564
- Tail the logs: `wrangler tail --env staging` and reproduce.
565
- - The `MOTD_LINES` or `SERVER_NAME` var was deleted — `ConnectionDO`'s
566
- `serverConfig()` falls back to defaults, but a malformed value can
567
- break parsing. Check the `[env.staging.vars]` block.
568
- - Client connected over `ws://` to a Worker that requires `wss://`.
569
- Use the `wss://` URL the deploy step printed.
570
-
571
- ### 12.4 `wrangler tail` shows `alarm() threw`
572
-
573
- The DO alarm fired but the handler threw. The most likely cause is a
574
- stale persisted-state version after a code change; the serializer in
575
- `packages/cf-adapter/src/serialize.ts` carries `PERSISTED_STATE_VERSION`
576
- and will reject incompatible blobs. If this happens after an upgrade,
577
- ship a `deserialize` migration that upgrades the old version (or, on
578
- staging, clear the DO storage from the dashboard).
579
-
580
- ### 12.5 Nick reservation always fails
581
-
582
- A client's `NICK` command always returns `433 ERR_NICKNAMEINUSE`, even
583
- for nicknames that should be free. This happens when two deployments
584
- share a `RegistryDO` namespace but were supposed to be separate —
585
- e.g., staging and production point at the same DO class name. Verify
586
- that each environment has its own DO namespace (the Worker name
587
- differs: `serverless-ircd` vs `serverless-ircd-staging`, and wrangler
588
- allocates a distinct namespace per Worker).
589
-
590
- ### 12.6 High request cost / DO request spike
591
-
592
- Hot-channel fanout is the dominant cost driver. Each PRIVMSG to a
593
- channel of N members produces ~N `ConnectionDO.deliver()` RPC requests
594
- (one per member). Mitigations:
595
-
596
- - Cap channel size in application policy (no built-in cap today beyond
597
- `+l` mode).
598
- - For very hot announcement channels, consider a future batched-fanout
599
- path (post-v1 — PLAN §10).
600
-
601
- Reach for the Workers Analytics dashboard to identify the channel
602
- driving the spike before optimizing.
603
-
604
- ### 12.7 Lost connection state after a deploy
605
-
606
- DO storage persists across Worker code deploys by design. If state
607
- appears lost, either:
608
-
609
- - The DO namespace was deleted (irreversible — see 12.1).
610
- - A `[[migrations]]` rename or split moved the data to a different
611
- class id without a migration entry mapping the old class to the new.
612
- - The persisted-state version changed without a deserialize migration
613
- (see 12.4).
614
-
615
- For staging, the fastest recovery is usually to clear DO storage and
616
- re-test. For production, treat any migration tag change as a release
617
- blocker requiring a runbook entry.
618
-
619
- ---
620
-
621
- ## 13. Quick reference
622
-
623
- ```bash
624
- # One-time account setup
625
- npx wrangler login # or set CLOUDFLARE_API_TOKEN
626
-
627
- # Local dev
628
- pnpm install
629
- pnpm build
630
- pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
631
- node apps/cf-worker/scripts/smoke.mjs # smoke against localhost
632
-
633
- # Staging
634
- pnpm deploy:cf:staging
635
- node apps/cf-worker/scripts/smoke.mjs \
636
- --url wss://serverless-ircd-staging.<subdomain>.workers.dev
637
- npx wrangler tail --env staging # live logs
638
-
639
- # Production (manual only)
640
- pnpm deploy:cf:prod
641
- npx wrangler tail # default env = production
642
-
643
- # Tests / lint
644
- pnpm test # full workspace
645
- pnpm --filter @serverless-ircd/cf-adapter test
646
- pnpm --filter @serverless-ircd/cf-worker test
647
- pnpm lint
648
- ```
649
-
650
- Key files:
651
-
652
- | Path | What |
653
- |-----------------------------------------|----------------------------------------------|
654
- | `apps/cf-worker/wrangler.toml` | Deploy config, DO bindings, migrations. |
655
- | `apps/cf-worker/src/worker.ts` | Edge fetch handler; re-exports DO classes. |
656
- | `apps/cf-worker/scripts/smoke.mjs` | CI + local smoke e2e. |
657
- | `packages/cf-adapter/src/` | `ConnectionDO`, `RegistryDO`, `ChannelDO`, `CfRuntime`, sharding. |
658
- | `packages/cf-adapter/src/sharding.ts` | Shard function + `DEFAULT_REGISTRY_SHARDS`. |
659
- | `packages/cf-adapter/src/env.ts` | `Env` interface (binding contract). |
660
- | `.github/workflows/deploy-cf.yml` | Staging deploy + smoke e2e CI. |
package/docs/Home.md DELETED
@@ -1 +0,0 @@
1
- Welcome to the Wiki.