@celilo/e2e 0.10.3 → 0.11.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/README.md CHANGED
@@ -16,25 +16,34 @@ The network consists of 7 Docker bridge networks simulating a realistic home lab
16
16
 
17
17
  | Network | Subnet | Purpose |
18
18
  |---------|--------|---------|
19
- | **internal** | 192.168.0.0/24 | Home LAN (management, firewalls) |
20
- | **dmz** | 10.0.10.0/24 | Public-facing services |
21
- | **app** | 10.0.20.0/24 | Internal applications |
22
- | **secure** | 10.0.30.0/24 | Sensitive services |
19
+ | **internal** | 10.226.1.0/24 | Home LAN (management, firewalls) |
20
+ | **dmz** | 10.226.10.0/24 | Public-facing services |
21
+ | **app** | 10.226.20.0/24 | Internal applications |
22
+ | **secure** | 10.226.30.0/24 | Sensitive services |
23
23
  | **isp-external** | 100.100.0.0/24 | ISP network (between home and internet) |
24
24
  | **internet-external** | 100.64.0.0/24 | Simulated internet (DNS, ACME, etc.) |
25
25
  | **real-internet** | 172.30.0.0/24 | Bridge to actual internet (for apt, pip, etc.) |
26
26
 
27
27
  All networks except `real-internet` have Docker IP masquerade **disabled** — traffic flows through explicit iptables routing, not Docker's NAT. The `real-internet` network has masquerade enabled to provide actual internet access for package downloads.
28
28
 
29
+ ### Why these addresses (#539)
30
+
31
+ Every simulated **private** zone lives inside `SIM_PRIVATE_SUPERNET` (`10.226.0.0/16` — mnemonic `0xE2` = "e2"), declared once in `src/types.ts` and derived from there by the compose generator, the zone classifier and the harness's `system init`. Two reasons, both load-bearing:
32
+
33
+ 1. **A leaked stack must not be able to collide with a real fleet.** The sim used to number its zones exactly like a production celilo deployment; on celilo's own forgejo-builder, which lives in the real dmz, a leaked bridge claimed the builder's own /24 and blackholed every containerized CI job's route to the forge. Teardown cannot be made reliable enough to prevent that (a SIGKILL runs no handler), so the sim moved instead.
34
+ 2. **Nothing may be magic about production's octets.** A suite that only passes on one site's address plan is asserting an accident, not celilo's behaviour.
35
+
36
+ Don't hardcode an octet: use `zoneIp(zone, host)` / `ZONE_SUBNETS` / `ZONE_GATEWAYS`. `src/address-plan.test.ts` fails if a zone leaves the supernet, or if any retired fleet prefix reappears anywhere in the suite.
37
+
29
38
  ## Machines
30
39
 
31
40
  ### Fixed Infrastructure (always present)
32
41
 
33
42
  | Machine | Role | Networks | IPs |
34
43
  |---------|------|----------|-----|
35
- | **management** | Celilo CLI, Ansible, SSH | internal | 192.168.0.100 |
36
- | **fw-main** | iptables firewall (managed by Celilo) | internal, dmz, app, secure | 192.168.0.254, 10.0.10.1, 10.0.20.1, 10.0.30.1 |
37
- | **fw-isp** | Greenwave router simulator | internal, isp-external | 192.168.0.1, 100.100.0.100 |
44
+ | **management** | Celilo CLI, Ansible, SSH | internal | 10.226.1.100 |
45
+ | **fw-main** | iptables firewall (managed by Celilo) | internal, dmz, app, secure | 10.226.1.254, 10.226.10.1, 10.226.20.1, 10.226.30.1 |
46
+ | **fw-isp** | Greenwave router simulator | internal, isp-external | 10.226.1.1, 100.100.0.100 |
38
47
  | **fw-ext** | Edge router + transparent HTTPS proxy (Squid) | isp-external, internet-external, real-internet | 100.100.0.101, 100.64.0.1, 172.30.0.5 |
39
48
  | **comcast-resolver** | Unbound recursive DNS resolver | isp-external, real-internet | 100.100.0.1, 172.30.0.4 |
40
49
  | **root-dns** | Knot authoritative DNS (root zone) | internet-external | 100.64.0.53 |
@@ -49,9 +58,9 @@ Added per-test via the `network()` builder API. Each is an Ubuntu 22.04 containe
49
58
 
50
59
  | Example | Network | IP | Purpose |
51
60
  |---------|---------|-----|---------|
52
- | caddy | dmz | 10.0.10.10 | Caddy reverse proxy deployment |
53
- | idp | app | 10.0.20.100 | Identity provider deployment |
54
- | db | secure | 10.0.30.50 | Database deployment |
61
+ | caddy | dmz | 10.226.10.10 | Caddy reverse proxy deployment |
62
+ | idp | app | 10.226.20.100 | Identity provider deployment |
63
+ | db | secure | 10.226.30.50 | Database deployment |
55
64
 
