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:
+93
-170
@@ -37,43 +37,110 @@ class CaddyConfigGenerator:
|
||||
"""Generate Caddyfile configuration based on HTTPSConfig."""
|
||||
|
||||
@staticmethod
|
||||
def generate_caddyfile(config: Optional['HTTPSConfig'] = None) -> str:
|
||||
def generate_caddyfile(config: Optional['HTTPSConfig'] = None,
|
||||
http_fallback: bool = True,
|
||||
http_port: int = 80,
|
||||
https_port: int = 443) -> str:
|
||||
"""Generate a complete Caddyfile.
|
||||
|
||||
Behaviour:
|
||||
- HTTPS disabled / no domain → HTTP-only on port 80 (initial deploy mode).
|
||||
- HTTPS enabled + real domain → Caddy auto-provisions a Let's Encrypt cert
|
||||
for that domain; HTTP redirects to HTTPS automatically.
|
||||
- HTTPS enabled + IP only (no domain) → TLS with Caddy's internal CA
|
||||
(self-signed, trusted within the Docker network).
|
||||
Design goals
|
||||
------------
|
||||
* **One HTTP endpoint** (port 80) that always answers, whatever the Host
|
||||
header is — so ``http://<ip>`` and ``http://<hostname>`` both work.
|
||||
* **HTTPS on port 443** for the same names when it is enabled.
|
||||
* If HTTPS is disabled or never configured, port 80 simply serves the
|
||||
app — there is no separate "HTTP mode" to configure.
|
||||
|
||||
Behaviour by configuration
|
||||
--------------------------
|
||||
* HTTPS off, or no address configured → plain HTTP on ``:http_port``.
|
||||
* HTTPS on → the app is served on port 80 for every configured name and
|
||||
on port 443 over TLS. Whether port 80 *serves* or *redirects* to
|
||||
HTTPS is controlled by ``http_fallback``.
|
||||
|
||||
Which certificate each name gets
|
||||
--------------------------------
|
||||
* ``domain`` (when set) → Caddy obtains a certificate automatically
|
||||
(Let's Encrypt/ACME). Only valid for a **publicly resolvable** name.
|
||||
* ``ip_address`` / ``hostname`` → ``tls internal`` (Caddy's local CA).
|
||||
This needs no public DNS and no ACME, which is the right choice for an
|
||||
intranet name such as ``digiserver.sibiusb.harting.intra``.
|
||||
|
||||
Args:
|
||||
config: HTTPSConfig instance, or None to load from the database.
|
||||
http_fallback: When True, port 80 keeps *serving* the app alongside
|
||||
HTTPS. This is the resilient default: clients that cannot trust
|
||||
the internal CA (e.g. a Kivy player with ``verify_ssl: true``)
|
||||
are still able to connect. When False, port 80 issues a 301
|
||||
redirect to HTTPS instead.
|
||||
http_port: Port Caddy listens on for plain HTTP (default 80).
|
||||
https_port: Port used to build redirect targets when
|
||||
``http_fallback`` is False (default 443).
|
||||
|
||||
Returns:
|
||||
The complete Caddyfile as a string.
|
||||
"""
|
||||
if config is None:
|
||||
config = HTTPSConfig.get_config()
|
||||
|
||||
email = (config.email or "admin@localhost") if config else "admin@localhost"
|
||||
https_enabled = config.https_enabled if config else False
|
||||
https_enabled = bool(config.https_enabled) if config else False
|
||||
domain = (config.domain or "").strip() if config else ""
|
||||
ip_address = (config.ip_address or "").strip() if config else ""
|
||||
hostname = (config.hostname or "").strip() if config else ""
|
||||
|
||||
global_block = f"""{{\n admin 0.0.0.0:2019\n email {email}\n}}\n\n"""
|
||||
# Every name the server should answer to, in priority order, without
|
||||
# duplicates. The IP comes first because it always resolves.
|
||||
names: list[str] = []
|
||||
for candidate in (ip_address, hostname, domain):
|
||||
if candidate and candidate not in names:
|
||||
names.append(candidate)
|
||||
|
||||
if https_enabled and domain:
|
||||
# Caddy handles Let's Encrypt + HTTP→HTTPS redirect automatically
|
||||
# when a plain hostname (no scheme) is used.
|
||||
caddyfile = global_block
|
||||
caddyfile += f"{domain} {{\n{_PROXY_SNIPPET}}}\n"
|
||||
# Also accept requests on the raw IP (HTTP only, no cert needed)
|
||||
if ip_address:
|
||||
caddyfile += f"\nhttp://{ip_address} {{\n{_PROXY_SNIPPET}}}\n"
|
||||
elif https_enabled and ip_address:
|
||||
# No public domain — use Caddy's internal CA (self-signed)
|
||||
caddyfile = global_block
|
||||
caddyfile += f"https://{ip_address} {{\n tls internal\n{_PROXY_SNIPPET}}}\n"
|
||||
caddyfile += f"\nhttp://{ip_address} {{\n redir https://{ip_address}{{uri}} 301\n}}\n"
|
||||
else:
|
||||
# HTTP-only fallback (first deploy, before HTTPS is configured)
|
||||
caddyfile = "{\n admin 0.0.0.0:2019\n}\n\n"
|
||||
caddyfile += f":80 {{\n{_PROXY_SNIPPET}}}\n"
|
||||
global_block = f"{{\n admin 0.0.0.0:2019\n email {email}\n"
|
||||
|
||||
# ── TLS with no SNI ────────────────────────────────────────────────
|
||||
# Browsers do NOT send SNI when the URL is an IP address (an IP is not
|
||||
# a valid SNI hostname). Without a fallback Caddy would identify such a
|
||||
# connection by the container's own internal IP, match no certificate
|
||||
# and abort the handshake with:
|
||||
# "no certificate available for '<container-ip>'"
|
||||
# `default_sni` makes a SNI-less ClientHello resolve to a name we do
|
||||
# serve, so https://<ip> works in the browser.
|
||||
if https_enabled and not domain and ip_address:
|
||||
global_block += f" default_sni {ip_address}\n"
|
||||
|
||||
global_block += "}\n\n"
|
||||
|
||||
# ── Plain HTTP only: HTTPS disabled, or no address to certify ───────
|
||||
if not (https_enabled and names):
|
||||
return global_block + f":{http_port} {{\n{_PROXY_SNIPPET}}}\n"
|
||||
|
||||
caddyfile = global_block
|
||||
|
||||
# ── Port 80: catch-all so ANY Host header is answered ──────────────
|
||||
# Without this, a request for an unexpected name (e.g. a bare IP when
|
||||
# only a hostname is configured) would hit no site block and fail.
|
||||
caddyfile += f":{http_port} {{\n{_PROXY_SNIPPET}}}\n\n"
|
||||
|
||||
# ── Port 80: explicit per-name blocks ──────────────────────────────
|
||||
for name in names:
|
||||
if http_fallback:
|
||||
caddyfile += f"http://{name} {{\n{_PROXY_SNIPPET}}}\n\n"
|
||||
else:
|
||||
# Redirect to the port the host actually publishes.
|
||||
https_url = (f"https://{name}" if https_port == 443
|
||||
else f"https://{name}:{https_port}")
|
||||
caddyfile += f"http://{name} {{\n redir {https_url}{{uri}} 301\n}}\n\n"
|
||||
|
||||
# ── Port 443: TLS listeners ────────────────────────────────────────
|
||||
for name in names:
|
||||
if domain and name == domain:
|
||||
# Public name → let Caddy obtain a real certificate.
|
||||
caddyfile += f"https://{name} {{\n{_PROXY_SNIPPET}}}\n\n"
|
||||
else:
|
||||
# IP or intranet name → Caddy's internal CA.
|
||||
caddyfile += (f"https://{name} {{\n tls internal\n"
|
||||
f"{_PROXY_SNIPPET}}}\n\n")
|
||||
|
||||
return caddyfile
|
||||
|
||||
@@ -121,148 +188,4 @@ class CaddyConfigGenerator:
|
||||
return response.status == 200
|
||||
except Exception as e:
|
||||
print(f"Caddy reload error: {str(e)}")
|
||||
return False
|
||||
|
||||
"""Generate complete Caddyfile content.
|
||||
|
||||
Args:
|
||||
config: HTTPSConfig instance or None
|
||||
|
||||
Returns:
|
||||
Complete Caddyfile content as string
|
||||
"""
|
||||
# Get config from database if not provided
|
||||
if config is None:
|
||||
config = HTTPSConfig.get_config()
|
||||
|
||||
# Base configuration
|
||||
email = "admin@localhost"
|
||||
if config and config.email:
|
||||
email = config.email
|
||||
|
||||
base_config = f"""{{
|
||||
# Global options
|
||||
email {email}
|
||||
# Admin API for configuration management (listen on all interfaces)
|
||||
admin 0.0.0.0:2019
|
||||
# Uncomment for testing to avoid rate limits
|
||||
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
}}
|
||||
|
||||
# Shared reverse proxy configuration
|
||||
(reverse_proxy_config) {{
|
||||
reverse_proxy digiserver-app:5000 {{
|
||||
header_up Host {{host}}
|
||||
header_up X-Real-IP {{remote_host}}
|
||||
header_up X-Forwarded-Proto {{scheme}}
|
||||
|
||||
# Timeouts for large uploads
|
||||
transport http {{
|
||||
read_timeout 300s
|
||||
write_timeout 300s
|
||||
}}
|
||||
}}
|
||||
|
||||
# File upload size limit (2GB)
|
||||
request_body {{
|
||||
max_size 2GB
|
||||
}}
|
||||
|
||||
# Security headers
|
||||
header {{
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
}}
|
||||
|
||||
# Logging
|
||||
log {{
|
||||
output file /var/log/caddy/access.log
|
||||
}}
|
||||
}}
|
||||
|
||||
# Localhost (development/local access)
|
||||
http://localhost {{
|
||||
import reverse_proxy_config
|
||||
}}
|
||||
"""
|
||||
|
||||
# Add main domain/IP configuration if HTTPS is enabled
|
||||
if config and config.https_enabled and config.domain and config.ip_address:
|
||||
# Internal domain configuration
|
||||
domain_config = f"""
|
||||
# Internal domain (HTTP only - internal use)
|
||||
http://{config.domain} {{
|
||||
import reverse_proxy_config
|
||||
}}
|
||||
|
||||
# Handle IP address access
|
||||
http://{config.ip_address} {{
|
||||
import reverse_proxy_config
|
||||
}}
|
||||
"""
|
||||
base_config += domain_config
|
||||
else:
|
||||
# Default fallback configuration
|
||||
base_config += """
|
||||
# Internal domain (HTTP only - internal use)
|
||||
http://digiserver.sibiusb.harting.intra {
|
||||
import reverse_proxy_config
|
||||
}
|
||||
|
||||
# Handle IP address access
|
||||
http://10.76.152.164 {
|
||||
import reverse_proxy_config
|
||||
}
|
||||
"""
|
||||
|
||||
# Add catch-all for any other HTTP requests
|
||||
base_config += """
|
||||
# Catch-all for any other HTTP requests
|
||||
http://* {
|
||||
import reverse_proxy_config
|
||||
}
|
||||
"""
|
||||
|
||||
return base_config
|
||||
|
||||
@staticmethod
|
||||
def write_caddyfile(caddyfile_content: str, path: str = '/app/Caddyfile') -> bool:
|
||||
"""Write Caddyfile to disk.
|
||||
|
||||
Args:
|
||||
caddyfile_content: Content to write
|
||||
path: Path to Caddyfile
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
with open(path, 'w') as f:
|
||||
f.write(caddyfile_content)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error writing Caddyfile: {str(e)}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def reload_caddy() -> bool:
|
||||
"""Reload Caddy configuration without restart.
|
||||
|
||||
Note: Caddy monitoring is handled via file watching. After writing the Caddyfile,
|
||||
Caddy should automatically reload. If it doesn't, you may need to restart the
|
||||
Caddy container manually.
|
||||
|
||||
Returns:
|
||||
True if configuration was written successfully (Caddy will auto-reload)
|
||||
"""
|
||||
try:
|
||||
# Just verify that Caddy is reachable
|
||||
import urllib.request
|
||||
response = urllib.request.urlopen('http://caddy:2019/config/', timeout=2)
|
||||
return response.status == 200
|
||||
except Exception as e:
|
||||
# Caddy might not be reachable, but Caddyfile was already written
|
||||
# Caddy should reload automatically when it detects file changes
|
||||
print(f"Note: Caddy reload check returned: {str(e)}")
|
||||
return True # Return True anyway since Caddyfile was written
|
||||
|
||||
Reference in New Issue
Block a user