serverless-ircd 0.8.0 → 0.10.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 +4 -0
- package/.github/workflows/deploy-aws.yml +156 -32
- package/.github/workflows/deploy-cf-tcp.yml +11 -9
- package/.github/workflows/deploy-cf.yml +14 -14
- package/CHANGELOG.md +550 -0
- package/README.md +275 -222
- package/apps/aws-stack/README.md +3 -5
- package/apps/aws-stack/bin/aws.ts +82 -9
- package/apps/aws-stack/cdk.json +0 -3
- package/apps/aws-stack/package.json +3 -4
- package/apps/aws-stack/src/aws-stack.ts +177 -52
- package/apps/aws-stack/src/static-site.ts +323 -0
- package/apps/aws-stack/tests/smoke-helpers.test.ts +1 -1
- package/apps/aws-stack/tests/stack.test.ts +267 -92
- package/apps/aws-stack/tests/static-site.test.ts +491 -0
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +0 -1
- package/apps/cf-tcp-container/package.json +2 -3
- package/apps/cf-tcp-container/src/container-server.ts +33 -10
- 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-tcp-container/wrangler.toml +1 -10
- package/apps/cf-worker/package.json +3 -4
- package/apps/cf-worker/wrangler.toml +12 -71
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/server.ts +115 -48
- 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/favicon.ico +0 -0
- package/apps/web/landing/index.html +227 -3
- package/apps/web/package.json +3 -2
- package/apps/web/scripts/build.mjs +91 -6
- package/apps/web/src/build-env.ts +125 -4
- package/apps/web/src/config-schema.ts +20 -6
- package/apps/web/src/render-docs.ts +292 -0
- package/apps/web/static/{config.staging.json → config.prod-aws.json} +3 -2
- package/apps/web/tests/build-env.test.ts +210 -9
- package/apps/web/tests/build-smoke.test.ts +33 -4
- package/apps/web/tests/config-schema.test.ts +149 -25
- 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 +670 -96
- package/docs/AWS-TCP-Deployment.md +20 -45
- package/docs/Cloudflare-Deployment-Guide.md +87 -113
- package/docs/Cloudflare-TCP-Deployment.md +25 -49
- package/docs/Release-Process.md +27 -23
- package/docs/Services.md +102 -23
- package/docs/WebClientGuide.md +35 -26
- package/package.json +7 -10
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +15 -1
- package/packages/aws-adapter/src/cdk-table-defs.ts +6 -11
- package/packages/aws-adapter/src/config-loader.ts +19 -2
- package/packages/aws-adapter/src/dynamo-services-store.ts +7 -0
- package/packages/aws-adapter/src/handlers/connect.ts +26 -0
- package/packages/aws-adapter/src/handlers/default.ts +190 -123
- package/packages/aws-adapter/src/handlers/index.ts +67 -23
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +13 -8
- package/packages/aws-adapter/src/index.ts +5 -7
- package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
- package/packages/aws-adapter/src/serialize.ts +15 -0
- package/packages/aws-adapter/src/tables.ts +2 -12
- package/packages/aws-adapter/tests/aws-harness.ts +0 -1
- package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
- package/packages/aws-adapter/tests/config-loader.test.ts +66 -0
- package/packages/aws-adapter/tests/connect.test.ts +124 -1
- package/packages/aws-adapter/tests/connection-counter.test.ts +17 -0
- package/packages/aws-adapter/tests/default-occ.test.ts +219 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +11 -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 +117 -11
- package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
- package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
- package/packages/aws-adapter/tests/origin-allowlist.test.ts +110 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +0 -1
- package/packages/aws-adapter/tests/stats.test.ts +0 -3
- package/packages/aws-adapter/tests/sweeper.test.ts +20 -1
- package/packages/aws-adapter/tests/tables.test.ts +1 -8
- package/packages/aws-adapter/tests/transactions.test.ts +0 -1
- package/packages/cf-adapter/package.json +1 -5
- package/packages/cf-adapter/src/cf-runtime.ts +59 -8
- package/packages/cf-adapter/src/channel-do.ts +13 -3
- package/packages/cf-adapter/src/connection-do.ts +284 -115
- package/packages/cf-adapter/src/d1-services-store.ts +63 -26
- package/packages/cf-adapter/src/env.ts +11 -10
- package/packages/cf-adapter/src/index.ts +0 -6
- package/packages/cf-adapter/tests/cf-runtime.test.ts +101 -1
- package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
- package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +222 -51
- package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
- package/packages/cf-adapter/tests/d1-services-store.test.ts +53 -2
- package/packages/cf-adapter/tests/serialize.test.ts +25 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/account-migration.ts +140 -0
- package/packages/irc-core/src/commands/account-auth.ts +60 -35
- package/packages/irc-core/src/commands/chanserv.ts +288 -4
- package/packages/irc-core/src/commands/hostserv.ts +38 -3
- package/packages/irc-core/src/commands/index.ts +1 -0
- package/packages/irc-core/src/commands/join.ts +41 -35
- package/packages/irc-core/src/commands/memoserv.ts +1 -1
- package/packages/irc-core/src/commands/nickserv.ts +138 -15
- package/packages/irc-core/src/commands/registration.ts +28 -17
- package/packages/irc-core/src/commands/sasl.ts +22 -31
- package/packages/irc-core/src/commands/service-aliases.ts +52 -0
- package/packages/irc-core/src/commands/topic.ts +23 -10
- package/packages/irc-core/src/config.ts +35 -9
- package/packages/irc-core/src/credential-hashing.ts +11 -54
- package/packages/irc-core/src/index.ts +1 -0
- package/packages/irc-core/src/ports.ts +159 -179
- package/packages/irc-core/src/state/channel.ts +17 -0
- package/packages/irc-core/src/types.ts +38 -10
- package/packages/irc-core/tests/account-migration.test.ts +133 -0
- package/packages/irc-core/tests/commands/chanserv.test.ts +668 -1
- package/packages/irc-core/tests/commands/hostserv.test.ts +71 -0
- package/packages/irc-core/tests/commands/join.test.ts +179 -0
- package/packages/irc-core/tests/commands/markread.test.ts +54 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +19 -0
- package/packages/irc-core/tests/commands/nickserv.test.ts +422 -3
- package/packages/irc-core/tests/commands/oper.test.ts +15 -0
- package/packages/irc-core/tests/commands/registration.test.ts +336 -108
- package/packages/irc-core/tests/commands/sasl.test.ts +194 -169
- package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
- package/packages/irc-core/tests/commands/unified-account.test.ts +102 -84
- package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
- package/packages/irc-core/tests/message-store.test.ts +5 -0
- package/packages/irc-core/tests/persistent-services-store.test.ts +71 -12
- package/packages/irc-core/tests/ports.test.ts +71 -0
- package/packages/irc-core/tests/services-store.test.ts +204 -0
- package/packages/irc-core/vitest.config.ts +6 -1
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +80 -44
- package/packages/irc-server/tests/actor.test.ts +384 -50
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +8 -5
- package/packages/irc-test-support/src/scenarios.ts +21 -6
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +19 -0
- package/packages/irc-test-support/vitest.config.ts +6 -1
- package/pnpm-workspace.yaml +1 -0
- package/scripts/__tests__/deploy-web-aws.test.ts +491 -0
- package/scripts/deploy-web-aws.mjs +290 -0
- package/scripts/package.json +23 -0
- package/scripts/tsconfig.test.json +12 -0
- package/scripts/vitest.config.ts +19 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/src/index.ts +2 -0
- package/tools/ci-hardening/src/validate.ts +57 -0
- package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
- package/tools/ci-hardening/tests/validate.test.ts +42 -0
- 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/migrate-accounts-to-services.ts +270 -0
- package/tools/package.json +2 -1
- package/tools/seed-aws-accounts.ts +35 -10
- package/tools/seed-cf-accounts.ts +42 -9
- 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 +51 -0
- package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
- package/packages/aws-adapter/src/account-store.ts +0 -121
- package/packages/aws-adapter/src/dynamo-account-store.ts +0 -95
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +0 -223
- package/packages/aws-adapter/tests/account-store.test.ts +0 -276
- package/packages/cf-adapter/src/d1-account-store.ts +0 -198
- package/packages/cf-adapter/tests/d1-account-store.test.ts +0 -274
- package/packages/irc-core/tests/account-store.test.ts +0 -131
|
@@ -12,7 +12,7 @@ the two are independent and a deployment can enable either or both.
|
|
|
12
12
|
> This doc covers the *how*.
|
|
13
13
|
|
|
14
14
|
**Acceptance criterion (TICKET-058):** a new contributor can deploy
|
|
15
|
-
their own TCP+TLS
|
|
15
|
+
their own TCP+TLS instance following only this doc.
|
|
16
16
|
|
|
17
17
|
Cross-reference: `apps/cf-tcp-container/` (origin),
|
|
18
18
|
`apps/cf-tcp-container/wrangler.toml` (container config),
|
|
@@ -77,7 +77,7 @@ Two artifacts are deployed and configured:
|
|
|
77
77
|
| **Cloudflare Enterprise**| **Spectrum is an Enterprise-tier feature.** Required for the TCP path. |
|
|
78
78
|
| Containers beta access | Cloudflare Containers is in beta; request access in the dashboard. |
|
|
79
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
|
|
80
|
+
| Git checkout | Clean working tree on `main` for deploys. |
|
|
81
81
|
|
|
82
82
|
> The wss path runs on the Workers Free/Paid plan. The TCP path does
|
|
83
83
|
> **not** — Spectrum mandates Enterprise. If you do not have Enterprise,
|
|
@@ -169,25 +169,25 @@ pnpm --filter @serverless-ircd/cf-tcp-container test
|
|
|
169
169
|
|
|
170
170
|
---
|
|
171
171
|
|
|
172
|
-
## 5. Deploy
|
|
172
|
+
## 5. Deploy
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
The deploy is `workflow_dispatch`-only (a maintainer triggers it by
|
|
175
|
+
hand); you can also deploy manually.
|
|
176
176
|
|
|
177
177
|
### 5.1 Deploy the container origin
|
|
178
178
|
|
|
179
179
|
```bash
|
|
180
180
|
# From the repo root:
|
|
181
|
-
pnpm deploy:cf-tcp
|
|
181
|
+
pnpm deploy:cf-tcp
|
|
182
182
|
# Equivalent to:
|
|
183
|
-
# pnpm build && pnpm --filter @serverless-ircd/cf-tcp-container deploy
|
|
184
|
-
# → wrangler deploy
|
|
183
|
+
# pnpm build && pnpm --filter @serverless-ircd/cf-tcp-container run deploy
|
|
184
|
+
# → wrangler deploy (inside apps/cf-tcp-container)
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
This builds the container image from `apps/cf-tcp-container/Dockerfile`
|
|
188
|
-
and deploys the `sirc-tcp-origin
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
and deploys the `sirc-tcp-origin` Container. The first deploy prints
|
|
189
|
+
the **origin address** — copy it into `CF_TCP_ORIGIN_ADDRESS` (used by
|
|
190
|
+
the Spectrum step next).
|
|
191
191
|
|
|
192
192
|
### 5.2 Apply the Spectrum TLS config
|
|
193
193
|
|
|
@@ -197,7 +197,7 @@ terraform init
|
|
|
197
197
|
terraform apply \
|
|
198
198
|
-var zone_id=$CF_ZONE_ID \
|
|
199
199
|
-var origin_address=$CF_TCP_ORIGIN_ADDRESS \
|
|
200
|
-
-var hostname=irc
|
|
200
|
+
-var hostname=irc.example.com
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
This creates the `cloudflare_spectrum_application.irc_tls` resource:
|
|
@@ -208,7 +208,7 @@ TLS on :6697 → plaintext TCP to the container origin on :6667.
|
|
|
208
208
|
```bash
|
|
209
209
|
# Minimal smoke: register + quit over the real TLS endpoint.
|
|
210
210
|
printf 'NICK smoke\r\nUSER smoke 0 * :Smoke\r\nQUIT :smoke\r\n' | \
|
|
211
|
-
timeout 10 openssl s_client -connect irc
|
|
211
|
+
timeout 10 openssl s_client -connect irc.example.com:6697 -quiet | \
|
|
212
212
|
grep -q '001' && echo "SMOKE PASS" || echo "SMOKE FAIL"
|
|
213
213
|
```
|
|
214
214
|
|
|
@@ -218,7 +218,7 @@ CI does the same — see `.github/workflows/deploy-cf-tcp.yml` step
|
|
|
218
218
|
### 5.4 Connect a real IRC client
|
|
219
219
|
|
|
220
220
|
```
|
|
221
|
-
/server add ircd irc
|
|
221
|
+
/server add ircd irc.example.com/6697
|
|
222
222
|
/set irc.server.ircd.ssl on
|
|
223
223
|
/connect ircd
|
|
224
224
|
/join #test
|
|
@@ -226,27 +226,6 @@ CI does the same — see `.github/workflows/deploy-cf-tcp.yml` step
|
|
|
226
226
|
|
|
227
227
|
---
|
|
228
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
229
|
## 7. Configuration reference
|
|
251
230
|
|
|
252
231
|
### 7.1 Container — `apps/cf-tcp-container/wrangler.toml`
|
|
@@ -360,7 +339,7 @@ and SASL credentials ship as `[vars]` (`OPER_*`, `SASL_ACCOUNTS`). When
|
|
|
360
339
|
strong-secret handling lands, set them via:
|
|
361
340
|
|
|
362
341
|
```bash
|
|
363
|
-
wrangler secret put OPER_PASSWORD
|
|
342
|
+
wrangler secret put OPER_PASSWORD
|
|
364
343
|
```
|
|
365
344
|
|
|
366
345
|
CI uses these **GitHub Actions secrets/variables** (configure under
|
|
@@ -384,19 +363,20 @@ Settings → Secrets and variables → Actions):
|
|
|
384
363
|
(the wss path). The two are independent — a deployment can enable
|
|
385
364
|
either or both.
|
|
386
365
|
|
|
387
|
-
Triggers:
|
|
388
|
-
|
|
366
|
+
Triggers: `workflow_dispatch` only (a maintainer triggers it by hand;
|
|
367
|
+
there is no automatic push-to-main deploy). Steps (in order):
|
|
389
368
|
|
|
390
369
|
1. Checkout → pnpm 11 + Node 24 → `pnpm install --frozen-lockfile`.
|
|
391
370
|
2. `pnpm build`, `pnpm typecheck`, `pnpm --filter cf-tcp-container test`.
|
|
392
|
-
3. `wrangler deploy
|
|
371
|
+
3. `wrangler deploy` (container).
|
|
393
372
|
4. `terraform init && terraform apply -auto-approve` (Spectrum).
|
|
394
373
|
5. TLS smoke (`openssl s_client`), gated on `CF_TCP_SMOKE_HOST`.
|
|
395
374
|
|
|
396
|
-
Concurrency is serialized via `concurrency.group: cf-tcp-
|
|
375
|
+
Concurrency is serialized via `concurrency.group: cf-tcp-deploy`.
|
|
397
376
|
|
|
398
|
-
There is
|
|
399
|
-
|
|
377
|
+
There is a single deploy target — the staging/prod split was collapsed,
|
|
378
|
+
so the same workflow + account ships to whichever Container namespace
|
|
379
|
+
the credentials target.
|
|
400
380
|
|
|
401
381
|
---
|
|
402
382
|
|
|
@@ -481,16 +461,12 @@ terraform version # >= 1.5
|
|
|
481
461
|
pnpm build
|
|
482
462
|
pnpm --filter @serverless-ircd/cf-tcp-container start
|
|
483
463
|
|
|
484
|
-
#
|
|
485
|
-
pnpm deploy:cf-tcp
|
|
464
|
+
# Deploy (single target — staging vs prod is which account you point at)
|
|
465
|
+
pnpm deploy:cf-tcp # container
|
|
486
466
|
cd apps/cf-tcp-container/terraform && \
|
|
487
467
|
terraform apply -var zone_id=$CF_ZONE_ID \
|
|
488
468
|
-var origin_address=$CF_TCP_ORIGIN_ADDRESS \
|
|
489
|
-
-var hostname=irc
|
|
490
|
-
|
|
491
|
-
# Production (manual)
|
|
492
|
-
pnpm deploy:cf-tcp:prod
|
|
493
|
-
cd apps/cf-tcp-container/terraform && terraform apply ... # prod hostname
|
|
469
|
+
-var hostname=irc.example.com # Spectrum
|
|
494
470
|
|
|
495
471
|
# Tests
|
|
496
472
|
pnpm --filter @serverless-ircd/cf-tcp-container test
|
package/docs/Release-Process.md
CHANGED
|
@@ -37,7 +37,7 @@ this repo itself) should pin exact versions.
|
|
|
37
37
|
|
|
38
38
|
### 1.1 Workspace version lock
|
|
39
39
|
|
|
40
|
-
All
|
|
40
|
+
All sixteen `package.json` files (root + 15 workspace packages) carry the
|
|
41
41
|
**same** version string. They are bumped together in the release
|
|
42
42
|
commit. The pre-release checklist in §4 enforces this.
|
|
43
43
|
|
|
@@ -63,8 +63,8 @@ not always possible.
|
|
|
63
63
|
| Push to `main` | Maintainers | Merging the release PR. |
|
|
64
64
|
| Push tags (`v*`) | Maintainers | `git push origin v0.x.0`. |
|
|
65
65
|
| Create GitHub Release | Maintainers | Publishing the release notes. |
|
|
66
|
-
| `CLOUDFLARE_API_TOKEN` secret | Repo Settings → Secrets |
|
|
67
|
-
| `CF_SMOKE_URL` variable | Repo Settings → Variables |
|
|
66
|
+
| `CLOUDFLARE_API_TOKEN` secret | Repo Settings → Secrets | Manual + CI deploys (single target). |
|
|
67
|
+
| `CF_SMOKE_URL` variable | Repo Settings → Variables | Smoke e2e in CI (manual dispatch). |
|
|
68
68
|
|
|
69
69
|
There is no separate "release engineer" role. The maintainer who cuts
|
|
70
70
|
the tag owns the release end-to-end, including the production deploy
|
|
@@ -101,7 +101,7 @@ git pull --ff-only origin main
|
|
|
101
101
|
# 2. CI is green on the tip of main.
|
|
102
102
|
# Open: https://github.com/<owner>/<repo>/actions
|
|
103
103
|
# - CI workflow: lint / typecheck / coverage all green.
|
|
104
|
-
# - deploy-cf workflow:
|
|
104
|
+
# - deploy-cf workflow: deploy + smoke e2e green (manual dispatch).
|
|
105
105
|
|
|
106
106
|
# 3. Local full verify.
|
|
107
107
|
pnpm install --frozen-lockfile
|
|
@@ -120,9 +120,9 @@ pnpm build # every package produces dist/
|
|
|
120
120
|
# be green (step 2), so locally re-run `pnpm coverage` as a
|
|
121
121
|
# belt-and-braces check before tagging.
|
|
122
122
|
|
|
123
|
-
# 5.
|
|
123
|
+
# 5. Smoke e2e passes against the deployed tip-of-main.
|
|
124
124
|
node apps/cf-worker/scripts/smoke.mjs \
|
|
125
|
-
--url wss://serverless-ircd
|
|
125
|
+
--url wss://serverless-ircd.<subdomain>.workers.dev
|
|
126
126
|
|
|
127
127
|
# 6. CHANGELOG.md is complete and accurate (§5).
|
|
128
128
|
# 7. All package.json versions are bumped and lock-step (§6).
|
|
@@ -187,8 +187,9 @@ understand what changed and what they need to do.
|
|
|
187
187
|
|
|
188
188
|
## 6. Bumping versions
|
|
189
189
|
|
|
190
|
-
There are
|
|
191
|
-
version
|
|
190
|
+
There are sixteen `package.json` files (root + 15 workspace packages).
|
|
191
|
+
They must end up at the same version (`tools/package.json` is a parent
|
|
192
|
+
container with no `version` field and is not bumped):
|
|
192
193
|
|
|
193
194
|
```
|
|
194
195
|
./package.json (workspace root)
|
|
@@ -206,6 +207,7 @@ apps/web/package.json
|
|
|
206
207
|
tools/tcp-ws-forwarder/package.json
|
|
207
208
|
tools/load-test/package.json
|
|
208
209
|
tools/ci-hardening/package.json
|
|
210
|
+
scripts/package.json (deploy helpers)
|
|
209
211
|
```
|
|
210
212
|
|
|
211
213
|
To cut `v0.X.Y`:
|
|
@@ -217,8 +219,8 @@ git checkout -b release/v0.X.Y
|
|
|
217
219
|
# bump each file. The version field is the only change.
|
|
218
220
|
# (Optional helper: `pnpm -r exec -- node -e '...'` if you script it.)
|
|
219
221
|
|
|
220
|
-
# verify all
|
|
221
|
-
rg '"version":' package.json packages/*/package.json apps/*/package.json tools/*/package.json
|
|
222
|
+
# verify all files agree:
|
|
223
|
+
rg '"version":' package.json packages/*/package.json apps/*/package.json tools/*/package.json scripts/package.json
|
|
222
224
|
```
|
|
223
225
|
|
|
224
226
|
The lockfile (`pnpm-lock.yaml`) is unaffected — none of the workspace
|
|
@@ -270,7 +272,7 @@ git pull --ff-only origin main
|
|
|
270
272
|
git log -1 --oneline # expect: chore(release): v0.X.Y
|
|
271
273
|
|
|
272
274
|
# Confirm versions are at the new release.
|
|
273
|
-
rg '"version":' package.json packages/*/package.json apps/*/package.json tools/*/package.json
|
|
275
|
+
rg '"version":' package.json packages/*/package.json apps/*/package.json tools/*/package.json scripts/package.json
|
|
274
276
|
```
|
|
275
277
|
|
|
276
278
|
### 8.1 Tag and push
|
|
@@ -299,8 +301,10 @@ configured (`git config tag.gpgSign true`).
|
|
|
299
301
|
|
|
300
302
|
### 8.3 Production deploy (manual)
|
|
301
303
|
|
|
302
|
-
There is
|
|
303
|
-
prod
|
|
304
|
+
There is a single deploy target per platform (`pnpm deploy:cf`); the
|
|
305
|
+
staging/prod split was collapsed, so the same command ships to whichever
|
|
306
|
+
Cloudflare account the credentials target. The maintainer runs the
|
|
307
|
+
deploy by hand, from a clean checkout on the tag:
|
|
304
308
|
|
|
305
309
|
```bash
|
|
306
310
|
git checkout v0.X.Y
|
|
@@ -313,17 +317,17 @@ pnpm build
|
|
|
313
317
|
pnpm typecheck
|
|
314
318
|
pnpm test
|
|
315
319
|
|
|
316
|
-
# Deploy the Cloudflare Worker
|
|
317
|
-
pnpm deploy:cf
|
|
318
|
-
# → wrangler deploy
|
|
320
|
+
# Deploy the Cloudflare Worker.
|
|
321
|
+
pnpm deploy:cf
|
|
322
|
+
# → wrangler deploy
|
|
319
323
|
|
|
320
|
-
# Tail
|
|
324
|
+
# Tail logs while the first connections arrive.
|
|
321
325
|
npx wrangler tail
|
|
322
326
|
|
|
323
|
-
# Smoke-check
|
|
324
|
-
# You must point it at the
|
|
327
|
+
# Smoke-check the deploy with the same script CI uses.
|
|
328
|
+
# You must point it at the wss:// URL:
|
|
325
329
|
node apps/cf-worker/scripts/smoke.mjs \
|
|
326
|
-
--url wss://<
|
|
330
|
+
--url wss://<workers-subdomain>.workers.dev
|
|
327
331
|
```
|
|
328
332
|
|
|
329
333
|
If smoke fails on prod:
|
|
@@ -355,7 +359,7 @@ To redeploy a previous release:
|
|
|
355
359
|
git checkout v0.W.Z # the previous tag
|
|
356
360
|
pnpm install --frozen-lockfile
|
|
357
361
|
pnpm build
|
|
358
|
-
pnpm deploy:cf
|
|
362
|
+
pnpm deploy:cf
|
|
359
363
|
```
|
|
360
364
|
|
|
361
365
|
This works as long as the previous release's code is compatible with
|
|
@@ -430,7 +434,7 @@ pnpm lint && pnpm typecheck && pnpm test && pnpm build
|
|
|
430
434
|
|
|
431
435
|
# 2. Branch, bump versions + CHANGELOG, open PR (§6, §7).
|
|
432
436
|
git checkout -b release/v0.X.Y
|
|
433
|
-
# ... edit
|
|
437
|
+
# ... edit 16 × package.json + CHANGELOG.md ...
|
|
434
438
|
git commit -am "chore(release): v0.X.Y"
|
|
435
439
|
git push -u origin release/v0.X.Y
|
|
436
440
|
# ... open PR, get it merged ...
|
|
@@ -445,7 +449,7 @@ git push origin v0.X.Y
|
|
|
445
449
|
# 5. Prod deploy (§8.3).
|
|
446
450
|
git checkout v0.X.Y
|
|
447
451
|
pnpm install --frozen-lockfile && pnpm build
|
|
448
|
-
pnpm deploy:cf
|
|
452
|
+
pnpm deploy:cf
|
|
449
453
|
node apps/cf-worker/scripts/smoke.mjs --url wss://<prod>.workers.dev
|
|
450
454
|
```
|
|
451
455
|
|
package/docs/Services.md
CHANGED
|
@@ -46,8 +46,9 @@ ServerlessIRCd chose **integrated services**. The rationale (full detail in
|
|
|
46
46
|
- The pure-reducer + location-of-authority architecture already has the
|
|
47
47
|
right seam: a `ServicesStore` port absorbs the side effects, and reducers
|
|
48
48
|
stay pure and unit-testable.
|
|
49
|
-
- One storage layer is
|
|
50
|
-
|
|
49
|
+
- One storage layer (`ServicesStore`) is the **single credential home**
|
|
50
|
+
for SASL PLAIN, SASL EXTERNAL, PASS-auth, and NickServ IDENTIFY —
|
|
51
|
+
authentication and nick ownership compose, not duplicate.
|
|
51
52
|
- The serverless substrates (Cloudflare Durable Objects, AWS DynamoDB)
|
|
52
53
|
handle persistence natively; a separate long-running services process
|
|
53
54
|
would defeat the deployment model.
|
|
@@ -90,6 +91,12 @@ If no store is bound, every services command replies:
|
|
|
90
91
|
milliseconds, NickServ waits before force-disconnecting a `ghost`-enforced
|
|
91
92
|
client. `0` makes `ghost` behave like `kill` (immediate). Omit for the
|
|
92
93
|
**30 000 ms** default (`DEFAULT_NICK_ENFORCE_GRACE_MS`).
|
|
94
|
+
- **`nickServ.minPasswordLength`** (`integer > 0`, optional) — minimum
|
|
95
|
+
length enforced by NickServ `SET PASSWORD` for the new password. Omit for
|
|
96
|
+
the **8** default (`DEFAULT_MIN_PASSWORD_LENGTH`); a shorter new password
|
|
97
|
+
is rejected before hashing with
|
|
98
|
+
`Password is too short (minimum <n> characters).`. The maximum (256) is
|
|
99
|
+
fixed, not configurable, to bound the scrypt input budget.
|
|
93
100
|
|
|
94
101
|
---
|
|
95
102
|
|
|
@@ -126,6 +133,38 @@ Notable conventions:
|
|
|
126
133
|
`519` (ChanServ `+R` JOIN gate), `432` (OperServ JUPE), `404` (ChanServ
|
|
127
134
|
`+R`/`+M` send gates), `472` (services-only channel-mode letters).
|
|
128
135
|
|
|
136
|
+
### Shortcut aliases (`/NICKSERV`, `/NS`, `/CS`, …)
|
|
137
|
+
|
|
138
|
+
For compatibility with the Atheme/Anope services convention (and IRCd
|
|
139
|
+
alias modules such as InspIRCd `m_alias` / UnrealIRCd `alias{}`), every
|
|
140
|
+
service also accepts a **shortcut verb** that the daemon rewrites to the
|
|
141
|
+
canonical `PRIVMSG <ServiceNick> :<joined args>` before routing. These are
|
|
142
|
+
not RFC-standardized but are widely expected by clients and users.
|
|
143
|
+
|
|
144
|
+
| Shortcut verb(s) | Equivalent to |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `NICKSERV`, `NS` | `PRIVMSG NickServ :<args>` |
|
|
147
|
+
| `CHANSERV`, `CS` | `PRIVMSG ChanServ :<args>` |
|
|
148
|
+
| `HOSTSERV`, `HS` | `PRIVMSG HostServ :<args>` |
|
|
149
|
+
| `MEMOSERV`, `MS` | `PRIVMSG MemoServ :<args>` |
|
|
150
|
+
| `OPERSERV`, `OS` | `PRIVMSG OperServ :<args>` |
|
|
151
|
+
|
|
152
|
+
The alias verb is the first token; everything after it is joined with
|
|
153
|
+
single spaces and becomes the `PRIVMSG` trailing body, so e.g.
|
|
154
|
+
`NICKSERV IDENTIFY hunter2` behaves identically to
|
|
155
|
+
`PRIVMSG NickServ :IDENTIFY hunter2`, and `NS REGISTER pw email` is the
|
|
156
|
+
same as `PRIVMSG NickServ :REGISTER pw email`. A colon-trailing form
|
|
157
|
+
(`NS :IDENTIFY hunter2`) is accepted too. The verbs are matched
|
|
158
|
+
case-insensitively. The alias → service nick table is the single source
|
|
159
|
+
of truth in `packages/irc-core/src/commands/service-aliases.ts`
|
|
160
|
+
(`SHORTCUT_TO_SERVICE`); both the dispatcher rewrite and the service-nick
|
|
161
|
+
routing consult it, so no service-reducer changes were needed.
|
|
162
|
+
|
|
163
|
+
When no `ServicesStore` is bound, a shortcut behaves exactly like a direct
|
|
164
|
+
`PRIVMSG` to the service nick — it falls through to the normal nick-target
|
|
165
|
+
path (a `401 ERR_NOSUCHNICK` for the unrecognised pseudo-client), never a
|
|
166
|
+
bare `421 ERR_UNKNOWNCOMMAND`.
|
|
167
|
+
|
|
129
168
|
---
|
|
130
169
|
|
|
131
170
|
## 4. NickServ — nick registration & identification
|
|
@@ -142,8 +181,9 @@ policy, and nick-info queries. A registered nick is an account.
|
|
|
142
181
|
| `REGISTER` | `PRIVMSG NickServ :REGISTER <password> <email>` | Registers the *current* nick. Does **not** auto-identify (no `+r` set). Reply: `Nickname <nick> is now registered.` Already-registered → `Nickname <nick> is already registered.` |
|
|
143
182
|
| `IDENTIFY` | `PRIVMSG NickServ :IDENTIFY [nick] <password>` | One-arg form identifies the current nick; two-arg form identifies a named nick. On success: sets `state.account`, sets user mode **`+r`**, fans `ACCOUNT` to `account-notify` peers, then delivers unread MemoServ memos. Reply: `You are now identified for nick <account>.` |
|
|
144
183
|
| `DROP` | `PRIVMSG NickServ :DROP [nick]` | Requires prior identify. Drops the registration, clears `state.account` and `+r`, fans `ACCOUNT *`. Reply: `Nickname <nick> has been dropped.` |
|
|
145
|
-
| `INFO` | `PRIVMSG NickServ :INFO [nick]` | Defaults to the current nick. Replies (NOTICE): `Nick:`, `Account:`, `Email:`. Unregistered → `Nick <target> is not registered.` |
|
|
184
|
+
| `INFO` | `PRIVMSG NickServ :INFO [nick]` | Defaults to the current nick. Replies (NOTICE): `Nick:`, `Account:`, and (only if the caller is identified as the owning account **or** is an oper) `Email:`. A non-owner sees `Nick:` / `Account:` only. Unregistered → `Nick <target> is not registered.` |
|
|
146
185
|
| `SET ENFORCE` | `PRIVMSG NickServ :SET ENFORCE none|ghost|kill` | Requires identify as the owning account. See enforcement below. |
|
|
186
|
+
| `SET PASSWORD` | `PRIVMSG NickServ :SET PASSWORD <old-password> <new-password>` | Requires identify as the owning account **and** re-supplies the current password (defence against a hijacked `+r` session). Re-hashes the new password (scrypt) and overwrites the credential. New-password length floor: `nickServ.minPasswordLength` (default **8**); fixed ceiling **256**. Reply: `Password changed.` Wrong old password → `Invalid password.` (same text as a failed `IDENTIFY`; no leak). The current session stays `+r`; **other sessions of the same account are NOT kicked.** |
|
|
147
187
|
|
|
148
188
|
Help / unknown command: `Available commands: REGISTER, IDENTIFY, DROP, INFO, SET`.
|
|
149
189
|
|
|
@@ -399,27 +439,24 @@ Both accept `--accounts alice:s3cret bob:pw …` or `--file accounts.txt`
|
|
|
399
439
|
```
|
|
400
440
|
# Cloudflare
|
|
401
441
|
node --import tsx tools/seed-cf-accounts.ts \
|
|
402
|
-
--database serverless-ircd-accounts
|
|
442
|
+
--database serverless-ircd-accounts --remote \
|
|
403
443
|
--accounts alice:s3cret
|
|
404
444
|
|
|
405
|
-
# AWS
|
|
445
|
+
# AWS (the --table is the Services table, not the legacy Accounts table)
|
|
406
446
|
node --import tsx tools/seed-aws-accounts.ts \
|
|
407
|
-
--table
|
|
447
|
+
--table StagingServices --endpoint http://localhost:8000 \
|
|
408
448
|
--accounts alice:s3cret
|
|
409
449
|
```
|
|
410
450
|
|
|
411
|
-
> **NickServ accounts vs SASL accounts.**
|
|
412
|
-
> the **`ServicesStore`** (`nickserv_accounts`)
|
|
413
|
-
>
|
|
414
|
-
>
|
|
415
|
-
>
|
|
416
|
-
>
|
|
417
|
-
>
|
|
418
|
-
>
|
|
419
|
-
>
|
|
420
|
-
> (see [§11](#11-security-characteristics--known-gaps)), bulk-seeded rows
|
|
421
|
-
> must supply a scrypt `algorithm`/`salt`/`hash` triple rather than a
|
|
422
|
-
> plaintext password.
|
|
451
|
+
> **NickServ accounts vs SASL accounts — unified.** Both SASL PLAIN and
|
|
452
|
+
> NickServ IDENTIFY consult the **`ServicesStore`** (`nickserv_accounts`).
|
|
453
|
+
> There is no separate SASL credential table: a NickServ registration is a
|
|
454
|
+
> SASL account and vice versa, both scrypt-hashed (see the
|
|
455
|
+
> [walkthrough](#13-end-to-end-walkthrough)). The recommended way to create
|
|
456
|
+
> a NickServ account is the live `PRIVMSG NickServ :REGISTER …` flow over
|
|
457
|
+
> an established connection. The seed scripts above populate
|
|
458
|
+
> `nickserv_accounts` directly via the same scrypt-hashed `registerNick`
|
|
459
|
+
> path; the legacy standalone `accounts` / `Accounts` table was dropped.
|
|
423
460
|
|
|
424
461
|
Operators can manage AKILL/JUPE entries via `PRIVMSG OperServ` (oper-only,
|
|
425
462
|
see [§7](#7-operserv--network-bans-jupes-raw-oper-only)), or pre-populate
|
|
@@ -432,13 +469,13 @@ them by writing directly into the `ServicesStore` backing tables.
|
|
|
432
469
|
Be aware of the following when operating or contributing to services:
|
|
433
470
|
|
|
434
471
|
- **NickServ passwords are stored scrypt-hashed** in the `ServicesStore`
|
|
435
|
-
(
|
|
436
|
-
`InMemoryServicesStore.registerNick` hashes via `hashAccountCredential`
|
|
472
|
+
(`InMemoryServicesStore.registerNick` hashes via `hashAccountCredential`
|
|
437
473
|
before persisting; `verifyNick` re-derives via `verifyHashedPassword`;
|
|
438
474
|
the D1 `nickserv_accounts` table stores `algorithm`/`salt`/`hash`
|
|
439
|
-
columns, never the plaintext password).
|
|
440
|
-
|
|
441
|
-
|
|
475
|
+
columns, never the plaintext password). The `ServicesStore` is the
|
|
476
|
+
**single credential home**: SASL PLAIN, SASL EXTERNAL, PASS-auth
|
|
477
|
+
(`PASS <nick>:<password>`), and NickServ `IDENTIFY` all verify through
|
|
478
|
+
the same `verifyNick` / `verifyCertFP` surface. Treat the services backing store (D1 database / DynamoDB table) as
|
|
442
479
|
sensitive regardless: restrict access and enable at-rest encryption
|
|
443
480
|
(DynamoDB encryption-at-rest is on by default).
|
|
444
481
|
- **HostServ `REQUEST` approval mode is configurable** — the default
|
|
@@ -544,3 +581,45 @@ When `bob` later identifies and joins, ChanServ auto-ops them:
|
|
|
544
581
|
|
|
545
582
|
That completes the core nick + channel registration flow. From here, the
|
|
546
583
|
per-service references in §4–§8 cover every shipped command.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## 14. Unified credential verification — four entry points
|
|
588
|
+
|
|
589
|
+
The `ServicesStore` is the **single credential home**. Every
|
|
590
|
+
account-setting path reduces to "this connection is now account X" via
|
|
591
|
+
one of four entry points, all of which consult the same scrypt-hashed
|
|
592
|
+
credential on the `nickserv_accounts` row:
|
|
593
|
+
|
|
594
|
+
| Entry point | Mechanism | Credential verify call | Reducer |
|
|
595
|
+
|-------------|-----------|------------------------|---------|
|
|
596
|
+
| **SASL PLAIN** | `AUTHENTICATE PLAIN` (IRCv3 `sasl`) | `services.verifyNick(username, password)` | `commands/sasl.ts` |
|
|
597
|
+
| **SASL EXTERNAL** (CertFP) | `AUTHENTICATE EXTERNAL` (IRCv3 `sasl` + mTLS) | `services.verifyCertFP(certSubject)` (subject resolved by the unchanged `MtlsIdentityProvider`) | `commands/sasl.ts` |
|
|
598
|
+
| **NickServ `IDENTIFY`** | `PRIVMSG NickServ :IDENTIFY <password>` | `services.verifyNick(state.nick, password)` | `commands/nickserv.ts` |
|
|
599
|
+
| **PASS-auth** | `PASS <nick>:<password>` at or after registration | `services.verifyNick(nick, password)` | `commands/account-auth.ts` (`attemptPassAccountAuth`) |
|
|
600
|
+
|
|
601
|
+
All four share the same `applyAccountSuccess` downstream chain: stamp
|
|
602
|
+
`state.account`, set read-only `+r`, seed read-markers, replay persisted
|
|
603
|
+
away, deliver queued memos, re-apply assigned vhost, and emit `900
|
|
604
|
+
RPL_LOGGEDIN` + `903 RPL_SASLSUCCESS` + the `account-notify` `ACCOUNT`
|
|
605
|
+
broadcast.
|
|
606
|
+
|
|
607
|
+
A NickServ-registered nick authenticates through any of the four entry
|
|
608
|
+
points without a second registration — the credential set by
|
|
609
|
+
`PRIVMSG NickServ :REGISTER` IS the SASL/PASS credential.
|
|
610
|
+
|
|
611
|
+
### SASL EXTERNAL (CertFP)
|
|
612
|
+
|
|
613
|
+
SASL EXTERNAL maps a verified client-certificate subject to an account.
|
|
614
|
+
The edge platform (CF API Shield mTLS, AWS API Gateway custom-domain
|
|
615
|
+
mTLS) terminates TLS and validates the client cert; the verified subject
|
|
616
|
+
is surfaced to the Worker / Lambda via the unchanged
|
|
617
|
+
`MtlsIdentityProvider` port. The subject→account lookup lives in
|
|
618
|
+
`ServicesStore.verifyCertFP`, which scans `certSubjects: string[]` on
|
|
619
|
+
each registered nick (byte-exact, case-sensitive comparison — cert
|
|
620
|
+
subjects are NOT case-folded). `addCertFP(account, subject)` /
|
|
621
|
+
`removeCertFP(account, subject)` mutators are shipped for an upcoming
|
|
622
|
+
NickServ `CERT ADD`/`LIST`/`DEL` command (Atheme parity); the
|
|
623
|
+
`cert_subjects` column on `nickserv_accounts` / the `certSubjects`
|
|
624
|
+
attribute on the DynamoDB `Services` item carries the array (JSON-encoded
|
|
625
|
+
`'[]'` by default).
|
package/docs/WebClientGuide.md
CHANGED
|
@@ -11,9 +11,19 @@ Cross-reference: `PLAN.md` §6.1 (CF mapping), `docs/PlanWebClient.md`
|
|
|
11
11
|
(design), `docs/Cloudflare-Deployment-Guide.md` (Worker deploy),
|
|
12
12
|
`apps/cf-worker/wrangler.toml`, `apps/web/`.
|
|
13
13
|
|
|
14
|
+
> **AWS counterpart.** This doc covers the **Cloudflare** web-client
|
|
15
|
+
> path, where the Worker serves both the SPA and the WebSocket from
|
|
16
|
+
> one origin. The AWS deployment **cannot** unify them — the SPA
|
|
17
|
+
> lives on CloudFront (S3 + OAC) and the WebSocket endpoint on API
|
|
18
|
+
> Gateway, so the baked config carries the API Gateway wss URL as a
|
|
19
|
+
> literal and the deploy is a separate two-phase pipeline (stack →
|
|
20
|
+
> bake → s3 sync → invalidate). For the AWS web client — construct,
|
|
21
|
+
> build, deploy pipeline, the `WEB_ORIGINS` CSWSH defence, and custom
|
|
22
|
+
> domain — see **`docs/AWS-Deployment.md` §16 (Web client)**.
|
|
23
|
+
|
|
14
24
|
**Acceptance criterion (TICKET-139):** a new contributor can
|
|
15
25
|
`git submodule update --init`, `pnpm install`,
|
|
16
|
-
`pnpm --filter web build
|
|
26
|
+
`pnpm --filter web build`, and `wrangler deploy`
|
|
17
27
|
following only this doc.
|
|
18
28
|
|
|
19
29
|
---
|
|
@@ -82,7 +92,7 @@ pnpm --version # 11.x
|
|
|
82
92
|
|
|
83
93
|
---
|
|
84
94
|
|
|
85
|
-
## 3. Quickstart (clean checkout → deployed
|
|
95
|
+
## 3. Quickstart (clean checkout → deployed SPA)
|
|
86
96
|
|
|
87
97
|
```bash
|
|
88
98
|
# 1. Pull submodules (docs wiki + Kiwi IRC upstream sources).
|
|
@@ -91,9 +101,9 @@ git submodule update --init
|
|
|
91
101
|
# 2. Install workspace deps.
|
|
92
102
|
pnpm install
|
|
93
103
|
|
|
94
|
-
# 3. Build the SPA + landing page (default config =
|
|
95
|
-
pnpm --filter web build
|
|
96
|
-
# Equivalent: pnpm --filter @serverless-ircd/web build
|
|
104
|
+
# 3. Build the SPA + landing page (default config = dev target).
|
|
105
|
+
pnpm --filter web build
|
|
106
|
+
# Equivalent: pnpm --filter @serverless-ircd/web build
|
|
97
107
|
# → apps/web/dist/index.html (landing page)
|
|
98
108
|
# → apps/web/dist/webclient/index.html (Kiwi SPA)
|
|
99
109
|
# → apps/web/dist/webclient/static/config.json (baked, env-specific)
|
|
@@ -107,9 +117,9 @@ pnpm --filter @serverless-ircd/cf-worker dev
|
|
|
107
117
|
# → http://localhost:8787/webclient/ Kiwi SPA
|
|
108
118
|
# → ws://localhost:8787/ IRC-over-WebSocket (same endpoint)
|
|
109
119
|
|
|
110
|
-
# 6. Deploy
|
|
111
|
-
pnpm deploy:cf
|
|
112
|
-
# → wrangler deploy
|
|
120
|
+
# 6. Deploy (Worker + assets in one command).
|
|
121
|
+
pnpm deploy:cf
|
|
122
|
+
# → wrangler deploy
|
|
113
123
|
```
|
|
114
124
|
|
|
115
125
|
Open `http://localhost:8787/webclient/` in a browser — Kiwi boots, reads
|
|
@@ -131,14 +141,14 @@ The build is an orchestrated layering over the upstream Kiwi build —
|
|
|
131
141
|
|
|
132
142
|
| `pnpm ...` command | Reads | Bakes into `dist/webclient/static/config.json` |
|
|
133
143
|
|---|---|---|
|
|
134
|
-
| `--filter web build` | `static/config.json` | default
|
|
135
|
-
| `--filter web build:staging` | `static/config.staging.json` | staging config |
|
|
144
|
+
| `--filter web build` | `static/config.json` | dev default config |
|
|
136
145
|
| `--filter web build:prod` | `static/config.prod.json` | prod config |
|
|
137
146
|
|
|
138
|
-
`--env` is parsed by `src/build-env.ts`; an unknown value
|
|
139
|
-
build with a readable error. The selected
|
|
140
|
-
Zod schema (`src/config-schema.ts`)
|
|
141
|
-
malformed config fails the build rather
|
|
147
|
+
`--env` is parsed by `src/build-env.ts`; an unknown value (including the
|
|
148
|
+
retired `staging`) aborts the build with a readable error. The selected
|
|
149
|
+
file is validated against the Zod schema (`src/config-schema.ts`)
|
|
150
|
+
**before** it is baked, so a malformed config fails the build rather
|
|
151
|
+
than shipping a broken SPA.
|
|
142
152
|
|
|
143
153
|
### 4.2 What the build does, in order
|
|
144
154
|
|
|
@@ -172,9 +182,9 @@ The schema models only the fields ServerlessIRCd cares about; the rest
|
|
|
172
182
|
of Kiwi's surface is passed through untouched (`.passthrough()`).
|
|
173
183
|
|
|
174
184
|
```jsonc
|
|
175
|
-
// static/config.
|
|
185
|
+
// static/config.prod.json
|
|
176
186
|
{
|
|
177
|
-
"windowTitle": "ServerlessIRCd
|
|
187
|
+
"windowTitle": "ServerlessIRCd",
|
|
178
188
|
"startupOptions": {
|
|
179
189
|
"server": "{{hostname}}", // resolved to window.location.hostname at boot
|
|
180
190
|
"direct": true, // MUST be true — direct WS, no kiwiServer gateway
|
|
@@ -191,7 +201,7 @@ Field notes (see the schema docstrings for the full rationale):
|
|
|
191
201
|
- **`startupOptions.server`** — `{{hostname}}` (or `{{host}}` to keep
|
|
192
202
|
a non-default port) is a Kiwi template resolved by the browser to
|
|
193
203
|
`window.location.hostname` at load time. This is what lets one build
|
|
194
|
-
artifact target
|
|
204
|
+
artifact target prod, preview, and PR deploys without a
|
|
195
205
|
per-env hostname. A literal hostname is also accepted.
|
|
196
206
|
- **`startupOptions.direct`** — MUST be `true`. ServerlessIRCd is a
|
|
197
207
|
direct IRC-over-WebSocket server; the Kiwi default (`false`) routes
|
|
@@ -209,7 +219,7 @@ Field notes (see the schema docstrings for the full rationale):
|
|
|
209
219
|
A malformed config aborts the build with an aggregated error, e.g.:
|
|
210
220
|
|
|
211
221
|
```
|
|
212
|
-
config.
|
|
222
|
+
config.prod.json: Invalid Kiwi web config:
|
|
213
223
|
- startupOptions.port: port must be a valid TCP port (1-65535)
|
|
214
224
|
- startupOptions.channel: must begin with '#'
|
|
215
225
|
```
|
|
@@ -275,7 +285,7 @@ WEB_ORIGINS = "https://irc.example.com,https://web.example.com"
|
|
|
275
285
|
|
|
276
286
|
```bash
|
|
277
287
|
# or per-deploy via the secret store / dashboard env vars:
|
|
278
|
-
wrangler secret put WEB_ORIGINS
|
|
288
|
+
wrangler secret put WEB_ORIGINS
|
|
279
289
|
```
|
|
280
290
|
|
|
281
291
|
### 5.3 Responses
|
|
@@ -323,8 +333,8 @@ node apps/cf-worker/scripts/smoke.mjs
|
|
|
323
333
|
### 6.2 Iterate on the SPA config without rebuilding Kiwi
|
|
324
334
|
|
|
325
335
|
The Kiwi upstream build is the slow step. For a config-only change,
|
|
326
|
-
edit `static/config.
|
|
327
|
-
`pnpm --filter web build:
|
|
336
|
+
edit `static/config.prod.json`, then re-run
|
|
337
|
+
`pnpm --filter web build:prod` — step 2 of the build reuses
|
|
328
338
|
`upstream/node_modules` and skips straight to re-baking the config
|
|
329
339
|
(steps 4–6 are fast).
|
|
330
340
|
|
|
@@ -502,8 +512,7 @@ git submodule update --init # docs + apps/web/upstream (Kiwi)
|
|
|
502
512
|
pnpm install
|
|
503
513
|
|
|
504
514
|
# Build the SPA + landing page (pick the env)
|
|
505
|
-
pnpm --filter web build # default config (
|
|
506
|
-
pnpm --filter web build:staging # static/config.staging.json
|
|
515
|
+
pnpm --filter web build # default config (dev target)
|
|
507
516
|
pnpm --filter web build:prod # static/config.prod.json
|
|
508
517
|
|
|
509
518
|
# Local dev (Worker serves SPA + landing page + WS on one origin)
|
|
@@ -511,8 +520,8 @@ pnpm build # build the rest of the workspace
|
|
|
511
520
|
pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
|
|
512
521
|
node apps/cf-worker/scripts/smoke.mjs # WS smoke
|
|
513
522
|
|
|
514
|
-
# Deploy
|
|
515
|
-
pnpm deploy:cf
|
|
523
|
+
# Deploy (Worker + assets in one command)
|
|
524
|
+
pnpm deploy:cf # wrangler deploy
|
|
516
525
|
|
|
517
526
|
# Tests / lint
|
|
518
527
|
pnpm --filter web test
|
|
@@ -527,7 +536,7 @@ Key files:
|
|
|
527
536
|
| `apps/web/scripts/build.mjs` | Orchestrates the upstream Kiwi build + layers our config. |
|
|
528
537
|
| `apps/web/src/config-schema.ts` | Zod schema for `static/config.*.json` (the only TS we own). |
|
|
529
538
|
| `apps/web/src/build-env.ts` | `--env` → config-file resolution (unit-tested). |
|
|
530
|
-
| `apps/web/static/config.{,
|
|
539
|
+
| `apps/web/static/config.{,prod}.json` | Kiwi configs (validated at build time). |
|
|
531
540
|
| `apps/web/landing/index.html` | Static landing page (project front door), copied to `dist/index.html`. |
|
|
532
541
|
| `apps/web/tests/build-smoke.test.ts` | Landing-page + build-output assertions. |
|
|
533
542
|
| `apps/cf-worker/src/worker.ts` | Edge handler: WS → `ConnectionDO`, `/health`, else `env.ASSETS`. |
|