@celilo/e2e 0.5.1 → 0.6.1

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.
Files changed (59) hide show
  1. package/README.md +2 -1
  2. package/bin/cele2e +14 -0
  3. package/bin/e2e-cache-images +57 -0
  4. package/bin/e2e-shell +36 -13
  5. package/bin/e2e-status +8 -2
  6. package/bin/e2e-up +5 -5
  7. package/completions/install-zsh-completion.sh +78 -0
  8. package/config/dns/celilo.computer.zone +16 -0
  9. package/config/dns/iamtheinternet.org.zone +7 -6
  10. package/config/dns/knot-namecheap.conf +8 -0
  11. package/config/dns/knot-tld.conf +4 -0
  12. package/config/dns/lunacycle.net.zone +16 -0
  13. package/config/dns/net.zone +11 -0
  14. package/config/dns/org.zone +5 -0
  15. package/config/dns/root.zone +2 -0
  16. package/config/dns/templates/iamtheinternet.org.zone +16 -0
  17. package/config/dns/templates/lunacycle.net.zone +16 -0
  18. package/config/resolver/unbound.conf +10 -0
  19. package/config/routing/docker-image-preload.service +14 -0
  20. package/config/routing/docker-image-preload.sh +24 -0
  21. package/config/routing/management-routes.sh +24 -2
  22. package/config/routing/resolver-internal-routes.sh +59 -0
  23. package/docker/Dockerfile.internal-resolver +16 -0
  24. package/docker/Dockerfile.isitup +37 -0
  25. package/docker/Dockerfile.management +2 -0
  26. package/docker/Dockerfile.registry +34 -0
  27. package/docker/Dockerfile.target-machine +20 -0
  28. package/docker/Dockerfile.target-machine-docker +11 -19
  29. package/docker/Dockerfile.target-technitium +97 -0
  30. package/netapps/authentik.netapp +0 -0
  31. package/netapps/caddy.netapp +0 -0
  32. package/netapps/greenwave.netapp +0 -0
  33. package/netapps/homebridge.netapp +0 -0
  34. package/netapps/iptables.netapp +0 -0
  35. package/netapps/knot-unbound-internal.netapp +0 -0
  36. package/netapps/namecheap.netapp +0 -0
  37. package/netapps/technitium.netapp +0 -0
  38. package/netapps/test-ansible-output.netapp +0 -0
  39. package/package.json +8 -1
  40. package/simulators/greenwave/state.ts +7 -12
  41. package/simulators/isitup/Caddyfile +11 -0
  42. package/simulators/isitup/entrypoint.sh +47 -0
  43. package/simulators/isitup/server.ts +112 -0
  44. package/simulators/namecheap-ddns/server.ts +31 -2
  45. package/src/cli/build.ts +214 -0
  46. package/src/cli/command-registry.ts +100 -0
  47. package/src/cli/command-tree-parser.ts +52 -0
  48. package/src/cli/completion.ts +195 -0
  49. package/src/cli/index.ts +273 -0
  50. package/src/cli/module-discovery.ts +120 -0
  51. package/src/cli/scaffold.ts +108 -0
  52. package/src/container-manager.ts +373 -76
  53. package/src/docker-compose-generator.ts +127 -23
  54. package/src/network-builder.ts +21 -0
  55. package/src/parse-line.ts +147 -0
  56. package/src/runner.ts +197 -247
  57. package/src/simulator-ips.ts +42 -0
  58. package/src/types.ts +28 -0
  59. package/bin/cache-docker-images +0 -46
package/README.md CHANGED
@@ -187,7 +187,8 @@ c machine add 192.168.0.254 --ssh-user root --earmark iptables
187
187
  c machine add 10.0.10.10 --ssh-user root
188
188
 
189
189
  # Pre-configure secrets and non-derivable config
190
- c module secret set namecheap ddns_password test123
190
+ c module config set namecheap domains '["iamtheinternet.org"]'
191
+ c module secret set namecheap ddns_passwords '{"iamtheinternet.org":"test123"}'
191
192
  c module config set greenwave router_ip 192.168.0.1
192
193
  c module secret set greenwave router_username admin
193
194
  c module secret set greenwave router_password admin
