@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.
- package/README.md +2 -1
- package/bin/cele2e +14 -0
- package/bin/e2e-cache-images +57 -0
- package/bin/e2e-shell +36 -13
- package/bin/e2e-status +8 -2
- package/bin/e2e-up +5 -5
- package/completions/install-zsh-completion.sh +78 -0
- package/config/dns/celilo.computer.zone +16 -0
- package/config/dns/iamtheinternet.org.zone +7 -6
- 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/docker-image-preload.service +14 -0
- package/config/routing/docker-image-preload.sh +24 -0
- package/config/routing/management-routes.sh +24 -2
- 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.management +2 -0
- package/docker/Dockerfile.registry +34 -0
- package/docker/Dockerfile.target-machine +20 -0
- package/docker/Dockerfile.target-machine-docker +11 -19
- package/docker/Dockerfile.target-technitium +97 -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 +8 -1
- package/simulators/greenwave/state.ts +7 -12
- 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 +214 -0
- package/src/cli/command-registry.ts +100 -0
- package/src/cli/command-tree-parser.ts +52 -0
- package/src/cli/completion.ts +195 -0
- package/src/cli/index.ts +273 -0
- package/src/cli/module-discovery.ts +120 -0
- package/src/cli/scaffold.ts +108 -0
- package/src/container-manager.ts +373 -76
- package/src/docker-compose-generator.ts +127 -23
- package/src/network-builder.ts +21 -0
- package/src/parse-line.ts +147 -0
- package/src/runner.ts +197 -247
- package/src/simulator-ips.ts +42 -0
- package/src/types.ts +28 -0
- 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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 "$
|
|
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="
|
|
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
|
|
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="
|
|
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 "
|
|
152
|
-
echo "
|
|
153
|
-
echo "
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
package/config/dns/knot-tld.conf
CHANGED
|
@@ -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
|
package/config/dns/org.zone
CHANGED
|
@@ -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)
|
package/config/dns/root.zone
CHANGED
|
@@ -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 —
|
|
32
|
-
|
|
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)
|