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.
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/deploy-aws.yml +1 -3
- package/.github/workflows/deploy-cf-tcp.yml +87 -0
- package/.github/workflows/deploy-cf.yml +1 -1
- package/.node-version +1 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +258 -18
- package/README.md +72 -30
- package/apps/aws-stack/README.md +2 -2
- package/apps/aws-stack/bin/aws.ts +7 -0
- package/apps/aws-stack/package.json +4 -4
- package/apps/aws-stack/src/aws-stack.ts +118 -6
- package/apps/aws-stack/tests/stack.test.ts +98 -3
- package/apps/cf-tcp-container/Dockerfile +69 -0
- package/apps/cf-tcp-container/package.json +34 -0
- package/apps/cf-tcp-container/src/config-loader.ts +145 -0
- package/apps/cf-tcp-container/src/container-do.ts +38 -0
- package/apps/cf-tcp-container/src/container-server.ts +363 -0
- package/apps/cf-tcp-container/src/main.ts +77 -0
- package/apps/cf-tcp-container/src/persistence.ts +144 -0
- package/apps/cf-tcp-container/src/worker.ts +41 -0
- package/apps/cf-tcp-container/terraform/provider.tf +24 -0
- package/apps/cf-tcp-container/terraform/spectrum.tf +81 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +217 -0
- package/apps/cf-tcp-container/tests/container-server.test.ts +465 -0
- package/apps/cf-tcp-container/tests/persistence.test.ts +227 -0
- package/apps/cf-tcp-container/tests/tls-e2e.test.ts +275 -0
- package/apps/cf-tcp-container/tsconfig.build.json +17 -0
- package/apps/cf-tcp-container/tsconfig.test.json +15 -0
- package/apps/cf-tcp-container/vitest.config.ts +26 -0
- package/apps/cf-tcp-container/wrangler.toml +63 -0
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/scripts/smoke.mjs +0 -0
- package/apps/cf-worker/src/worker.ts +8 -2
- package/apps/cf-worker/tests/smoke.test.ts +1 -0
- package/apps/cf-worker/wrangler.test.toml +11 -1
- package/apps/cf-worker/wrangler.toml +69 -19
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +11 -0
- package/apps/local-cli/src/main.ts +1 -1
- package/apps/local-cli/src/server.ts +46 -3
- package/apps/local-cli/tests/e2e.test.ts +52 -0
- package/package.json +19 -16
- package/packages/aws-adapter/package.json +3 -3
- package/packages/aws-adapter/src/account-store.ts +121 -0
- package/packages/aws-adapter/src/admission.ts +74 -0
- package/packages/aws-adapter/src/aws-runtime.ts +124 -34
- package/packages/aws-adapter/src/cdk-table-defs.ts +5 -1
- package/packages/aws-adapter/src/config-loader.ts +62 -0
- package/packages/aws-adapter/src/dynamo-account-store.ts +95 -0
- package/packages/aws-adapter/src/handlers/connect.ts +64 -8
- package/packages/aws-adapter/src/handlers/default.ts +43 -2
- package/packages/aws-adapter/src/handlers/disconnect.ts +27 -8
- package/packages/aws-adapter/src/handlers/index.ts +120 -9
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +481 -0
- package/packages/aws-adapter/src/handlers/ping-checker.ts +1 -1
- package/packages/aws-adapter/src/index.ts +13 -0
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +182 -0
- package/packages/aws-adapter/tests/account-store.test.ts +279 -0
- package/packages/aws-adapter/tests/admission.test.ts +70 -0
- package/packages/aws-adapter/tests/aws-harness.ts +13 -1
- package/packages/aws-adapter/tests/config-loader.test.ts +75 -0
- package/packages/aws-adapter/tests/connect.test.ts +78 -0
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +343 -0
- package/packages/aws-adapter/tests/gone-exception.test.ts +31 -26
- package/packages/aws-adapter/tests/handlers.test.ts +194 -47
- package/packages/aws-adapter/tests/nlb-stream.test.ts +478 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +34 -29
- package/packages/aws-adapter/tests/sweeper.test.ts +25 -18
- package/packages/aws-adapter/tests/transactions.test.ts +25 -20
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/src/cf-runtime.ts +40 -22
- package/packages/cf-adapter/src/channel-do.ts +40 -1
- package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
- package/packages/cf-adapter/src/config-loader.ts +62 -0
- package/packages/cf-adapter/src/connection-do.ts +181 -31
- package/packages/cf-adapter/src/d1-account-store.ts +198 -0
- package/packages/cf-adapter/src/env.ts +58 -8
- package/packages/cf-adapter/src/index.ts +11 -9
- package/packages/cf-adapter/tests/cf-harness.ts +11 -1
- package/packages/cf-adapter/tests/cf-integration.test.ts +2 -1
- package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +22 -0
- package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +37 -0
- package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +52 -0
- package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +166 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +40 -0
- package/packages/cf-adapter/tests/d1-account-store.test.ts +226 -0
- package/packages/cf-adapter/tests/raw-modules.d.ts +11 -0
- package/packages/cf-adapter/tests/worker/main.ts +9 -1
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
- package/packages/cf-adapter/wrangler.test.toml +18 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +14 -28
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +19 -0
- package/packages/irc-core/package.json +8 -2
- package/packages/irc-core/scripts/generate-build-info.mjs +31 -0
- package/packages/irc-core/src/caps/capabilities.ts +23 -2
- package/packages/irc-core/src/case-fold.ts +64 -0
- package/packages/irc-core/src/cloak.ts +1 -1
- package/packages/irc-core/src/commands/cap.ts +8 -1
- package/packages/irc-core/src/commands/index.ts +11 -0
- package/packages/irc-core/src/commands/invite.ts +6 -9
- package/packages/irc-core/src/commands/ison.ts +61 -0
- package/packages/irc-core/src/commands/isupport.ts +1 -1
- package/packages/irc-core/src/commands/join.ts +4 -3
- package/packages/irc-core/src/commands/kick.ts +4 -11
- package/packages/irc-core/src/commands/list.ts +5 -4
- package/packages/irc-core/src/commands/mode.ts +5 -9
- package/packages/irc-core/src/commands/motd-lines.ts +127 -0
- package/packages/irc-core/src/commands/motd.ts +6 -110
- package/packages/irc-core/src/commands/oper.ts +151 -0
- package/packages/irc-core/src/commands/quit.ts +12 -0
- package/packages/irc-core/src/commands/registration.ts +26 -19
- package/packages/irc-core/src/commands/sasl.ts +72 -9
- package/packages/irc-core/src/commands/server-info.ts +129 -0
- package/packages/irc-core/src/commands/tagmsg.ts +205 -0
- package/packages/irc-core/src/commands/userhost.ts +84 -0
- package/packages/irc-core/src/commands/whowas.ts +113 -0
- package/packages/irc-core/src/config.ts +84 -3
- package/packages/irc-core/src/credential-hashing.ts +124 -0
- package/packages/irc-core/src/effects.ts +6 -29
- package/packages/irc-core/src/index.ts +3 -0
- package/packages/irc-core/src/ports.ts +240 -7
- package/packages/irc-core/src/protocol/numerics.ts +14 -8
- package/packages/irc-core/src/types.ts +60 -1
- package/packages/irc-core/tests/account-store.test.ts +131 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +4 -3
- package/packages/irc-core/tests/case-fold.test.ts +80 -0
- package/packages/irc-core/tests/commands/cap.test.ts +33 -1
- package/packages/irc-core/tests/commands/ison.test.ts +166 -0
- package/packages/irc-core/tests/commands/kick.test.ts +15 -0
- package/packages/irc-core/tests/commands/oper.test.ts +257 -0
- package/packages/irc-core/tests/commands/quit.test.ts +69 -2
- package/packages/irc-core/tests/commands/registration.test.ts +256 -19
- package/packages/irc-core/tests/commands/sasl.test.ts +118 -10
- package/packages/irc-core/tests/commands/server-info.test.ts +274 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +662 -0
- package/packages/irc-core/tests/commands/userhost.test.ts +264 -0
- package/packages/irc-core/tests/commands/who.test.ts +22 -4
- package/packages/irc-core/tests/commands/whois.test.ts +8 -1
- package/packages/irc-core/tests/commands/whowas.test.ts +312 -0
- package/packages/irc-core/tests/config.test.ts +170 -1
- package/packages/irc-core/tests/credential-hashing.test.ts +170 -0
- package/packages/irc-core/tests/effects.test.ts +0 -27
- package/packages/irc-core/tests/nick-history-store.test.ts +162 -0
- package/packages/irc-core/tests/numerics.test.ts +12 -0
- package/packages/irc-core/tests/types.test.ts +35 -1
- package/packages/irc-core/tsconfig.build.json +1 -1
- package/packages/irc-core/tsconfig.test.json +1 -1
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +182 -14
- package/packages/irc-server/src/dispatch.ts +0 -3
- package/packages/irc-server/src/index.ts +10 -2
- package/packages/irc-server/src/routing.ts +21 -0
- package/packages/irc-server/src/transport.ts +101 -0
- package/packages/irc-server/tests/actor.test.ts +617 -1
- package/packages/irc-server/tests/dispatch.test.ts +0 -17
- package/packages/irc-server/tests/routing.test.ts +7 -0
- package/packages/irc-server/tests/transport.test.ts +230 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/harness.ts +44 -9
- package/packages/irc-test-support/src/in-memory-harness.ts +78 -13
- package/packages/irc-test-support/src/index.ts +3 -0
- package/packages/irc-test-support/src/scenarios.ts +132 -2
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +2 -1
- package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +23 -9
- package/pnpm-workspace.yaml +9 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/package.json +4 -0
- package/tools/seed-aws-accounts.ts +79 -0
- package/tools/seed-cf-accounts.ts +104 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/AGENTS.md +0 -5
- package/MOTD.txt +0 -3
- package/PLAN-FIXES.md +0 -358
- package/PLAN.md +0 -420
- package/dashboards/cloudwatch-irc.json +0 -139
- package/docs/AWS-Adapter-Architecture.md +0 -494
- package/docs/AWS-Deployment.md +0 -1107
- package/docs/Cloudflare-Deployment-Guide.md +0 -660
- package/docs/Home.md +0 -1
- package/docs/Observability.md +0 -87
- package/docs/PlanIRCv3Websocket.md +0 -489
- package/docs/PlanWebClient.md +0 -451
- package/docs/Release-Process.md +0 -440
- package/progress.md +0 -107
- package/tickets.md +0 -2485
- package/webircgateway/LICENSE +0 -201
- package/webircgateway/Makefile +0 -44
- package/webircgateway/README.md +0 -134
- package/webircgateway/config.conf.example +0 -135
- package/webircgateway/go.mod +0 -16
- package/webircgateway/go.sum +0 -89
- package/webircgateway/main.go +0 -118
- package/webircgateway/pkg/dnsbl/dnsbl.go +0 -121
- package/webircgateway/pkg/identd/identd.go +0 -86
- package/webircgateway/pkg/identd/rpcclient.go +0 -59
- package/webircgateway/pkg/irc/isupport.go +0 -56
- package/webircgateway/pkg/irc/message.go +0 -217
- package/webircgateway/pkg/irc/state.go +0 -79
- package/webircgateway/pkg/proxy/proxy.go +0 -129
- package/webircgateway/pkg/proxy/server.go +0 -237
- package/webircgateway/pkg/recaptcha/recaptcha.go +0 -59
- package/webircgateway/pkg/webircgateway/client.go +0 -741
- package/webircgateway/pkg/webircgateway/client_command_handlers.go +0 -495
- package/webircgateway/pkg/webircgateway/config.go +0 -385
- package/webircgateway/pkg/webircgateway/gateway.go +0 -278
- package/webircgateway/pkg/webircgateway/gateway_utils.go +0 -133
- package/webircgateway/pkg/webircgateway/hooks.go +0 -152
- package/webircgateway/pkg/webircgateway/letsencrypt.go +0 -41
- package/webircgateway/pkg/webircgateway/messagetags.go +0 -103
- package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +0 -206
- package/webircgateway/pkg/webircgateway/transport_sockjs.go +0 -107
- package/webircgateway/pkg/webircgateway/transport_tcp.go +0 -113
- package/webircgateway/pkg/webircgateway/transport_websocket.go +0 -126
- package/webircgateway/pkg/webircgateway/utils.go +0 -147
- package/webircgateway/plugins/example/plugin.go +0 -11
- package/webircgateway/plugins/stats/plugin.go +0 -52
- package/webircgateway/staticcheck.conf +0 -1
- package/webircgateway/webircgateway.svg +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serverless-ircd/aws-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "AWS CDK v2 stack: API Gateway v2 WebSocket API, Lambda, DynamoDB tables, least-privilege IAM",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"smoke:staging": "node scripts/smoke.mjs"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"aws-cdk": "^2.
|
|
23
|
-
"aws-cdk-lib": "^2.
|
|
22
|
+
"aws-cdk": "^2.262.0",
|
|
23
|
+
"aws-cdk-lib": "^2.262.0",
|
|
24
24
|
"constructs": "^10.4.0",
|
|
25
25
|
"@types/node": "^26.1.1",
|
|
26
26
|
"@types/ws": "^8.5.13",
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
"@serverless-ircd/aws-adapter": "workspace:*"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": ">=24"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* • 5 DynamoDB tables sourced from `@serverless-ircd/aws-adapter/cdk-table-defs`
|
|
5
5
|
* (Connections, ChannelMeta, ChannelMembers, Nicks, Accounts).
|
|
6
|
-
* • ONE Node
|
|
6
|
+
* • ONE Node 24 `NodejsFunction` bundling the AWS Lambda handler
|
|
7
7
|
* (`$connect` / `$disconnect` / `$default` dispatched on
|
|
8
8
|
* `event.requestContext.routeKey`).
|
|
9
|
-
* • ONE Node
|
|
9
|
+
* • ONE Node 24 `NodejsFunction` for the gone-connection sweeper
|
|
10
10
|
* (`sweeperHandler`), invoked on a fixed EventBridge schedule.
|
|
11
11
|
* • API Gateway v2 WebSocket API + auto-deploying `prod` stage.
|
|
12
12
|
* • Least-privilege IAM: scoped DynamoDB data-plane grants per table +
|
|
@@ -19,7 +19,17 @@ import { TABLE_DEFS } from '@serverless-ircd/aws-adapter/cdk-table-defs';
|
|
|
19
19
|
import { CfnOutput, Duration, RemovalPolicy, Stack, type StackProps } from 'aws-cdk-lib';
|
|
20
20
|
import { WebSocketApi, WebSocketStage } from 'aws-cdk-lib/aws-apigatewayv2';
|
|
21
21
|
import { WebSocketLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
|
|
22
|
+
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
|
|
22
23
|
import { Table } from 'aws-cdk-lib/aws-dynamodb';
|
|
24
|
+
import { Vpc } from 'aws-cdk-lib/aws-ec2';
|
|
25
|
+
import {
|
|
26
|
+
NetworkListener,
|
|
27
|
+
NetworkLoadBalancer,
|
|
28
|
+
NetworkTargetGroup,
|
|
29
|
+
Protocol,
|
|
30
|
+
TargetType,
|
|
31
|
+
} from 'aws-cdk-lib/aws-elasticloadbalancingv2';
|
|
32
|
+
import { LambdaTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';
|
|
23
33
|
import { Rule, Schedule } from 'aws-cdk-lib/aws-events';
|
|
24
34
|
import { LambdaFunction } from 'aws-cdk-lib/aws-events-targets';
|
|
25
35
|
import { Runtime } from 'aws-cdk-lib/aws-lambda';
|
|
@@ -29,6 +39,9 @@ import type { Construct } from 'constructs';
|
|
|
29
39
|
/** Stage name served by the auto-deploying WebSocket stage. */
|
|
30
40
|
const STAGE_NAME = 'prod';
|
|
31
41
|
|
|
42
|
+
/** IRC over TLS port served by the NLB listener (RFC-compliant :6697). */
|
|
43
|
+
const IRC_TLS_PORT = 6697;
|
|
44
|
+
|
|
32
45
|
/** Fixed rate for the gone-connection sweeper (matches the architecture doc). */
|
|
33
46
|
const SWEEPER_SCHEDULE_RATE = Duration.minutes(5);
|
|
34
47
|
|
|
@@ -91,6 +104,21 @@ export interface IrcStackProps extends StackProps {
|
|
|
91
104
|
* a `string[]` correctly. Defaults to a short two-line banner.
|
|
92
105
|
*/
|
|
93
106
|
readonly motdLines?: string[];
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Domain name for the TCP+TLS (IRC `:6697`) endpoint.
|
|
110
|
+
*
|
|
111
|
+
* When provided, the stack provisions:
|
|
112
|
+
* - An ACM certificate for this domain.
|
|
113
|
+
* - A Network Load Balancer with a `TLS` listener on `:6697`.
|
|
114
|
+
* - A Lambda streaming target (the `nlbStreamHandler` export).
|
|
115
|
+
*
|
|
116
|
+
* The NLB terminates TLS at the edge and forwards plaintext TCP
|
|
117
|
+
* invocations to the Lambda. Connection identity is derived from
|
|
118
|
+
* NLB flow metadata (source IP + port). When omitted, only the wss
|
|
119
|
+
* (WebSocket) transport is deployed.
|
|
120
|
+
*/
|
|
121
|
+
readonly tcpTlsDomainName?: string;
|
|
94
122
|
}
|
|
95
123
|
|
|
96
124
|
export class IrcAwsStack extends Stack {
|
|
@@ -118,11 +146,11 @@ export class IrcAwsStack extends Stack {
|
|
|
118
146
|
);
|
|
119
147
|
|
|
120
148
|
const handler = new NodejsFunction(this, 'IrcHandler', {
|
|
121
|
-
runtime: Runtime.
|
|
149
|
+
runtime: Runtime.NODEJS_24_X,
|
|
122
150
|
entry: handlerEntry(),
|
|
123
151
|
handler: 'handler',
|
|
124
152
|
bundling: {
|
|
125
|
-
// AWS SDK v3 is in the Lambda Node
|
|
153
|
+
// AWS SDK v3 is in the Lambda Node 24 runtime; exclude it so the
|
|
126
154
|
// bundle stays small. irc-core / irc-server are bundled because
|
|
127
155
|
// they are first-party.
|
|
128
156
|
externalModules: ['@aws-sdk/*'],
|
|
@@ -134,7 +162,7 @@ export class IrcAwsStack extends Stack {
|
|
|
134
162
|
// Catches connections that vanished without APIGW emitting $disconnect;
|
|
135
163
|
// see packages/aws-adapter/src/handlers/sweeper.ts.
|
|
136
164
|
const sweeper = new NodejsFunction(this, 'IrcSweeper', {
|
|
137
|
-
runtime: Runtime.
|
|
165
|
+
runtime: Runtime.NODEJS_24_X,
|
|
138
166
|
entry: handlerEntry(),
|
|
139
167
|
handler: 'sweeperHandler',
|
|
140
168
|
bundling: {
|
|
@@ -148,7 +176,7 @@ export class IrcAwsStack extends Stack {
|
|
|
148
176
|
// packages/aws-adapter/src/handlers/ping-checker.ts. The Cloudflare
|
|
149
177
|
// adapter implements the equivalent flow inside `ConnectionDO.alarm()`.
|
|
150
178
|
const pingChecker = new NodejsFunction(this, 'IrcPingChecker', {
|
|
151
|
-
runtime: Runtime.
|
|
179
|
+
runtime: Runtime.NODEJS_24_X,
|
|
152
180
|
entry: handlerEntry(),
|
|
153
181
|
handler: 'pingCheckerHandler',
|
|
154
182
|
bundling: {
|
|
@@ -232,6 +260,90 @@ export class IrcAwsStack extends Stack {
|
|
|
232
260
|
targets: [new LambdaFunction(pingChecker)],
|
|
233
261
|
});
|
|
234
262
|
|
|
263
|
+
// -------------------------------------------------------------------------
|
|
264
|
+
// TCP+TLS adapter via Network Load Balancer.
|
|
265
|
+
//
|
|
266
|
+
// An NLB with a `TLS` listener terminates TLS at the edge and invokes a
|
|
267
|
+
// Lambda streaming function (`nlbStreamHandler`) for each chunk of client
|
|
268
|
+
// data. The Lambda response body is streamed back over the held-open TCP
|
|
269
|
+
// connection. Only provisioned when `tcpTlsDomainName` is supplied.
|
|
270
|
+
// -------------------------------------------------------------------------
|
|
271
|
+
if (props.tcpTlsDomainName !== undefined) {
|
|
272
|
+
const tcpTlsDomain = props.tcpTlsDomainName;
|
|
273
|
+
|
|
274
|
+
// ACM certificate for TLS termination at the NLB.
|
|
275
|
+
const cert = new Certificate(this, 'TlsCertificate', {
|
|
276
|
+
domainName: tcpTlsDomain,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// NLB requires a VPC. A dedicated two-AZ VPC keeps the NLB path
|
|
280
|
+
// isolated from the serverless (APIGW) path which has no VPC.
|
|
281
|
+
// Cast bypasses a CDK `exactOptionalPropertyTypes` friction between
|
|
282
|
+
// the concrete `Vpc` and the `IVpc` interface (optional vs required
|
|
283
|
+
// properties in the CDK jsii layer).
|
|
284
|
+
const vpc = new Vpc(this, 'NlbVpc', {
|
|
285
|
+
maxAzs: 2,
|
|
286
|
+
natGateways: 0,
|
|
287
|
+
}) as never;
|
|
288
|
+
|
|
289
|
+
// The NLB streaming Lambda — same entry, different handler export.
|
|
290
|
+
// Shares the bundled aws-adapter code (tree-shaken to the nlb-stream
|
|
291
|
+
// path by esbuild at synth time). Reuses the same DynamoDB tables,
|
|
292
|
+
// server identity, and MANAGEMENT_URL as the wss handler.
|
|
293
|
+
const nlbHandler = new NodejsFunction(this, 'IrcNlbHandler', {
|
|
294
|
+
runtime: Runtime.NODEJS_24_X,
|
|
295
|
+
entry: handlerEntry(),
|
|
296
|
+
handler: 'nlbStreamHandler',
|
|
297
|
+
bundling: {
|
|
298
|
+
externalModules: ['@aws-sdk/*'],
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Grant the NLB handler the same DynamoDB access as the wss handler.
|
|
303
|
+
for (const table of Object.values(tables)) {
|
|
304
|
+
table.grantReadWriteData(nlbHandler);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Inject the same table names, server identity, and management URL.
|
|
308
|
+
for (const logicalId of Object.keys(TABLE_DEFS)) {
|
|
309
|
+
const envName = `${logicalId.toUpperCase()}_TABLE`;
|
|
310
|
+
nlbHandler.addEnvironment(envName, prefixedTableName(environmentName, logicalId));
|
|
311
|
+
}
|
|
312
|
+
nlbHandler.addEnvironment('SERVER_NAME', serverName);
|
|
313
|
+
nlbHandler.addEnvironment('NETWORK_NAME', networkName);
|
|
314
|
+
nlbHandler.addEnvironment('MOTD', motd);
|
|
315
|
+
nlbHandler.addEnvironment('MANAGEMENT_URL', stage.callbackUrl);
|
|
316
|
+
|
|
317
|
+
const nlb = new NetworkLoadBalancer(this, 'IrcNlb', {
|
|
318
|
+
vpc,
|
|
319
|
+
internetFacing: true,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const targetGroup = new NetworkTargetGroup(this, 'IrcNlbTargetGroup', {
|
|
323
|
+
port: IRC_TLS_PORT,
|
|
324
|
+
protocol: Protocol.TCP,
|
|
325
|
+
targetType: TargetType.LAMBDA,
|
|
326
|
+
targets: [new LambdaTarget(nlbHandler)],
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
new NetworkListener(this, 'IrcNlbTlsListener', {
|
|
330
|
+
// Cast bypasses a CDK `exactOptionalPropertyTypes` friction between
|
|
331
|
+
// the concrete `NetworkLoadBalancer` and the `INetworkLoadBalancer`
|
|
332
|
+
// interface (optional vs required `securityGroups`).
|
|
333
|
+
loadBalancer: nlb as never,
|
|
334
|
+
port: IRC_TLS_PORT,
|
|
335
|
+
protocol: Protocol.TLS,
|
|
336
|
+
certificates: [{ certificateArn: cert.certificateArn }],
|
|
337
|
+
defaultTargetGroups: [targetGroup],
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
new CfnOutput(this, 'TcpConnectUrl', {
|
|
341
|
+
description:
|
|
342
|
+
'TCP+TLS connect endpoint (irc+tls :6697). Connect via the NLB DNS name on port 6697.',
|
|
343
|
+
value: `${nlb.loadBalancerDnsName}:6697`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
235
347
|
new CfnOutput(this, 'ConnectUrl', {
|
|
236
348
|
description: 'WebSocket connect endpoint clients dial (wss).',
|
|
237
349
|
value: stage.url,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These are the primary red→green tests for the infrastructure ticket:
|
|
5
5
|
* they assert the exact CloudFormation shape that `cdk synth` must
|
|
6
6
|
* produce — five DynamoDB tables with the PLAN §4 schema, a single
|
|
7
|
-
* Node
|
|
7
|
+
* Node 24 Lambda wired to all three WebSocket routes, the API Gateway
|
|
8
8
|
* v2 WebSocket API, the two stack outputs, and a least-privilege guard
|
|
9
9
|
* that no IAM policy grants `Resource: '*'` or `Action: '*'`.
|
|
10
10
|
*
|
|
@@ -157,10 +157,10 @@ describe('IrcAwsStack — Lambda', () => {
|
|
|
157
157
|
template.resourceCountIs('AWS::Lambda::Function', 3);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
it('targets the Node
|
|
160
|
+
it('targets the Node 24 runtime for every function', () => {
|
|
161
161
|
const { template } = makeTemplate();
|
|
162
162
|
template.allResourcesProperties('AWS::Lambda::Function', {
|
|
163
|
-
Runtime: '
|
|
163
|
+
Runtime: 'nodejs24.x',
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
166
|
});
|
|
@@ -462,3 +462,98 @@ describe('IrcAwsStack — server identity props (configurable deploy identity)',
|
|
|
462
462
|
).toBeUndefined();
|
|
463
463
|
});
|
|
464
464
|
});
|
|
465
|
+
|
|
466
|
+
describe('IrcAwsStack — TCP+TLS NLB adapter', () => {
|
|
467
|
+
/** Makes a template WITH the tcpTlsDomainName prop (enables the NLB path). */
|
|
468
|
+
function makeTcpTemplate(domainName = 'irc.example.com'): {
|
|
469
|
+
app: App;
|
|
470
|
+
stack: Stack;
|
|
471
|
+
template: Template;
|
|
472
|
+
} {
|
|
473
|
+
const app = new App();
|
|
474
|
+
const stack = new IrcAwsStack(app, 'TestStack', {
|
|
475
|
+
environmentName: 'staging',
|
|
476
|
+
tcpTlsDomainName: domainName,
|
|
477
|
+
});
|
|
478
|
+
const template = Template.fromStack(stack);
|
|
479
|
+
return { app, stack, template };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
describe('when tcpTlsDomainName is omitted (default)', () => {
|
|
483
|
+
it('creates no Network Load Balancer', () => {
|
|
484
|
+
const { template } = makeTemplate();
|
|
485
|
+
template.resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 0);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('creates no ACM certificate', () => {
|
|
489
|
+
const { template } = makeTemplate();
|
|
490
|
+
template.resourceCountIs('AWS::CertificateManager::Certificate', 0);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('still creates exactly three Lambda functions', () => {
|
|
494
|
+
const { template } = makeTemplate();
|
|
495
|
+
template.resourceCountIs('AWS::Lambda::Function', 3);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe('when tcpTlsDomainName is provided', () => {
|
|
500
|
+
it('creates a Network Load Balancer', () => {
|
|
501
|
+
const { template } = makeTcpTemplate();
|
|
502
|
+
template.resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('creates an ACM certificate for the provided domain', () => {
|
|
506
|
+
const { template } = makeTcpTemplate('irc.my.net');
|
|
507
|
+
template.hasResourceProperties('AWS::CertificateManager::Certificate', {
|
|
508
|
+
DomainName: 'irc.my.net',
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('creates a TLS listener on port 6697', () => {
|
|
513
|
+
const { template } = makeTcpTemplate();
|
|
514
|
+
template.hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', {
|
|
515
|
+
Port: 6697,
|
|
516
|
+
Protocol: 'TLS',
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('creates a target group with Lambda as the target type', () => {
|
|
521
|
+
const { template } = makeTcpTemplate();
|
|
522
|
+
template.hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', {
|
|
523
|
+
TargetType: 'lambda',
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('creates a fourth Lambda function for the NLB streaming handler', () => {
|
|
528
|
+
const { template } = makeTcpTemplate();
|
|
529
|
+
template.resourceCountIs('AWS::Lambda::Function', 4);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('targets the Node 24 runtime for the NLB handler Lambda', () => {
|
|
533
|
+
const { template } = makeTcpTemplate();
|
|
534
|
+
template.allResourcesProperties('AWS::Lambda::Function', {
|
|
535
|
+
Runtime: 'nodejs24.x',
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('grants the NLB permission to invoke the handler Lambda', () => {
|
|
540
|
+
const { template } = makeTcpTemplate();
|
|
541
|
+
template.hasResourceProperties('AWS::Lambda::Permission', {
|
|
542
|
+
Principal: 'elasticloadbalancing.amazonaws.com',
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('still creates the wss WebSocket API (both transports coexist)', () => {
|
|
547
|
+
const { template } = makeTcpTemplate();
|
|
548
|
+
template.hasResourceProperties('AWS::ApiGatewayV2::Api', {
|
|
549
|
+
ProtocolType: 'WEBSOCKET',
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('emits a TcpConnectUrl output pointing at the NLB DNS', () => {
|
|
554
|
+
const { template } = makeTcpTemplate();
|
|
555
|
+
const outputs = template.toJSON().Outputs ?? {};
|
|
556
|
+
expect(outputs).toHaveProperty('TcpConnectUrl');
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Cloudflare Container / Docker image for the ServerlessIRCd TCP origin.
|
|
2
|
+
#
|
|
3
|
+
# Spectrum terminates TLS at the edge and forwards plaintext TCP to this
|
|
4
|
+
# container. The container runs the IRC core (ConnectionActor +
|
|
5
|
+
# InMemoryRuntime) over the TcpByteStreamTransport seam on port 6667.
|
|
6
|
+
#
|
|
7
|
+
# Build context: the repository root (monorepo). The container needs the
|
|
8
|
+
# irc-core, irc-server, and in-memory-runtime packages.
|
|
9
|
+
#
|
|
10
|
+
# docker build -f apps/cf-tcp-container/Dockerfile -t sirc-tcp-origin .
|
|
11
|
+
# docker run -p 6667:6667 -e SERVER_NAME=irc.example.com sirc-tcp-origin
|
|
12
|
+
#
|
|
13
|
+
# For Cloudflare Containers deployment, the same image is referenced from
|
|
14
|
+
# wrangler.toml.
|
|
15
|
+
|
|
16
|
+
FROM node:24-slim AS builder
|
|
17
|
+
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
|
|
20
|
+
# Install pnpm.
|
|
21
|
+
RUN corepack enable && corepack prepare pnpm@11.17.0 --activate
|
|
22
|
+
|
|
23
|
+
# Copy workspace config + lockfile first for layer caching.
|
|
24
|
+
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
|
25
|
+
COPY tsconfig.base.json ./
|
|
26
|
+
|
|
27
|
+
# Copy all workspace packages needed for the build.
|
|
28
|
+
COPY packages/irc-core/ packages/irc-core/
|
|
29
|
+
COPY packages/irc-server/ packages/irc-server/
|
|
30
|
+
COPY packages/in-memory-runtime/ packages/in-memory-runtime/
|
|
31
|
+
COPY apps/cf-tcp-container/ apps/cf-tcp-container/
|
|
32
|
+
|
|
33
|
+
# Install dependencies and build.
|
|
34
|
+
RUN pnpm install --frozen-lockfile
|
|
35
|
+
RUN pnpm --filter '@serverless-ircd/irc-core' build && \
|
|
36
|
+
pnpm --filter '@serverless-ircd/irc-server' build && \
|
|
37
|
+
pnpm --filter '@serverless-ircd/in-memory-runtime' build && \
|
|
38
|
+
pnpm --filter '@serverless-ircd/cf-tcp-container' build
|
|
39
|
+
|
|
40
|
+
# --- Runtime stage ---
|
|
41
|
+
FROM node:24-slim AS runtime
|
|
42
|
+
|
|
43
|
+
WORKDIR /app
|
|
44
|
+
|
|
45
|
+
# Copy built artifacts + package manifests (for workspace resolution).
|
|
46
|
+
COPY --from=builder /app/package.json /app/pnpm-workspace.yaml ./
|
|
47
|
+
COPY --from=builder /app/packages/irc-core/package.json packages/irc-core/
|
|
48
|
+
COPY --from=builder /app/packages/irc-core/dist/ packages/irc-core/dist/
|
|
49
|
+
COPY --from=builder /app/packages/irc-server/package.json packages/irc-server/
|
|
50
|
+
COPY --from=builder /app/packages/irc-server/dist/ packages/irc-server/dist/
|
|
51
|
+
COPY --from=builder /app/packages/in-memory-runtime/package.json packages/in-memory-runtime/
|
|
52
|
+
COPY --from=builder /app/packages/in-memory-runtime/dist/ packages/in-memory-runtime/dist/
|
|
53
|
+
COPY --from=builder /app/apps/cf-tcp-container/package.json apps/cf-tcp-container/
|
|
54
|
+
COPY --from=builder /app/apps/cf-tcp-container/dist/ apps/cf-tcp-container/dist/
|
|
55
|
+
|
|
56
|
+
# Production install (only the workspace deps, no devDependencies).
|
|
57
|
+
RUN corepack enable && corepack prepare pnpm@11.17.0 --activate && \
|
|
58
|
+
pnpm install --frozen-lockfile --prod
|
|
59
|
+
|
|
60
|
+
# Persistence volume: Spectrum-origin channel state survives restarts.
|
|
61
|
+
VOLUME ["/data"]
|
|
62
|
+
|
|
63
|
+
EXPOSE 6667
|
|
64
|
+
|
|
65
|
+
ENV TCP_PORT=6667
|
|
66
|
+
ENV TCP_HOST=0.0.0.0
|
|
67
|
+
ENV PERSISTENCE_PATH=/data/state.json
|
|
68
|
+
|
|
69
|
+
CMD ["node", "--enable-source-maps", "apps/cf-tcp-container/dist/main.js"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@serverless-ircd/cf-tcp-container",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Cloudflare Container TCP origin for the irc+tls :6697 transport — Spectrum terminates TLS at the edge, this container runs the IRC core over plaintext TCP",
|
|
6
|
+
"license": "BSD-3-Clause",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/main.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.build.json",
|
|
11
|
+
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|
|
12
|
+
"start": "node --enable-source-maps ./dist/main.js",
|
|
13
|
+
"deploy:staging": "wrangler deploy --env staging",
|
|
14
|
+
"deploy:prod": "wrangler deploy",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"coverage": "vitest run --coverage",
|
|
18
|
+
"clean": "rimraf dist coverage .tsbuildinfo .turbo"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@serverless-ircd/in-memory-runtime": "workspace:*",
|
|
22
|
+
"@serverless-ircd/irc-core": "workspace:*",
|
|
23
|
+
"@serverless-ircd/irc-server": "workspace:*"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@cloudflare/containers": "^0.1.0",
|
|
27
|
+
"@types/node": "^26.1.1",
|
|
28
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
29
|
+
"rimraf": "^6.0.0",
|
|
30
|
+
"typescript": "^5.6.0",
|
|
31
|
+
"vitest": "^4.1.0",
|
|
32
|
+
"wrangler": "4.112.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CF TCP container config loader.
|
|
3
|
+
*
|
|
4
|
+
* Adapts platform-native env vars (Cloudflare Container runtime env or
|
|
5
|
+
* Docker/Kubernetes env) into the shared `ServerConfigSchema` from
|
|
6
|
+
* `irc-core`, plus a handful of container-specific knobs (TCP port/host,
|
|
7
|
+
* persistence path, snapshot interval).
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `packages/cf-adapter/src/config-loader.ts` in shape — both
|
|
10
|
+
* adapters consume the same `ServerConfigSchema`; only the env-mapping
|
|
11
|
+
* and the extra container knobs differ.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { type ParsedServerConfig, parseServerConfig } from '@serverless-ircd/irc-core';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The env vars this loader reads. Every field is a string (the platform
|
|
18
|
+
* env shape); numeric fields are coerced inside {@link loadContainerConfig}.
|
|
19
|
+
*/
|
|
20
|
+
export interface ContainerConfigEnv {
|
|
21
|
+
SERVER_NAME?: string;
|
|
22
|
+
NETWORK_NAME?: string;
|
|
23
|
+
SERVER_VERSION?: string;
|
|
24
|
+
CREATED_AT?: string;
|
|
25
|
+
MOTD_LINES?: string;
|
|
26
|
+
MAX_CLIENTS?: string;
|
|
27
|
+
CHANNEL_PREFIXES?: string;
|
|
28
|
+
OPER_USER?: string;
|
|
29
|
+
OPER_PASSWORD?: string;
|
|
30
|
+
MAX_CHANNELS_PER_USER?: string;
|
|
31
|
+
MAX_TARGETS_PER_COMMAND?: string;
|
|
32
|
+
NICK_LEN?: string;
|
|
33
|
+
CHANNEL_LEN?: string;
|
|
34
|
+
TOPIC_LEN?: string;
|
|
35
|
+
MAX_LIST_ENTRIES?: string;
|
|
36
|
+
QUIT_MESSAGE?: string;
|
|
37
|
+
SASL_ACCOUNTS?: string;
|
|
38
|
+
/** Container-specific: plaintext TCP port Spectrum forwards to. */
|
|
39
|
+
TCP_PORT?: string;
|
|
40
|
+
/** Container-specific: bind address (default 0.0.0.0 for Spectrum origin). */
|
|
41
|
+
TCP_HOST?: string;
|
|
42
|
+
/** Container-specific: path to the persistence snapshot file. */
|
|
43
|
+
PERSISTENCE_PATH?: string;
|
|
44
|
+
/** Container-specific: auto-snapshot interval in milliseconds. */
|
|
45
|
+
SNAPSHOT_INTERVAL_MS?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Fully-resolved container config: IRC server config + container knobs. */
|
|
49
|
+
export interface ContainerConfig {
|
|
50
|
+
server: ParsedServerConfig;
|
|
51
|
+
tcpPort: number;
|
|
52
|
+
tcpHost: string;
|
|
53
|
+
persistencePath: string | undefined;
|
|
54
|
+
snapshotIntervalMs: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DEFAULT_TCP_PORT = 6667;
|
|
58
|
+
const DEFAULT_TCP_HOST = '0.0.0.0';
|
|
59
|
+
const DEFAULT_SNAPSHOT_INTERVAL_MS = 60_000;
|
|
60
|
+
|
|
61
|
+
/** Default server name / network when env vars are missing. */
|
|
62
|
+
const DEFAULT_SERVER_NAME = 'irc.example.com';
|
|
63
|
+
const DEFAULT_NETWORK_NAME = 'ExampleNet';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Loads and validates the IRC server config plus container-specific knobs
|
|
67
|
+
* from a platform env. Throws on an invalid IRC config (bad port number,
|
|
68
|
+
* missing required fields, etc.) so boot fails fast.
|
|
69
|
+
*/
|
|
70
|
+
export function loadContainerConfig(env: ContainerConfigEnv): ContainerConfig {
|
|
71
|
+
const server = parseServerConfig(buildServerInput(env));
|
|
72
|
+
return {
|
|
73
|
+
server,
|
|
74
|
+
tcpPort: parseIntOrDefault(env.TCP_PORT, DEFAULT_TCP_PORT),
|
|
75
|
+
tcpHost: env.TCP_HOST?.trim() || DEFAULT_TCP_HOST,
|
|
76
|
+
persistencePath:
|
|
77
|
+
env.PERSISTENCE_PATH !== undefined && env.PERSISTENCE_PATH.length > 0
|
|
78
|
+
? env.PERSISTENCE_PATH
|
|
79
|
+
: undefined,
|
|
80
|
+
snapshotIntervalMs: parseIntOrDefault(env.SNAPSHOT_INTERVAL_MS, DEFAULT_SNAPSHOT_INTERVAL_MS),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseIntOrDefault(raw: string | undefined, def: number): number {
|
|
85
|
+
if (raw === undefined || raw.trim().length === 0) return def;
|
|
86
|
+
const n = Number.parseInt(raw, 10);
|
|
87
|
+
return Number.isFinite(n) ? n : def;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildServerInput(env: ContainerConfigEnv): Record<string, unknown> {
|
|
91
|
+
const input: Record<string, unknown> = {
|
|
92
|
+
serverName: env.SERVER_NAME ?? DEFAULT_SERVER_NAME,
|
|
93
|
+
networkName: env.NETWORK_NAME ?? DEFAULT_NETWORK_NAME,
|
|
94
|
+
};
|
|
95
|
+
if (env.SERVER_VERSION !== undefined) input.serverVersion = env.SERVER_VERSION;
|
|
96
|
+
if (env.CREATED_AT !== undefined) input.createdAt = parseCreatedAt(env.CREATED_AT);
|
|
97
|
+
if (env.MOTD_LINES !== undefined && env.MOTD_LINES.length > 0) {
|
|
98
|
+
input.motdLines = env.MOTD_LINES.split('\n');
|
|
99
|
+
}
|
|
100
|
+
if (env.MAX_CLIENTS !== undefined) input.maxClients = Number.parseInt(env.MAX_CLIENTS, 10);
|
|
101
|
+
if (env.CHANNEL_PREFIXES !== undefined) input.channelPrefixes = env.CHANNEL_PREFIXES;
|
|
102
|
+
if (env.OPER_USER !== undefined || env.OPER_PASSWORD !== undefined) {
|
|
103
|
+
input.operCreds = [{ user: env.OPER_USER ?? '', password: env.OPER_PASSWORD ?? '' }];
|
|
104
|
+
}
|
|
105
|
+
if (env.MAX_CHANNELS_PER_USER !== undefined) {
|
|
106
|
+
input.maxChannelsPerUser = Number.parseInt(env.MAX_CHANNELS_PER_USER, 10);
|
|
107
|
+
}
|
|
108
|
+
if (env.MAX_TARGETS_PER_COMMAND !== undefined) {
|
|
109
|
+
input.maxTargetsPerCommand = Number.parseInt(env.MAX_TARGETS_PER_COMMAND, 10);
|
|
110
|
+
}
|
|
111
|
+
if (env.NICK_LEN !== undefined) input.nickLen = Number.parseInt(env.NICK_LEN, 10);
|
|
112
|
+
if (env.CHANNEL_LEN !== undefined) input.channelLen = Number.parseInt(env.CHANNEL_LEN, 10);
|
|
113
|
+
if (env.TOPIC_LEN !== undefined) input.topicLen = Number.parseInt(env.TOPIC_LEN, 10);
|
|
114
|
+
if (env.MAX_LIST_ENTRIES !== undefined) {
|
|
115
|
+
input.maxListEntries = Number.parseInt(env.MAX_LIST_ENTRIES, 10);
|
|
116
|
+
}
|
|
117
|
+
if (env.QUIT_MESSAGE !== undefined) input.quitMessage = env.QUIT_MESSAGE;
|
|
118
|
+
if (env.SASL_ACCOUNTS !== undefined && env.SASL_ACCOUNTS.length > 0) {
|
|
119
|
+
input.saslAccounts = parseSaslAccountsLines(env.SASL_ACCOUNTS);
|
|
120
|
+
}
|
|
121
|
+
return input;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseCreatedAt(raw: string): string | number {
|
|
125
|
+
if (/^\d+$/.test(raw.trim())) {
|
|
126
|
+
const n = Number(raw.trim());
|
|
127
|
+
if (Number.isFinite(n)) return n;
|
|
128
|
+
}
|
|
129
|
+
return raw;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseSaslAccountsLines(raw: string): Array<{ username: string; password: string }> {
|
|
133
|
+
const out: Array<{ username: string; password: string }> = [];
|
|
134
|
+
for (const line of raw.split('\n')) {
|
|
135
|
+
const trimmed = line.trim();
|
|
136
|
+
if (trimmed.length === 0) continue;
|
|
137
|
+
const sep = trimmed.indexOf(':');
|
|
138
|
+
if (sep <= 0) continue;
|
|
139
|
+
const username = trimmed.slice(0, sep);
|
|
140
|
+
const password = trimmed.slice(sep + 1);
|
|
141
|
+
if (username.length === 0 || password.length === 0) continue;
|
|
142
|
+
out.push({ username, password });
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Container DO for the IRC TCP origin.
|
|
3
|
+
*
|
|
4
|
+
* The container runs the IRC core as a plaintext TCP server (port 6667).
|
|
5
|
+
* Spectrum terminates TLS on :6697 and forwards plaintext to this
|
|
6
|
+
* container. The DO wrapper provides persistent identity and lifecycle
|
|
7
|
+
* management.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Container } from '@cloudflare/containers';
|
|
11
|
+
|
|
12
|
+
export class IrcTcpOrigin extends Container {
|
|
13
|
+
// The IRC TCP server port inside the container.
|
|
14
|
+
defaultPort = 6667;
|
|
15
|
+
requiredPorts = [6667];
|
|
16
|
+
|
|
17
|
+
// Keep the container alive for long-lived IRC connections.
|
|
18
|
+
sleepAfter = '2h';
|
|
19
|
+
|
|
20
|
+
enableInternet = true;
|
|
21
|
+
|
|
22
|
+
envVars = {
|
|
23
|
+
TCP_PORT: '6667',
|
|
24
|
+
TCP_HOST: '0.0.0.0',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
onStart(): void {
|
|
28
|
+
console.log('IrcTcpOrigin container started');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onStop(): void {
|
|
32
|
+
console.log('IrcTcpOrigin container stopping');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onError(error: Error): void {
|
|
36
|
+
console.error('IrcTcpOrigin container error:', error);
|
|
37
|
+
}
|
|
38
|
+
}
|