feat: peer sync configurable via env vars + document CoreDNS file cache 🔗

- PeerSyncWorker reads DADNS_PEER_SYNC_PEER_URL / _USERNAME / _PASSWORD env
  vars to populate a single peer without a config file; deduped against any
  config-file peers so the URL never appears twice
- 2 new tests (119 total, all passing)
- README: peer sync single-peer env var table; Topology C compose example
  updated to use env vars only (no config file needed for two-node setup)
- README: document cybercinch/coredns_mysql_extend built-in file caching —
  serves from cache during MySQL outages, eliminates per-query round-trips
This commit is contained in:
2026-02-20 06:41:46 +13:00
parent fbb6220728
commit d98f08a408
3 changed files with 63 additions and 10 deletions

View File

@@ -323,9 +323,11 @@ Key differences from the upstream plugin:
- Fully authoritative responses — correct AA flag and NXDOMAIN on misses
- Wildcard record support (`*` entries served correctly)
- NS records returned in the additional section
- **Built-in file caching** — CoreDNS caches zone data from MySQL to local files, so queries are served from the cache if MySQL is temporarily unreachable. This also eliminates the per-query MySQL round-trip for frequently resolved names.
Use the BIND backend if you want a zero-dependency setup with no custom CoreDNS
build required.
The file cache makes Topology B significantly more resilient to MySQL hiccups: CoreDNS keeps serving from cache while directdnsonly's retry queue waits for MySQL to recover.
Use the NSD or BIND backend if you want a zero-dependency setup with no custom CoreDNS build required.
---
@@ -502,12 +504,20 @@ The built-in env var mapping targets the backend named `coredns_mysql`. For mult
#### Peer sync
| Config key | Environment variable | Default | Description |
|---|---|---|---|
| `peer_sync.enabled` | `DADNS_PEER_SYNC_ENABLED` | `false` | Enable background peer-to-peer zone sync |
| `peer_sync.interval_minutes` | `DADNS_PEER_SYNC_INTERVAL_MINUTES` | `15` | How often each peer is polled |
| Config key / Environment variable | Default | Description |
|---|---|---|
| `peer_sync.enabled` / `DADNS_PEER_SYNC_ENABLED` | `false` | Enable background peer-to-peer zone sync |
| `peer_sync.interval_minutes` / `DADNS_PEER_SYNC_INTERVAL_MINUTES` | `15` | How often each peer is polled |
> The `peer_sync.peers` list (peer URLs, credentials) requires a config file — it cannot be expressed as simple env vars.
For a **single peer** (the typical two-node Topology C setup) the peer can be configured entirely via env vars — no config file required:
| Environment variable | Default | Description |
|---|---|---|
| `DADNS_PEER_SYNC_PEER_URL` | _(unset)_ | URL of the single peer (e.g. `http://ddo-2:2222`). When set, this peer is automatically appended to the peers list. |
| `DADNS_PEER_SYNC_PEER_USERNAME` | `directdnsonly` | Basic auth username for the peer |
| `DADNS_PEER_SYNC_PEER_PASSWORD` | _(empty)_ | Basic auth password for the peer |
> For **multiple peers**, use a config file with the `peer_sync.peers` list. A peer defined via env var is deduped — if the same URL already appears in the config file it will not be added twice.
---
@@ -540,8 +550,11 @@ services:
DADNS_APP_AUTH_PASSWORD: my-strong-secret
DADNS_DNS_DEFAULT_BACKEND: nsd
DADNS_DNS_BACKENDS_NSD_ENABLED: "true"
DADNS_PEER_SYNC_ENABLED: "true"
DADNS_PEER_SYNC_PEER_URL: http://directdnsonly-mlb:2222
DADNS_PEER_SYNC_PEER_USERNAME: directdnsonly
DADNS_PEER_SYNC_PEER_PASSWORD: my-strong-secret
volumes:
- ./config/syd:/app/config # contains peer_sync.peers list
- syd-data:/app/data
directdnsonly-mlb:
@@ -553,8 +566,11 @@ services:
DADNS_APP_AUTH_PASSWORD: my-strong-secret
DADNS_DNS_DEFAULT_BACKEND: nsd
DADNS_DNS_BACKENDS_NSD_ENABLED: "true"
DADNS_PEER_SYNC_ENABLED: "true"
DADNS_PEER_SYNC_PEER_URL: http://directdnsonly-syd:2222
DADNS_PEER_SYNC_PEER_USERNAME: directdnsonly
DADNS_PEER_SYNC_PEER_PASSWORD: my-strong-secret
volumes:
- ./config/mlb:/app/config # contains peer_sync.peers list
- mlb-data:/app/data
volumes:

View File

@@ -19,6 +19,7 @@ Safety properties:
with older peer data
"""
import datetime
import os
import threading
from loguru import logger
import requests
@@ -36,7 +37,22 @@ class PeerSyncWorker:
def __init__(self, peer_sync_config: dict):
self.enabled = peer_sync_config.get("enabled", False)
self.interval_seconds = peer_sync_config.get("interval_minutes", 15) * 60
self.peers = peer_sync_config.get("peers") or []
self.peers = list(peer_sync_config.get("peers") or [])
# Support single-peer config via env vars for env-var-only deployments.
# DADNS_PEER_SYNC_PEER_URL, DADNS_PEER_SYNC_PEER_USERNAME, DADNS_PEER_SYNC_PEER_PASSWORD
env_url = os.environ.get("DADNS_PEER_SYNC_PEER_URL", "").strip()
if env_url and not any(p.get("url") == env_url for p in self.peers):
self.peers.append(
{
"url": env_url,
"username": os.environ.get(
"DADNS_PEER_SYNC_PEER_USERNAME", "directdnsonly"
),
"password": os.environ.get("DADNS_PEER_SYNC_PEER_PASSWORD", ""),
}
)
logger.debug(f"[peer_sync] Added peer from env vars: {env_url}")
self._stop_event = threading.Event()
self._thread = None

View File

@@ -58,6 +58,27 @@ def test_peers_stored():
assert worker.peers[0]["url"] == "http://ddo-2:2222"
def test_peer_from_env_var(monkeypatch):
"""DADNS_PEER_SYNC_PEER_URL adds a peer without a config file."""
monkeypatch.setenv("DADNS_PEER_SYNC_PEER_URL", "http://ddo-env:2222")
monkeypatch.setenv("DADNS_PEER_SYNC_PEER_USERNAME", "admin")
monkeypatch.setenv("DADNS_PEER_SYNC_PEER_PASSWORD", "secret")
worker = PeerSyncWorker({"enabled": True})
assert len(worker.peers) == 1
assert worker.peers[0]["url"] == "http://ddo-env:2222"
assert worker.peers[0]["username"] == "admin"
assert worker.peers[0]["password"] == "secret"
def test_env_peer_not_duplicated_when_also_in_config(monkeypatch):
"""Env var peer is not added if it already appears in the config file peers list."""
monkeypatch.setenv("DADNS_PEER_SYNC_PEER_URL", "http://ddo-2:2222")
worker = PeerSyncWorker(BASE_CONFIG)
# BASE_CONFIG already has http://ddo-2:2222 — must remain exactly one entry
urls = [p["url"] for p in worker.peers]
assert urls.count("http://ddo-2:2222") == 1
def test_start_skips_when_disabled(caplog):
worker = PeerSyncWorker({"enabled": False})
worker.start()