56
65
  ## Routing
57
66
 
@@ -59,8 +68,8 @@ Traffic flows through explicit routing chains, mimicking a real network:
59
68
 
60
69
  | From | To | Path |
61
70
  |------|----|------|
62
- | management | dmz/app/secure | via fw-main (192.168.0.254) |
63
- | management | internet | via fw-isp (192.168.0.1) → fw-ext |
71
+ | management | dmz/app/secure | via fw-main (10.226.1.254) |
72
+ | management | internet | via fw-isp (10.226.1.1) → fw-ext |
64
73
  | dmz machines | internet | via fw-main → fw-isp → fw-ext |
65
74
  | Pebble | caddy (HTTP-01 challenge) | via fw-ext → fw-isp DNAT → fw-main DNAT → caddy |
66
75
 
@@ -104,12 +113,12 @@ When called, it updates the Knot DNS zone file and signals a zone reload. DNS pr
104
113
 
105
114
  ### Greenwave Router
106
115
 
107
- Runs on `fw-isp` (192.168.0.1). A Bun HTTPS server implementing the C4000XG REST API subset:
116
+ Runs on `fw-isp` (10.226.1.1). A Bun HTTPS server implementing the C4000XG REST API subset:
108
117
  - `POST /cgi/cgi_action` — Login/logout
109
118
  - `GET /cgi/cgi_get` — Read config (public IP, port mappings)
110
119
  - `POST /cgi/cgi_set` — Add/remove port forwarding rules
111
120
 
112
- Port forwarding rules are applied via actual iptables commands, making NAT functional in the simulated network. The management HTTPS interface binds to the internal interface only (192.168.0.1), not the external interface.
121
+ Port forwarding rules are applied via actual iptables commands, making NAT functional in the simulated network. The management HTTPS interface binds to the internal interface only (10.226.1.1), not the external interface.
113
122
 
114
123
  ### Pebble (Let's Encrypt)
115
124
 
@@ -136,7 +145,7 @@ cd e2e
136
145
  ./bin/e2e-up --full-stack
137
146
 
138
147
  # Custom machine spec
139
- ./bin/e2e-up --custom '{"dmz":{"caddy":"10.0.10.10","web":"10.0.10.20"}}'
148
+ ./bin/e2e-up --custom '{"dmz":{"caddy":"10.226.10.10","web":"10.226.10.20"}}'
140
149
  ```
141
150
 
142
151
  After startup, you're dropped into the management machine shell with tab completion for `celilo` commands (aliased as `c`).
@@ -166,10 +175,10 @@ cd /celilo/modules
166
175
 
167
176
  # System init
168
177
  c system init --accept-defaults \
169
- network.dmz.subnet=10.0.10.0/24 \
170
- network.app.subnet=10.0.20.0/24 \
171
- network.secure.subnet=10.0.30.0/24 \
172
- network.internal.subnet=192.168.0.0/24 \
178
+ network.dmz.subnet=10.226.10.0/24 \
179
+ network.app.subnet=10.226.20.0/24 \
180
+ network.secure.subnet=10.226.30.0/24 \
181
+ network.internal.subnet=10.226.1.0/24 \
173
182
  primary_domain=iamtheinternet.org \
174
183
  admin.email=admin@iamtheinternet.org \
175
184
  dns.primary=100.100.0.1 \
@@ -182,17 +191,17 @@ c module import iptables
182
191
  c module import caddy
183
192
 
184
193
  # Add machines (--ssh-user root needed for non-interactive)
185
- c machine add 192.168.0.1 --ssh-user root --earmark greenwave
186
- c machine add 192.168.0.254 --ssh-user root --earmark iptables
187
- c machine add 10.0.10.10 --ssh-user root
194
+ c machine add 10.226.1.1 --ssh-user root --earmark greenwave
195
+ c machine add 10.226.1.254 --ssh-user root --earmark iptables
196
+ c machine add 10.226.10.10 --ssh-user root
188
197
 
189
198
  # Pre-configure secrets and non-derivable config
190
199
  c module config set namecheap domains '["iamtheinternet.org"]'
191
200
  c module secret set namecheap ddns_passwords '{"iamtheinternet.org":"test123"}'
192
- c module config set greenwave router_ip 192.168.0.1
201
+ c module config set greenwave router_ip 10.226.1.1
193
202
  c module secret set greenwave router_username admin
194
203
  c module secret set greenwave router_password admin
195
- c module config set iptables nat_ip 192.168.0.253
204
+ c module config set iptables nat_ip 10.226.1.253
196
205
  c module config set caddy hostname www
197
206
  c module config set caddy acme_ca https://acme-v02.api.letsencrypt.org/dir
198
207
 
@@ -249,7 +258,7 @@ describe('my deployment test', () => {
249
258
  net = await CADDY_DEPLOYMENT().start();
250
259
 
251
260
  // 2. Add machines, import modules, configure
252
- await net.celilo('machine add 10.0.10.10 --ssh-user root');
261
+ await net.celilo('machine add 10.226.10.10 --ssh-user root');
253
262
  await net.celilo('module import /celilo/modules/caddy');
254
263
  await net.celilo('module config set caddy hostname www');
255
264
 
@@ -330,9 +339,9 @@ INFRASTRUCTURE_ONLY() // no dynamic machines
330
339
  import { network } from '../src/network-builder';
331
340
 
332
341
  const net = await network()
333
- .dmz({ caddy: '10.0.10.10', web: '10.0.10.20' })
334
- .app({ api: '10.0.20.100' })
335
- .secure({ db: '10.0.30.50' })
342
+ .dmz({ caddy: '10.226.10.10', web: '10.226.10.20' })
343
+ .app({ api: '10.226.20.100' })
344
+ .secure({ db: '10.226.30.50' })
336
345
  .start();
337
346
  ```