package/bin/cele2e ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # cele2e — E2E test CLI for Celilo-deployed applications.
3
+
4
+ # Resolve symlinks to find the actual package directory
5
+ SOURCE="${BASH_SOURCE[0]}"
6
+ while [ -L "$SOURCE" ]; do
7
+ DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
8
+ SOURCE="$(readlink "$SOURCE")"
9
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
10
+ done
11
+ SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
12
+ PKG_DIR="$(dirname "$SCRIPT_DIR")"
13
+
14
+ exec bun "$PKG_DIR/src/cli/index.ts" "$@"
@@ -0,0 +1,57 @@
1
+ #!/bin/bash
2
+ #
3
+ # Save application Docker images to the local cache for e2e acceleration.
4
+ #
5
+ # The cached tarballs are mounted into app-zone target machines and
6
+ # pre-loaded at startup, avoiding slow pulls from ghcr.io / docker.io
7
+ # during every test run.
8
+ #
9
+ # Run once after pulling new image versions. The cache directory is
10
+ # gitignored — each developer maintains their own copy.
11
+ #
12
+ # Usage:
13
+ # ./e2e/bin/e2e-cache-images
14
+ #
15
+
16
+ set -e
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ E2E_DIR="$(dirname "$SCRIPT_DIR")"
20
+ CACHE_DIR="$E2E_DIR/docker-image-cache"
21
+
22
+ mkdir -p "$CACHE_DIR"
23
+
24
+ BOLD='\033[1m'
25
+ GREEN='\033[0;32m'
26
+ DIM='\033[2m'
27
+ NC='\033[0m'
28
+
29
+ # Images used by deployed modules in the app zone.
30
+ # Update versions here when bumping module image versions.
31
+ declare -A IMAGES=(
32
+ ["docker.io_library_postgres_15-alpine"]="docker.io/library/postgres:15-alpine"
33
+ ["docker.io_library_redis_alpine"]="docker.io/library/redis:alpine"
34
+ ["ghcr.io_goauthentik_server_2024.2.2"]="ghcr.io/goauthentik/server:2024.2.2"
35
+ )
36
+
37
+ echo -e "${BOLD}Pulling and caching application images...${NC}"
38
+ echo ""
39
+
40
+ for filename in "${!IMAGES[@]}"; do
41
+ image="${IMAGES[$filename]}"
42
+ tarball="$CACHE_DIR/${filename}.tar"
43
+
44
+ printf " %-50s " "$image"
45
+ START=$(date +%s)
46
+
47
+ docker pull "$image" > /dev/null 2>&1
48
+ docker save "$image" -o "$tarball"
49
+
50
+ END=$(date +%s)
51
+ SIZE=$(du -h "$tarball" | cut -f1)
52
+ echo -e "${GREEN}✔${NC} ${DIM}$((END - START))s ${SIZE}${NC}"
53
+ done
54
+
55
+ echo ""
56
+ echo -e "${GREEN}Images cached to $CACHE_DIR${NC}"
57
+ echo "These will be pre-loaded into app-zone containers on next e2e run."
package/bin/e2e-shell CHANGED
@@ -2,26 +2,49 @@
2
2
  # Connect to a running E2E container.
3
3
  #
4
4
  # Usage:
5
- # ./bin/e2e-shell # Shell into management machine
5
+ # ./bin/e2e-shell # Shell into management (interactive or test project)
6
6
  # ./bin/e2e-shell caddy # Shell into caddy container
7
- # ./bin/e2e-shell fw-main # Shell into fw-main
8
- # ./bin/e2e-shell namecheap-dns # Shell into namecheap-dns
7
+ # ./bin/e2e-shell authentik # Shell into authentik container (test project)
9
8
 
10
9
  set -e
11
- PROJECT_NAME="conductor-e2e-interactive"
12
10
  CONTAINER="${1:-management}"
13
11
 
