From ae1e89a236b986d366b5582ef1bedccf133e19d1 Mon Sep 17 00:00:00 2001 From: Aaron Guise Date: Thu, 19 Feb 2026 12:07:37 +1300 Subject: [PATCH] =?UTF-8?q?feat:=20conditional=20BIND=20startup;=20config?= =?UTF-8?q?=20search=20path=20priority=20fix=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - entrypoint: only start named when a bind backend is configured and enabled in app.yml; CoreDNS-only deployments skip named entirely - config: user-supplied paths (/etc/directdnsonly, ./config) now searched before the bundled app.yml so mounted configs take effect - docs: deployment topology reference — Topology A (dual BIND HA) and Topology B (single instance, multi-DC CoreDNS MySQL) - chore: bump version to 2.1.0 - justfile: add build-docker recipe --- README.md | 138 ++++++++++++++++++++++++++++++- directdnsonly/config/__init__.py | 8 +- docker/entrypoint.sh | 46 +++++++++-- entrypoint.sh | 0 justfile | 3 + pyproject.toml | 2 +- 6 files changed, 184 insertions(+), 13 deletions(-) delete mode 100644 entrypoint.sh diff --git a/README.md b/README.md index ca028cf..0f4df01 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,138 @@ -# DaDNS - DNS Management System +# DirectDNSOnly - DNS Management System + +## Deployment Topologies + +Two reference topologies are documented below. Choose the one that matches your infrastructure. + +--- + +### Topology A — Dual BIND Instances (High-Availability / Multi-Server) + +Two independent DirectDNSOnly containers, each running a bundled BIND9 instance. Both are registered as Extra DNS servers in the same DirectAdmin Multi-Server environment, so DA pushes every zone change to both simultaneously. + +``` +DirectAdmin Multi-Server + │ + ├─ POST /CMD_API_DNS_ADMIN ──▶ directdnsonly-1 (container, BIND backend) + │ │ + │ writes zone file + │ reloads named + │ (serves authoritative DNS on :53) + │ + └─ POST /CMD_API_DNS_ADMIN ──▶ directdnsonly-2 (container, BIND backend) + │ + writes zone file + reloads named + (serves authoritative DNS on :53) +``` + +**Each instance is completely independent** — no shared state, no cross-talk. Redundancy comes from DA pushing to both. If one container goes down, DA continues to push to the other. + +#### `config/app.yml` — instance 1 + +```yaml +app: + auth_username: directdnsonly + auth_password: your-secret + +dns: + default_backend: bind + backends: + bind: + type: bind + enabled: true + zones_dir: /etc/named/zones + named_conf: /etc/bind/named.conf.local +``` + +#### `docker-compose.yml` sketch — instance 1 + +```yaml +services: + directdnsonly-1: + image: guisea/directdnsonly:2.0.0 + ports: + - "2222:2222" # DA pushes here + - "53:53/udp" # authoritative DNS + volumes: + - ./config:/app/config + - ./data:/app/data +``` + +Register both containers as separate Extra DNS entries in DA → DNS Administration → Extra DNS Servers, with the same credentials configured in each `config/app.yml`. + +--- + +### Topology B — Single Instance, Dual CoreDNS MySQL Backends (Multi-DC) + +One DirectDNSOnly instance receives zone pushes from DirectAdmin and fans out to two (or more) CoreDNS MySQL databases in parallel. CoreDNS servers in each data centre read from their local database. The directdnsonly instance is the sole write path — it does **not** serve DNS itself. + +``` +DirectAdmin + │ + └─ POST /CMD_API_DNS_ADMIN ──▶ directdnsonly (single container) + │ + Persistent Queue (survive restarts) + │ + ThreadPoolExecutor (one thread per backend) + │ │ + ▼ ▼ + coredns_mysql_primary coredns_mysql_secondary + (MySQL DC1 10.0.0.80) (MySQL DC2 10.0.1.29) + │ │ + ▼ ▼ + CoreDNS (DC1) CoreDNS (DC2) + serves :53 from DB serves :53 from DB +``` + +Both MySQL backends are written **concurrently** within the same zone update. A slow or unreachable secondary does not block the primary write. Per-backend record verification runs after each write. + +#### `config/app.yml` + +```yaml +app: + auth_username: directdnsonly + auth_password: your-secret + +dns: + default_backend: coredns_mysql_primary + backends: + coredns_mysql_primary: + type: coredns_mysql + enabled: true + host: 10.0.0.80 + port: 3306 + database: coredns + username: coredns + password: your-db-password + + coredns_mysql_secondary: + type: coredns_mysql + enabled: true + host: 10.0.1.29 + port: 3306 + database: coredns + username: coredns + password: your-db-password +``` + +Adding a third data centre is a single stanza in the config — no code changes required. + +--- + +### Topology Comparison + +| | Topology A — Dual BIND | Topology B — CoreDNS MySQL | +|---|---|---| +| DNS server | BIND9 (bundled in container) | CoreDNS (separate, reads MySQL) | +| Redundancy | Two independent app+DNS units | One app, N MySQL replicas | +| Zone storage | Zone files on container disk | MySQL database rows | +| DA registration | Two Extra DNS server entries | One Extra DNS server entry | +| Failure mode | One container can go down | MySQL connectivity required | +| Horizontal scaling | Add more DA Extra DNS entries | Add more MySQL backends in config | +| Best for | Simple HA, no external DB | Multi-DC, existing CoreDNS fleet | + +--- ## Features - Multi-backend DNS management (BIND, CoreDNS MySQL) @@ -16,7 +150,7 @@ ## Concurrent Multi-Backend Processing -DaDNS propagates every zone update to all enabled backends in parallel using a +DirectDNSOnly propagates every zone update to all enabled backends in parallel using a queue-based worker architecture. ### Architecture diff --git a/directdnsonly/config/__init__.py b/directdnsonly/config/__init__.py index fd9d542..bbfa49a 100644 --- a/directdnsonly/config/__init__.py +++ b/directdnsonly/config/__init__.py @@ -10,10 +10,12 @@ from typing import Any, Dict def load_config() -> Vyper: # Initialize Vyper v.set_config_name("app") # Looks for app.yaml/app.yml - # Bundled config colocated with this module (always present in the package) + # User-supplied paths checked first so they override the bundled defaults + v.add_config_path("/etc/directdnsonly") # system-level mount + v.add_config_path(".") # CWD (e.g. /app when run directly) + v.add_config_path("./config") # docker-compose volume mount at /app/config + # Bundled config colocated with this module — last-resort fallback v.add_config_path(str(Path(__file__).parent)) - v.add_config_path(".") # Search in current directory - v.add_config_path("./config") v.set_env_prefix("DADNS") v.set_env_key_replacer("_", ".") v.automatic_env() diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 1bbaadd..81aae23 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,12 +1,44 @@ #!/bin/bash +set -e -# Start BIND -/usr/sbin/named -u bind -f & +# --------------------------------------------------------------------------- +# Detect whether any bind backend is configured and enabled. +# Uses the same config search order as the application itself. +# --------------------------------------------------------------------------- +BIND_ENABLED=$(python3 - <<'EOF' +import yaml, sys, os -## Initialize MySQL schema if needed -#if [ -f /app/schema/coredns_mysql.sql ]; then -# mysql -h mysql -u root -prootpassword coredns < /app/schema/coredns_mysql.sql -#fi +config_paths = [ + "/etc/directdnsonly/app.yml", + "/etc/directdnsonly/app.yaml", + "/app/app.yml", + "/app/app.yaml", + "/app/config/app.yml", + "/app/config/app.yaml", +] + +config = {} +for path in config_paths: + if os.path.exists(path): + with open(path) as f: + config = yaml.safe_load(f) or {} + break + +backends = config.get("dns", {}).get("backends", {}) +for cfg in backends.values(): + if isinstance(cfg, dict) and cfg.get("type") == "bind" and cfg.get("enabled", False): + print("true") + sys.exit(0) +print("false") +EOF +) + +if [ "$BIND_ENABLED" = "true" ]; then + echo "[entrypoint] BIND backend enabled — starting named" + /usr/sbin/named -u bind -f & +else + echo "[entrypoint] No BIND backend configured — skipping named" +fi # Start the application -poetry run python directdnsonly/main.py \ No newline at end of file +exec python -m directdnsonly diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index e69de29..0000000 diff --git a/justfile b/justfile index 35b4a81..26d632e 100644 --- a/justfile +++ b/justfile @@ -87,6 +87,9 @@ build: directdnsonly/main.py rm -f *.spec +build-docker: + export DOCKER_CONFIG="/home/guisea/.docker/guisea" && \ + docker buildx build --platform linux/amd64,linux/arm64 -t guisea/directdnsonly:dev --push --progress plain --file Dockerfile . # --------------------------------------------------------------------------- # Clean # --------------------------------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 2c25773..a1dbafa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "directdnsonly" -version = "1.0.9" +version = "2.1.0" description = "DNS Management System - DirectAdmin to multiple backends" authors = [ {name = "Aaron Guise",email = "aaron@guise.net.nz"}