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.
This commit is contained in:
2026-09-11 12:18:34 +03:00
parent 1c5186463a
commit 46602f1933
226 changed files with 3999 additions and 15737 deletions
+142 -56
View File
@@ -25,19 +25,23 @@ FAILED=0
WARNINGS=0
# Helper functions
# NOTE: `((VAR++))` evaluates to the PRE-increment value, so the very first
# increment returns 0 — a non-zero exit status that `set -e` treats as a fatal
# error, aborting the whole script after the first check. Use `VAR=$((VAR+1))`
# (always status 0) instead.
pass() {
echo -e "${GREEN}${NC} $1"
((PASSED++))
PASSED=$((PASSED + 1))
}
fail() {
echo -e "${RED}${NC} $1"
((FAILED++))
FAILED=$((FAILED + 1))
}
warn() {
echo -e "${YELLOW}${NC} $1"
((WARNINGS++))
WARNINGS=$((WARNINGS + 1))
}
info() {
@@ -99,19 +103,38 @@ else
fail "Docker not installed"
fi
if command -v docker-compose &> /dev/null; then
pass "Docker Compose installed"
# Accept either the modern compose plugin or the standalone v1 binary.
if docker compose version &> /dev/null; then
COMPOSE="docker compose"
pass "Docker Compose plugin installed"
DC_VERSION=$(docker compose version --short 2>/dev/null || docker compose version | head -1)
info "Compose version: $DC_VERSION"
elif command -v docker-compose &> /dev/null; then
COMPOSE="docker-compose"
warn "Using legacy 'docker-compose' (v1); the 'docker compose' plugin is unavailable"
DC_VERSION=$(docker-compose --version | cut -d' ' -f3 | tr -d ',')
info "Docker Compose version: $DC_VERSION"
# Compose v1 builds require buildx >= 0.17; older buildx must use `docker build`.
BUILDX_VER=$(docker buildx version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -1 || true)
if [ -n "$BUILDX_VER" ]; then
_maj="${BUILDX_VER%%.*}"; _min="${BUILDX_VER##*.}"
if [ "$_maj" -eq 0 ] && [ "$_min" -lt 17 ]; then
info "buildx $BUILDX_VER too old for compose v1 builds — deploy.sh falls back to 'docker build'"
fi
else
info "buildx not available — deploy.sh falls back to 'docker build'"
fi
else
fail "Docker Compose not installed"
COMPOSE=""
fi
if [ -f docker-compose.yml ]; then
pass "docker-compose.yml exists"
# Validate syntax
if docker-compose config > /dev/null 2>&1; then
if [ -n "$COMPOSE" ] && $COMPOSE config > /dev/null 2>&1; then
pass "docker-compose.yml syntax valid"
else
fail "docker-compose.yml syntax error"
@@ -169,7 +192,7 @@ if [ -f requirements.txt ]; then
done
# Check for specific versions
FLASK_VERSION=$(grep "^Flask==" requirements.txt | cut -d'=' -f3)
FLASK_VERSION=$(grep "^Flask==" requirements.txt 2>/dev/null | cut -d'=' -f3 || true)
SQLALCHEMY_VERSION=$(grep "^SQLAlchemy==" requirements.txt | cut -d'=' -f3)
if [ -n "$FLASK_VERSION" ]; then
@@ -202,35 +225,41 @@ else
fi
# ============================================================================
section "7. SSL/TLS Certificate"
section "7. TLS Certificate (Caddy)"
# ============================================================================
if [ -f data/nginx-ssl/cert.pem ]; then
pass "SSL certificate found"
CERT_EXPIRY=$(openssl x509 -enddate -noout -in data/nginx-ssl/cert.pem 2>/dev/null | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$CERT_EXPIRY" +%s 2>/dev/null || echo 0)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
info "Certificate expires: $CERT_EXPIRY"
info "Days remaining: $DAYS_LEFT days"
if [ "$DAYS_LEFT" -lt 0 ]; then
fail "Certificate has expired!"
elif [ "$DAYS_LEFT" -lt 30 ]; then
warn "Certificate expires in less than 30 days"
else
pass "Certificate is valid"
fi
if [ -f data/nginx-ssl/key.pem ]; then
pass "SSL private key found"
else
warn "SSL private key not found"
# Caddy stores its internal CA and issued certificates under data/caddy-data.
# Those files are created by the (root) Caddy process, so read them through the
# container rather than from the host filesystem.
CADDY_CA_IN_CONTAINER="/data/caddy/pki/authorities/local/root.crt"
if $COMPOSE exec -T caddy sh -c "test -f $CADDY_CA_IN_CONTAINER" 2>/dev/null; then
pass "Caddy internal CA root certificate found"
CERT_EXPIRY=$($COMPOSE exec -T caddy sh -c \
"caddy version >/dev/null 2>&1; cat $CADDY_CA_IN_CONTAINER" 2>/dev/null \
| openssl x509 -enddate -noout 2>/dev/null | cut -d= -f2)
if [ -n "$CERT_EXPIRY" ]; then
EXPIRY_EPOCH=$(date -d "$CERT_EXPIRY" +%s 2>/dev/null || echo 0)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
info "Root CA expires: $CERT_EXPIRY"
info "Days remaining: $DAYS_LEFT days"
if [ "$DAYS_LEFT" -lt 0 ]; then
fail "Caddy internal CA has expired!"
elif [ "$DAYS_LEFT" -lt 30 ]; then
warn "Caddy internal CA expires in less than 30 days"
else
pass "Caddy internal CA is valid"
fi
fi
info "Install this CA on client devices to trust the internal certificate:"
info " $COMPOSE cp caddy:$CADDY_CA_IN_CONTAINER ./caddy-root.crt"
else
warn "SSL certificate not found (self-signed required)"
info "No Caddy internal CA yet (created on first 'tls internal' issuance)"
fi
# ============================================================================
@@ -255,42 +284,92 @@ else
fail "app/config.py not found"
fi
if [ -f nginx.conf ]; then
pass "nginx.conf exists"
if grep -q "ssl_protocols" nginx.conf; then
pass "SSL protocols configured"
# The reverse proxy is Caddy; data/Caddyfile is the live config.
if [ -f data/Caddyfile ]; then
pass "data/Caddyfile exists"
if grep -q "reverse_proxy" data/Caddyfile; then
pass "Caddy reverse_proxy configured"
else
warn "SSL protocols not configured"
warn "No reverse_proxy directive in Caddyfile"
fi
if grep -q "access-control-allow" nginx.conf; then
pass "CORS headers in nginx"
if grep -q "admin " data/Caddyfile; then
pass "Caddy admin API configured (needed for live reloads)"
else
info "CORS headers may be handled by Flask only"
warn "Caddy admin API not configured — HTTPS changes cannot hot-reload"
fi
if grep -qE "tls internal" data/Caddyfile; then
info "Using Caddy internal CA (intranet/non-public hostname)"
elif grep -qE "^https://|^[a-zA-Z0-9.-]+ \{" data/Caddyfile; then
info "TLS enabled (Let's Encrypt)"
else
info "HTTP-only configuration"
fi
elif [ -f Caddyfile.example ]; then
info "data/Caddyfile not present yet; deploy.sh seeds it from Caddyfile.example"
else
warn "nginx.conf not found"
fail "Neither data/Caddyfile nor Caddyfile.example found"
fi
# ============================================================================
section "9. Runtime Verification"
# ============================================================================
if docker-compose ps 2>/dev/null | grep -q "Up"; then
if [ -n "$COMPOSE" ] && $COMPOSE ps 2>/dev/null | grep -q "Up"; then
pass "Docker containers are running"
# Check if app is healthy
if docker-compose ps 2>/dev/null | grep -q "digiserver-app.*healthy"; then
if $COMPOSE ps 2>/dev/null | grep -q "digiserver-v2.*healthy"; then
pass "DigiServer app container is healthy"
else
warn "DigiServer app container health status unknown"
fi
if docker-compose ps 2>/dev/null | grep -q "digiserver-nginx.*healthy"; then
pass "Nginx container is healthy"
if $COMPOSE ps 2>/dev/null | grep -q "digiserver-caddy.*healthy"; then
pass "Caddy container is healthy"
else
warn "Nginx container health status unknown"
warn "Caddy container health status unknown"
fi
# Probe the actual endpoints rather than trusting status alone.
if command -v curl >/dev/null 2>&1; then
HTTP_PORT="${HTTP_PORT:-80}"
HTTPS_PORT="${HTTPS_PORT:-443}"
HTTP_URL="http://localhost"
[ "$HTTP_PORT" != "80" ] && HTTP_URL="http://localhost:$HTTP_PORT"
HTTPS_URL="https://localhost"
[ "$HTTPS_PORT" != "443" ] && HTTPS_URL="https://localhost:$HTTPS_PORT"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 5 "$HTTP_URL/" 2>/dev/null || echo 000)
if [ "$HTTP_CODE" != "000" ]; then
pass "HTTP endpoint responds ($HTTP_URL, status $HTTP_CODE)"
else
warn "HTTP endpoint not responding ($HTTP_URL)"
fi
# TLS site blocks match on the requested name (SNI) or on default_sni.
# localhost matches neither when the server is configured for an IP /
# intranet hostname, so probe the address the config actually serves.
# NOTE: default_sni is indented inside the global block, so anchor on
# optional leading whitespace, not `^`.
PROBE_HOST=$(awk '/^[[:space:]]*default_sni/{print $2; exit}' data/Caddyfile 2>/dev/null || true)
if [ -z "$PROBE_HOST" ]; then
PROBE_HOST=$(awk '/^[[:space:]]*https:\/\//{sub(/^[[:space:]]*https:\/\//,""); sub(/[[:space:]{].*$/,""); print; exit}' data/Caddyfile 2>/dev/null || true)
fi
if [ -z "$PROBE_HOST" ]; then
PROBE_HOST="localhost"
fi
if curl -sk -o /dev/null -m 5 --resolve "$PROBE_HOST:$HTTPS_PORT:127.0.0.1" \
"https://$PROBE_HOST:$HTTPS_PORT/" 2>/dev/null; then
pass "HTTPS endpoint responds (https://$PROBE_HOST:$HTTPS_PORT)"
elif grep -qE "tls internal|^[[:space:]]*https://" data/Caddyfile 2>/dev/null; then
warn "HTTPS configured but not responding on https://$PROBE_HOST:$HTTPS_PORT"
else
info "HTTPS not configured (HTTP-only deployment)"
fi
fi
else
info "Docker containers not running (will start on deployment)"
@@ -307,11 +386,18 @@ else
warn "Possible hardcoded secrets detected (verify they use os.getenv)"
fi
# Check for debug mode
if grep -q "DEBUG.*=.*True" app/config.py 2>/dev/null; then
fail "DEBUG mode is enabled"
# Check for debug mode. Only ProductionConfig matters — config.py intentionally
# sets DEBUG=True for DevelopmentConfig and TestingConfig.
# NOTE: grep exits 1 when it matches nothing; under `set -e` that would abort
# the script, so the whole pipeline must end with a command that always succeeds.
DEBUG_LINE=$(awk '/class ProductionConfig/,/^class |^# Configuration/' app/config.py 2>/dev/null \
| grep -E "^[[:space:]]*DEBUG[[:space:]]*=[[:space:]]*True" || true)
if [ -n "$DEBUG_LINE" ]; then
fail "DEBUG mode is enabled in ProductionConfig"
elif grep -q "class ProductionConfig" app/config.py 2>/dev/null; then
pass "Debug mode disabled in ProductionConfig"
else
pass "DEBUG mode is disabled"
warn "ProductionConfig class not found — cannot verify debug mode"
fi
# ============================================================================