14
- # Check if network is running
15
- if ! docker compose -p "$PROJECT_NAME" ps --status running 2>/dev/null | grep -q "$CONTAINER"; then
16
- echo "Container '$CONTAINER' is not running."
17
- echo "Available containers:"
18
- docker compose -p "$PROJECT_NAME" ps --format ' {{.Name}} ({{.Status}})' 2>/dev/null || echo " No E2E network running. Start with: ./bin/e2e-up"
12
+ # Resolve project name: prefer interactive project, fall back to any running test project
13
+ resolve_project() {
14
+ # 1. Interactive project (e2e-up)
15
+ if docker compose -p celilo-e2e-interactive ps --status running 2>/dev/null | grep -q "$CONTAINER"; then
16
+ echo "celilo-e2e-interactive"
17
+ return
18
+ fi
19
+
20
+ # 2. Test run project: find any celilo-e2e-* container matching the name
21
+ local container_id
22
+ container_id=$(docker ps --filter "name=${CONTAINER}" --filter "name=celilo-e2e-" --format '{{.Names}}' | head -1)
23
+ if [ -n "$container_id" ]; then
24
+ # Extract project from container name: celilo-e2e-<project>-<service>-1
25
+ # We don't need the compose project name — just exec directly
26
+ echo "_direct:${container_id}"
27
+ return
28
+ fi
29
+
30
+ echo ""
31
+ }
32
+
33
+ PROJECT=$(resolve_project)
34
+
35
+ if [ -z "$PROJECT" ]; then
36
+ echo "Container '$CONTAINER' is not running in any celilo-e2e project."
37
+ echo ""
38
+ echo "Running celilo-e2e containers:"
39
+ docker ps --filter "name=celilo-e2e-" --format ' {{.Names}} ({{.Status}})' 2>/dev/null || echo " none"
19
40
  exit 1
20
41
  fi
21
42
 
22
- # Use zsh for management (has conductor aliases + completion), bash for others
23
- if [ "$CONTAINER" = "management" ]; then
24
- docker compose -p "$PROJECT_NAME" exec "$CONTAINER" zsh -li
43
+ SHELL_CMD="bash"
44
+
45
+ if [[ "$PROJECT" == _direct:* ]]; then
46
+ FULL_NAME="${PROJECT#_direct:}"
47
+ exec docker exec -it "$FULL_NAME" $SHELL_CMD
25
48
  else
26
- docker compose -p "$PROJECT_NAME" exec "$CONTAINER" bash
49
+ exec docker compose -p "$PROJECT" exec "$CONTAINER" $SHELL_CMD
27
50
  fi
package/bin/e2e-status CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/bin/bash
2
2
  # Show status of the E2E test network.
3
3
 
4
- PROJECT_NAME="conductor-e2e-interactive"
4
+ PROJECT_NAME="${CELILO_E2E_PROJECT:-celilo-e2e-interactive}"
5
5
 
6
6
  echo "=== Containers ==="
7
- docker compose -p "$PROJECT_NAME" ps 2>/dev/null || { echo "No E2E network running."; exit 0; }
7
+ if ! docker compose -p "$PROJECT_NAME" ps 2>/dev/null | grep -q .; then
8
+ echo "No E2E network running (project: $PROJECT_NAME)."
9
+ echo ""
10
+ echo "Start one with: cele2e up or cele2e run --keep"
11
+ exit 0
12
+ fi
13
+ docker compose -p "$PROJECT_NAME" ps
8
14
 
9
15
  echo ""
10
16
  echo "=== DNS Check ==="
package/bin/e2e-up CHANGED
@@ -15,7 +15,7 @@ set -e
15
15
  SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
16
16
  cd "$SCRIPT_DIR"
17
17
 
18
- PROJECT_NAME="conductor-e2e-interactive"
18
+ PROJECT_NAME="celilo-e2e-interactive"
19
19
 
20
20
  # Parse args
21
21
  PRESET="infrastructure"
@@ -148,13 +148,13 @@ echo " letsencrypt: 100.64.0.100 (Pebble ACME)"
148
148
  echo " apt-cache: 100.64.0.2 (apt proxy → real internet)"
149
149
  echo ""
150
150
  echo "Commands:"
151
- echo " ./bin/e2e-shell Reconnect to management"
152
- echo " ./bin/e2e-shell <container> Shell into any container"
153
- echo " ./bin/e2e-down Tear everything down"
151
+ echo " cele2e shell Reconnect to management"
152
+ echo " cele2e shell <container> Shell into any container"
153
+ echo " cele2e down Tear everything down"
154
154
  echo " docker compose -p $PROJECT_NAME ps List containers"
155
155
  echo ""
156
156
  echo "Dropping you into the management machine..."
157
- echo "(use 'conductor <cmd>' or 'c <cmd>' — tab completion works)"
157
+ echo "(use 'celilo <cmd>' — tab completion works)"
158
158
  echo ""
159
159
 