338
347
 
package/bin/e2e-status CHANGED
@@ -22,7 +22,7 @@ docker compose -p "$PROJECT_NAME" exec -T comcast-resolver dig @127.0.0.1 www.ia
22
22
 
23
23
  echo ""
24
24
  echo "=== Connectivity (from management) ==="
25
- for target in "fw-main:192.168.0.254" "fw-isp:192.168.0.1" "comcast-resolver:100.100.0.1" "root-dns:100.64.0.53" "namecheap-dns:100.64.0.55"; do
25
+ for target in "fw-main:10.226.1.254" "fw-isp:10.226.1.1" "comcast-resolver:100.100.0.1" "root-dns:100.64.0.53" "namecheap-dns:100.64.0.55"; do
26
26
  name="${target%%:*}"
27
27
  ip="${target##*:}"
28
28
  echo -n " $name ($ip): "
package/bin/e2e-up CHANGED
@@ -5,7 +5,7 @@
5
5
  # ./bin/e2e-up # Infrastructure only
6
6
  # ./bin/e2e-up --caddy # + caddy machine in dmz
7
7
  # ./bin/e2e-up --full-stack # + caddy, idp, db
8
- # ./bin/e2e-up --custom '{"dmz":{"caddy":"10.0.10.10","web":"10.0.10.20"}}'
8
+ # ./bin/e2e-up --custom '{"dmz":{"caddy":"10.226.10.10","web":"10.226.10.20"}}'
9
9
  #
10
10
  # After startup, drops you into a shell on the management machine.
11
11
  # Use `exit` to leave the shell (network stays running).
