46602f1933
Remove dead code identified in docs/SANITIZATION-REVIEW.md:
- app/blueprints/content_old.py and app/blueprints/playlist.py
- app/models/group.py, app/utils/nginx_config_reader.py
- orphaned templates (content_list, edit_content, upload_content,
player_page) and the related group/Template references
Result: 6 blueprints, 82 routes, no dead modules or orphan templates.
Add files that deploy.sh and docker-entrypoint.sh already require but
which were never tracked:
- https_manager.py (referenced by deploy.sh, migrate_network.sh,
docker-entrypoint.sh)
- Caddyfile.example (seeded by deploy.sh; its absence aborts deploy)
Relocate generated Graphify artifacts from graphify-out/ to
docs/graphify-out/ (110 files, no content change) and archive the
superseded docs under docs/.
Ignore hygiene:
- ignore ad-hoc .env backups (.env.bak*) — they contain live secrets
- keep the pre-sanitization snapshots (docs/legacy code/,
docs/old_code_documentation/) on disk but out of the repo
Fix .env.example: drop a duplicated config block, genericize the
hardcoded host IP, and document HOSTNAME_INTERNAL.
180 lines
5.6 KiB
Bash
180 lines
5.6 KiB
Bash
#!/bin/bash
|
|
# End-to-end runtime test of the HTTP/HTTPS + fallback behaviour.
|
|
#
|
|
# Spins up a stub backend + Caddy on a throwaway Docker network and proves:
|
|
# 1. HTTPS disabled -> http://<ip>:PORT answers on plain HTTP
|
|
# 2. HTTPS enabled -> https://<ip>:PORT answers over TLS (internal CA)
|
|
# 3. HTTPS enabled -> http://<ip>:PORT STILL answers (fallback)
|
|
# 4. Both the IP and a hostname resolve to the app
|
|
#
|
|
# Uses a stub backend so no 1.17 GB app image build is needed; the reverse-proxy
|
|
# behaviour under test is entirely Caddy's.
|
|
set -u
|
|
|
|
NET=e2e-caddy-net
|
|
BACKEND=e2e-backend
|
|
CADDY=e2e-caddy
|
|
HTTP_PORT=18080
|
|
HTTPS_PORT=18443
|
|
IP=127.0.0.1
|
|
|
|
cleanup() {
|
|
docker rm -f "$CADDY" "$BACKEND" >/dev/null 2>&1 || true
|
|
docker network rm "$NET" >/dev/null 2>&1 || true
|
|
rm -f /tmp/e2e_Caddyfile
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
cleanup
|
|
docker network create "$NET" >/dev/null
|
|
|
|
# Stub "digiserver-app" serving a recognisable body on :5000
|
|
docker run -d --name "$BACKEND" --network "$NET" --network-alias digiserver-app \
|
|
python:3.13-slim \
|
|
python -c "from http.server import BaseHTTPRequestHandler,HTTPServer
|
|
class H(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
self.send_response(200); self.send_header('Content-Type','text/plain'); self.end_headers()
|
|
self.wfile.write(b'BACKEND-OK')
|
|
def log_message(self,*a): pass
|
|
HTTPServer(('0.0.0.0',5000),H).serve_forever()" >/dev/null
|
|
|
|
echo "waiting for stub backend..."
|
|
for i in $(seq 1 20); do
|
|
docker exec "$BACKEND" python -c "
|
|
import urllib.request,sys
|
|
try:
|
|
urllib.request.urlopen('http://localhost:5000/',timeout=1); sys.exit(0)
|
|
except Exception: sys.exit(1)" 2>/dev/null && break
|
|
sleep 1
|
|
done
|
|
echo "stub backend ready"
|
|
echo
|
|
|
|
start_caddy() { # $1 = caddyfile content
|
|
printf '%s' "$1" > /tmp/e2e_Caddyfile
|
|
docker rm -f "$CADDY" >/dev/null 2>&1 || true
|
|
docker run -d --name "$CADDY" --network "$NET" \
|
|
-p "${HTTP_PORT}:80" -p "${HTTPS_PORT}:443" \
|
|
-v /tmp/e2e_Caddyfile:/etc/caddy/Caddyfile:ro \
|
|
caddy:2-alpine >/dev/null
|
|
# wait for the admin API to accept connections
|
|
for i in $(seq 1 25); do
|
|
docker exec "$CADDY" wget -q -O- http://localhost:2019/config/ >/dev/null 2>&1 && return 0
|
|
sleep 1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
result() { # $1 label, $2 expected substring, $3 actual body
|
|
if printf '%s' "$3" | grep -q "$2"; then
|
|
echo " PASS $1"
|
|
return 0
|
|
fi
|
|
echo " FAIL $1 (got: $(printf '%s' "$3" | head -c 80))"
|
|
return 1
|
|
}
|
|
|
|
FAILED=0
|
|
|
|
# ── Case 1: HTTPS disabled -> plain HTTP only ────────────────────────────────
|
|
echo "CASE 1: HTTPS disabled -> plain HTTP on port $HTTP_PORT"
|
|
start_caddy '{
|
|
admin 0.0.0.0:2019
|
|
}
|
|
|
|
:80 {
|
|
reverse_proxy digiserver-app:5000
|
|
}
|
|
' || echo " (caddy admin not ready; continuing)"
|
|
|
|
BODY=$(curl -sS -m 5 "http://${IP}:${HTTP_PORT}/" 2>&1)
|
|
result "http://IP answers" "BACKEND-OK" "$BODY" || FAILED=1
|
|
|
|
BODY=$(curl -sS -m 5 -H "Host: digiserver" "http://${IP}:${HTTP_PORT}/" 2>&1)
|
|
result "http with Host: digiserver answers (catch-all)" "BACKEND-OK" "$BODY" || FAILED=1
|
|
|
|
echo
|
|
|
|
# ── Case 2/3: HTTPS on, internal CA, with HTTP fallback ──────────────────────
|
|
echo "CASE 2+3: HTTPS on (internal CA) + HTTP fallback"
|
|
# Generated by CaddyConfigGenerator for ip=127.0.0.1, hostname=digiserver.
|
|
# `default_sni` is REQUIRED: browsers send no SNI when the URL is an IP, so
|
|
# without it Caddy matches no certificate and aborts with
|
|
# "no certificate available for '<container-ip>'".
|
|
start_caddy "{
|
|
admin 0.0.0.0:2019
|
|
email admin@example.com
|
|
default_sni ${IP}
|
|
}
|
|
|
|
:80 {
|
|
reverse_proxy digiserver-app:5000
|
|
}
|
|
|
|
http://${IP} {
|
|
reverse_proxy digiserver-app:5000
|
|
}
|
|
|
|
http://digiserver {
|
|
reverse_proxy digiserver-app:5000
|
|
}
|
|
|
|
https://${IP} {
|
|
tls internal
|
|
reverse_proxy digiserver-app:5000
|
|
}
|
|
|
|
https://digiserver {
|
|
tls internal
|
|
reverse_proxy digiserver-app:5000
|
|
}
|
|
" || echo " (caddy admin not ready; continuing)"
|
|
|
|
echo " (waiting for internal CA issuance)"
|
|
for i in $(seq 1 15); do
|
|
OUT=$(curl -sS -k -m 4 "https://${IP}:${HTTPS_PORT}/" 2>&1)
|
|
printf '%s' "$OUT" | grep -q "BACKEND-OK" && break
|
|
sleep 1
|
|
done
|
|
result "https://IP answers over TLS (SNI-less)" "BACKEND-OK" "$OUT" || FAILED=1
|
|
|
|
OUT2=$(curl -sS -k -m 6 --resolve "digiserver:${HTTPS_PORT}:${IP}" \
|
|
"https://digiserver:${HTTPS_PORT}/" 2>&1)
|
|
result "https://hostname answers over TLS (with SNI)" "BACKEND-OK" "$OUT2" || FAILED=1
|
|
|
|
BODY=$(curl -sS -m 5 "http://${IP}:${HTTP_PORT}/" 2>&1)
|
|
result "http://IP STILL answers (fallback)" "BACKEND-OK" "$BODY" || FAILED=1
|
|
|
|
BODY=$(curl -sS -m 5 -H "Host: digiserver" "http://${IP}:${HTTP_PORT}/" 2>&1)
|
|
result "http:// with Host: digiserver answers" "BACKEND-OK" "$BODY" || FAILED=1
|
|
|
|
HANDSHAKE_ERRORS=$(docker logs "$CADDY" 2>&1 | grep -ci "handshake error" || true)
|
|
if [ "$HANDSHAKE_ERRORS" -eq 0 ]; then
|
|
echo " PASS no TLS handshake errors logged"
|
|
else
|
|
echo " FAIL $HANDSHAKE_ERRORS TLS handshake error(s) logged"
|
|
FAILED=1
|
|
fi
|
|
|
|
# Certificate must come from Caddy's local CA
|
|
ISSUER=$(echo | openssl s_client -connect "${IP}:${HTTPS_PORT}" -servername localhost 2>/dev/null \
|
|
| openssl x509 -noout -issuer 2>/dev/null)
|
|
if printf '%s' "$ISSUER" | grep -qi "local\|caddy"; then
|
|
echo " PASS certificate issued by the internal CA"
|
|
echo " $ISSUER"
|
|
else
|
|
echo " WARN unexpected issuer: ${ISSUER:-none}"
|
|
fi
|
|
|
|
echo
|
|
docker logs "$CADDY" 2>&1 | grep -iE "error|cannot|fail" | head -5 || true
|
|
|
|
echo
|
|
if [ "$FAILED" -eq 0 ]; then
|
|
echo "ALL RUNTIME CHECKS PASSED"
|
|
else
|
|
echo "SOME RUNTIME CHECKS FAILED"
|
|
fi
|
|
exit "$FAILED"
|