A plugin-based DNS reconciliation service.
The GitHub Actions workflow in .github/workflows/container.yml builds the
Containerfile and runs the tests inside the image on every push and pull request.
Successful pushes publish to ghcr.io/<owner>/<repository> with a
sha-<full-commit-sha> tag. Pushes to the default branch also update latest.
Pull requests build and test without publishing. The workflow can also be run
manually from the Actions tab. Images currently target Linux amd64.
Publishing uses GitHub's built-in GITHUB_TOKEN with packages: write; no
additional registry secret is required. Router and NPM secrets are only needed
at runtime and are not supplied to the build. The workflow adds an image source
label to link the package to its repository.
After the first successful publish, the image appears under the account's GitHub
Packages. To run that image with the supplied Quadlet, change its Image= line
to ghcr.io/<owner>/<repository>:latest. Private packages require Podman registry
authentication before pulling. Publishing an image does not automatically
update an already-running container.
Source modules live under dns_sync/plugins/sources/ and export a class named Plugin with:
namediscover() -> list[DesiredRecord]
Provider modules live under dns_sync/plugins/providers/ and export Plugin with:
namecapabilitiesconnect()list_records()add_record()update_record()delete_record()verify_record()capacity()
Providers may also export close() to release their session. The CLI calls it
after reconciliation, including dry runs and failures. The Verizon provider logs
out on close to avoid accumulating router sessions between syncs. Login failures
report the router's rejection reason when available, including session limits
and temporary login lockouts.
Plugins are convention-loaded from environment variables. No registry edit is needed:
PROXY=npmloadsdns_sync.plugins.sources.npm.PluginDNS_PROVIDER=verizonloadsdns_sync.plugins.providers.verizon.Plugin
Sources: npm, file
Providers: verizon
PROXY=npm
PROXY_IP=192.168.1.101
PROXY_API_PORT=81
PROXY_API_SCHEME=http
DNS_PROVIDER=verizon
DNS_MODE=auto
DOMAIN_MATCHING=false
DOMAIN=example.com
SYNC_INTERVAL=1
NPM_IDENTITY=...
NPM_SECRET=...
ROUTER_PASSWORD=...
DNS_TARGET_IP optionally overrides the default DNS destination, which is PROXY_IP.
Filtering is performed by the central engine after source discovery and before reconciliation, so all source plugins get identical behavior.
DOMAIN_MATCHING=true
DOMAIN=example.com
This includes the apex and every subdomain:
example.com
app.example.com
deep.app.example.com
*.example.com
It excludes unrelated names such as:
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.
DNS_MODE controls how the engine expresses desired DNS state:
records: discover hostnames from the source plugin and reconcile individual records.wildcard: do not enumerate source hosts; create one wildcard desired record forDOMAIN.DOMAINis required and the provider must advertise wildcard support.auto(default): use wildcard mode whenDOMAINis set and the provider supports wildcard records; otherwise use individual records.
Examples:
DNS_MODE=records
DOMAIN_MATCHING=true
DOMAIN=example.com
imports only example.com and its subdomains from the source.
DNS_MODE=wildcard
DOMAIN=example.com
creates the normalized wildcard desired record *.example.com -> PROXY_IP. A dnsmasq-capable provider can translate this to its native domain-wide rule such as address=/example.com/IP.
DNS_MODE=auto
DOMAIN=example.com
uses wildcard mode on providers that advertise wildcard support and falls back to individual records on providers such as Verizon.