@celilo/e2e 0.1.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 +360 -0
- package/bin/cache-docker-images +46 -0
- package/bin/e2e-build +66 -0
- package/bin/e2e-down +67 -0
- package/bin/e2e-infra +92 -0
- package/bin/e2e-load +40 -0
- package/bin/e2e-run +354 -0
- package/bin/e2e-shell +27 -0
- package/bin/e2e-status +29 -0
- package/bin/e2e-up +160 -0
- package/config/dhcp/dnsmasq.conf +27 -0
- package/config/dns/com.zone +11 -0
- package/config/dns/iamtheinternet.org.zone +15 -0
- package/config/dns/knot-namecheap.conf +11 -0
- package/config/dns/knot-root.conf +7 -0
- package/config/dns/knot-tld.conf +11 -0
- package/config/dns/org.zone +14 -0
- package/config/dns/park-your-domain.com.zone +10 -0
- package/config/dns/root.zone +14 -0
- package/config/pebble/pebble-ca.crt +20 -0
- package/config/pebble/pebble-config.json +11 -0
- package/config/pebble/pebble-tls.crt +21 -0
- package/config/pebble/pebble-tls.key +28 -0
- package/config/proxy/generate-ca.sh +20 -0
- package/config/proxy/squid-ca.crt +22 -0
- package/config/proxy/squid-ca.pem +50 -0
- package/config/proxy/squid.conf +38 -0
- package/config/proxy/startup.sh +11 -0
- package/config/resolver/root.hints +2 -0
- package/config/resolver/unbound.conf +28 -0
- package/config/routing/apt-cache-routes.sh +21 -0
- package/config/routing/dhcp-client-setup.sh +17 -0
- package/config/routing/dns-routes.sh +12 -0
- package/config/routing/fw-ext-routes.sh +50 -0
- package/config/routing/fw-isp-routes.sh +37 -0
- package/config/routing/fw-main-routes.sh +31 -0
- package/config/routing/management-routes.sh +96 -0
- package/config/routing/namecheap-startup.sh +17 -0
- package/config/routing/pebble-startup.sh +9 -0
- package/config/routing/resolver-routes.sh +15 -0
- package/config/routing/target-routes.sh +23 -0
- package/config/routing/target-setup.service +13 -0
- package/config/routing/target-setup.sh +36 -0
- package/config/ssh/generate-keys.sh +17 -0
- package/docker/Dockerfile.apt-cache +16 -0
- package/docker/Dockerfile.authoritative-dns +15 -0
- package/docker/Dockerfile.dhcp-client +16 -0
- package/docker/Dockerfile.firewall +28 -0
- package/docker/Dockerfile.forward-proxy +28 -0
- package/docker/Dockerfile.greenwave-sim +29 -0
- package/docker/Dockerfile.management +30 -0
- package/docker/Dockerfile.namecheap-dns +25 -0
- package/docker/Dockerfile.pebble +18 -0
- package/docker/Dockerfile.resolver +20 -0
- package/docker/Dockerfile.router +33 -0
- package/docker/Dockerfile.target-machine +58 -0
- package/docker/Dockerfile.target-machine-docker +105 -0
- package/package.json +45 -0
- package/simulators/greenwave/server.ts +164 -0
- package/simulators/greenwave/state.ts +206 -0
- package/simulators/greenwave/types.ts +31 -0
- package/simulators/namecheap-ddns/server.ts +97 -0
- package/simulators/namecheap-ddns/types.ts +12 -0
- package/simulators/namecheap-ddns/zone-updater.ts +49 -0
- package/src/container-manager.ts +324 -0
- package/src/docker-compose-generator.ts +499 -0
- package/src/fixtures.ts +40 -0
- package/src/index.ts +59 -0
- package/src/network-builder.ts +91 -0
- package/src/shared-infra.ts +163 -0
- package/src/types.ts +74 -0
package/bin/e2e-run
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# E2E Test Runner with Live Progress Reporting
|
|
4
|
+
#
|
|
5
|
+
# Runs each test file sequentially with real-time progress display.
|
|
6
|
+
# Shows sub-steps as they happen (building, deploying, configuring).
|
|
7
|
+
# Captures full output per test in e2e/results/<timestamp>/.
|
|
8
|
+
#
|
|
9
|
+
# Shared infrastructure (DNS, Pebble, apt-cache) is started once
|
|
10
|
+
# before the first test and torn down after the last test. Per-test
|
|
11
|
+
# containers connect to the shared networks.
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# ./e2e/bin/e2e-run # run all tests
|
|
15
|
+
# ./e2e/bin/e2e-run smoke caddy # run tests matching these patterns
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
# Package root (where docker/, config/ live)
|
|
22
|
+
PKG_DIR="$(dirname "$SCRIPT_DIR")"
|
|
23
|
+
# Test directory — can be overridden via E2E_TEST_DIR env var.
|
|
24
|
+
# Default: look for tests/ relative to CWD, or fall back to package dir.
|
|
25
|
+
E2E_TEST_DIR="${E2E_TEST_DIR:-$(pwd)}"
|
|
26
|
+
cd "$PKG_DIR"
|
|
27
|
+
|
|
28
|
+
SHARED_PROJECT="celilo-e2e-shared"
|
|
29
|
+
SHARED_COMPOSE="docker-compose.shared.yml"
|
|
30
|
+
|
|
31
|
+
# Handle Ctrl+C: kill running test, clean up, and exit immediately.
|
|
32
|
+
INTERRUPTED=false
|
|
33
|
+
cleanup_and_exit() {
|
|
34
|
+
INTERRUPTED=true
|
|
35
|
+
echo ""
|
|
36
|
+
echo -e "${RED}Interrupted. Cleaning up...${NC}"
|
|
37
|
+
# Kill any running test process
|
|
38
|
+
[ -n "$TEST_PID" ] && kill $TEST_PID 2>/dev/null
|
|
39
|
+
# Remove named pipe
|
|
40
|
+
[ -n "$PIPE" ] && rm -f "$PIPE"
|
|
41
|
+
# Tear down shared infra
|
|
42
|
+
echo -e "${DIM} Stopping shared infrastructure...${NC}"
|
|
43
|
+
docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" down --volumes --remove-orphans > /dev/null 2>&1 || true
|
|
44
|
+
# Clean up Docker
|
|
45
|
+
docker network prune -f > /dev/null 2>&1 || true
|
|
46
|
+
echo -e "${RED}Aborted.${NC}"
|
|
47
|
+
exit 130
|
|
48
|
+
}
|
|
49
|
+
trap cleanup_and_exit INT TERM
|
|
50
|
+
|
|
51
|
+
# Colors
|
|
52
|
+
GREEN='\033[0;32m'
|
|
53
|
+
RED='\033[0;31m'
|
|
54
|
+
DIM='\033[2m'
|
|
55
|
+
BOLD='\033[1m'
|
|
56
|
+
CYAN='\033[0;36m'
|
|
57
|
+
NC='\033[0m'
|
|
58
|
+
|
|
59
|
+
# Create results directory (in the test project, not the package)
|
|
60
|
+
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
|
61
|
+
RESULTS_DIR="$E2E_TEST_DIR/results/$TIMESTAMP"
|
|
62
|
+
mkdir -p "$RESULTS_DIR"
|
|
63
|
+
|
|
64
|
+
# Discover test files from E2E_TEST_DIR
|
|
65
|
+
TESTS_PATH="$E2E_TEST_DIR/tests"
|
|
66
|
+
if [ ! -d "$TESTS_PATH" ]; then
|
|
67
|
+
echo -e "${RED}No tests/ directory found at $TESTS_PATH${NC}"
|
|
68
|
+
echo "Set E2E_TEST_DIR to the directory containing your tests/ folder."
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ $# -gt 0 ]; then
|
|
73
|
+
TEST_FILES=()
|
|
74
|
+
for pattern in "$@"; do
|
|
75
|
+
for f in "$TESTS_PATH"/*${pattern}*.test.ts; do
|
|
76
|
+
[ -f "$f" ] && TEST_FILES+=("$f")
|
|
77
|
+
done
|
|
78
|
+
done
|
|
79
|
+
else
|
|
80
|
+
TEST_FILES=("$TESTS_PATH"/*.test.ts)
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
TOTAL=${#TEST_FILES[@]}
|
|
84
|
+
PASSED=0
|
|
85
|
+
FAILED=0
|
|
86
|
+
RESULTS=()
|
|
87
|
+
|
|
88
|
+
echo ""
|
|
89
|
+
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
|
|
90
|
+
echo -e "${BOLD}║ Celilo E2E Tests ║${NC}"
|
|
91
|
+
printf "${BOLD}║ %d test(s) queued — %s ║${NC}\n" "$TOTAL" "$(date +%H:%M:%S)"
|
|
92
|
+
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
|
|
93
|
+
echo ""
|
|
94
|
+
|
|
95
|
+
SUITE_START=$(date +%s)
|
|
96
|
+
|
|
97
|
+
# ─── Shared Infrastructure Startup ────────────────────────────────────
|
|
98
|
+
# The first test will call ensureSharedInfra() which starts the shared
|
|
99
|
+
# project. But we also pre-check here so the runner can show progress.
|
|
100
|
+
echo -e "${BOLD}Shared Infrastructure${NC}"
|
|
101
|
+
|
|
102
|
+
# Check if shared infra is already running
|
|
103
|
+
SHARED_RUNNING=false
|
|
104
|
+
SHARED_CONTAINERS=$(docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" ps -q 2>/dev/null || true)
|
|
105
|
+
if [ -n "$SHARED_CONTAINERS" ]; then
|
|
106
|
+
# Verify DNS is healthy
|
|
107
|
+
DNS_CHECK=$(docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" exec -T comcast-resolver dig @127.0.0.1 iamtheinternet.org NS +short +timeout=2 2>/dev/null || true)
|
|
108
|
+
if [ -n "$DNS_CHECK" ]; then
|
|
109
|
+
SHARED_RUNNING=true
|
|
110
|
+
echo -e " ${GREEN}✔ already running (DNS healthy)${NC}"
|
|
111
|
+
else
|
|
112
|
+
echo -e " ${DIM}DNS unhealthy, restarting...${NC}"
|
|
113
|
+
docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" down --volumes --remove-orphans > /dev/null 2>&1 || true
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
if [ "$SHARED_RUNNING" = false ]; then
|
|
118
|
+
echo -e " ${DIM}Starting DNS hierarchy, Pebble ACME, apt-cache...${NC}"
|
|
119
|
+
# The actual startup happens inside the first test's ensureSharedInfra()
|
|
120
|
+
# call. We just report it here for visibility.
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
SHARED_END=$(date +%s)
|
|
124
|
+
echo ""
|
|
125
|
+
|
|
126
|
+
# Track step count per test for percentage
|
|
127
|
+
STEP_COUNT=0
|
|
128
|
+
ESTIMATED_STEPS=15
|
|
129
|
+
|
|
130
|
+
# Parse a line of test output and display as a progress step.
|
|
131
|
+
show_progress() {
|
|
132
|
+
local line="$1"
|
|
133
|
+
local test_start="$2"
|
|
134
|
+
local step=""
|
|
135
|
+
|
|
136
|
+
# Shared infra progress (from ensureSharedInfra)
|
|
137
|
+
if [[ "$line" == *"[e2e] Shared infrastructure ready"* ]]; then
|
|
138
|
+
step="✔ shared infrastructure ready"
|
|
139
|
+
elif [[ "$line" == *"[e2e] Building shared"* ]]; then
|
|
140
|
+
step="building shared infrastructure images"
|
|
141
|
+
elif [[ "$line" == *"[e2e] Starting shared"* ]]; then
|
|
142
|
+
step="starting shared infrastructure"
|
|
143
|
+
elif [[ "$line" == *"[e2e] Cleaning up stale shared"* ]]; then
|
|
144
|
+
step="cleaning up stale shared infra"
|
|
145
|
+
|
|
146
|
+
# Per-test container manager progress
|
|
147
|
+
elif [[ "$line" == *"[e2e] Building images"* ]]; then
|
|
148
|
+
step="building test images"
|
|
149
|
+
elif [[ "$line" == *"[e2e] Starting containers"* ]]; then
|
|
150
|
+
step="starting test containers"
|
|
151
|
+
elif [[ "$line" == *"[e2e] Cleaning up"* ]]; then
|
|
152
|
+
step="cleaning up stale test resources"
|
|
153
|
+
elif [[ "$line" == *"[e2e] Waiting for DNS"* ]]; then
|
|
154
|
+
step="waiting for DNS convergence"
|
|
155
|
+
elif [[ "$line" == *"[e2e] Verifying routing"* ]]; then
|
|
156
|
+
step="verifying routing"
|
|
157
|
+
elif [[ "$line" == *"[e2e] Waiting for target"* ]]; then
|
|
158
|
+
step="waiting for target machines"
|
|
159
|
+
elif [[ "$line" == *"[e2e] Waiting for DHCP"* ]]; then
|
|
160
|
+
step="waiting for DHCP"
|
|
161
|
+
elif [[ "$line" == *"[e2e] Initializing celilo"* ]]; then
|
|
162
|
+
step="initializing celilo"
|
|
163
|
+
elif [[ "$line" == *"[e2e] Network ready"* ]]; then
|
|
164
|
+
step="network ready"
|
|
165
|
+
elif [[ "$line" == *"[e2e] Stopping network"* ]]; then
|
|
166
|
+
step="stopping network"
|
|
167
|
+
|
|
168
|
+
# Test-level progress (console.log from tests)
|
|
169
|
+
elif [[ "$line" == *"Adding machines"* ]]; then
|
|
170
|
+
step="adding machines"
|
|
171
|
+
elif [[ "$line" == *"Importing modules"* ]]; then
|
|
172
|
+
step="importing modules"
|
|
173
|
+
elif [[ "$line" == *"Configuring modules"* ]]; then
|
|
174
|
+
step="configuring modules"
|
|
175
|
+
elif [[ "$line" == *"Prerequisites ready"* ]]; then
|
|
176
|
+
step="prerequisites ready"
|
|
177
|
+
|
|
178
|
+
# Celilo CLI: "Deploying module: namecheap"
|
|
179
|
+
elif [[ "$line" == *"Deploying module:"* ]]; then
|
|
180
|
+
local module=$(echo "$line" | sed -n 's/.*Deploying module: \([^ ]*\).*/\1/p')
|
|
181
|
+
if [ -n "$module" ]; then
|
|
182
|
+
step="deploying $module"
|
|
183
|
+
else
|
|
184
|
+
step="deploying module"
|
|
185
|
+
fi
|
|
186
|
+
# Celilo CLI: "Deploying software..."
|
|
187
|
+
elif [[ "$line" == *"Deploying software"* ]]; then
|
|
188
|
+
step="running Ansible"
|
|
189
|
+
|
|
190
|
+
# Test console.log: "[45s] Deploying namecheap..."
|
|
191
|
+
elif [[ "$line" =~ Deploying\ ([a-z_-]+) ]]; then
|
|
192
|
+
step="deploying ${BASH_REMATCH[1]}"
|
|
193
|
+
|
|
194
|
+
# Celilo CLI: "✓ Module 'namecheap' deployed successfully"
|
|
195
|
+
elif [[ "$line" == *"deployed successfully"* ]]; then
|
|
196
|
+
local module=$(echo "$line" | sed -n "s/.*Module '\([^']*\)' deployed.*/\1/p")
|
|
197
|
+
step="✔ ${module:-module} deployed"
|
|
198
|
+
|
|
199
|
+
# Celilo CLI: "Successfully imported module: namecheap"
|
|
200
|
+
elif [[ "$line" == *"Successfully imported module:"* ]]; then
|
|
201
|
+
local module=$(echo "$line" | sed -n 's/.*imported module: \([^ ]*\).*/\1/p')
|
|
202
|
+
step="✔ ${module:-module} imported"
|
|
203
|
+
|
|
204
|
+
# Celilo CLI: "Infrastructure selected: machine"
|
|
205
|
+
elif [[ "$line" == *"Infrastructure selected"* ]]; then
|
|
206
|
+
step="infrastructure selected"
|
|
207
|
+
# Celilo CLI: "Terraform"
|
|
208
|
+
elif [[ "$line" == *"Terraform skipped"* ]]; then
|
|
209
|
+
step="terraform skipped (existing machine)"
|
|
210
|
+
elif [[ "$line" == *"terraform init"* ]] || [[ "$line" == *"Terraform init"* ]]; then
|
|
211
|
+
step="terraform init"
|
|
212
|
+
elif [[ "$line" == *"terraform apply"* ]] || [[ "$line" == *"Terraform apply"* ]]; then
|
|
213
|
+
step="terraform apply"
|
|
214
|
+
|
|
215
|
+
# Ansible health retries
|
|
216
|
+
elif [[ "$line" == *"RETRYING"* ]] && [[ "$line" == *"healthy"* ]]; then
|
|
217
|
+
local retries=$(echo "$line" | sed -n 's/.*(\([0-9]*\) retries left).*/\1/p')
|
|
218
|
+
step="waiting for health check ($retries retries left)"
|
|
219
|
+
|
|
220
|
+
# Skip noise
|
|
221
|
+
elif [[ "$line" == *"Set config for"* ]]; then return
|
|
222
|
+
elif [[ "$line" == *"Set secret for"* ]]; then return
|
|
223
|
+
elif [[ "$line" == *"Auto-generated"* ]]; then return
|
|
224
|
+
elif [[ "$line" == *"Warning: Permanently added"* ]]; then return
|
|
225
|
+
|
|
226
|
+
# Error detection
|
|
227
|
+
elif [[ "$line" == *"Error:"* ]] && [[ "$line" != *"vitest"* ]] && [[ "$line" != *"bun test"* ]] && [[ "$line" != *"ipv4: Address already"* ]]; then
|
|
228
|
+
local err=$(echo "$line" | sed 's/.*Error: //' | cut -c1-70)
|
|
229
|
+
step="✗ $err"
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
if [ -n "$step" ]; then
|
|
233
|
+
STEP_COUNT=$((STEP_COUNT + 1))
|
|
234
|
+
# Update estimate upward if we're exceeding it
|
|
235
|
+
if [ $STEP_COUNT -gt $ESTIMATED_STEPS ]; then
|
|
236
|
+
ESTIMATED_STEPS=$((STEP_COUNT + 5))
|
|
237
|
+
fi
|
|
238
|
+
local pct=$(( (STEP_COUNT * 100) / ESTIMATED_STEPS ))
|
|
239
|
+
[ $pct -gt 99 ] && pct=99 # Don't show 100% until actually done
|
|
240
|
+
local now=$(date +%s)
|
|
241
|
+
local elapsed=$((now - test_start))
|
|
242
|
+
printf " ${DIM}%3ds${NC} ${CYAN}%2d%%${NC} %s\n" "$elapsed" "$pct" "$step"
|
|
243
|
+
fi
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for ((i=0; i<TOTAL; i++)); do
|
|
247
|
+
FILE="${TEST_FILES[$i]}"
|
|
248
|
+
NAME=$(basename "$FILE" .test.ts)
|
|
249
|
+
TEST_DIR="$RESULTS_DIR/$NAME"
|
|
250
|
+
mkdir -p "$TEST_DIR"
|
|
251
|
+
|
|
252
|
+
# Percentage complete (based on test count, not time)
|
|
253
|
+
PCT=$(( (i * 100) / TOTAL ))
|
|
254
|
+
ELAPSED_TOTAL=$(($(date +%s) - SUITE_START))
|
|
255
|
+
if [ $i -gt 0 ]; then
|
|
256
|
+
AVG_PER_TEST=$((ELAPSED_TOTAL / i))
|
|
257
|
+
REMAINING=$(( (TOTAL - i) * AVG_PER_TEST ))
|
|
258
|
+
ETA_MIN=$((REMAINING / 60))
|
|
259
|
+
ETA_SEC=$((REMAINING % 60))
|
|
260
|
+
ETA_STR=" — ~${ETA_MIN}m ${ETA_SEC}s remaining"
|
|
261
|
+
else
|
|
262
|
+
ETA_STR=""
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
echo -e "${BOLD}[${i}/${TOTAL}] ${CYAN}${PCT}%${NC}${BOLD} ${NAME}${NC}${DIM}${ETA_STR}${NC}"
|
|
266
|
+
|
|
267
|
+
STEP_COUNT=0
|
|
268
|
+
TEST_START=$(date +%s)
|
|
269
|
+
PIPE=$(mktemp -u)
|
|
270
|
+
mkfifo "$PIPE"
|
|
271
|
+
|
|
272
|
+
# Background: run bun test, merge stdout+stderr, tee to log + pipe
|
|
273
|
+
(bun test --timeout 300000 "$FILE" 2>&1 | tee "$TEST_DIR/output.log" > "$PIPE") &
|
|
274
|
+
TEST_PID=$!
|
|
275
|
+
|
|
276
|
+
# Foreground: read pipe line-by-line and show progress
|
|
277
|
+
while IFS= read -r line; do
|
|
278
|
+
show_progress "$line" "$TEST_START"
|
|
279
|
+
done < "$PIPE"
|
|
280
|
+
|
|
281
|
+
# Wait for test to finish and get exit code
|
|
282
|
+
wait $TEST_PID 2>/dev/null
|
|
283
|
+
EXIT_CODE=$?
|
|
284
|
+
rm -f "$PIPE"
|
|
285
|
+
|
|
286
|
+
TEST_END=$(date +%s)
|
|
287
|
+
DURATION=$((TEST_END - TEST_START))
|
|
288
|
+
|
|
289
|
+
if [ $EXIT_CODE -eq 0 ]; then
|
|
290
|
+
echo -e " ${GREEN}✔ passed (${DURATION}s)${NC}"
|
|
291
|
+
PASSED=$((PASSED + 1))
|
|
292
|
+
RESULTS+=("✓|$NAME|${DURATION}s")
|
|
293
|
+
echo '{"status":"pass","duration":'$DURATION'}' > "$TEST_DIR/result.json"
|
|
294
|
+
else
|
|
295
|
+
echo -e " ${RED}✗ failed (${DURATION}s)${NC}"
|
|
296
|
+
FAILED=$((FAILED + 1))
|
|
297
|
+
ERROR=$(grep -E "Error:|expected.*to be|Timeout" "$TEST_DIR/output.log" 2>/dev/null | grep -v "vitest\|bun test" | head -1 | sed 's/.*Error: //' | cut -c1-80)
|
|
298
|
+
RESULTS+=("✗|$NAME|${DURATION}s|$ERROR")
|
|
299
|
+
echo '{"status":"fail","duration":'$DURATION',"error":"'"${ERROR//\"/\\\"}"'"}' > "$TEST_DIR/result.json"
|
|
300
|
+
echo -e " ${RED} └─ ${ERROR}${NC}"
|
|
301
|
+
fi
|
|
302
|
+
echo ""
|
|
303
|
+
|
|
304
|
+
# Clean up per-test Docker resources between tests (not shared infra)
|
|
305
|
+
docker network prune -f > /dev/null 2>&1 || true
|
|
306
|
+
done
|
|
307
|
+
|
|
308
|
+
# ─── Shared Infrastructure Teardown ───────────────────────────────────
|
|
309
|
+
echo -e "${DIM}Stopping shared infrastructure...${NC}"
|
|
310
|
+
docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" down --volumes --remove-orphans > /dev/null 2>&1 || true
|
|
311
|
+
docker network prune -f > /dev/null 2>&1 || true
|
|
312
|
+
|
|
313
|
+
SUITE_END=$(date +%s)
|
|
314
|
+
SUITE_DURATION=$((SUITE_END - SUITE_START))
|
|
315
|
+
SUITE_MINUTES=$((SUITE_DURATION / 60))
|
|
316
|
+
SUITE_SECONDS=$((SUITE_DURATION % 60))
|
|
317
|
+
|
|
318
|
+
echo ""
|
|
319
|
+
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
|
|
320
|
+
printf "${BOLD}║ Results: ${GREEN}%d passed${NC}${BOLD}, ${RED}%d failed${NC}${BOLD} — %dm %ds total ║${NC}\n" "$PASSED" "$FAILED" "$SUITE_MINUTES" "$SUITE_SECONDS"
|
|
321
|
+
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
322
|
+
|
|
323
|
+
for result in "${RESULTS[@]}"; do
|
|
324
|
+
IFS='|' read -r icon name duration error <<< "$result"
|
|
325
|
+
if [ "$icon" = "✓" ]; then
|
|
326
|
+
printf "${BOLD}║${NC} ${GREEN}%s${NC} %-28s ${DIM}%6s${NC}\n" "$icon" "$name" "$duration"
|
|
327
|
+
else
|
|
328
|
+
printf "${BOLD}║${NC} ${RED}%s${NC} %-28s ${DIM}%6s${NC}\n" "$icon" "$name" "$duration"
|
|
329
|
+
if [ -n "$error" ]; then
|
|
330
|
+
printf "${BOLD}║${NC} ${RED}└─ %s${NC}\n" "$error"
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
done
|
|
334
|
+
|
|
335
|
+
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
|
|
336
|
+
echo ""
|
|
337
|
+
echo -e "Full logs: ${DIM}$RESULTS_DIR${NC}"
|
|
338
|
+
|
|
339
|
+
# Write summary
|
|
340
|
+
cat > "$RESULTS_DIR/summary.json" <<EOF
|
|
341
|
+
{
|
|
342
|
+
"timestamp": "$TIMESTAMP",
|
|
343
|
+
"total": $TOTAL,
|
|
344
|
+
"passed": $PASSED,
|
|
345
|
+
"failed": $FAILED,
|
|
346
|
+
"duration": $SUITE_DURATION
|
|
347
|
+
}
|
|
348
|
+
EOF
|
|
349
|
+
|
|
350
|
+
# Add results dir to gitignore if not already there
|
|
351
|
+
grep -q "^results/" "$E2E_TEST_DIR/.gitignore" 2>/dev/null || echo "results/" >> "$E2E_TEST_DIR/.gitignore"
|
|
352
|
+
|
|
353
|
+
# Exit with failure if any tests failed
|
|
354
|
+
[ $FAILED -eq 0 ]
|
package/bin/e2e-shell
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Connect to a running E2E container.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./bin/e2e-shell # Shell into management machine
|
|
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
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
PROJECT_NAME="conductor-e2e-interactive"
|
|
12
|
+
CONTAINER="${1:-management}"
|
|
13
|
+
|
|
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"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
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
|
|
25
|
+
else
|
|
26
|
+
docker compose -p "$PROJECT_NAME" exec "$CONTAINER" bash
|
|
27
|
+
fi
|
package/bin/e2e-status
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Show status of the E2E test network.
|
|
3
|
+
|
|
4
|
+
PROJECT_NAME="conductor-e2e-interactive"
|
|
5
|
+
|
|
6
|
+
echo "=== Containers ==="
|
|
7
|
+
docker compose -p "$PROJECT_NAME" ps 2>/dev/null || { echo "No E2E network running."; exit 0; }
|
|
8
|
+
|
|
9
|
+
echo ""
|
|
10
|
+
echo "=== DNS Check ==="
|
|
11
|
+
echo -n " iamtheinternet.org NS: "
|
|
12
|
+
docker compose -p "$PROJECT_NAME" exec -T comcast-resolver dig @127.0.0.1 iamtheinternet.org NS +short +timeout=2 2>/dev/null || echo "(failed)"
|
|
13
|
+
|
|
14
|
+
echo -n " www.iamtheinternet.org A: "
|
|
15
|
+
docker compose -p "$PROJECT_NAME" exec -T comcast-resolver dig @127.0.0.1 www.iamtheinternet.org A +short +timeout=2 2>/dev/null || echo "(not set)"
|
|
16
|
+
|
|
17
|
+
echo ""
|
|
18
|
+
echo "=== Connectivity (from management) ==="
|
|
19
|
+
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
|
|
20
|
+
name="${target%%:*}"
|
|
21
|
+
ip="${target##*:}"
|
|
22
|
+
echo -n " $name ($ip): "
|
|
23
|
+
docker compose -p "$PROJECT_NAME" exec -T management ping -c1 -W2 "$ip" >/dev/null 2>&1 && echo "OK" || echo "FAIL"
|
|
24
|
+
done
|
|
25
|
+
|
|
26
|
+
echo ""
|
|
27
|
+
echo "=== DDNS Simulator ==="
|
|
28
|
+
echo -n " Status: "
|
|
29
|
+
docker compose -p "$PROJECT_NAME" exec -T namecheap-dns curl -s http://localhost:8080/status 2>/dev/null || echo "(not running)"
|
package/bin/e2e-up
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Start the E2E test network interactively.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./bin/e2e-up # Infrastructure only
|
|
6
|
+
# ./bin/e2e-up --caddy # + caddy machine in dmz
|
|
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"}}'
|
|
9
|
+
#
|
|
10
|
+
# After startup, drops you into a shell on the management machine.
|
|
11
|
+
# Use `exit` to leave the shell (network stays running).
|
|
12
|
+
# Use `./bin/e2e-down` to tear everything down.
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
16
|
+
cd "$SCRIPT_DIR"
|
|
17
|
+
|
|
18
|
+
PROJECT_NAME="conductor-e2e-interactive"
|
|
19
|
+
|
|
20
|
+
# Parse args
|
|
21
|
+
PRESET="infrastructure"
|
|
22
|
+
CUSTOM_JSON=""
|
|
23
|
+
while [[ $# -gt 0 ]]; do
|
|
24
|
+
case "$1" in
|
|
25
|
+
--caddy) PRESET="caddy"; shift ;;
|
|
26
|
+
--full-stack) PRESET="full-stack"; shift ;;
|
|
27
|
+
--custom) shift; CUSTOM_JSON="$1"; shift ;;
|
|
28
|
+
--help|-h)
|
|
29
|
+
echo "Usage: e2e-up [--caddy|--full-stack|--custom '<json>']"
|
|
30
|
+
echo ""
|
|
31
|
+
echo "Presets:"
|
|
32
|
+
echo " (default) Infrastructure only (management, firewalls, DNS, Pebble)"
|
|
33
|
+
echo " --caddy + caddy machine at 10.0.10.10 in dmz"
|
|
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\"}}'"
|
|
36
|
+
echo ""
|
|
37
|
+
echo "After startup, you'll be dropped into the management machine shell."
|
|
38
|
+
echo "Use ./bin/e2e-down to tear down, or ./bin/e2e-shell to reconnect."
|
|
39
|
+
exit 0
|
|
40
|
+
;;
|
|
41
|
+
*) shift ;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
# Generate docker-compose.yml
|
|
46
|
+
export PRESET CUSTOM_JSON
|
|
47
|
+
echo "Generating docker-compose.yml (preset: $PRESET)..."
|
|
48
|
+
bun -e "
|
|
49
|
+
import { generateComposeYaml } from './src/docker-compose-generator';
|
|
50
|
+
|
|
51
|
+
const presets = {
|
|
52
|
+
'infrastructure': {
|
|
53
|
+
topology: 'default',
|
|
54
|
+
dmzMachines: [],
|
|
55
|
+
appMachines: [],
|
|
56
|
+
secureMachines: [],
|
|
57
|
+
domain: 'iamtheinternet.org',
|
|
58
|
+
ddnsPassword: 'test123',
|
|
59
|
+
verifyRouting: false,
|
|
60
|
+
},
|
|
61
|
+
'caddy': {
|
|
62
|
+
topology: 'default',
|
|
63
|
+
dmzMachines: [{ name: 'caddy', ip: '10.0.10.10', zone: 'dmz' }],
|
|
64
|
+
appMachines: [],
|
|
65
|
+
secureMachines: [],
|
|
66
|
+
domain: 'iamtheinternet.org',
|
|
67
|
+
ddnsPassword: 'test123',
|
|
68
|
+
verifyRouting: false,
|
|
69
|
+
},
|
|
70
|
+
'full-stack': {
|
|
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' }],
|
|
75
|
+
domain: 'iamtheinternet.org',
|
|
76
|
+
ddnsPassword: 'test123',
|
|
77
|
+
verifyRouting: false,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const customJson = process.env.CUSTOM_JSON;
|
|
82
|
+
let config;
|
|
83
|
+
if (customJson) {
|
|
84
|
+
const spec = JSON.parse(customJson);
|
|
85
|
+
config = { ...presets['infrastructure'] };
|
|
86
|
+
for (const [zone, machines] of Object.entries(spec)) {
|
|
87
|
+
const key = zone + 'Machines';
|
|
88
|
+
if (key in config) {
|
|
89
|
+
config[key] = Object.entries(machines).map(([name, ip]) => ({ name, ip, zone }));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
config = presets[process.env.PRESET || 'infrastructure'];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(generateComposeYaml(config));
|
|
97
|
+
" > docker-compose.yml
|
|
98
|
+
|
|
99
|
+
# Build and start
|
|
100
|
+
echo "Building images (this may take a while on first run)..."
|
|
101
|
+
docker compose -p "$PROJECT_NAME" build
|
|
102
|
+
|
|
103
|
+
echo "Starting containers..."
|
|
104
|
+
docker compose -p "$PROJECT_NAME" up -d
|
|
105
|
+
|
|
106
|
+
echo ""
|
|
107
|
+
echo "Waiting for management machine to initialize..."
|
|
108
|
+
for i in $(seq 1 15); do
|
|
109
|
+
if docker compose -p "$PROJECT_NAME" exec -T management test -f /root/.zshrc 2>/dev/null; then
|
|
110
|
+
break
|
|
111
|
+
fi
|
|
112
|
+
sleep 1
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
echo "Waiting for DNS convergence..."
|
|
116
|
+
for i in $(seq 1 30); do
|
|
117
|
+
if docker compose -p "$PROJECT_NAME" exec -T comcast-resolver dig @127.0.0.1 iamtheinternet.org NS +short +timeout=2 2>/dev/null | grep -q '.'; then
|
|
118
|
+
echo "DNS is ready!"
|
|
119
|
+
break
|
|
120
|
+
fi
|
|
121
|
+
if [ "$i" = "30" ]; then
|
|
122
|
+
echo "WARNING: DNS did not converge after 60s. Continuing anyway."
|
|
123
|
+
fi
|
|
124
|
+
sleep 2
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
echo ""
|
|
128
|
+
echo "================================================"
|
|
129
|
+
echo " E2E Network is running!"
|
|
130
|
+
echo " Project: $PROJECT_NAME"
|
|
131
|
+
echo "================================================"
|
|
132
|
+
echo ""
|
|
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"
|
|
138
|
+
echo " isp-external: 100.100.0.0/24"
|
|
139
|
+
echo " internet-external: 100.64.0.0/24"
|
|
140
|
+
echo ""
|
|
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)"
|
|
145
|
+
echo " comcast-resolver: 100.100.0.1 (DNS resolver)"
|
|
146
|
+
echo " namecheap-dns: 100.64.0.55 (DDNS simulator)"
|
|
147
|
+
echo " letsencrypt: 100.64.0.100 (Pebble ACME)"
|
|
148
|
+
echo " apt-cache: 100.64.0.2 (apt proxy → real internet)"
|
|
149
|
+
echo ""
|
|
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"
|
|
154
|
+
echo " docker compose -p $PROJECT_NAME ps List containers"
|
|
155
|
+
echo ""
|
|
156
|
+
echo "Dropping you into the management machine..."
|
|
157
|
+
echo "(use 'conductor <cmd>' or 'c <cmd>' — tab completion works)"
|
|
158
|
+
echo ""
|
|
159
|
+
|
|
160
|
+
docker compose -p "$PROJECT_NAME" exec management zsh -li
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# dnsmasq DHCP server for e2e internal network
|
|
2
|
+
# Runs on the greenwave simulator (fw-isp) container
|
|
3
|
+
|
|
4
|
+
# Only DHCP, no DNS (technitium handles DNS)
|
|
5
|
+
port=0
|
|
6
|
+
|
|
7
|
+
# Listen on internal interface only
|
|
8
|
+
interface=eth0
|
|
9
|
+
bind-interfaces
|
|
10
|
+
|
|
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
|
|
13
|
+
|
|
14
|
+
# Default gateway (the greenwave router itself)
|
|
15
|
+
dhcp-option=option:router,192.168.0.1
|
|
16
|
+
|
|
17
|
+
# DNS server (initially the router, updated by greenwave API)
|
|
18
|
+
dhcp-option=option:dns-server,192.168.0.1
|
|
19
|
+
|
|
20
|
+
# Domain name / search domain
|
|
21
|
+
# dhcp-option=option:domain-name,iamtheinternet.org
|
|
22
|
+
|
|
23
|
+
# Short lease time for testing
|
|
24
|
+
dhcp-lease-max=20
|
|
25
|
+
|
|
26
|
+
# Log DHCP transactions
|
|
27
|
+
log-dhcp
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
$ORIGIN iamtheinternet.org.
|
|
2
|
+
$TTL 300
|
|
3
|
+
|
|
4
|
+
@ IN SOA ns1.iamtheinternet.org. admin.iamtheinternet.org. (
|
|
5
|
+
2024010686 300 60 604800 300
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
@ IN NS ns1.iamtheinternet.org.
|
|
9
|
+
ns1 IN A 100.64.0.55
|
|
10
|
+
@ 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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
$ORIGIN org.
|
|
2
|
+
$TTL 86400
|
|
3
|
+
|
|
4
|
+
@ IN SOA tld-dns.lab. admin.lab. (
|
|
5
|
+
2024010101 1800 900 604800 86400
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
org. IN NS tld-dns.lab.
|
|
9
|
+
|
|
10
|
+
iamtheinternet IN NS ns1.iamtheinternet.org.
|
|
11
|
+
ns1.iamtheinternet.org. IN A 100.64.0.55
|
|
12
|
+
|
|
13
|
+
; Note: Let's Encrypt ACME endpoints are handled by unbound local-data
|
|
14
|
+
; (resolves acme-v02.api.letsencrypt.org → 100.64.0.100 directly)
|