160
160
  docker compose -p "$PROJECT_NAME" exec management zsh -li
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # cele2e - Zsh Completion Installer
4
+
5
+ set -e
6
+
7
+ RED='\033[0;31m'
8
+ GREEN='\033[0;32m'
9
+ YELLOW='\033[1;33m'
10
+ BLUE='\033[0;34m'
11
+ NC='\033[0m'
12
+
13
+ COMPLETION_FILE="_cele2e"
14
+ COMPLETION_DIR="$HOME/.zsh/completions"
15
+ ZSHRC="$HOME/.zshrc"
16
+
17
+ echo -e "${BLUE}🧪 cele2e - Zsh Completion Installer${NC}\n"
18
+
19
+ if ! command -v zsh &> /dev/null; then
20
+ echo -e "${RED}✗ zsh not found${NC}"; exit 1
21
+ fi
22
+ echo -e "${GREEN}✓${NC} zsh detected"
23
+
24
+ if ! command -v cele2e &> /dev/null; then
25
+ echo -e "${RED}✗ cele2e not found in PATH${NC}"
26
+ echo -e " Run: bun install -g ~/hobby/infra/packages/e2e"; exit 1
27
+ fi
28
+ echo -e "${GREEN}✓${NC} Found cele2e CLI"
29
+
30
+ echo -e "${YELLOW}→${NC} Generating completion script..."
31
+ GENERATED=$(cele2e completion zsh 2>/dev/null)
32
+ if [[ -z "$GENERATED" ]]; then
33
+ echo -e "${RED}✗ Failed to generate completion script${NC}"; exit 1
34
+ fi
35
+ echo -e "${GREEN}✓${NC} Completion script generated"
36
+
37
+ mkdir -p "$COMPLETION_DIR"
38
+ echo -e "${YELLOW}→${NC} Installing completion script..."
39
+ echo "$GENERATED" > "$COMPLETION_DIR/$COMPLETION_FILE"
40
+ chmod 644 "$COMPLETION_DIR/$COMPLETION_FILE"
41
+ echo -e "${GREEN}✓${NC} Installed to $COMPLETION_DIR/$COMPLETION_FILE"
42
+
43
+ [[ -f "$ZSHRC" ]] || touch "$ZSHRC"
44
+
45
+ if grep -q "fpath=.*\.zsh/completions" "$ZSHRC"; then
46
+ echo -e "${GREEN}✓${NC} Completion directory already in fpath"
47
+ else
48
+ echo -e "${YELLOW}→${NC} Adding completion directory to .zshrc..."
49
+ if grep -qn "autoload.*compinit" "$ZSHRC"; then
50
+ COMPINIT_LINE=$(grep -n "autoload.*compinit" "$ZSHRC" | head -1 | cut -d: -f1)
51
+ sed -i '' "${COMPINIT_LINE}i\\
52
+ # cele2e completions\\
53
+ fpath=(~/.zsh/completions \$fpath)\\
54
+
55
+ " "$ZSHRC"
56
+ else
57
+ echo -e "\n# cele2e completions\nfpath=(~/.zsh/completions \$fpath)\nautoload -Uz compinit && compinit" >> "$ZSHRC"
58
+ fi
59
+ echo -e "${GREEN}✓${NC} fpath configured"
60
+ fi
61
+
62
+ if ! grep -q "autoload.*compinit" "$ZSHRC"; then
63
+ echo "autoload -Uz compinit && compinit" >> "$ZSHRC"
64
+ fi
65
+
66
+ echo -e "${YELLOW}→${NC} Clearing completion cache..."
67
+ rm -rf "$HOME"/.zcompdump*
68
+ echo -e "${GREEN}✓${NC} Cache cleared"
69
+
70
+ echo ""
71
+ echo -e "${GREEN}✅ Installation complete!${NC}"
72
+ echo ""
73
+ echo "To activate:"
74
+ echo -e " ${BLUE}exec zsh${NC}"
75
+ echo ""
76
+ echo "Try it out:"
77
+ echo -e " ${BLUE}cele2e <TAB>${NC}"
78
+ echo -e " ${BLUE}cele2e run <TAB>${NC}"
@@ -0,0 +1,16 @@
1
+ $ORIGIN celilo.computer.
2
+ $TTL 300
3
+
4
+ @ IN SOA ns1.celilo.computer. admin.celilo.computer. (
5
+ 2024020117 300 60 604800 300
6
+ )
7
+
8
+ ; Public DNS — RFC 1918 addresses NEVER appear here. The whole point of
9
+ ; the e2e simulation is to model real internet connectivity, where
10
+ ; private IPs are unreachable from outside. All public-facing names
11
+ ; resolve to the firewall's external IP (100.100.0.100), which DNATs
12
+ ; inbound to the right internal host.
13
+ @ IN NS ns1.celilo.computer.
14
+ ns1 IN A 100.64.0.55
15
+ @ IN A 100.100.0.100
16
+ www IN A 100.100.0.100
@@ -2,14 +2,15 @@ $ORIGIN iamtheinternet.org.
2
2
  $TTL 300
