Files
digiserver-v2/docs/tools/test_http_https_runtime.sh
ske087 46602f1933 Sanitize codebase, reorganize docs, and add missing deploy files
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.
2026-09-11 12:18:34 +03:00

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"