serverless-ircd 0.7.0 → 0.9.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 +7 -3
- package/.gitmodules +1 -1
- package/CHANGELOG.md +518 -29
- package/README.md +221 -161
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +186 -18
- package/apps/aws-stack/tests/stack.test.ts +400 -56
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/container-server.ts +21 -1
- package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
- package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
- package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
- package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/src/worker.ts +4 -4
- package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
- package/apps/cf-worker/tests/smoke.test.ts +5 -5
- package/apps/cf-worker/wrangler.test.toml +6 -6
- package/apps/cf-worker/wrangler.toml +7 -5
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +8 -0
- package/apps/local-cli/src/main.ts +16 -0
- package/apps/local-cli/src/server.ts +95 -31
- package/apps/local-cli/tests/config-loader.test.ts +14 -0
- package/apps/local-cli/tests/config-resolution.test.ts +65 -0
- package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
- package/apps/local-cli/tests/rehash.test.ts +147 -0
- package/apps/local-cli/tests/server-helpers.test.ts +63 -0
- package/apps/local-cli/tests/tcp.test.ts +89 -0
- package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
- package/apps/web/landing/index.html +237 -16
- package/apps/web/package.json +3 -2
- package/apps/web/scripts/build.mjs +46 -16
- package/apps/web/src/build-env.ts +1 -1
- package/apps/web/src/config-schema.ts +6 -6
- package/apps/web/src/render-docs.ts +292 -0
- package/apps/web/tests/build-smoke.test.ts +43 -14
- package/apps/web/tests/config-schema.test.ts +1 -1
- package/apps/web/tests/landing-content.test.ts +103 -0
- package/apps/web/tests/render-docs.test.ts +198 -0
- package/docs/AWS-Adapter-Architecture.md +3 -2
- package/docs/AWS-Deployment.md +21 -8
- package/docs/Cloudflare-Deployment-Guide.md +22 -6
- package/docs/PlanExtensions.md +113 -3
- package/docs/Release-Process.md +23 -13
- package/docs/Services.md +578 -0
- package/docs/WebClientGuide.md +32 -31
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +20 -1
- package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
- package/packages/aws-adapter/src/config-loader.ts +11 -0
- package/packages/aws-adapter/src/connection-counter.ts +89 -0
- package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
- package/packages/aws-adapter/src/handlers/connect.ts +55 -51
- package/packages/aws-adapter/src/handlers/default.ts +36 -4
- package/packages/aws-adapter/src/handlers/index.ts +15 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +25 -2
- package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
- package/packages/aws-adapter/src/index.ts +4 -0
- package/packages/aws-adapter/src/stats.ts +6 -1
- package/packages/aws-adapter/src/tables.ts +34 -4
- package/packages/aws-adapter/tests/aws-harness.ts +3 -0
- package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
- package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
- package/packages/aws-adapter/tests/connect.test.ts +158 -32
- package/packages/aws-adapter/tests/connection-counter.test.ts +144 -0
- package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
- package/packages/aws-adapter/tests/global-setup.ts +28 -1
- package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
- package/packages/aws-adapter/tests/handlers.test.ts +105 -3
- package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
- package/packages/aws-adapter/tests/sweeper.test.ts +20 -0
- package/packages/aws-adapter/tests/tables.test.ts +6 -1
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/src/config-loader.ts +11 -0
- package/packages/cf-adapter/src/connection-do.ts +130 -8
- package/packages/cf-adapter/src/d1-services-store.ts +703 -0
- package/packages/cf-adapter/src/env.ts +8 -0
- package/packages/cf-adapter/src/index.ts +5 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
- package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +130 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
- package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
- package/packages/cf-adapter/tests/serialize.test.ts +1 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
- package/packages/irc-core/src/commands/account-auth.ts +200 -0
- package/packages/irc-core/src/commands/chanserv.ts +1166 -0
- package/packages/irc-core/src/commands/hostserv.ts +522 -0
- package/packages/irc-core/src/commands/index.ts +13 -0
- package/packages/irc-core/src/commands/join.ts +171 -9
- package/packages/irc-core/src/commands/markread.ts +202 -0
- package/packages/irc-core/src/commands/memoserv.ts +319 -0
- package/packages/irc-core/src/commands/mode.ts +96 -4
- package/packages/irc-core/src/commands/nickserv.ts +402 -0
- package/packages/irc-core/src/commands/oper.ts +18 -1
- package/packages/irc-core/src/commands/operserv.ts +346 -0
- package/packages/irc-core/src/commands/pre-away.ts +3 -1
- package/packages/irc-core/src/commands/privmsg.ts +42 -0
- package/packages/irc-core/src/commands/read-marker.ts +8 -8
- package/packages/irc-core/src/commands/registration.ts +78 -12
- package/packages/irc-core/src/commands/sasl.ts +18 -49
- package/packages/irc-core/src/commands/service-aliases.ts +52 -0
- package/packages/irc-core/src/commands/tagmsg.ts +41 -6
- package/packages/irc-core/src/commands/topic.ts +60 -10
- package/packages/irc-core/src/config.ts +36 -5
- package/packages/irc-core/src/effects.ts +56 -1
- package/packages/irc-core/src/ports.ts +1653 -84
- package/packages/irc-core/src/protocol/numerics.ts +8 -0
- package/packages/irc-core/src/state/channel.ts +38 -1
- package/packages/irc-core/src/state/connection.ts +25 -1
- package/packages/irc-core/src/types.ts +48 -12
- package/packages/irc-core/tests/commands/chanserv.test.ts +2335 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
- package/packages/irc-core/tests/commands/hostserv.test.ts +1006 -0
- package/packages/irc-core/tests/commands/join.test.ts +572 -1
- package/packages/irc-core/tests/commands/markread.test.ts +361 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
- package/packages/irc-core/tests/commands/mode.test.ts +381 -2
- package/packages/irc-core/tests/commands/nickserv.test.ts +990 -0
- package/packages/irc-core/tests/commands/oper.test.ts +13 -0
- package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
- package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
- package/packages/irc-core/tests/commands/registration.test.ts +1015 -20
- package/packages/irc-core/tests/commands/sasl.test.ts +229 -12
- package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
- package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
- package/packages/irc-core/tests/commands/topic.test.ts +94 -2
- package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
- package/packages/irc-core/tests/config.test.ts +49 -5
- package/packages/irc-core/tests/effects.test.ts +19 -0
- package/packages/irc-core/tests/message-store.test.ts +63 -0
- package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
- package/packages/irc-core/tests/services-store.test.ts +1289 -0
- package/packages/irc-core/tests/state/channel.test.ts +3 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +121 -16
- package/packages/irc-server/src/dispatch.ts +94 -7
- package/packages/irc-server/src/routing.ts +19 -0
- package/packages/irc-server/tests/actor.test.ts +988 -15
- package/packages/irc-server/tests/dispatch.test.ts +270 -2
- package/packages/irc-server/tests/routing.test.ts +6 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +37 -8
- package/packages/irc-test-support/src/index.ts +1 -0
- package/packages/irc-test-support/src/scenarios.ts +21 -6
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +51 -0
- package/packages/irc-test-support/vitest.config.ts +6 -1
- package/tools/ci-hardening/package.json +1 -1
- package/tools/load-test/package.json +1 -1
- package/tools/load-test/src/client.ts +13 -13
- package/tools/load-test/tests/client.test.ts +258 -2
- package/tools/load-test/tests/config.test.ts +39 -0
- package/tools/load-test/tests/harness.test.ts +21 -0
- package/tools/load-test/tests/metrics.test.ts +7 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
- package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +53 -2
- package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +0 -18
- package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
package/README.md
CHANGED
|
@@ -6,46 +6,58 @@ platform-agnostic core, and two thin adapters run it on **Cloudflare Workers**
|
|
|
6
6
|
|
|
7
7
|
One TypeScript codebase. Two serverless substrates.
|
|
8
8
|
|
|
9
|
-
> **Status:** **
|
|
10
|
-
>
|
|
11
|
-
>
|
|
9
|
+
> **Status:** **preview.** The pure protocol core, the `IrcRuntime`
|
|
10
|
+
> port + in-memory runtime, a runnable local CLI server, the
|
|
11
|
+
> **Cloudflare Workers** adapter, and the **AWS** (API Gateway
|
|
12
12
|
> WebSocket + Lambda + DynamoDB + CDK) adapter are all functional and
|
|
13
|
-
> deployed to staging.
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
>
|
|
19
|
-
>
|
|
20
|
-
>
|
|
21
|
-
>
|
|
22
|
-
>
|
|
23
|
-
>
|
|
24
|
-
> `
|
|
25
|
-
> `
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
>
|
|
30
|
-
>
|
|
31
|
-
>
|
|
32
|
-
>
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
37
|
-
>
|
|
38
|
-
>
|
|
39
|
-
>
|
|
13
|
+
> deployed to staging.
|
|
14
|
+
>
|
|
15
|
+
> **Dual transport.** Both adapters speak a real `irc+tls :6697`
|
|
16
|
+
> (TLS-over-TCP) surface for stock IRC clients — Cloudflare via
|
|
17
|
+
> Spectrum + a Container origin, AWS via a Network Load Balancer +
|
|
18
|
+
> Lambda streaming — alongside the WebSocket path, plus **SASL
|
|
19
|
+
> `EXTERNAL` via mTLS** and a **Cloudflare D1-backed SASL account
|
|
20
|
+
> store**.
|
|
21
|
+
>
|
|
22
|
+
> **Complete protocol surface.** The deferred IRC verbs (`KILL`,
|
|
23
|
+
> `REHASH`, `LUSERS` + `STATS`, `TRACE`, `WALLOPS`, `SETNAME`) are
|
|
24
|
+
> implemented, and the S2S (`CONNECT` / `SQUIT` / `LINKS`) and obsolete
|
|
25
|
+
> RFC 2812 (`SERVICE` / `SUMMON` / `USERS`) verbs are formally dropped.
|
|
26
|
+
>
|
|
27
|
+
> **IRCv3 extensions.** Negotiated caps include `account-notify`,
|
|
28
|
+
> `msgid`, `standard-replies`, `MONITOR`, `labeled-response`, `sts`,
|
|
29
|
+
> `draft/typing`, `draft/multiline`, `draft/read-marker`, and
|
|
30
|
+
> `draft/pre-away`, plus the ISUPPORT tokens for each and the
|
|
31
|
+
> read-only user mode `S` (TLS connected).
|
|
32
|
+
>
|
|
33
|
+
> **Web client.** A vendored **Kiwi IRC** SPA is served at
|
|
34
|
+
> `/webclient/` and a static landing page at `/` directly by the
|
|
35
|
+
> Cloudflare Worker, with **Cross-Site WebSocket Hijacking (CSWSH)
|
|
36
|
+
> defense** wired into the WS upgrade path (same-origin auto-derive by
|
|
37
|
+
> default, optional `WEB_ORIGINS` allowlist for cross-origin
|
|
38
|
+
> deploys). The `docs/` wiki is rendered to standalone HTML at
|
|
39
|
+
> `/docs/`.
|
|
40
|
+
>
|
|
41
|
+
> **Integrated IRC services.** NickServ, ChanServ, HostServ,
|
|
42
|
+
> OperServ, and MemoServ run inside the daemon (no separate services
|
|
43
|
+
> process, no S2S link) backed by a `ServicesStore` port with
|
|
44
|
+
> **persistent D1 (Cloudflare) and DynamoDB (AWS) backends**
|
|
45
|
+
> (write-behind, surviving redeploys). The SASL `AccountStore` and
|
|
46
|
+
> NickServ accounts are **unified** under one scrypt-hashed credential
|
|
47
|
+
> store, `PASS <nick>:<password>` identifies at or after registration,
|
|
48
|
+
> and `draft/read-marker` / `draft/pre-away` persist through the
|
|
49
|
+
> `ServicesStore`.
|
|
50
|
+
>
|
|
51
|
+
> See `CHANGELOG.md` for the per-release manifests.
|
|
40
52
|
|
|
41
53
|
---
|
|
42
54
|
|
|
43
55
|
## Why
|
|
44
56
|
|
|
45
57
|
IRC servers have historically been long-running stateful processes. This
|
|
46
|
-
project
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
project moves the protocol logic into a set of **pure reducers** so that
|
|
59
|
+
the only platform-specific code is the side-effect layer (transports,
|
|
60
|
+
registries, fanout). The consequences:
|
|
49
61
|
|
|
50
62
|
- Every command handler is a trivial unit test: arrange state, apply a message,
|
|
51
63
|
assert the new state and emitted effects.
|
|
@@ -57,7 +69,7 @@ reducers** and the only platform-specific code is the side-effect plumbing
|
|
|
57
69
|
|
|
58
70
|
## Architecture
|
|
59
71
|
|
|
60
|
-
Hexagonal / ports-and-adapters. The core
|
|
72
|
+
Hexagonal / ports-and-adapters. The core implements the IRC protocol; adapters handle I/O.
|
|
61
73
|
|
|
62
74
|
```
|
|
63
75
|
┌─────────────────────────────────────────────────────────────────┐
|
|
@@ -97,7 +109,7 @@ function. Both feed the same `ConnectionActor` through a `Transport`
|
|
|
97
109
|
seam (`WsTextFrameTransport` vs. `TcpByteStreamTransport`); the
|
|
98
110
|
parser/reducer/dispatch pipeline is identical.
|
|
99
111
|
|
|
100
|
-
###
|
|
112
|
+
### Design: pure reducers + location-of-authority
|
|
101
113
|
|
|
102
114
|
Each command handler is a pure function:
|
|
103
115
|
|
|
@@ -122,6 +134,7 @@ Each reducer runs in whichever authority **owns** the state it mutates:
|
|
|
122
134
|
| PRIVMSG/NOTICE to channel | Channel entity | Fanout (reads roster) |
|
|
123
135
|
| Nick collision check | Registry entity | Nick → Connection map |
|
|
124
136
|
| NAMES / WHO / WHOIS | Reads snapshots | (no mutation) |
|
|
137
|
+
| Services (`PRIVMSG NickServ`/`ChanServ`/…) | Connection entity | `ServicesStore` (account/channel/vhost/memo state) |
|
|
125
138
|
|
|
126
139
|
---
|
|
127
140
|
|
|
@@ -130,7 +143,7 @@ Each reducer runs in whichever authority **owns** the state it mutates:
|
|
|
130
143
|
```
|
|
131
144
|
ServerlessIRCd/
|
|
132
145
|
├── packages/
|
|
133
|
-
│ ├── irc-core/ pure protocol + reducers
|
|
146
|
+
│ ├── irc-core/ pure protocol + reducers
|
|
134
147
|
│ ├── irc-server/ orchestration, IrcRuntime port, dispatch
|
|
135
148
|
│ ├── in-memory-runtime/ reference runtime (used by tests + local CLI)
|
|
136
149
|
│ ├── irc-test-support/ parametrized IRC scenario suite + harness seam
|
|
@@ -140,11 +153,11 @@ ServerlessIRCd/
|
|
|
140
153
|
│ ├── cf-worker/ worker entry, DO migrations, bindings, [assets] + CSWSH gate
|
|
141
154
|
│ ├── cf-tcp-container/ Spectrum + Container origin for irc+tls :6697
|
|
142
155
|
│ ├── aws-stack/ CDK stack (APIGW WS + NLB + Lambda streaming + DynamoDB)
|
|
143
|
-
│ ├── web/ vendored Kiwi IRC SPA (→ /
|
|
156
|
+
│ ├── web/ vendored Kiwi IRC SPA (→ /webclient/) + static landing page (→ /)
|
|
144
157
|
│ └── local-cli/ runnable WS + TCP server using in-memory-runtime
|
|
145
158
|
├── tools/
|
|
146
159
|
│ ├── tcp-ws-forwarder/ local TCP↔ws/wss bridge for stock IRC clients
|
|
147
|
-
│ ├── load-test/ synthetic WebSocket IRC client
|
|
160
|
+
│ ├── load-test/ synthetic WebSocket IRC client pool (10k conns, p50/p95/p99, drop rate)
|
|
148
161
|
│ ├── ci-hardening/ coverage-gate + mutation-config validators
|
|
149
162
|
│ ├── seed-aws-accounts.ts scrypt-hash SASL PLAIN accounts into DynamoDB
|
|
150
163
|
│ └── seed-cf-accounts.ts scrypt-hash SASL PLAIN accounts into Cloudflare D1
|
|
@@ -201,7 +214,7 @@ needs the upstream sources (`git submodule update --init apps/web/upstream`)
|
|
|
201
214
|
and `yarn` (Kiwi ships a `yarn.lock`; `corepack enable` provides it):
|
|
202
215
|
|
|
203
216
|
```bash
|
|
204
|
-
pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/
|
|
217
|
+
pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/webclient/ (/webclient/)
|
|
205
218
|
```
|
|
206
219
|
|
|
207
220
|
Coverage reports are written to `packages/*/coverage/`. CI (`.github/workflows/ci.yml`)
|
|
@@ -223,7 +236,7 @@ that share one runtime, so a TCP client and a WS client can see each other
|
|
|
223
236
|
|
|
224
237
|
- a **WebSocket** listener (the serverless transport) on `--port`, and
|
|
225
238
|
- an **RFC-style TCP** listener on `--tcp-port` (default `<port> + 1`) so
|
|
226
|
-
real IRC clients (WeeChat, HexChat, irssi, …) can
|
|
239
|
+
real IRC clients (WeeChat, HexChat, irssi, …) can connect directly.
|
|
227
240
|
|
|
228
241
|
First build the workspace (the CLI runs from compiled `dist/`):
|
|
229
242
|
|
|
@@ -256,11 +269,60 @@ On startup it logs a JSON line like:
|
|
|
256
269
|
| `--motd-file <p>` | built-in | Read MOTD lines from this file (one per line). |
|
|
257
270
|
| `--server-name <h>`| `irc.localhost` | Server hostname advertised in `001`/`005`. Production MUST override. |
|
|
258
271
|
| `--network-name <n>`| `LocalNet` | Network name advertised in `005 NETWORK=…`. |
|
|
272
|
+
| `--server-password <p>`| | Server-password gate. When set, every connection must supply `PASS <p>` (or SASL-identify) before `001`. Treat as a secret. |
|
|
259
273
|
| `-h, --help` | | Show help and exit. |
|
|
260
274
|
|
|
261
275
|
Pass `--host 0.0.0.0` to expose the server on all interfaces. `SIGINT` /
|
|
262
276
|
`SIGTERM` perform a graceful shutdown (closes active sockets, then exits).
|
|
263
277
|
|
|
278
|
+
### Server password (`SERVER_PASSWORD` / `--server-password`)
|
|
279
|
+
|
|
280
|
+
All three adapters (Cloudflare Worker, AWS Lambda, local CLI) support an
|
|
281
|
+
optional **server-password gate**. When the knob is set, every connection
|
|
282
|
+
must supply the matching `PASS <value>` before `001 RPL_WELCOME` is
|
|
283
|
+
emitted; when unset or empty the gate is disabled (the default).
|
|
284
|
+
|
|
285
|
+
| Adapter | Knob |
|
|
286
|
+
|----------------|-------------------------------------------------------------------------------|
|
|
287
|
+
| Cloudflare | `wrangler secret put SERVER_PASSWORD` (Workers secret; never a `[vars]` entry). |
|
|
288
|
+
| AWS | `SERVER_PASSWORD` Lambda env var (sourced from Secrets Manager / SSM). |
|
|
289
|
+
| local CLI | `--server-password <p>` flag, or `serverPassword` on `StartServerOptions`. |
|
|
290
|
+
|
|
291
|
+
On a mismatched/missing `PASS` the server emits `464 ERR_PASSWDMISMATCH`
|
|
292
|
+
and disconnects with reason `Bad Password`. **SASL short-circuit:** a
|
|
293
|
+
connection that has authenticated an account via SASL
|
|
294
|
+
(`AUTHENTICATE PLAIN` / `EXTERNAL`) is exempt — the reducer's gate
|
|
295
|
+
treats `state.account !== undefined` as already authorised, so a
|
|
296
|
+
deployment with both `SERVER_PASSWORD` and configured SASL accounts
|
|
297
|
+
does not need to hand the shared password to identified users. The
|
|
298
|
+
server-wide gate is a shared deployment secret (never log it); per-user
|
|
299
|
+
credentials still go through the hashed SASL account store
|
|
300
|
+
(`HashedAccountStore` / D1 / DynamoDB), which is unaffected.
|
|
301
|
+
|
|
302
|
+
**PASS-based account login:** in addition to SASL, a client may identify
|
|
303
|
+
to its NickServ account by sending `PASS <nick>:<password>` (the same
|
|
304
|
+
`<nick>:<password>` literal the `SASL_ACCOUNTS` seed tooling uses).
|
|
305
|
+
When an `AccountStore` is configured, `NICK alice` + `PASS alice:hunter2`
|
|
306
|
+
+ `USER …` verifies the credentials against the account store and, on
|
|
307
|
+
success, logs the connection in as `alice` — emitting `900 RPL_LOGGEDIN`
|
|
308
|
+
+ `903 RPL_SASLSUCCESS` (the same "account is set" numerics SASL uses)
|
|
309
|
+
before `001 RPL_WELCOME`, stamping user mode `+r`, and running the same
|
|
310
|
+
read-marker / away / memo replay as a SASL login. The login is also
|
|
311
|
+
honoured when `PASS <nick>:<password>` arrives **after** registration
|
|
312
|
+
has completed (e.g. a client that sent `NICK`+`USER` before `PASS`, or
|
|
313
|
+
any `PASS` arriving after `CAP END` resolved): the same verify +
|
|
314
|
+
`applyAccountSuccess` pipeline runs, emitting `900`/`903` and stamping
|
|
315
|
+
`+r` in-band. The nick left of the `:` must match the connection's
|
|
316
|
+
current nick; a bare `PASS <value>` with no colon is always treated as
|
|
317
|
+
a server-password candidate (silent no-op once registration has
|
|
318
|
+
completed), never as account credentials. A wrong password or unknown
|
|
319
|
+
nick is indistinguishable (no `904`/`464` from the auth path) and the
|
|
320
|
+
connection simply proceeds un-identified when no server password is
|
|
321
|
+
set. This composes with the gate above by precedence: an
|
|
322
|
+
already-identified connection (SASL or PASS-auth) satisfies the gate;
|
|
323
|
+
a `<nick>:<password>` that fails verify never matches a bare shared
|
|
324
|
+
secret, so a server with both configured still rejects it with `464`.
|
|
325
|
+
|
|
264
326
|
### Connecting
|
|
265
327
|
|
|
266
328
|
The WebSocket listener speaks WebSocket text frames (one IRC message per
|
|
@@ -283,9 +345,9 @@ given.
|
|
|
283
345
|
The local CLI ships a built-in TCP listener, so no bridge is needed for local
|
|
284
346
|
development. Deployed stacks (Cloudflare Workers and AWS API Gateway) are
|
|
285
347
|
WebSocket-only, so a stock TCP IRC client (WeeChat, HexChat, irssi, …) cannot
|
|
286
|
-
|
|
348
|
+
connect to them directly. `tools/tcp-ws-forwarder` is a local bridge for that case:
|
|
287
349
|
it listens on a TCP port and, for each connection, opens one WebSocket to a
|
|
288
|
-
`ws://` / `wss://` target and
|
|
350
|
+
`ws://` / `wss://` target and forwards IRC lines both directions — reassembling the TCP
|
|
289
351
|
byte stream into one message per WS frame outbound, and normalizing inbound
|
|
290
352
|
frames back to canonical CRLF.
|
|
291
353
|
|
|
@@ -330,9 +392,9 @@ against any line-oriented WebSocket endpoint.
|
|
|
330
392
|
|
|
331
393
|
## Load testing (`tools/load-test`)
|
|
332
394
|
|
|
333
|
-
A synthetic WebSocket IRC client
|
|
395
|
+
A synthetic WebSocket IRC client pool. It opens N
|
|
334
396
|
connections to a `ws://` / `wss://` target, registers each (NICK/USER),
|
|
335
|
-
joins a channel, and optionally chats. Per-
|
|
397
|
+
joins a channel, and optionally chats. Per-stage **p50 / p95 / p99
|
|
336
398
|
latency** (connect / register / join / message) and the **drop rate**
|
|
337
399
|
are captured, then printed as a markdown report to stdout (and written
|
|
338
400
|
to `--report <path>` when given).
|
|
@@ -379,12 +441,12 @@ report).
|
|
|
379
441
|
|
|
380
442
|
## Web client (`apps/web`)
|
|
381
443
|
|
|
382
|
-
A vendored **Kiwi IRC** SPA served at `/
|
|
444
|
+
A vendored **Kiwi IRC** SPA served at `/webclient/` and a static project
|
|
383
445
|
landing page served at `/`, both baked out of `apps/web/dist/` and
|
|
384
446
|
served directly by the Cloudflare Worker via its `[assets]` binding.
|
|
385
|
-
The browser opens a native `wss://`
|
|
447
|
+
The browser opens a native `wss://` directly to the Worker's
|
|
386
448
|
IRC-over-WebSocket endpoint — no proxy, gateway, or transport
|
|
387
|
-
adaptation in the
|
|
449
|
+
adaptation in the request path. See `docs/WebClientGuide.md` for the
|
|
388
450
|
end-to-end contributor/operator guide.
|
|
389
451
|
|
|
390
452
|
Build the SPA + landing page (needs the submodule + `yarn`, provided by
|
|
@@ -392,12 +454,22 @@ Build the SPA + landing page (needs the submodule + `yarn`, provided by
|
|
|
392
454
|
|
|
393
455
|
```bash
|
|
394
456
|
git submodule update --init apps/web/upstream # one-time per clone
|
|
457
|
+
git submodule update --init docs # one-time per clone (docs site)
|
|
395
458
|
pnpm --filter web build:staging # or :prod / default
|
|
396
459
|
# → apps/web/dist/index.html (landing page, served at /)
|
|
397
|
-
# → apps/web/dist/
|
|
398
|
-
# → apps/web/dist/
|
|
460
|
+
# → apps/web/dist/webclient/index.html (Kiwi SPA, served at /webclient/)
|
|
461
|
+
# → apps/web/dist/webclient/static/config.json (baked, env-specific)
|
|
462
|
+
# → apps/web/dist/docs/<slug>.html (rendered docs/, served at /docs/)
|
|
399
463
|
```
|
|
400
464
|
|
|
465
|
+
The build also renders the `docs/` submodule (the Gitea wiki, 23 GFM
|
|
466
|
+
markdown files) to standalone HTML under `apps/web/dist/docs/` so the
|
|
467
|
+
existing `[assets]` binding serves them at `/docs/<slug>.html` (and
|
|
468
|
+
`/docs/` for the index — `Home.md` becomes `index.html`). Requires the
|
|
469
|
+
`docs/` submodule checkout; the build fails loudly with the recovery
|
|
470
|
+
command if it is missing or empty (mirroring the Kiwi `upstream/` guard).
|
|
471
|
+
`Home.md` is the docs landing page; `ADR-Index.md` lists every ADR.
|
|
472
|
+
|
|
401
473
|
Then run everything locally (Worker serves SPA + landing page + WS on
|
|
402
474
|
one origin):
|
|
403
475
|
|
|
@@ -405,7 +477,8 @@ one origin):
|
|
|
405
477
|
pnpm build # workspace packages
|
|
406
478
|
pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
|
|
407
479
|
# / → landing page
|
|
408
|
-
# /
|
|
480
|
+
# /webclient/ → Kiwi SPA (opens ws://localhost:8787/)
|
|
481
|
+
# /docs/ → rendered docs/ (Home → index, per-page HTML)
|
|
409
482
|
# /health → plaintext liveness
|
|
410
483
|
```
|
|
411
484
|
|
|
@@ -496,84 +569,33 @@ port and an `IdFactory` port are injected so tests are fully deterministic.
|
|
|
496
569
|
|
|
497
570
|
## Roadmap
|
|
498
571
|
|
|
499
|
-
A v1 ships when
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
- **
|
|
511
|
-
|
|
512
|
-
- **
|
|
513
|
-
|
|
514
|
-
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
mTLS → SASL `EXTERNAL`, transport-parametrized contract suite, ADR-009.
|
|
523
|
-
- **Phase 7 — Load testing & compatibility sweep.** 🔄 partial as of
|
|
524
|
-
**v0.6.0**: the `tools/load-test` 10k-connection harness is in-tree;
|
|
525
|
-
the formal 10k-connection report per platform and the client
|
|
526
|
-
compatibility matrix (WeeChat / HexChat / IRCCloud / TheLounge) are
|
|
527
|
-
still pending.
|
|
528
|
-
- **Phase 8 — PLAN-FIXES remediation.** ✅ landed across v0.3.0–v0.4.0
|
|
529
|
-
(MessageStore wired into all three adapters, AccountStore end-to-end,
|
|
530
|
-
the DynamoDB `Accounts` table decided, CF enumeration RPCs, AWS QUIT
|
|
531
|
-
fanout, `OPER` / `TAGMSG` / `WHOWAS`, RFC 1459 case-mapping, real
|
|
532
|
-
MOTD, config-driven server identity, AWS max-clients admission,
|
|
533
|
-
CF D1 SASL store, and a sweep of dead-code / stale-comment cleanup).
|
|
534
|
-
- **Phase 9 — Runtime & tooling baseline.** ✅ landed in **v0.4.0**
|
|
535
|
-
(Node ≥ 24 / pnpm 11).
|
|
536
|
-
- **Phase 10 — IRCv3 WebSocket extension.** ✅ landed in **v0.4.0**:
|
|
537
|
-
`text.ircv3.net` / `binary.ircv3.net` subprotocol negotiation +
|
|
538
|
-
per-message framing across the CF Worker, AWS APIGW, local CLI, and
|
|
539
|
-
`tcp-ws-forwarder` (510-byte budget, `1009`/`1003` close codes,
|
|
540
|
-
legacy fallback).
|
|
541
|
-
- **Phase 11 — Deferred IRC verbs.** ✅ landed in **v0.5.0**: `KILL`,
|
|
542
|
-
`REHASH`, `LUSERS` + `STATS`, `TRACE`, `WALLOPS`, `SETNAME`, plus
|
|
543
|
-
the formal drop of the S2S (`CONNECT`/`SQUIT`/`LINKS`) and obsolete
|
|
544
|
-
RFC 2812 (`SERVICE`/`SUMMON`/`USERS`) verbs. The protocol surface is
|
|
545
|
-
feature-complete for modern clients.
|
|
546
|
-
- **Phase 12 — IRCv3 extension sweep.** ✅ landed in **v0.6.0**: ten
|
|
547
|
-
negotiated caps (`account-notify`, `msgid`, `standard-replies`,
|
|
548
|
-
`MONITOR`, `labeled-response`, `sts`, `draft/typing`,
|
|
549
|
-
`draft/multiline`, `draft/read-marker`, `draft/pre-away`), the
|
|
550
|
-
ISUPPORT tokens for the new caps (`MONITOR`/`MULTILINE`/`TYPING`/
|
|
551
|
-
`STATUSMSG`/`EXTBAN`/`ACCOUNTEXTBAN`), the read-only user mode `S`
|
|
552
|
-
(TLS connected), and the async push fanout seam (Registry hooks →
|
|
553
|
-
`MONITOR` online/offline, `BroadcastWallops`-style caps-gated
|
|
554
|
-
delivery).
|
|
555
|
-
- **Phase 13 — IRC services.** ⬳ pending: `ServicesStore` port +
|
|
556
|
-
NickServ / ChanServ / HostServ / OperServ / MemoServ. Adapter
|
|
557
|
-
backends (D1 / DynamoDB) ship alongside; until then
|
|
558
|
-
`draft/read-marker` and `draft/pre-away` use in-memory reference
|
|
559
|
-
stores and do not survive an adapter restart.
|
|
560
|
-
- **Phase 14 — Web client.** 🔄 partial as of **v0.7.0**: a vendored
|
|
561
|
-
Kiwi IRC SPA is served at `/app/` and a static landing page at `/`
|
|
562
|
-
by the Cloudflare Worker via an `[assets]` binding, with a Zod-
|
|
563
|
-
validated per-env config matrix and **CSWSH defense** (same-origin
|
|
564
|
-
auto-derive + optional `WEB_ORIGINS` allowlist). The Playwright
|
|
565
|
-
headless-browser e2e is the remaining open ticket.
|
|
566
|
-
See `docs/WebClientGuide.md` for the contributor/operator guide.
|
|
567
|
-
- **Coverage hardening.** 🔄 partial as of **v0.7.0**: `aws-adapter`
|
|
568
|
-
and `aws-stack` clear the 90% gate (v0.6.0); the CF packages run
|
|
569
|
-
under istanbul (v0.6.0). The remaining packages (`cf-adapter`,
|
|
570
|
-
`local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
|
|
571
|
-
`irc-test-support`, `web`) are **above the gate but below 100%** —
|
|
572
|
-
follow-ups drive each to 100%.
|
|
572
|
+
A v1 ships when the parametrized contract suite passes against every
|
|
573
|
+
runtime, the client compatibility matrix is signed off, and every
|
|
574
|
+
package clears its coverage gate. Per-release manifests live in
|
|
575
|
+
`CHANGELOG.md`.
|
|
576
|
+
|
|
577
|
+
Active follow-ups:
|
|
578
|
+
|
|
579
|
+
- **Client compatibility sweep** — formal sign-off on ≥3 reference
|
|
580
|
+
clients (WeeChat / HexChat / IRCCloud / TheLounge) against both
|
|
581
|
+
deployed adapters, on both the WebSocket and `irc+tls :6697`
|
|
582
|
+
surfaces.
|
|
583
|
+
- **Web client e2e** — Playwright headless-browser e2e exercising the
|
|
584
|
+
vendored Kiwi IRC SPA against a deployed stack.
|
|
585
|
+
- **Coverage hardening** — `aws-adapter` and `aws-stack` clear the 90%
|
|
586
|
+
gate; the CF packages run under istanbul. The remaining packages
|
|
587
|
+
(`cf-adapter`, `local-cli`, `load-test`, `cf-tcp-container`,
|
|
588
|
+
`tcp-ws-forwarder`, `irc-test-support`, `web`) sit above the gate
|
|
589
|
+
but below 100%; follow-ups drive each to full coverage. `irc-core`,
|
|
590
|
+
`irc-server`, and `in-memory-runtime` are at 100%.
|
|
591
|
+
- **Persistent ChanServ ban list** — ban masks currently live on
|
|
592
|
+
`ChannelState.banMasks` and do not survive an empty-recreate of a
|
|
593
|
+
channel. Extending `ServicesStore` with a persistent ban list is the
|
|
594
|
+
documented next step.
|
|
573
595
|
|
|
574
596
|
---
|
|
575
597
|
|
|
576
|
-
## Protocol scope
|
|
598
|
+
## Protocol scope
|
|
577
599
|
|
|
578
600
|
**Core (RFC 1459/2812 subset):** registration (`NICK`/`USER`/`CAP`/`PASS`),
|
|
579
601
|
`PING`/`PONG`, `QUIT`, `JOIN`, `PART`, `PRIVMSG`, `NOTICE`, `MODE` (user +
|
|
@@ -590,14 +612,59 @@ disconnect), `REHASH` (config reload via `IrcRuntime.reloadConfig()`),
|
|
|
590
612
|
`WALLOPS` (global broadcast to every `+w` user).
|
|
591
613
|
|
|
592
614
|
(Out of scope: the S2S verbs `CONNECT`/`SQUIT`/`LINKS` and the obsolete
|
|
593
|
-
RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped**
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
**Channel modes:** `o v b i k l t n m s p
|
|
615
|
+
RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped** —
|
|
616
|
+
they return `421 ERR_UNKNOWNCOMMAND`, and their reserved numerics have
|
|
617
|
+
been removed. S2S linking is a stated non-goal.)
|
|
618
|
+
|
|
619
|
+
**Channel modes:** `o v b i k l t n m s p`, plus the
|
|
620
|
+
services-derived modes `r` (registered), `R` (block unidentified
|
|
621
|
+
join/message), `M` (moderated-identified) — settable only via ChanServ,
|
|
622
|
+
not via `MODE`.
|
|
598
623
|
**User modes:** `i`, `o` (local only), `w`, `s`, and the read-only `S`
|
|
599
624
|
(TLS connected — set by the transport at registration, surfaced in WHOIS
|
|
600
|
-
via `276 RPL_WHOISSECURE`, not settable via `MODE`)
|
|
625
|
+
via `276 RPL_WHOISSECURE`, not settable via `MODE`) and the read-only
|
|
626
|
+
`r` (registered — set by NickServ `IDENTIFY` / SASL login, surfaced as
|
|
627
|
+
`+r`, not settable via `MODE`).
|
|
628
|
+
|
|
629
|
+
**Integrated IRC services:** the service nicks `NickServ`, `ChanServ`,
|
|
630
|
+
`HostServ`, `OperServ`, and `MemoServ` are reserved and routed to
|
|
631
|
+
dedicated pure reducers when a `ServicesStore` is bound — there is **no
|
|
632
|
+
separate services process and no S2S link**. Each service also accepts a
|
|
633
|
+
**shortcut verb** (`/NICKSERV`, `/NS`, `/CS`, `/HS`, `/MS`, `/OS`, …)
|
|
634
|
+
that the daemon rewrites to the equivalent `PRIVMSG <Service> :<args>`
|
|
635
|
+
for clients with a dedicated slash-command UX. Reached via `PRIVMSG
|
|
636
|
+
<Service> :<subcommand>`:
|
|
637
|
+
|
|
638
|
+
- **NickServ** — `REGISTER` / `IDENTIFY` (alias: `ID`) / `DROP` / `INFO`
|
|
639
|
+
(owner + oper only for the `Email:` line) / `SET ENFORCE` + nick
|
|
640
|
+
enforcement on the `NICK` path. `INFO <nick>` with no target resolves
|
|
641
|
+
to the caller's own account.
|
|
642
|
+
- **ChanServ** — `REGISTER` / `DROP` / `SET` (`FOUNDER` / `MLOCK` /
|
|
643
|
+
`RESTRICTED` / `KEEPTOPIC`) / `INFO` / `ACCESS` (`SOP` / `AOP` / `HOP`
|
|
644
|
+
/ `VOP`, each `ADD` / `DEL` / `LIST`) / `LEVELS`, plus the prefix /
|
|
645
|
+
roster mutation verbs `OP`, `DEOP`, `VOICE`, `DEVOICE`, `KICK`, and
|
|
646
|
+
`BAN` / `UNBAN` (each emits the corresponding `:ChanServ MODE` /
|
|
647
|
+
`KICK` broadcast and `ApplyChannelDelta` against the channel
|
|
648
|
+
authority). Auto-op / auto-voice on `JOIN` follows the access list;
|
|
649
|
+
the first joiner of a registered channel no longer gets auto-opped
|
|
650
|
+
merely for being first.
|
|
651
|
+
- **HostServ** — `ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` /
|
|
652
|
+
`ACTIVATE` / `REJECT` / `LIST`, with `CHGHOST` fanout. An assigned
|
|
653
|
+
vhost is **auto-applied on identify** (SASL, NickServ `IDENTIFY`, or
|
|
654
|
+
PASS-auth): `state.host` is set, `state.vhostActive` is stamped, and
|
|
655
|
+
any currently-joined `chghost`-capable peers see the broadcast.
|
|
656
|
+
Oper-only subcommands are hidden from non-opers in the help NOTICE.
|
|
657
|
+
- **OperServ** — `AKILL` / `JUPE` / `UNJUPE` / `RAW` (oper-only).
|
|
658
|
+
- **MemoServ** — `SEND` / `LIST` / `READ` / `DEL` with queue delivery
|
|
659
|
+
at identify.
|
|
660
|
+
|
|
661
|
+
The SASL `AccountStore` and NickServ accounts share one scrypt-hashed
|
|
662
|
+
credential store, so a registered nick is also a SASL login and vice
|
|
663
|
+
versa. Backends: D1 on Cloudflare, DynamoDB on AWS, in-memory for the
|
|
664
|
+
local CLI / tests (all write-behind; registrations survive redeploys).
|
|
665
|
+
When no store is bound, services commands reply `501` and the rest of
|
|
666
|
+
the daemon is unaffected. See `docs/Services.md` for the full
|
|
667
|
+
reference.
|
|
601
668
|
|
|
602
669
|
**IRCv3 extensions (negotiated via `CAP`):** `message-tags` (incl. the
|
|
603
670
|
`TAGMSG` command), `server-time`, `account-tag`, `account-notify`
|
|
@@ -614,7 +681,9 @@ shared between live and `draft/chathistory` replay), `standard-replies`
|
|
|
614
681
|
so compliant clients upgrade from plaintext to TLS and pin the secure
|
|
615
682
|
listener), `draft/chathistory`, `safelist`, `draft/typing` (typing-indicator
|
|
616
683
|
broadcast), `draft/multiline=<n>` (multi-line `BATCH`, default 4096-byte
|
|
617
|
-
budget), `draft/read-marker` (per-account persisted last-read
|
|
684
|
+
budget), `draft/read-marker` (per-account persisted last-read, with the
|
|
685
|
+
timestamp-based `MARKREAD <target> [timestamp]` verb plus the
|
|
686
|
+
`+draft/read-marker` tag fanout), and
|
|
618
687
|
`draft/pre-away` (per-account persisted away reason, replayed at
|
|
619
688
|
identify). Case-insensitive nick/channel comparison uses **RFC 1459**
|
|
620
689
|
case-mapping (advertised via `005 CASEMAPPING=rfc1459`). The new
|
|
@@ -634,32 +703,23 @@ bridges a stock TCP client to a deployed WebSocket endpoint.
|
|
|
634
703
|
|
|
635
704
|
## Further reading
|
|
636
705
|
|
|
637
|
-
- `CHANGELOG.md` — per-release manifests
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
`
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
web client: build pipeline, per-env config matrix, CSWSH rationale and
|
|
658
|
-
the optional `WEB_ORIGINS` var, local dev, optional Cloudflare Pages
|
|
659
|
-
alternative, and troubleshooting.
|
|
660
|
-
- `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md` —
|
|
661
|
-
end-to-end guides for the `:6697` TCP+TLS variants (Spectrum/Container on
|
|
662
|
-
CF, NLB + Lambda streaming on AWS), including mTLS trust-store setup.
|
|
663
|
-
- `README.md` in each `packages/*` and `apps/*` — per-package notes (e.g.
|
|
664
|
-
`packages/aws-adapter/README.md` for the DynamoDB-Local test setup,
|
|
665
|
-
`apps/aws-stack/README.md` for CDK commands and localstack validation).
|
|
706
|
+
- `CHANGELOG.md` — per-release manifests in Keep a Changelog format.
|
|
707
|
+
- `docs/Services.md` — operator and contributor reference for the
|
|
708
|
+
integrated IRC services: architecture (integrated vs. pseudo-client),
|
|
709
|
+
the per-service command tables (including ChanServ `OP`/`DEOP`/
|
|
710
|
+
`VOICE`/`DEVOICE`/`KICK`/`BAN`/`UNBAN`, HostServ oper approval flow,
|
|
711
|
+
the `+r`/`+R`/`+M` modes, nick enforcement, the unified scrypt
|
|
712
|
+
account store, service shortcut verbs), adapter backends, and an
|
|
713
|
+
end-to-end registration walkthrough.
|
|
714
|
+
- `docs/WebClientGuide.md` — end-to-end contributor/operator doc for
|
|
715
|
+
the web client: build pipeline, per-env config matrix, CSWSH
|
|
716
|
+
rationale and the optional `WEB_ORIGINS` var, local dev, optional
|
|
717
|
+
Cloudflare Pages alternative, and troubleshooting.
|
|
718
|
+
- `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md`
|
|
719
|
+
— end-to-end guides for the `:6697` TCP+TLS variants
|
|
720
|
+
(Spectrum/Container on CF, NLB + Lambda streaming on AWS), including
|
|
721
|
+
mTLS trust-store setup.
|
|
722
|
+
- `README.md` in each `packages/*` and `apps/*` — per-package notes
|
|
723
|
+
(e.g. `packages/aws-adapter/README.md` for the DynamoDB-Local test
|
|
724
|
+
setup, `apps/aws-stack/README.md` for CDK commands and localstack
|
|
725
|
+
validation).
|