@celilo/e2e 0.6.0 → 0.6.2
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/README.md +2 -1
- package/config/dns/celilo.computer.zone +16 -0
- package/config/dns/iamtheinternet.org.zone +6 -4
- package/config/dns/knot-namecheap.conf +8 -0
- package/config/dns/knot-tld.conf +4 -0
- package/config/dns/lunacycle.net.zone +16 -0
- package/config/dns/net.zone +11 -0
- package/config/dns/org.zone +5 -0
- package/config/dns/root.zone +2 -0
- package/config/dns/templates/iamtheinternet.org.zone +16 -0
- package/config/dns/templates/lunacycle.net.zone +16 -0
- package/config/resolver/unbound.conf +10 -0
- package/config/routing/management-routes.sh +19 -0
- package/config/routing/resolver-internal-routes.sh +59 -0
- package/docker/Dockerfile.internal-resolver +16 -0
- package/docker/Dockerfile.isitup +37 -0
- package/docker/Dockerfile.registry +34 -0
- package/netapps/authentik.netapp +0 -0
- package/netapps/caddy.netapp +0 -0
- package/netapps/greenwave.netapp +0 -0
- package/netapps/homebridge.netapp +0 -0
- package/netapps/iptables.netapp +0 -0
- package/netapps/knot-unbound-internal.netapp +0 -0
- package/netapps/namecheap.netapp +0 -0
- package/netapps/technitium.netapp +0 -0
- package/netapps/test-ansible-output.netapp +0 -0
- package/package.json +2 -1
- package/simulators/isitup/Caddyfile +11 -0
- package/simulators/isitup/entrypoint.sh +47 -0
- package/simulators/isitup/server.ts +112 -0
- package/simulators/namecheap-ddns/server.ts +31 -2
- package/src/cli/build.ts +17 -3
- package/src/cli/command-registry.ts +3 -2
- package/src/cli/completion.ts +29 -5
- package/src/cli/index.ts +85 -15
- package/src/cli/module-discovery.ts +120 -0
- package/src/cli/scaffold.ts +4 -5
- package/src/container-manager.ts +358 -71
- package/src/docker-compose-generator.ts +119 -17
- package/src/parse-line.ts +14 -3
- package/src/runner.ts +122 -165
- package/src/simulator-ips.ts +42 -0
- package/src/types.ts +24 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stringify } from 'yaml';
|
|
2
|
+
import { SIMULATOR_IPS } from './simulator-ips';
|
|
2
3
|
import type { MachineSpec, NetworkConfig, TopologyPreset } from './types';
|
|
3
4
|
import { ZONE_GATEWAYS } from './types';
|
|
4
5
|
|
|
@@ -46,6 +47,7 @@ function baseService(opts: {
|
|
|
46
47
|
environment?: Record<string, string>;
|
|
47
48
|
tmpfs?: string[];
|
|
48
49
|
privileged?: boolean;
|
|
50
|
+
dns?: string[];
|
|
49
51
|
}) {
|
|
50
52
|
// Auto-assign an image tag from the Dockerfile name so Docker can
|
|
51
53
|
// skip builds when pre-built images exist.
|
|
@@ -162,7 +164,7 @@ export function generateSharedInfraYaml(): string {
|
|
|
162
164
|
|
|
163
165
|
services['root-dns'] = baseService({
|
|
164
166
|
build: { context: '.', dockerfile: 'docker/Dockerfile.authoritative-dns' },
|
|
165
|
-
networks: { 'internet-external': { ipv4_address:
|
|
167
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.ROOT_DNS } },
|
|
166
168
|
cap_add: ['NET_ADMIN'],
|
|
167
169
|
volumes: [
|
|
168
170
|
'./config/dns/knot-root.conf:/config/knot.conf:ro',
|
|
@@ -172,22 +174,31 @@ export function generateSharedInfraYaml(): string {
|
|
|
172
174
|
|
|
173
175
|
services['tld-dns'] = baseService({
|
|
174
176
|
build: { context: '.', dockerfile: 'docker/Dockerfile.authoritative-dns' },
|
|
175
|
-
networks: { 'internet-external': { ipv4_address:
|
|
177
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.TLD_DNS } },
|
|
176
178
|
cap_add: ['NET_ADMIN'],
|
|
177
179
|
volumes: [
|
|
178
180
|
'./config/dns/knot-tld.conf:/config/knot.conf:ro',
|
|
179
181
|
'./config/dns/com.zone:/config/com.zone:ro',
|
|
180
182
|
'./config/dns/org.zone:/config/org.zone:ro',
|
|
183
|
+
'./config/dns/net.zone:/config/net.zone:ro',
|
|
181
184
|
],
|
|
182
185
|
});
|
|
183
186
|
|
|
184
187
|
services['namecheap-dns'] = baseService({
|
|
185
188
|
build: { context: '.', dockerfile: 'docker/Dockerfile.namecheap-dns' },
|
|
186
|
-
networks: { 'internet-external': { ipv4_address:
|
|
189
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.NAMECHEAP_DNS } },
|
|
187
190
|
cap_add: ['NET_ADMIN'],
|
|
191
|
+
// Per-domain DDNS passwords for the cross-domain test. Fallback
|
|
192
|
+
// `test123` still applies to any domain not listed.
|
|
193
|
+
environment: {
|
|
194
|
+
DDNS_PASSWORDS:
|
|
195
|
+
'{"iamtheinternet.org":"test123","celilo.computer":"test456","lunacycle.net":"test789"}',
|
|
196
|
+
},
|
|
188
197
|
volumes: [
|
|
189
198
|
'./config/dns/knot-namecheap.conf:/config/knot.conf:ro',
|
|
190
199
|
'./config/dns/iamtheinternet.org.zone:/config/iamtheinternet.org.zone',
|
|
200
|
+
'./config/dns/celilo.computer.zone:/config/celilo.computer.zone',
|
|
201
|
+
'./config/dns/lunacycle.net.zone:/config/lunacycle.net.zone',
|
|
191
202
|
'./config/dns/park-your-domain.com.zone:/config/park-your-domain.com.zone',
|
|
192
203
|
],
|
|
193
204
|
});
|
|
@@ -204,7 +215,7 @@ export function generateSharedInfraYaml(): string {
|
|
|
204
215
|
|
|
205
216
|
services.letsencrypt = baseService({
|
|
206
217
|
build: { context: '.', dockerfile: 'docker/Dockerfile.pebble' },
|
|
207
|
-
networks: { 'internet-external': { ipv4_address:
|
|
218
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.PEBBLE } },
|
|
208
219
|
cap_add: ['NET_ADMIN'],
|
|
209
220
|
command: '-config /config/pebble-config.json -dnsserver 100.100.0.1:53',
|
|
210
221
|
volumes: [
|
|
@@ -214,6 +225,28 @@ export function generateSharedInfraYaml(): string {
|
|
|
214
225
|
],
|
|
215
226
|
});
|
|
216
227
|
|
|
228
|
+
// Registry stand-in. Context widened to `..` (the `packages/` dir) so the
|
|
229
|
+
// Dockerfile can COPY from packages/registry-server (single source of truth
|
|
230
|
+
// shared with the celilo-registry module).
|
|
231
|
+
services.registry = baseService({
|
|
232
|
+
build: { context: '..', dockerfile: 'e2e/docker/Dockerfile.registry' },
|
|
233
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.REGISTRY } },
|
|
234
|
+
cap_add: ['NET_ADMIN'],
|
|
235
|
+
volumes: ['../../modules:/modules:ro'],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// External website prober simulator — mimics isitup.org's /api.json endpoint.
|
|
239
|
+
// Sits on internet-external so probe requests exercise the real public-facing
|
|
240
|
+
// path (target domain → DNS → ISP → firewall → Caddy). Uses comcast-resolver
|
|
241
|
+
// at 100.100.0.1 as DNS so it can recursively resolve the simulated public
|
|
242
|
+
// zones (e.g. iamtheinternet.org).
|
|
243
|
+
services.isitup = baseService({
|
|
244
|
+
build: { context: '.', dockerfile: 'docker/Dockerfile.isitup' },
|
|
245
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.ISITUP } },
|
|
246
|
+
cap_add: ['NET_ADMIN'],
|
|
247
|
+
dns: ['100.100.0.1'],
|
|
248
|
+
});
|
|
249
|
+
|
|
217
250
|
const compose = { networks, volumes, services };
|
|
218
251
|
return stringify(compose, { lineWidth: 120 });
|
|
219
252
|
}
|
|
@@ -252,7 +285,8 @@ export function generateTestComposeYaml(config: NetworkConfig, celiloRoot?: stri
|
|
|
252
285
|
const services: Record<string, unknown> = {};
|
|
253
286
|
|
|
254
287
|
// --- Management (always present) ---
|
|
255
|
-
const managementDefaultGw =
|
|
288
|
+
const managementDefaultGw =
|
|
289
|
+
config.topology === 'direct-internet' ? '192.168.0.254' : '192.168.0.1';
|
|
256
290
|
|
|
257
291
|
services.management = baseService({
|
|
258
292
|
build: { context: '.', dockerfile: 'docker/Dockerfile.management' },
|
|
@@ -264,7 +298,28 @@ export function generateTestComposeYaml(config: NetworkConfig, celiloRoot?: stri
|
|
|
264
298
|
...(config.managementVolumes ?? []),
|
|
265
299
|
],
|
|
266
300
|
environment: {
|
|
267
|
-
DDNS_ENDPOINT:
|
|
301
|
+
DDNS_ENDPOINT: `http://${SIMULATOR_IPS.NAMECHEAP_DNS}:8080/update`,
|
|
302
|
+
DEFAULT_GATEWAY: managementDefaultGw,
|
|
303
|
+
CELILO_REGISTRY_URL: 'http://e2e-registry.lab',
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// --- Internal split-horizon DNS resolver ---
|
|
308
|
+
// Resolves the test domain to caddy's internal DMZ IP so management can
|
|
309
|
+
// reach it via a direct route rather than through the external interface
|
|
310
|
+
// (hairpin NAT would be needed otherwise). Management already has
|
|
311
|
+
// nameserver 192.168.0.10 as its primary resolver in /etc/resolv.conf.
|
|
312
|
+
const caddyIp =
|
|
313
|
+
config.dmzMachines.find((m) => m.name === 'caddy')?.ip ??
|
|
314
|
+
config.dmzMachines[0]?.ip ??
|
|
315
|
+
'10.0.10.10';
|
|
316
|
+
services['dns-int'] = baseService({
|
|
317
|
+
build: { context: '.', dockerfile: 'docker/Dockerfile.internal-resolver' },
|
|
318
|
+
networks: { internal: { ipv4_address: '192.168.0.10' } },
|
|
319
|
+
cap_add: ['NET_ADMIN'],
|
|
320
|
+
environment: {
|
|
321
|
+
CADDY_IP: caddyIp,
|
|
322
|
+
DOMAIN: config.domain ?? 'iamtheinternet.org',
|
|
268
323
|
DEFAULT_GATEWAY: managementDefaultGw,
|
|
269
324
|
},
|
|
270
325
|
});
|
|
@@ -307,8 +362,11 @@ export function generateTestComposeYaml(config: NetworkConfig, celiloRoot?: stri
|
|
|
307
362
|
const gateway = ZONE_GATEWAYS[machine.zone];
|
|
308
363
|
|
|
309
364
|
const needsDocker = machine.zone === 'app' || machine.docker === true;
|
|
310
|
-
const dockerfile =
|
|
311
|
-
|
|
365
|
+
const dockerfile =
|
|
366
|
+
machine.dockerfile ||
|
|
367
|
+
(needsDocker
|
|
368
|
+
? 'docker/Dockerfile.target-machine-docker'
|
|
369
|
+
: 'docker/Dockerfile.target-machine');
|
|
312
370
|
|
|
313
371
|
const machineVolumes = ['ssh-keys:/ssh-keys:ro'];
|
|
314
372
|
|
|
@@ -366,7 +424,8 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
366
424
|
|
|
367
425
|
const services: Record<string, unknown> = {};
|
|
368
426
|
|
|
369
|
-
const managementDefaultGw =
|
|
427
|
+
const managementDefaultGw =
|
|
428
|
+
config.topology === 'direct-internet' ? '192.168.0.254' : '192.168.0.1';
|
|
370
429
|
|
|
371
430
|
services.management = baseService({
|
|
372
431
|
build: { context: '.', dockerfile: 'docker/Dockerfile.management' },
|
|
@@ -378,14 +437,30 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
378
437
|
...(config.managementVolumes ?? []),
|
|
379
438
|
],
|
|
380
439
|
environment: {
|
|
381
|
-
DDNS_ENDPOINT:
|
|
440
|
+
DDNS_ENDPOINT: `http://${SIMULATOR_IPS.NAMECHEAP_DNS}:8080/update`,
|
|
382
441
|
DEFAULT_GATEWAY: managementDefaultGw,
|
|
442
|
+
CELILO_REGISTRY_URL: 'http://e2e-registry.lab',
|
|
383
443
|
},
|
|
384
444
|
});
|
|
385
445
|
|
|
386
446
|
const topologyFn = TOPOLOGY_SERVICES[config.topology] || TOPOLOGY_SERVICES.default;
|
|
387
447
|
Object.assign(services, topologyFn());
|
|
388
448
|
|
|
449
|
+
const legacyCaddyIp =
|
|
450
|
+
config.dmzMachines.find((m) => m.name === 'caddy')?.ip ??
|
|
451
|
+
config.dmzMachines[0]?.ip ??
|
|
452
|
+
'10.0.10.10';
|
|
453
|
+
services['dns-int'] = baseService({
|
|
454
|
+
build: { context: '.', dockerfile: 'docker/Dockerfile.internal-resolver' },
|
|
455
|
+
networks: { internal: { ipv4_address: '192.168.0.10' } },
|
|
456
|
+
cap_add: ['NET_ADMIN'],
|
|
457
|
+
environment: {
|
|
458
|
+
CADDY_IP: legacyCaddyIp,
|
|
459
|
+
DOMAIN: config.domain ?? 'iamtheinternet.org',
|
|
460
|
+
DEFAULT_GATEWAY: managementDefaultGw,
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
389
464
|
services['fw-ext'] = baseService({
|
|
390
465
|
build: { context: '.', dockerfile: 'docker/Dockerfile.router' },
|
|
391
466
|
networks: {
|
|
@@ -408,7 +483,7 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
408
483
|
|
|
409
484
|
services['root-dns'] = baseService({
|
|
410
485
|
build: { context: '.', dockerfile: 'docker/Dockerfile.authoritative-dns' },
|
|
411
|
-
networks: { 'internet-external': { ipv4_address:
|
|
486
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.ROOT_DNS } },
|
|
412
487
|
cap_add: ['NET_ADMIN'],
|
|
413
488
|
volumes: [
|
|
414
489
|
'./config/dns/knot-root.conf:/config/knot.conf:ro',
|
|
@@ -418,22 +493,31 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
418
493
|
|
|
419
494
|
services['tld-dns'] = baseService({
|
|
420
495
|
build: { context: '.', dockerfile: 'docker/Dockerfile.authoritative-dns' },
|
|
421
|
-
networks: { 'internet-external': { ipv4_address:
|
|
496
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.TLD_DNS } },
|
|
422
497
|
cap_add: ['NET_ADMIN'],
|
|
423
498
|
volumes: [
|
|
424
499
|
'./config/dns/knot-tld.conf:/config/knot.conf:ro',
|
|
425
500
|
'./config/dns/com.zone:/config/com.zone:ro',
|
|
426
501
|
'./config/dns/org.zone:/config/org.zone:ro',
|
|
502
|
+
'./config/dns/net.zone:/config/net.zone:ro',
|
|
427
503
|
],
|
|
428
504
|
});
|
|
429
505
|
|
|
430
506
|
services['namecheap-dns'] = baseService({
|
|
431
507
|
build: { context: '.', dockerfile: 'docker/Dockerfile.namecheap-dns' },
|
|
432
|
-
networks: { 'internet-external': { ipv4_address:
|
|
508
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.NAMECHEAP_DNS } },
|
|
433
509
|
cap_add: ['NET_ADMIN'],
|
|
510
|
+
// Per-domain DDNS passwords for the cross-domain test. Fallback
|
|
511
|
+
// `test123` still applies to any domain not listed.
|
|
512
|
+
environment: {
|
|
513
|
+
DDNS_PASSWORDS:
|
|
514
|
+
'{"iamtheinternet.org":"test123","celilo.computer":"test456","lunacycle.net":"test789"}',
|
|
515
|
+
},
|
|
434
516
|
volumes: [
|
|
435
517
|
'./config/dns/knot-namecheap.conf:/config/knot.conf:ro',
|
|
436
518
|
'./config/dns/iamtheinternet.org.zone:/config/iamtheinternet.org.zone',
|
|
519
|
+
'./config/dns/celilo.computer.zone:/config/celilo.computer.zone',
|
|
520
|
+
'./config/dns/lunacycle.net.zone:/config/lunacycle.net.zone',
|
|
437
521
|
'./config/dns/park-your-domain.com.zone:/config/park-your-domain.com.zone',
|
|
438
522
|
],
|
|
439
523
|
});
|
|
@@ -450,7 +534,7 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
450
534
|
|
|
451
535
|
services.letsencrypt = baseService({
|
|
452
536
|
build: { context: '.', dockerfile: 'docker/Dockerfile.pebble' },
|
|
453
|
-
networks: { 'internet-external': { ipv4_address:
|
|
537
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.PEBBLE } },
|
|
454
538
|
cap_add: ['NET_ADMIN'],
|
|
455
539
|
command: '-config /config/pebble-config.json -dnsserver 100.100.0.1:53',
|
|
456
540
|
volumes: [
|
|
@@ -460,6 +544,16 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
460
544
|
],
|
|
461
545
|
});
|
|
462
546
|
|
|
547
|
+
// Registry stand-in. Context widened to `..` (the `packages/` dir) so the
|
|
548
|
+
// Dockerfile can COPY from packages/registry-server (single source of truth
|
|
549
|
+
// shared with the celilo-registry module).
|
|
550
|
+
services.registry = baseService({
|
|
551
|
+
build: { context: '..', dockerfile: 'e2e/docker/Dockerfile.registry' },
|
|
552
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.REGISTRY } },
|
|
553
|
+
cap_add: ['NET_ADMIN'],
|
|
554
|
+
volumes: ['../../modules:/modules:ro'],
|
|
555
|
+
});
|
|
556
|
+
|
|
463
557
|
const allMachines = [
|
|
464
558
|
...config.dmzMachines,
|
|
465
559
|
...config.appMachines,
|
|
@@ -472,8 +566,11 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
472
566
|
const gateway = ZONE_GATEWAYS[machine.zone];
|
|
473
567
|
|
|
474
568
|
const needsDocker = machine.zone === 'app' || machine.docker === true;
|
|
475
|
-
const dockerfile =
|
|
476
|
-
|
|
569
|
+
const dockerfile =
|
|
570
|
+
machine.dockerfile ||
|
|
571
|
+
(needsDocker
|
|
572
|
+
? 'docker/Dockerfile.target-machine-docker'
|
|
573
|
+
: 'docker/Dockerfile.target-machine');
|
|
477
574
|
|
|
478
575
|
const machineVolumes = ['ssh-keys:/ssh-keys:ro'];
|
|
479
576
|
|
|
@@ -504,5 +601,10 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
504
601
|
}
|
|
505
602
|
|
|
506
603
|
export function getAllMachines(config: NetworkConfig): MachineSpec[] {
|
|
507
|
-
return [
|
|
604
|
+
return [
|
|
605
|
+
...config.dmzMachines,
|
|
606
|
+
...config.appMachines,
|
|
607
|
+
...config.secureMachines,
|
|
608
|
+
...config.internalMachines,
|
|
609
|
+
];
|
|
508
610
|
}
|
package/src/parse-line.ts
CHANGED
|
@@ -42,6 +42,13 @@ export function parseLine(line: string, display: DisplaySink): void {
|
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Protocol: [progress:sub] message (sub-event under the current step)
|
|
46
|
+
const subMatch = line.match(/\[progress:sub\] (.*)/);
|
|
47
|
+
if (subMatch) {
|
|
48
|
+
display.subEvent(subMatch[1]);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
// Legacy: [progress] message (treat as instant event)
|
|
46
53
|
const legacyMatch = line.match(/\[progress\] (.*)/);
|
|
47
54
|
if (legacyMatch) {
|
|
@@ -124,13 +131,17 @@ export function parseLine(line: string, display: DisplaySink): void {
|
|
|
124
131
|
return;
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
// Error lines
|
|
134
|
+
// Error lines. Require the identifier ending in "Error:" to be either
|
|
135
|
+
// standalone ("Error:") or PascalCase ("CeliloCommandError:") — NOT a
|
|
136
|
+
// camelCase variable like "stageError:" that bun prints as source context
|
|
137
|
+
// for failing tests.
|
|
138
|
+
const errorMatch = line.match(/\b(?:[A-Z]\w*)?Error: (.*)/);
|
|
128
139
|
if (
|
|
129
|
-
|
|
140
|
+
errorMatch &&
|
|
130
141
|
!line.includes('bun test') &&
|
|
131
142
|
!line.includes('ipv4: Address already')
|
|
132
143
|
) {
|
|
133
|
-
const err =
|
|
144
|
+
const err = errorMatch[1].slice(0, 70);
|
|
134
145
|
display.subEvent(`${red}✗${reset} ${err}`);
|
|
135
146
|
}
|
|
136
147
|
}
|