# dns-sync modular A plugin-based DNS reconciliation service. ## Plugin contract Source modules live under `dns_sync/plugins/sources/` and export a class named `Plugin` with: - `name` - `discover() -> list[DesiredRecord]` Provider modules live under `dns_sync/plugins/providers/` and export `Plugin` with: - `name` - `capabilities` - `connect()` - `list_records()` - `add_record()` - `update_record()` - `delete_record()` - `verify_record()` - `capacity()` Plugins are convention-loaded from environment variables. No registry edit is needed: - `PROXY=npm` loads `dns_sync.plugins.sources.npm.Plugin` - `DNS_PROVIDER=verizon` loads `dns_sync.plugins.providers.verizon.Plugin` ## Plugins The generic reconciliation engine uses adapter plugins. Built-in adapters: - `npm` - Nginx Proxy Manager proxy hosts and certificates External adapters are loaded from `DNS_SYNC_PLUGIN_PATH` and must also be explicitly enabled through `ENABLED_PLUGINS`. Example: ```text ENABLED_PLUGINS=npm,verizon DNS_SYNC_PLUGIN_PATH=/plugins ``` The Verizon adapter lives under `external_plugins/verizon` and is deliberately not copied into the dns-sync container image. The external plugin directory is mounted read-only at runtime. ## Container runtime The container periodically executes the generic reconciliation engine: ```text python -m dns_sync reconcile ``` The default container mode is read-only. ```text SYNC_INTERVAL=1 DNS_SYNC_APPLY=false DNS_SYNC_STATE_DB=/var/lib/dns-sync/state.sqlite3 DNS_SYNC_PLUGIN_PATH=/plugins ENABLED_PLUGINS=npm,verizon ``` `SYNC_INTERVAL` is expressed in minutes. The interval begins after each reconciliation completes, so runs do not overlap. To permit planned changes to be applied and verified: ```text DNS_SYNC_APPLY=true ``` An optional mutation budget can be specified: ```text DNS_SYNC_MAX_CHANGES=10 ``` When `DNS_SYNC_APPLY=false`, reconciliation still performs discovery, normalization, desired-state reconciliation, planning, and state persistence, but no endpoint mutations are executed. The SQLite database should be stored on persistent storage. External plugins should be mounted read-only at `/plugins`. ## Main environment ```text NPM_ENDPOINTS=192.168.1.3,192.168.1.4,192.168.1.5 PROXY_API_PORT=81 PROXY_API_SCHEME=http DNS_TARGET_IP=192.168.1.101 VERIZON_ENDPOINTS=192.168.1.1 DOMAIN_MATCHING=false DOMAIN=example.com NPM_IDENTITY=... NPM_SECRET=... ROUTER_PASSWORD=... ``` ## Domain filtering Filtering is performed by the central engine after source discovery and before reconciliation, so all source plugins get identical behavior. ```text DOMAIN_MATCHING=true DOMAIN=example.com ``` This includes the apex and every subdomain: ```text example.com app.example.com deep.app.example.com *.example.com ``` It excludes unrelated names such as: ```text example.net notexample.com example.com.evil.net ``` When `DOMAIN_MATCHING=false`, `DOMAIN` is ignored and all discovered records are eligible for reconciliation. When `DOMAIN_MATCHING=true`, `DOMAIN` is required.