The https profile is now ONE container (was 3): Traefik terminates TLS and, via
its embedded go-acme/lego (~100 DNS providers), fetches + auto-renews a single
*.<domain> wildcard cert over DNS-01; no DNS provider -> Traefik's default
self-signed cert. Catch-all router -> archivebox:8000 via an inline file-provider
config (no docker socket, no extra files). Config validated against the real
traefik v3.7 binary. Comments document the one manual step (wildcard DNS A-record).
Also address review: derive BASE_URL_IS_HTTPS from the RESOLVED base url
(get_base_url(), so CSRF_TRUSTED_ORIGINS-only upgrades keep HTTPS hardening);
clarify CLOUDFLARE_API_KEY is an API Token (Bearer), not the legacy global key.
- Drop REVERSE_PROXY_TRUST_FORWARDED_PROTO; derive SECURE_PROXY_SSL_HEADER +
Secure cookies from BASE_URL's https:// scheme (one knob, no overlapping config).
- Caddy https profile is no longer Cloudflare-specific: wildcard cert via lego
with ANY of ~150 DNS providers (ARCHIVEBOX_ACME_DNS + provider's native creds
passed through .env), OR no DNS provider at all -> plain Let's Encrypt on-demand
per-host (HTTP-01), OR self-signed internal locally. Caddyfiles validated.
- tunnel-init: use python:3-alpine instead of the heavy archivebox image.
- Rename CLOUDFLARE_DNS_API_TOKEN -> CLOUDFLARE_API_KEY for the tunnel's CF API use.
Always chmod 0600 (never world-readable), then best-effort chown to the
cloudflared uid 65532; if the mount disallows chown, warn and keep the private
0600 file rather than crashing tunnel-init or falling back to world-readable.
The archivebox image starts as root (privilege drop happens in its entrypoint,
which tunnel-init overrides), so chown to the cloudflared uid always succeeds and
the world-readable 0644 fallback was dead code that could leak the token. Always
write the connector token owned by 65532:65532 with 0600.
The cloudflared image runs as non-root (uid 65532), so a root-owned 0600 token
was unreadable by the connector. chown the token to 65532:65532 and keep 0600;
if tunnel-init isn't root (can't chown), fall back to 0644 so it stays readable.
- settings.py: gate CSRF_COOKIE_SECURE/SESSION_COOKIE_SECURE on
REVERSE_PROXY_TRUST_FORWARDED_PROTO so HTTPS deployments never send auth
cookies over plain HTTP (default-off path unchanged).
- docker-compose.yml tunnel-init: resolve the Cloudflare zone by walking the
domain labels to the registrable zone, so a subdomain BASE_URL no longer
crashes provisioning; write the connector token with 0o600 perms.
- Remove .env.example; the env vars are documented inline in docker-compose.yml.
- etc/README.md: drop the .env.example reference.
The reverse-proxy/TLS setup now lives entirely in the main docker-compose.yml as
the env-driven https/tunnel profiles; update etc/README.md which still pointed at
the removed docker-compose.proxy.yml, bin/proxy_lego.sh, and .env.proxy.example.
Make the tunnel ingress fully automatic from env vars — no dashboard clicks,
no cloudflared login, no cert.pem. A one-shot tunnel-init step (reusing the
archivebox image's python, so no extra image) uses CLOUDFLARE_DNS_API_TOKEN to:
create/reuse a tunnel named archivebox-<domain>, set its ingress
(*.<domain> + <domain> -> archivebox:8000, Host-routed), upsert the proxied
apex+wildcard CNAMEs to the tunnel, and write the connector token; cloudflared
then runs it (depends_on service_completed_successfully). Idempotent.
Add .env.example documenting BASE_URL / SERVER_SECURITY_MODE / COMPOSE_PROFILES
/ CLOUDFLARE_DNS_API_TOKEN as the only required config.
Consolidate the reverse-proxy setup into the single docker-compose.yml (no
overlay file, no separate Dockerfile or shell scripts — all inline) and drive
everything from normal env vars. Two opt-in ingress profiles:
https - caddy + go-acme/lego: obtains ONE *.<domain> Let's Encrypt wildcard
cert via DNS-01 (CLOUDFLARE_DNS_API_TOKEN) covering unlimited snap-*
subdomains, and GRACEFULLY DEGRADES to a self-signed internal cert if
no cert is available yet so the site always serves https.
tunnel - cloudflared: Cloudflare edge terminates TLS and routes *.<domain>
through a SINGLE tunnel to archivebox:8000, which routes by Host; no
public IP / wildcard cert needed (TUNNEL_TOKEN).
Base domain is derived from BASE_URL; lego v5 CLI syntax. Removes
docker-compose.proxy.yml, etc/Caddyfile, bin/proxy_lego.sh, .env.proxy.example.
Adds a dnsmasq 'resolver' service (--profile proxy-resolver) that answers
the entire base domain with ONE suffix-wildcard rule
(address=/<base>/<target>), never enumerating the millions of dynamically
generated snap-*/role subdomains. Covers the gap where non-browser clients
(wget/python/go) on hosts without systemd-resolved, or LAN/NAS internal
domains, need to resolve snapshot subdomains. Browsers, curl, and
systemd-resolved hosts already resolve *.localhost and need nothing.
Rule validated with 'dnsmasq --test'; env-driven via ARCHIVEBOX_PROXY_BASE
and ARCHIVEBOX_PROXY_DNS_TARGET.
Make the default safe-subdomains-fullreplay mode painless to deploy
without baking heavy proxy/cert deps into the archivebox image.
docker-compose.proxy.yml is an opt-in overlay that adds:
- caddy: wildcard catch-all reverse proxy (etc/Caddyfile) that routes
every dynamically-generated snapshot/role subdomain to archivebox by
suffix wildcard (never an enumerated list), preserves Host, and sets
X-Forwarded-Proto. Local default serves *.archivebox.localhost via
Caddy's internal CA.
- lego (proxy-public profile): a go-acme/lego DNS-01 sidecar
(bin/proxy_lego.sh) that fetches ONE *.<domain> wildcard cert covering
unlimited snap-* subdomains. Provider coverage (~150: cloudflare,
route53, gcloud, digitalocean, hetzner, namecheap, godaddy, ...) is
delegated to lego via standard env vars, with no per-provider code.
When proxied, the archivebox service stops publishing :8000 directly and
sets REVERSE_PROXY_TRUST_FORWARDED_PROTO=True so secure cookies + https
links work end-to-end. Replaces the stale nginx example in
docker-compose.yml. See .env.proxy.example for public setup.
Add REVERSE_PROXY_TRUST_FORWARDED_PROTO (default off). When enabled,
Django's SECURE_PROXY_SSL_HEADER is set so request.is_secure() and
request.scheme reflect the real client-facing scheme behind a
TLS-terminating proxy (the bundled Caddy profile, a user's own
caddy/traefik/nginx, or an ingress like Cloudflare).
Without it, the proxy->archivebox hop is plain HTTP, so the
cross-subdomain login-hint cookie loses its Secure flag and
request-derived schemes fall back to http. Gated behind a config flag
because trusting a forwarded header is only safe when a controlled proxy
always sets it.