End-to-end DoH-to-backend wiring for Mac auto-activation: Mac → dns.rebreak.org/dns-query/<token> → nginx → AdGuard → querylog.json (CP field) → watcher.py → POST /handshake → backend - ops/nginx/dns.rebreak.org.conf: vhost with `location ^~ /dns-query` prefix-match (not exact). proxy_pass without trailing slash preserves the full path so AdGuard parses the ClientID natively. - watcher.py: NDJSON tail with inode-based rotation safety, per-token 60s in-memory cooldown, urllib (no external deps), graceful 401/404/5xx - rebreak-handshake-watcher.service: systemd unit, EnvironmentFile with chmod 600 (HANDSHAKE_SECRET never in git), NoNewPrivileges + PrivateTmp - DOH_CLIENTID_HANDSHAKE.md: architecture + flow diagram + risk table - RUNBOOK.md: status/logs/restart commands + deploy ordering Not yet deployed. Verify-checklist before `nginx -s reload`: 1. confirm AdGuard DoH port (config assumes 127.0.0.1:3000) 2. confirm TLS cert exists for dns.rebreak.org 3. snapshot current nginx config 4. `nginx -t` dry-run 5. functional curl + grep CP in querylog before starting watcher
71 lines
2.9 KiB
Plaintext
71 lines
2.9 KiB
Plaintext
# nginx vhost: dns.rebreak.org
|
|
# Deployed on: rebreak-mdm (178.105.101.137)
|
|
# TLS termination for AdGuard Home DoH endpoint.
|
|
#
|
|
# CRITICAL: location uses prefix-match (^~) NOT exact-match (=).
|
|
# AdGuard Home parses the ClientID from the URL path natively:
|
|
# /dns-query -> normal DoH query (no ClientID)
|
|
# /dns-query/<cid> -> DoH query, AdGuard extracts <cid> into QueryLog.ClientID
|
|
#
|
|
# The full path MUST be forwarded to AdGuard unchanged (no $uri stripping).
|
|
# AdGuard reads the path segment after /dns-query/ as the ClientID.
|
|
# Stripping it (e.g. proxy_pass http://.../ without path) would break CID detection.
|
|
#
|
|
# AdGuard Home listens on 127.0.0.1:3000 (HTTPS UI) and plain DNS-over-HTTPS
|
|
# on a dedicated port. Verify actual DoH port on server:
|
|
# docker exec adguardhome cat /opt/adguardhome/conf/AdGuardHome.yaml | grep -A5 dns:
|
|
# Common defaults: port 3000 for UI+DoH combined, or separate port 5353/8053.
|
|
# Adjust proxy_pass port below to match actual AdGuard DoH port.
|
|
#
|
|
# Current assumption: AdGuard DoH on 127.0.0.1:3000 (same as UI, AdGuard's default).
|
|
# If AdGuard runs in docker: verify with `docker ps | grep adguard`.
|
|
|
|
server {
|
|
listen 80;
|
|
server_name dns.rebreak.org;
|
|
return 301 https://dns.rebreak.org$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name dns.rebreak.org;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/dns.rebreak.org/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/dns.rebreak.org/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
# ── DoH endpoint — prefix match, path forwarded unchanged ───────────────
|
|
# ^~ wins over regex locations. Catches both:
|
|
# /dns-query (plain DoH)
|
|
# /dns-query/abc1 (DoH with ClientID — AdGuard parses the suffix)
|
|
#
|
|
# proxy_pass terminates without trailing slash so $request_uri is appended
|
|
# as-is, preserving /dns-query/<cid> verbatim.
|
|
location ^~ /dns-query {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
proxy_http_version 1.1;
|
|
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# DoH requests are short-lived; tight timeouts are fine.
|
|
proxy_connect_timeout 5s;
|
|
proxy_send_timeout 10s;
|
|
proxy_read_timeout 10s;
|
|
}
|
|
|
|
# ── Health check (for monitoring / GH Actions deploy verify) ────────────
|
|
location /health {
|
|
return 200 "OK\n";
|
|
add_header Content-Type text/plain;
|
|
}
|
|
|
|
# ── Block everything else ────────────────────────────────────────────────
|
|
location / {
|
|
return 404;
|
|
}
|
|
}
|