3
3
 
4
4
  @ IN SOA ns1.iamtheinternet.org. admin.iamtheinternet.org. (
5
- 2024010778 300 60 604800 300
5
+ 2024020248 300 60 604800 300
6
6
  )
7
7
 
8
+ ; Public DNS — RFC 1918 addresses NEVER appear here. The whole point of
9
+ ; the e2e simulation is to model real internet connectivity, where
10
+ ; private IPs are unreachable from outside. All public-facing names
11
+ ; resolve to the firewall's external IP (100.100.0.100), which DNATs
12
+ ; inbound to the right internal host.
8
13
  @ IN NS ns1.iamtheinternet.org.
9
14
  ns1 IN A 100.64.0.55
10
15
  @ IN A 0.0.0.0
11
- caddy IN A 100.100.0.100
12
- www IN A 100.100.0.100
13
- deathstar IN A 100.100.0.100
14
- test IN A 0.0.0.0
15
- auth IN A 100.100.0.100
16
+ www IN A 100.100.0.100
@@ -6,6 +6,14 @@ zone:
6
6
  file: "/config/iamtheinternet.org.zone"
7
7
  storage: "/var/lib/knot"
8
8
 
9
+ - domain: "celilo.computer"
10
+ file: "/config/celilo.computer.zone"
11
+ storage: "/var/lib/knot"
12
+
13
+ - domain: "lunacycle.net"
14
+ file: "/config/lunacycle.net.zone"
15
+ storage: "/var/lib/knot"
16
+
9
17
  - domain: "park-your-domain.com"
10
18
  file: "/config/park-your-domain.com.zone"
11
19
  storage: "/var/lib/knot"
@@ -9,3 +9,7 @@ zone:
9
9
  - domain: "org"
10
10
  file: "/config/org.zone"
11
11
  storage: "/var/lib/knot"
12
+
13
+ - domain: "net"
14
+ file: "/config/net.zone"
15
+ storage: "/var/lib/knot"
@@ -0,0 +1,16 @@
1
+ $ORIGIN lunacycle.net.
2
+ $TTL 300
3
+
4
+ @ IN SOA ns1.lunacycle.net. admin.lunacycle.net. (
5
+ 2024020108 300 60 604800 300
6
+ )
7
+
8
+ ; Public DNS — RFC 1918 addresses NEVER appear here. The whole point of
9
+ ; the e2e simulation is to model real internet connectivity, where
10
+ ; private IPs are unreachable from outside. All public-facing names
11
+ ; resolve to the firewall's external IP (100.100.0.100), which DNATs
12
+ ; inbound to the right internal host.
13
+ @ IN NS ns1.lunacycle.net.
14
+ ns1 IN A 100.64.0.55
15
+ @ IN A 100.100.0.100
16
+ www IN A 100.100.0.100
@@ -0,0 +1,11 @@
1
+ $ORIGIN net.
2
+ $TTL 86400
3
+
4
+ @ IN SOA tld-dns.lab. admin.lab. (
5
+ 2024010101 1800 900 604800 86400
6
+ )
7
+
8
+ net. IN NS tld-dns.lab.
9
+
10
+ lunacycle IN NS ns1.lunacycle.net.
11
+ ns1.lunacycle.net. IN A 100.64.0.55
@@ -10,5 +10,10 @@ org. IN NS tld-dns.lab.
10
10
  iamtheinternet IN NS ns1.iamtheinternet.org.
11
11
  ns1.iamtheinternet.org. IN A 100.64.0.55
12
12
 
13
+ ; External prober simulator — mimics isitup.org's JSON API.
14
+ ; Served directly by tld-dns rather than delegated, since we only need a
15
+ ; single A record (no subdomains, no SOA of its own).
16
+ isitup IN A 100.64.0.57
17
+
13
18
  ; Note: Let's Encrypt ACME endpoints are handled by unbound local-data
