rebreak-monorepo/ops/mdm/RUNBOOK.md
chahinebrini db7875fb34 feat(ops/mdm): AdGuard ClientID handshake — nginx + watcher
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
2026-05-15 22:41:38 +02:00

5.3 KiB

MDM Server — Operations Runbook

SSH-Zugriff

ssh rebreak-mdm
# entspricht: ssh root@178.105.101.137

NanoMDM Container

Status prüfen

ssh rebreak-mdm "docker ps | grep nanomdm"
ssh rebreak-mdm "cd /opt/nanomdm && docker compose ps"

Logs anschauen

ssh rebreak-mdm "cd /opt/nanomdm && docker compose logs -f"
# Nur letzte 50 Zeilen:
ssh rebreak-mdm "cd /opt/nanomdm && docker compose logs --tail=50"

Restart

ssh rebreak-mdm "cd /opt/nanomdm && docker compose restart"

Stop + Start (hard restart)

ssh rebreak-mdm "cd /opt/nanomdm && docker compose down && docker compose up -d"

Auf neue Version updaten

ssh rebreak-mdm "cd /opt/nanomdm && docker compose pull && docker compose up -d"

PostgreSQL

Zugriff auf nanomdm-DB

ssh rebreak-mdm "sudo -u postgres psql nanomdm"

DB-Passwort abrufen

ssh rebreak-mdm "cat /root/.nanomdm_db_pass"

Tabellen-Übersicht

ssh rebreak-mdm "sudo -u postgres psql nanomdm -c '\dt'"

DB-Backup

ssh rebreak-mdm "sudo -u postgres pg_dump nanomdm > /tmp/nanomdm-$(date +%Y%m%d).sql"
# Lokal kopieren:
scp rebreak-mdm:/tmp/nanomdm-*.sql ./backups/

DB-Restore (nach Backup)

# Achtung: destructive — nur nach User-Bestätigung
ssh rebreak-mdm "sudo -u postgres psql nanomdm < /path/to/backup.sql"

nginx

Config testen

ssh rebreak-mdm "nginx -t"

Reload (nach Config-Änderung)

ssh rebreak-mdm "systemctl reload nginx"

Vhost-Config

ssh rebreak-mdm "cat /etc/nginx/sites-available/mdm.rebreak.org"

Logs

ssh rebreak-mdm "tail -f /var/log/nginx/access.log"
ssh rebreak-mdm "tail -f /var/log/nginx/error.log"

TLS-Zertifikat (Let's Encrypt)

Status prüfen

ssh rebreak-mdm "certbot certificates"
ssh rebreak-mdm "systemctl status certbot.timer"

Manuelle Renewal (Notfall)

# ACHTUNG: Rate-Limit bei --force-renewal. Nur wenn wirklich nötig.
# Erst ohne force testen:
ssh rebreak-mdm "certbot renew --dry-run"
# Dann renewal:
ssh rebreak-mdm "certbot renew"

Cert-Expiry prüfen

ssh rebreak-mdm "openssl x509 -in /etc/letsencrypt/live/mdm.rebreak.org/cert.pem -noout -dates"

Apple Push Zertifikat

Expiry prüfen

# Nach Phase D.1 (wenn push.pem vorhanden):
ssh rebreak-mdm "openssl x509 -in /opt/nanomdm/certs/push.pem -noout -dates"

Jährliche Renewal

  1. CSR-File ist noch da: /opt/nanomdm/certs/push.csr
  2. Gleichen CSR auf identity.apple.com hochladen (neues Cert, gleicher Key)
  3. Neues .pem auf Server kopieren: scp ./MDMCertificate.pem rebreak-mdm:/opt/nanomdm/certs/push.pem
  4. chmod 600 /opt/nanomdm/certs/push.pem
  5. docker compose restart auf Server

Neues CSR generieren (nur wenn push.key verloren!)

# ACHTUNG: Neuer Key = alle Geräte müssen re-enrollen
ssh rebreak-mdm "cd /opt/nanomdm/certs && openssl req -newkey rsa:2048 -nodes \
  -keyout push.key -out push.csr \
  -subj '/CN=ReBreak MDM Push/O=Raynis/C=DE' && chmod 600 push.key"
ssh rebreak-mdm "cat /opt/nanomdm/certs/push.csr"

Externer Health-Check

# Erwartet: HTTP 404 von nanomdm (normales Verhalten auf /)
curl -sI https://mdm.rebreak.org/
# Erwartet: "Bad Request" (MDM-Endpoint ohne gültigen Apple-Payload)
curl -s https://mdm.rebreak.org/mdm

Firewall (UFW)

ssh rebreak-mdm "ufw status numbered"
# Regel hinzufügen (Beispiel SSH von spezifischer IP):
ssh rebreak-mdm "ufw allow from 1.2.3.4 to any port 22"

System-Ressourcen

ssh rebreak-mdm "df -h && free -h && docker stats --no-stream"

Handshake-Watcher (DoH ClientID → Backend)

Status prüfen

ssh rebreak-mdm "systemctl status rebreak-handshake-watcher"

Logs live

ssh rebreak-mdm "journalctl -u rebreak-handshake-watcher -f"

Restart

ssh rebreak-mdm "systemctl restart rebreak-handshake-watcher"

EnvironmentFile-Pfad (Secrets, chmod 600)

/etc/rebreak-handshake-watcher.env

Inhalt (nie committen, kommt aus Infisical):

HANDSHAKE_SECRET=<32hex>
BACKEND_URL=https://staging.rebreak.org
QUERYLOG_PATH=/opt/adguardhome/data/querylog.json

Watcher-Code deployen (nach Code-Änderung)

scp ops/mdm/adguard-handshake-watcher/watcher.py rebreak-mdm:/opt/rebreak-handshake-watcher/watcher.py
ssh rebreak-mdm "systemctl restart rebreak-handshake-watcher"

Vollständige Architektur-Doku

ops/mdm/DOH_CLIENTID_HANDSHAKE.md

Troubleshooting

nanomdm startet nicht

ssh rebreak-mdm "cd /opt/nanomdm && docker compose logs --tail=50"

Häufige Ursachen:

  • DB-Verbindung: postgres://nanomdm:PASS@127.0.0.1:5432/nanomdm — postgres läuft? systemctl is-active postgresql@16-main
  • CA-Cert fehlt: /opt/nanomdm/certs/ca.crt vorhanden?
  • .env-File: cat /opt/nanomdm/.env — NANOMDM_DB_PASS gesetzt?
  • network_mode host nötig: in docker-compose.yml prüfen

502 Bad Gateway von nginx

Bedeutet nanomdm läuft nicht oder antwortet nicht auf 127.0.0.1:9000.

ssh rebreak-mdm "curl -sv http://127.0.0.1:9000/"
ssh rebreak-mdm "cd /opt/nanomdm && docker compose up -d"

Postgres startet nicht

ssh rebreak-mdm "journalctl -u postgresql@16-main -n 50"
ssh rebreak-mdm "pg_lsclusters"