@@ -30,9 +30,9 @@ while [[ $# -gt 0 ]]; do
30
30
  echo ""
31
31
  echo "Presets:"
32
32
  echo " (default) Infrastructure only (management, firewalls, DNS, Pebble)"
33
- echo " --caddy + caddy machine at 10.0.10.10 in dmz"
33
+ echo " --caddy + caddy machine at 10.226.10.10 in dmz"
34
34
  echo " --full-stack + caddy (dmz), idp (app), db (secure)"
35
- echo " --custom JSON Custom machine spec, e.g. '{\"dmz\":{\"caddy\":\"10.0.10.10\"}}'"
35
+ echo " --custom JSON Custom machine spec, e.g. '{\"dmz\":{\"caddy\":\"10.226.10.10\"}}'"
36
36
  echo ""
37
37
  echo "After startup, you'll be dropped into the management machine shell."
38
38
  echo "Use ./bin/e2e-down to tear down, or ./bin/e2e-shell to reconnect."
@@ -60,7 +60,7 @@ const presets = {
60
60
  },
61
61
  'caddy': {
62
62
  topology: 'default',
63
- dmzMachines: [{ name: 'caddy', ip: '10.0.10.10', zone: 'dmz' }],
63
+ dmzMachines: [{ name: 'caddy', ip: '10.226.10.10', zone: 'dmz' }],
64
64
  appMachines: [],
65
65
  secureMachines: [],
66
66
  domain: 'iamtheinternet.org',
@@ -69,9 +69,9 @@ const presets = {
69
69
  },
70
70
  'full-stack': {
71
71
  topology: 'default',
72
- dmzMachines: [{ name: 'caddy', ip: '10.0.10.10', zone: 'dmz' }],
73
- appMachines: [{ name: 'idp', ip: '10.0.20.100', zone: 'app' }],
74
- secureMachines: [{ name: 'db', ip: '10.0.30.50', zone: 'secure' }],
72
+ dmzMachines: [{ name: 'caddy', ip: '10.226.10.10', zone: 'dmz' }],
73
+ appMachines: [{ name: 'idp', ip: '10.226.20.100', zone: 'app' }],
74
+ secureMachines: [{ name: 'db', ip: '10.226.30.50', zone: 'secure' }],
75
75
  domain: 'iamtheinternet.org',
76
76
  ddnsPassword: 'test123',
77
77
  verifyRouting: false,
@@ -131,17 +131,17 @@ echo " Project: $PROJECT_NAME"
131
131
  echo "================================================"
132
132
  echo ""
133
133
  echo "Networks:"
134
- echo " internal: 192.168.0.0/24"
135
- echo " dmz: 10.0.10.0/24"
136
- echo " app: 10.0.20.0/24"
137
- echo " secure: 10.0.30.0/24"
134
+ echo " internal: 10.226.1.0/24"
135
+ echo " dmz: 10.226.10.0/24"
136
+ echo " app: 10.226.20.0/24"
137
+ echo " secure: 10.226.30.0/24"
138
138
  echo " isp-external: 100.100.0.0/24"
139
139
  echo " internet-external: 100.64.0.0/24"
140
140
  echo ""
141
141
  echo "Key machines:"
142
- echo " management: 192.168.0.100 (conductor CLI)"
143
- echo " fw-main: 192.168.0.254 (iptables firewall)"
144
- echo " fw-isp: 192.168.0.1 (greenwave router sim)"
142
+ echo " management: 10.226.1.100 (conductor CLI)"
143
+ echo " fw-main: 10.226.1.254 (iptables firewall)"
144
+ echo " fw-isp: 10.226.1.1 (greenwave router sim)"
145
145
  echo " comcast-resolver: 100.100.0.1 (DNS resolver)"
146
146
  echo " namecheap-dns: 100.64.0.55 (DDNS simulator)"
147
147
  echo " letsencrypt: 100.64.0.100 (Pebble ACME)"
@@ -9,13 +9,13 @@ interface=eth0
9
9
  bind-interfaces
10
10
 
11
11
  # DHCP range: .200-.220 (small range for test clients)
12
- dhcp-range=192.168.0.200,192.168.0.220,255.255.255.0,1h
12
+ dhcp-range=10.226.1.200,10.226.1.220,255.255.255.0,1h
13
13
 
14
14
  # Default gateway (the greenwave router itself)
15
- dhcp-option=option:router,192.168.0.1
15
+ dhcp-option=option:router,10.226.1.1
16
16
 
17
17
  # DNS server (initially the router, updated by greenwave API)
18
- dhcp-option=option:dns-server,192.168.0.1
18
+ dhcp-option=option:dns-server,10.226.1.1
19
19
 
20
20
  # Domain name / search domain
21
21
  # dhcp-option=option:domain-name,iamtheinternet.org
@@ -32,8 +32,8 @@ server:
32
32
  # sit on the `internal` network — celilo-mgr reaches a notification
33
33
  # transport locally; a transport needing public ingress could not tell
34
34
  # you the ingress was broken.
35
- local-data: "signal-cli.lab. IN A 192.168.0.90"
36
- local-data: "signal-sim.lab. IN A 192.168.0.91"
35
+ local-data: "signal-cli.lab. IN A 10.226.1.90"
36
+ local-data: "signal-sim.lab. IN A 10.226.1.91"
37
37
  # signal-cli release host — the sim internet's stand-in for GitHub
38
38
  # releases, so the signal module's deploy-time download resolves inside
39
39
  # the sealed network. See SIMULATOR_IPS.SIGNAL_RELEASE.
@@ -6,10 +6,10 @@ ip route del default 2>/dev/null || true
6
6
  # Default route via real-internet bridge (for Squid outbound to real internet)
7
7
  ip route add default via 172.30.0.1
8
8
  # Internal subnets via fw-isp
9
- ip route add 192.168.0.0/24 via 100.100.0.100 # Internal via fw-isp
10
- ip route add 10.0.10.0/24 via 100.100.0.100 # dmz via fw-isp
11
- ip route add 10.0.20.0/24 via 100.100.0.100 # app via fw-isp
12
- ip route add 10.0.30.0/24 via 100.100.0.100 # secure via fw-isp
9
+ ip route add 10.226.1.0/24 via 100.100.0.100 # Internal via fw-isp
10
+ ip route add 10.226.10.0/24 via 100.100.0.100 # dmz via fw-isp
11
+ ip route add 10.226.20.0/24 via 100.100.0.100 # app via fw-isp
12
+ ip route add 10.226.30.0/24 via 100.100.0.100 # secure via fw-isp
13
13
 
14
14
  # IP forwarding is set via sysctls in docker-compose
15
15
 
@@ -11,9 +11,9 @@ chmod 600 /root/.ssh/authorized_keys
11
11
  # Routing
12
12
  ip route del default 2>/dev/null || true
13
13
  ip route add default via 100.100.0.101 # Internet via fw-ext
14
- ip route add 10.0.10.0/24 via 192.168.0.254 # dmz via fw-main
15
- ip route add 10.0.20.0/24 via 192.168.0.254 # app via fw-main
16
- ip route add 10.0.30.0/24 via 192.168.0.254 # secure via fw-main
14
+ ip route add 10.226.10.0/24 via 10.226.1.254 # dmz via fw-main
15
+ ip route add 10.226.20.0/24 via 10.226.1.254 # app via fw-main
16
+ ip route add 10.226.30.0/24 via 10.226.1.254 # secure via fw-main
17
17
 
18
18
  # IP forwarding is set via sysctls in docker-compose
19
19
 
@@ -30,14 +30,25 @@ else
30
30
  # Two-layer: no external interface on fw-main; it routes outbound to the
31
31
  # upstream firewall (fw-isp) and MASQUERADEs toward it (egress leaves via the
32
32
  # internal-facing interface, so there is no single WAN interface to scope to).
33
- ip route add default via 192.168.0.1
34
- # Protected↔protected (dmz/app/secure ↔ each other, all in 10/8) is NOT NAT'd —
35
- # a real firewall routes between its segmented zones without NAT, preserving the
33
+ ip route add default via 10.226.1.1
34
+ # Protected↔protected (the segmented zones ↔ each other) is NOT NAT'd — a real
35
+ # firewall routes between its segmented zones without NAT, preserving the
36
36
  # client source so the dmz-resident resolver can serve source-based split-horizon
37
37
  # (ISS-0156). Only protected↔internal and egress get MASQUERADE'd below. Without
38
38
  # this RETURN, app→dmz and internal→dmz would both be SNAT'd to the dmz gateway
39
39
  # and become indistinguishable (confirmed via live probe).
40
- iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -d 10.0.0.0/8 -j RETURN
40
+ #
41
+ # PROTECTED_SUBNETS is passed from the compose generator (ZONE_SUBNETS). It used
42
+ # to be written as `-s 10.0.0.0/8 -d 10.0.0.0/8`, which meant "protected" only
43
+ # because `internal` happened to be numbered out of 192.168/16 and every other
44
+ # zone out of 10/8 — the firewall's NAT policy silently depended on the octets. With
45
+ # the whole sim in 10.226.0.0/16 (#539) that shorthand would have swept `internal`
46
+ # into the no-NAT class and handed LAN devices the protected DNS view.
47
+ for src in ${PROTECTED_SUBNETS:?PROTECTED_SUBNETS not set by compose}; do
48
+ for dst in ${PROTECTED_SUBNETS}; do
49
+ iptables -t nat -A POSTROUTING -s "$src" -d "$dst" -j RETURN
50
+ done
51
+ done
41
52
  iptables -t nat -A POSTROUTING -j MASQUERADE
42
53
  fi
43
54
 
@@ -20,29 +20,29 @@ echo "UserKnownHostsFile /dev/null" >> /root/.ssh/config
20
20
  chmod 600 /root/.ssh/config
21
21
 
22
22
  # Routing — DEFAULT_GATEWAY comes from docker-compose environment
23
- # default: 192.168.0.1 (fw-isp), direct-internet: 192.168.0.254 (fw-main)
24
- GATEWAY="${DEFAULT_GATEWAY:-192.168.0.1}"
23
+ # default: 10.226.1.1 (fw-isp), direct-internet: 10.226.1.254 (fw-main)
24
+ GATEWAY="${DEFAULT_GATEWAY:-10.226.1.1}"
25
25
  # Next hop into the segmented zones. On the default topology celilo-mgr is on the
26
- # internal LAN and reaches them via fw-main's internal leg (192.168.0.254). When
26
+ # internal LAN and reaches them via fw-main's internal leg (10.226.1.254). When
27
27
  # celilo-mgr lives on its OWN control-plane network (managementZone secure-mgmt),
28
28
  # it reaches them via fw-main's leg on THAT network instead.
29
- FW_MAIN="${FW_MAIN_HOP:-192.168.0.254}"
29
+ FW_MAIN="${FW_MAIN_HOP:-10.226.1.254}"
30
30
  ip route del default 2>/dev/null || true
31
31
  ip route add default via "$GATEWAY"
32
- ip route add 10.0.10.0/24 via "$FW_MAIN" # dmz via fw-main
33
- ip route add 10.0.20.0/24 via "$FW_MAIN" # app via fw-main
34
- ip route add 10.0.30.0/24 via "$FW_MAIN" # secure via fw-main
32
+ ip route add 10.226.10.0/24 via "$FW_MAIN" # dmz via fw-main
33
+ ip route add 10.226.20.0/24 via "$FW_MAIN" # app via fw-main
34
+ ip route add 10.226.30.0/24 via "$FW_MAIN" # secure via fw-main
35
35
  # Control plane. Only reachable when a secure-mgmt network exists — that is,
36
36
  # when celilo-mgr lives there itself, or when a module declaring
37
37
  # `zone: secure-mgmt` needs a system to land on (#436). Tolerated silently
38
38
  # otherwise so the default topology is unchanged.
39
- ip route add 10.0.120.0/24 via "$FW_MAIN" 2>/dev/null || true
39
+ ip route add 10.226.120.0/24 via "$FW_MAIN" 2>/dev/null || true
40
40
 
41
41
  # DNS — Technitium internal resolver (split-horizon) with public fallback.
42
- # Technitium at 192.168.0.10 is authoritative for iamtheinternet.org internally,
43
- # resolving www/auth to Caddy's DMZ IP (10.0.10.10) rather than the public WAN IP.
42
+ # Technitium at 10.226.1.10 is authoritative for iamtheinternet.org internally,
43
+ # resolving www/auth to Caddy's DMZ IP (10.226.10.10) rather than the public WAN IP.
44
44
  # Fall back to the simulated public resolver if Technitium is not yet up.
45
- printf 'nameserver %s\nnameserver 100.100.0.1\n' "${INTERNAL_RESOLVER:-192.168.0.10}" > /etc/resolv.conf
45
+ printf 'nameserver %s\nnameserver 100.100.0.1\n' "${INTERNAL_RESOLVER:-10.226.1.10}" > /etc/resolv.conf
46
46
 
47
47
  # Fetch Pebble's runtime ACME root CA (Pebble generates a fresh root at each
48
48
  # startup, unlike the static /config/pebble/pebble-ca.crt baked in at build
@@ -28,9 +28,9 @@ ip route add default via "${GATEWAY}"
28
28
  # A real consumer vantage NEVER has these — that is the whole point. Present only so a
29
29
  # `management`-style all-VLAN box can be modelled deliberately.
30
30
  if [ "${INTERZONE}" = "1" ]; then
31
- ip route add 10.0.10.0/24 via 192.168.0.254 2>/dev/null || true # dmz via fw-main
32
- ip route add 10.0.20.0/24 via 192.168.0.254 2>/dev/null || true # app via fw-main
33
- ip route add 10.0.30.0/24 via 192.168.0.254 2>/dev/null || true # secure via fw-main
31
+ ip route add 10.226.10.0/24 via 10.226.1.254 2>/dev/null || true # dmz via fw-main
32
+ ip route add 10.226.20.0/24 via 10.226.1.254 2>/dev/null || true # app via fw-main
33
+ ip route add 10.226.30.0/24 via 10.226.1.254 2>/dev/null || true # secure via fw-main
34
34
  fi
35
35
 
36
36
  # --- Resolver ---
@@ -1,10 +1,10 @@
1
1
  #!/bin/bash
2
2
  set -e
3
3
 
4
- # CADDY_IP: IP of the caddy container in the DMZ (default 10.0.10.10).
4
+ # CADDY_IP: IP of the caddy container in the DMZ (default 10.226.10.10).
5
5
  # All test-domain queries resolve to this IP so management can reach
6
6
  # caddy directly without hairpin NAT through the external interface.
7
- CADDY_IP="${CADDY_IP:-10.0.10.10}"
7
+ CADDY_IP="${CADDY_IP:-10.226.10.10}"
8
8
 
9
9
  # DOMAINS: comma-separated list of e2e test domains that should be
10
10
  # split-horizon redirected to caddy. Default covers the canonical
@@ -15,7 +15,7 @@ DOMAINS="${DOMAINS:-iamtheinternet.org,example.net}"
15
15
 
16
16
  # Routing — reach comcast-resolver (100.100.0.1) via fw-main or fw-isp.
17
17
  # DEFAULT_GATEWAY is set by docker-compose from the topology config.
18
- GATEWAY="${DEFAULT_GATEWAY:-192.168.0.254}"
18
+ GATEWAY="${DEFAULT_GATEWAY:-10.226.1.254}"
19
19
  ip route del default 2>/dev/null || true
20
20
  ip route add default via "$GATEWAY"
21
21
 
@@ -61,8 +61,8 @@ $(printf "${SPLIT_HORIZON_BLOCK}")
61
61
  # sit on the `internal` network — celilo-mgr reaches a notification
62
62
  # transport locally; a transport needing public ingress could not tell
63
63
  # you the ingress was broken.
64
- local-data: "signal-cli.lab. IN A 192.168.0.90"
65
- local-data: "signal-sim.lab. IN A 192.168.0.91"
64
+ local-data: "signal-cli.lab. IN A 10.226.1.90"
65
+ local-data: "signal-sim.lab. IN A 10.226.1.91"
66
66
  # signal-cli release host — the sim internet's stand-in for GitHub
67
67
  # releases, so the signal module's deploy-time download resolves inside
68
68
  # the sealed network. See SIMULATOR_IPS.SIGNAL_RELEASE.
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # Generic startup for target machines (dmz/app/secure).
3
- # The default gateway is passed as $1 (e.g., 10.0.10.1 for dmz).
3
+ # The default gateway is passed as $1 (e.g., 10.226.10.1 for dmz).
4
4
  set -e
5
5
 
6
6
  GATEWAY="${1:?Usage: target-routes.sh <gateway-ip>}"
@@ -63,21 +63,23 @@ echo "nameserver 100.100.0.1" > /etc/resolv.conf
63
63
  done
64
64
  ) &
65
65
 
66
- # Pre-warm apt cache before reporting "ready". Direct upstream (no
67
- # apt-cacher-ng) is less forgiving: the very first Ansible apt-update
68
- # was returning success with partially-fetched indexes (universe
69
- # missing), making "apt install knot" fail with "No package matching
70
- # 'knot' is available". Running update here, *before* the
71
- # target-setup service completes, guarantees Ansible finds populated
72
- # lists when it later connects via SSH.
73
- echo "Pre-warming apt cache..."
74
- for attempt in 1 2 3; do
75
- if apt-get update -qq; then
76
- echo "apt-cache ready (attempt $attempt)"
77
- break
78
- fi
79
- echo "apt-update attempt $attempt failed, retrying in 5s..."
80
- sleep 5
81
- done
66
+ # NOTHING BELOW MAY DEPEND ON EGRESS. Readiness is gated on this script
67
+ # (`systemctl is-active target-setup`, 60s), and this script runs the moment the
68
+ # container does — BEFORE fw-ext has installed its NAT. Until it does, a packet
69
+ # for the real internet is not refused, it is blackholed, so anything fetched
70
+ # here pays a full connect timeout rather than failing fast.
71
+ #
72
+ # This used to pre-warm the apt cache, whose sources include dl.cloudsmith.io:
73
+ # four IPs, each timing out, 64s against a 60s budget. Measured both ways on a
74
+ # live stack — from a converged target that same fetch is 302 in 0.19s; with
75
+ # fw-ext's NAT flushed it hangs past 70s. So the failure was never a slow CDN or
76
+ # a broken simulator, which is exactly what "Timeout waiting for caddy
77
+ # target-setup" sent everyone looking for, twice in one day.
78
+ #
79
+ # The apt lists it was warming are now populated at IMAGE BUILD time (see
80
+ # docker/Dockerfile.target-*): Ansible still gets complete indexes on first
81
+ # connect, and readiness no longer races the firewall. Egress itself is
82
+ # untouched — the topology still NATs out, and pebble/apt still use it at deploy
83
+ # time, by which point the firewall has long converged. See #560.
82
84
 
83
85
  echo "Target machine ready: $(hostname)"
@@ -10,7 +10,7 @@ VANTAGE="${VANTAGE:-isp-external}"
10
10
  case "$VANTAGE" in
11
11
  isp-external)
12
12
  # Residential-user view. Default route via fw-ext (.101) which DNATs
13
- # public-internet traffic into the dmz (caddy at 10.0.10.10). DNS via
13
+ # public-internet traffic into the dmz (caddy at 10.226.10.10). DNS via
14
14
  # comcast-resolver (.1) which walks the root → TLD → namecheap chain.
15
15
  GATEWAY="100.100.0.101"
16
16
  NAMESERVER="100.100.0.1"
@@ -19,8 +19,8 @@ case "$VANTAGE" in
19
19
  # Operator-on-LAN view. Default route via fw-isp (.1); split-horizon
20
20
  # resolver at .10 returns the firewall's same-subnet NAT IP for the
21
21
  # test domain, which DNATs to caddy.
22
- GATEWAY="192.168.0.1"
23
- NAMESERVER="192.168.0.10"
22
+ GATEWAY="10.226.1.1"
23
+ NAMESERVER="10.226.1.10"
24
24
  ;;
25
25
  *)
26
26
  echo "Unknown VANTAGE: $VANTAGE (expected isp-external or internal)" >&2
@@ -87,7 +87,21 @@ RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
87
87
  gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
88
88
  RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
89
89
  tee /etc/apt/sources.list.d/caddy-stable.list
90
- RUN apt-get update && apt-get install -y caddy && rm -rf /var/lib/apt/lists/*
90
+ # Once caddy is installed the cloudsmith source has no job left — but the file
91
+ # must STAY: the Ansible role skips re-adding it on `creates:`, so deleting it
92
+ # only moves the curl into the deploy. Neutered to a comment instead, so no
93
+ # `apt-get update` in the sim waits on a repo whose only package is already
94
+ # installed here.
95
+ #
96
+ # The lists are deliberately NOT cleaned afterwards. target-setup used to run
97
+ # `apt-get update` at boot to guarantee Ansible found complete indexes, paying
98
+ # an external round-trip inside the 60s readiness budget for it; baking the
99
+ # indexes here buys the same guarantee off that path. A source that fails to
100
+ # refresh at deploy time keeps this copy. See #560.
101
+ RUN apt-get update && apt-get install -y caddy \
102
+ && printf '# neutered at image build: caddy is pre-installed (see #560)\n' \
103
+ > /etc/apt/sources.list.d/caddy-stable.list \
104
+ && apt-get update
91
105
 
92
106
  STOPSIGNAL SIGRTMIN+3
93
107
  ENTRYPOINT ["/sbin/init"]
@@ -97,5 +97,11 @@ COPY config/routing/docker-image-preload.service /etc/systemd/system/docker-imag
97
97
  RUN systemctl enable docker-image-preload
98
98
 
99
99
 
100
+ # Bake complete apt indexes into the image. target-setup used to run
101
+ # `apt-get update` at boot for this, which put a third party's latency inside
102
+ # the 60s readiness budget (#560). Doing it here gives Ansible the same
103
+ # guarantee — populated lists on first connect — off the readiness path.
104
+ RUN apt-get update
105
+
100
106
  STOPSIGNAL SIGRTMIN+3
101
107
  ENTRYPOINT ["/sbin/init"]
@@ -94,5 +94,11 @@ RUN printf '[Unit]\nDescription=Technitium DNS Server\nAfter=network.target\n\n[
94
94
  > /etc/systemd/system/dns.service && \
95
95
  systemctl enable dns
96
96
 
97
+ # Bake complete apt indexes into the image. target-setup used to run
98
+ # `apt-get update` at boot for this, which put a third party's latency inside
99
+ # the 60s readiness budget (#560). Doing it here gives Ansible the same
100
+ # guarantee — populated lists on first connect — off the readiness path.
101
+ RUN apt-get update
102
+
97
103
  STOPSIGNAL SIGRTMIN+3
98
104
  ENTRYPOINT ["/sbin/init"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celilo/e2e",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "description": "E2E test infrastructure for Celilo-deployed applications. Provides a simulated internet with DNS hierarchy, ACME server, firewalls, and target machines in Docker.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "@celilo/cli-display": "^0.1.9",
41
- "@celilo/event-bus": "^0.1.9",
41
+ "@celilo/event-bus": "^0.1.10",
42
42
  "yaml": "^2.8.0",
43
43
  "zod": "^3.24.1"
44
44
  },
@@ -68,7 +68,7 @@ const tlsOptions = (() => {
68
68
 
69
69
  // Bind to internal interface only (like a real router's management UI)
70
70
  // The external interface should only handle port-forwarded traffic via iptables
71
- const BIND_HOST = process.env.ROUTER_BIND_HOST || '192.168.0.1';
71
+ const BIND_HOST = process.env.ROUTER_BIND_HOST || '10.226.1.1';
72
72
 
73
73
  const server = Bun.serve({
74
74
  port: PORT,
@@ -17,11 +17,11 @@ let nextIndex = 1;
17
17
  const dhcpPool: Record<string, string> = {
18
18
  Alias: 'cpe-Pool-1',
19
19
  Enable: 'true',
20
- MinAddress: '192.168.0.2',
21
- MaxAddress: '192.168.0.150',
20
+ MinAddress: '10.226.1.2',
21
+ MaxAddress: '10.226.1.150',
22
22
  SubnetMask: '255.255.255.0',
23
- IPRouters: '192.168.0.1',
24
- DNSServers: '192.168.0.1',
23
+ IPRouters: '10.226.1.1',
24
+ DNSServers: '10.226.1.1',
25
25
  DomainName: '',
26
26
  LeaseTime: '3600',
27
27
  Status: 'Enabled',
@@ -121,7 +121,7 @@ export function setDhcpPool(params: Record<string, string>): CgiObject[] {
121
121
  }
122
122
 
123
123
  function rewriteDnsmasqConfig(): void {
124
- const dnsServers = dhcpPool.DNSServers || '192.168.0.1';
124
+ const dnsServers = dhcpPool.DNSServers || '10.226.1.1';
125
125
  const domainName = dhcpPool.DomainName || '';
126
126
 
127
127
  const lines = [
@@ -129,8 +129,8 @@ function rewriteDnsmasqConfig(): void {
129
129
  'port=0',
130
130
  'interface=eth0',
131
131
  'bind-interfaces',
132
- 'dhcp-range=192.168.0.200,192.168.0.220,255.255.255.0,60s',
133
- 'dhcp-option=option:router,192.168.0.1',
132
+ 'dhcp-range=10.226.1.200,10.226.1.220,255.255.255.0,60s',
133
+ 'dhcp-option=option:router,10.226.1.1',
134
134
  `dhcp-option=option:dns-server,${dnsServers}`,
135
135
  'dhcp-lease-max=20',
136
136
  'log-dhcp',