14
19
  ; (resolves acme-v02.api.letsencrypt.org → 100.64.0.100 directly)
@@ -12,3 +12,5 @@ com. IN NS tld-dns.lab.
12
12
  tld-dns.lab. IN A 100.64.0.54
13
13
 
14
14
  org. IN NS tld-dns.lab.
15
+
16
+ net. IN NS tld-dns.lab.
@@ -0,0 +1,16 @@
1
+ $ORIGIN iamtheinternet.org.
2
+ $TTL 300
3
+
4
+ @ IN SOA ns1.iamtheinternet.org. admin.iamtheinternet.org. (
5
+ 2024020247 300 60 604800 300
6
+ )
7
+
8
+ ; Public DNS — RFC 1918 addresses NEVER appear here. The whole point of
9
+ ; the e2e simulation is to model real internet connectivity, where
10
+ ; private IPs are unreachable from outside. All public-facing names
11
+ ; resolve to the firewall's external IP (100.100.0.100), which DNATs
12
+ ; inbound to the right internal host.
13
+ @ IN NS ns1.iamtheinternet.org.
14
+ ns1 IN A 100.64.0.55
15
+ @ IN A 0.0.0.0
16
+ www IN A 100.100.0.100
@@ -0,0 +1,16 @@
1
+ $ORIGIN lunacycle.net.
2
+ $TTL 300
3
+
4
+ @ IN SOA ns1.lunacycle.net. admin.lunacycle.net. (
5
+ 2024020108 300 60 604800 300
6
+ )
7
+
8
+ ; Public DNS — RFC 1918 addresses NEVER appear here. The whole point of
9
+ ; the e2e simulation is to model real internet connectivity, where
10
+ ; private IPs are unreachable from outside. All public-facing names
11
+ ; resolve to the firewall's external IP (100.100.0.100), which DNATs
12
+ ; inbound to the right internal host.
13
+ @ IN NS ns1.lunacycle.net.
14
+ ns1 IN A 100.64.0.55
15
+ @ IN A 100.100.0.100
16
+ www IN A 100.100.0.100
@@ -11,12 +11,22 @@ server:
11
11
  # Let's Encrypt ACME endpoints → Pebble
12
12
  local-data: "acme-v02.api.letsencrypt.org. IN A 100.64.0.100"
13
13
  local-data: "acme-staging-v02.api.letsencrypt.org. IN A 100.64.0.100"
14
+ # E2E registry container
15
+ local-data: "e2e-registry.lab. IN A 100.64.0.56"
16
+ # External website prober simulator — without this override, isitup.org
17
+ # falls through the "forward-zone: ." below and resolves to the REAL
18
+ # Namecheap-parked domain on the public internet.
19
+ local-data: "isitup.org. IN A 100.64.0.57"
14
20
 
15
21
  # Simulated domains → namecheap-dns (authoritative)
16
22
  stub-zone:
17
23
  name: "iamtheinternet.org."
18
24
  stub-addr: 100.64.0.55
19
25
 
26
+ stub-zone:
27
+ name: "lunacycle.net."
28
+ stub-addr: 100.64.0.55
29
+
20
30
  stub-zone:
21
31
  name: "park-your-domain.com."
22
32
  stub-addr: 100.64.0.55
@@ -0,0 +1,14 @@
1
+ [Unit]
2
+ Description=Pre-load cached Docker images for e2e acceleration
3
+ After=docker.service
4
+ Requires=docker.service
5
+ ConditionPathIsDirectory=/docker-image-cache
6
+
7
+ [Service]
8
+ Type=oneshot
9
+ ExecStart=/usr/local/bin/docker-image-preload.sh
10
+ RemainAfterExit=yes
11
+ StandardOutput=journal
12
+
13
+ [Install]
14
+ WantedBy=multi-user.target
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+ # Load pre-cached Docker images for e2e acceleration.
3
+ # Runs at container startup via docker-image-preload.service.
4
+ # Gracefully skips if /docker-image-cache is empty or not mounted.
5
+
6
+ # Wait for the Docker daemon to accept connections (up to 30s)
7
+ for i in $(seq 1 30); do
8
+ if docker info > /dev/null 2>&1; then
9
+ break
10
+ fi
11
+ sleep 1
12
+ done
13
+
14
+ if ! docker info > /dev/null 2>&1; then
15
+ echo "docker-image-preload: Docker not ready after 30s, skipping"
16
+ exit 0
17
+ fi
18
+
19
+ shopt -s nullglob
20
+ for tar in /docker-image-cache/*.tar; do
21
+ echo "docker-image-preload: loading $(basename "$tar")"
22
+ docker load -i "$tar"
23
+ done
24
+ echo "docker-image-preload: done"
@@ -28,8 +28,30 @@ ip route add 10.0.10.0/24 via 192.168.0.254 # dmz via fw-main
28
28
  ip route add 10.0.20.0/24 via 192.168.0.254 # app via fw-main
29
29
  ip route add 10.0.30.0/24 via 192.168.0.254 # secure via fw-main
30
30
 
31
- # DNS — use the simulated resolver
32
- echo "nameserver 100.100.0.1" > /etc/resolv.conf
31
+ # DNS — Technitium internal resolver (split-horizon) with public fallback.
32
+ # Technitium at 192.168.0.10 is authoritative for iamtheinternet.org internally,
33
+ # resolving www/auth to Caddy's DMZ IP (10.0.10.10) rather than the public WAN IP.
34
+ # Fall back to the simulated public resolver if Technitium is not yet up.
35
+ printf 'nameserver 192.168.0.10\nnameserver 100.100.0.1\n' > /etc/resolv.conf
36
+
37
+ # Fetch Pebble's runtime ACME root CA (Pebble generates a fresh root at each
38
+ # startup, unlike the static /config/pebble/pebble-ca.crt baked in at build
39
+ # time which only signs Pebble's own API endpoint). Without this, management
40
+ # can't verify certs that Caddy obtained from Pebble (iamtheinternet.org,
41
+ # isitup.org, etc.). Mirrors the equivalent block in fw-ext-routes.sh.
42
+ echo "Waiting for Pebble ACME root CA..."
43
+ (
44
+ for i in $(seq 1 60); do
45
+ if curl -sk https://100.64.0.100:15000/roots/0 -o /usr/local/share/ca-certificates/pebble-acme-root.crt 2>/dev/null; then
46
+ if [ -s /usr/local/share/ca-certificates/pebble-acme-root.crt ]; then
47
+ update-ca-certificates 2>/dev/null
48
+ echo "Pebble ACME root CA installed"
49
+ break
50
+ fi
51
+ fi
52
+ sleep 2
53
+ done
54
+ ) &
33
55
 
34
56
  # Create celilo data directory (Linux default: /var/lib/celilo/)
35
57
  mkdir -p /var/lib/celilo/modules
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # CADDY_IP: IP of the caddy container in the DMZ (default 10.0.10.10).
5
+ # All test-domain queries resolve to this IP so management can reach
6
+ # caddy directly without hairpin NAT through the external interface.
7
+ CADDY_IP="${CADDY_IP:-10.0.10.10}"
8
+
9
+ # DOMAINS: comma-separated list of e2e test domains that should be
10
+ # split-horizon redirected to caddy. Default covers the canonical
11
+ # (iamtheinternet.org) plus the cross-domain test's second domain
12
+ # (lunacycle.net). Tests can override via DEFAULT_GATEWAY/DOMAINS env
13
+ # vars in docker-compose.
14
+ DOMAINS="${DOMAINS:-iamtheinternet.org,lunacycle.net}"
15
+
16
+ # Routing — reach comcast-resolver (100.100.0.1) via fw-main or fw-isp.
17
+ # DEFAULT_GATEWAY is set by docker-compose from the topology config.
18
+ GATEWAY="${DEFAULT_GATEWAY:-192.168.0.254}"
19
+ ip route del default 2>/dev/null || true
20
+ ip route add default via "$GATEWAY"
21
+
22
+ # Build per-domain split-horizon blocks.
23
+ # local-zone "redirect" returns the single A record for ALL names under
24
+ # the zone (including subdomains), so every hostname in the test domain
25
+ # reaches caddy which then reverse-proxies to the correct backend.
26
+ SPLIT_HORIZON_BLOCK=""
27
+ IFS=',' read -ra DOMAIN_LIST <<< "${DOMAINS}"
28
+ for d in "${DOMAIN_LIST[@]}"; do
29
+ SPLIT_HORIZON_BLOCK="${SPLIT_HORIZON_BLOCK} local-zone: \"${d}.\" redirect\n local-data: \"${d}. IN A ${CADDY_IP}\"\n"
30
+ done
31
+
32
+ # Generate unbound config with split-horizon for each test domain plus
33
+ # the standard simulator overrides (Pebble, registry).
34
+ cat > /etc/unbound/unbound.conf << EOF
35
+ server:
36
+ interface: 0.0.0.0
37
+ access-control: 0.0.0.0/0 allow
38
+ cache-min-ttl: 0
39
+ cache-max-ttl: 60
40
+ verbosity: 1
41
+ do-daemonize: no
42
+ do-not-query-localhost: no
43
+
44
+ $(printf "${SPLIT_HORIZON_BLOCK}")
45
+ # Mirror the public resolver's simulated endpoint overrides so that
46
+ # management uses Pebble for ACME and the e2e registry for modules.
47
+ local-data: "acme-v02.api.letsencrypt.org. IN A 100.64.0.100"
48
+ local-data: "acme-staging-v02.api.letsencrypt.org. IN A 100.64.0.100"
49
+ local-data: "e2e-registry.lab. IN A 100.64.0.56"
50
+
51
+ forward-zone:
52
+ name: "."
53
+ forward-addr: 100.100.0.1
54
+ EOF
55
+
56
+ /usr/sbin/unbound -d &
57
+
58
+ echo "internal resolver ready (split-horizon: ${DOMAINS} -> ${CADDY_IP})"
59
+ sleep infinity
@@ -0,0 +1,16 @@
1
+ FROM ubuntu:22.04
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ unbound \
5
+ iproute2 \
6
+ iputils-ping \
7
+ net-tools \
8
+ dnsutils \
9
+ procps \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ COPY config/routing/resolver-internal-routes.sh /startup.sh
13
+ RUN chmod +x /startup.sh
14
+
15
+ EXPOSE 53/tcp 53/udp
16
+ CMD ["/startup.sh"]
@@ -0,0 +1,37 @@
1
+ FROM oven/bun:latest
2
+
3
+ # Caddy (for ACME/TLS termination) + tools needed by entrypoint.sh.
4
+ # iproute2 provides `ip` so the entrypoint can rewrite the default route
5
+ # through fw-ext (same pattern as Dockerfile.pebble); without it DNS for
6
+ # simulated public hostnames fails.
7
+ RUN apt-get update && apt-get install -y \
8
+ ca-certificates \
9
+ curl \
10
+ iproute2 \
11
+ debian-keyring \
12
+ debian-archive-keyring \
13
+ apt-transport-https \
14
+ gnupg \
15
+ && curl -fsSL 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
16
+ | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg \
17
+ && curl -fsSL 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
18
+ > /etc/apt/sources.list.d/caddy-stable.list \
19
+ && apt-get update && apt-get install -y caddy \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ # Trust Pebble CA so Caddy's ACME client accepts the directory endpoint
23
+ COPY config/pebble/pebble-ca.crt /usr/local/share/ca-certificates/pebble-ca.crt
24
+ RUN update-ca-certificates
25
+
26
+ # Simulator app
27
+ COPY simulators/isitup/server.ts /simulator/server.ts
28
+
29
+ # Caddy config
30
+ COPY simulators/isitup/Caddyfile /etc/caddy/Caddyfile
31
+
32
+ # Entrypoint
33
+ COPY simulators/isitup/entrypoint.sh /entrypoint.sh
34
+ RUN chmod +x /entrypoint.sh
35
+
36
+ EXPOSE 80 443
37
+ CMD ["/entrypoint.sh"]
@@ -17,6 +17,8 @@ RUN chsh -s /bin/zsh root
17
17
 
18
18
  # Trust the E2E forward proxy CA (for transparent HTTPS interception)
19
19
  COPY config/proxy/squid-ca.crt /usr/local/share/ca-certificates/e2e-proxy-ca.crt
20
+ # Trust Pebble's test CA (so bun fetch() trusts certs issued by the simulated Let's Encrypt)
21
+ COPY config/pebble/pebble-ca.crt /usr/local/share/ca-certificates/pebble-ca.crt
20
22
  RUN update-ca-certificates
21
23
 
22
24
  # Point apt at the simulated apt cache proxy (for runtime apt usage)