Split Dns
Keep the DNS learned from DHCP for the whole host, but send one zone (or sub-zone) to that zone's authoritative nameservers — or to a pinned list — so records you manage update at the zone's speed instead of the DHCP resolver's cache. WHAT IT DOES
Split-horizon DNS for Debian, Ubuntu and NixOS, run on the host it
configures. A managed dnsmasq forwarder listens on loopback and the host
resolver points at it. dnsmasq keeps the DHCP nameservers for everything —
read live from systemd-resolved or NetworkManager, with
/etc/resolv.conf/resolvectl/nmcli fallbacks — and adds
server=/<zone>/<ns> exceptions for the zones you name. Empty
nameservers discovers the zone's authoritative NS. The model stages
apply, rollback and verification scripts unprivileged; the workflow runs
the apply script through @svendowideit/sudo's runScript operation, using
the host's granted sudo, doas, polkit or container route. Every change is
validated, verified live, and rolled back to the previous working snapshot
with the failing query and output on error.
INSTALL
swamp extension pull @svendowideit/split-dnsDEPENDENCIES
@svendowideit/sudo is pulled automatically. The target needs systemd
(Debian/Ubuntu) or NixOS with nixos-rebuild, and at least one elevation
route @svendowideit/sudo can prove without prompting (sudo -n, doas -n,
run0/systemd-run, a container runtime group, or a held capability). On
Debian/Ubuntu, dnsmasq-base and, when dig is absent, bind9-dnsutils are
installed through apt unless installPackages=false. The zone's nameservers
must be reachable from the target.
RUN
# Run this on the host being configured. It stages the scripts and runs
# the apply through @svendowideit/sudo on that same host.
swamp workflow run @svendowideit/split-dns-apply --input zone=fi.gy
# Dispatch the worker variant to a host's swamp worker. This is how blue
# is configured from x1yoga's orchestrator.
swamp workflow run @svendowideit/split-dns-apply-worker \
--server wss://swamp.x1yoga.fi.gy \
--input worker=figy-blue \
--input zone=fi.gy
# Send a delegated sub-zone to a faster provider instead. Omit
# `nameservers` to discover that sub-zone's authoritative NS records.
swamp workflow run @svendowideit/split-dns-apply \
--input zone=dev.fi.gy \
--input 'nameservers:json=["ns-164-a.gandi.net","ns-22-b.gandi.net"]'
# Plan and stage without running the elevation step.
swamp workflow run @svendowideit/split-dns-apply --input dryRun=true
# Read-only preflight of the fleet in the `split-dns` model's hosts list.
swamp model method run split-dns plan --input zone=fi.gy
# Report configured zones, upstream, service and live queries.
swamp model method run split-dns status
# Remove the forwarder and restore the pre-install resolver.
swamp workflow run @svendowideit/split-dns-removeCONFIGURE
The local apply workflow takes model, zone (default fi.gy),
nameservers (empty discovers authoritative NS), replaceZones,
removeZone, sudoModel (default sudo-split-dns), sudoTimeoutSeconds,
stagingDir (default ~/.cache/split-dns), configDir (default
/etc/split-dns), listenAddress, listenPort, manageResolver,
controlName, testName and dryRun. The -worker variants add a
worker input (default figy-blue) and dispatch every step to it.
stage, stageRemove and verify are host-local; the workflow's worker
target places them on the same host as the sudo step. replaceZones=false
merges, so one run per zone accumulates exceptions. manageResolver=false
installs and runs the forwarder without touching /etc/resolv.conf; with
manageResolver=true, listenPort must be 53 because resolv.conf cannot
carry a port. The model's plan/status methods still accept the hosts
fan-out for read-only fleet checks over SSH.
UPGRADE
swamp workflow run @svendowideit/split-dns-upgrade Run after swamp extension update, on each host (or with its worker).
It reconciles every zone already stored on that host, refreshes the DHCP
upstream view, stages and runs the apply (a no-op when unchanged), and
verifies the live queries. It is idempotent and safe to re-run.
WHAT IT INSTALLS
Debian/Ubuntu: the split-dns systemd unit in /etc/systemd/system, the
managed dnsmasq config and state under /etc/split-dns, a NetworkManager
drop-in (rc-manager=unmanaged) or a resolvconf head fragment, and a
static /etc/resolv.conf pointing at the forwarder. NixOS: the same
/etc/split-dns files plus /etc/nixos/split-dns.nix and
/etc/nixos/split-dns-configuration.nix, networking.resolvconf.enable = false, pkgs.dnsmasq and pkgs.bind.dnsutils in systemPackages, and a
staged script PATH/NIX_PATH that makes nixos-rebuild work from the
container route.
It installs dnsmasq-base and, when needed, bind9-dnsutils with apt on
Debian/Ubuntu. It stages scripts under ~/.cache/split-dns. Pre-apply
snapshots live under /etc/split-dns/backups; the first pre-install
snapshot lives under /etc/split-dns/original. It creates the
sudo-split-dns model of @svendowideit/sudo with runScript allowed.
No webhooks, cron triggers, or additional services.
Global Arguments
| Argument | Type | Description |
|---|---|---|
| hosts? | array | Default host targets. Empty means the local host. Accepts |
| connectTimeoutSec? | number | SSH connect timeout in seconds. |
| commandTimeoutSec? | number | Per-command timeout in seconds. `nixos-rebuild` can be slow. |
| parallel? | number | Maximum hosts to process at once. |
| listenPort? | number | Port dnsmasq listens on. |
| manageResolver? | boolean | Point the host's client resolver at dnsmasq. Set false to install and |
| installPackages? | boolean | Install `dnsmasq-base` with apt on Debian/Ubuntu when dnsmasq is missing. |
| nixosRebuild? | boolean | Run `nixos-rebuild switch` on NixOS so the generated module is active. |
| backupKeep? | number | How many pre-apply snapshots to keep for diagnostics. |
| Argument | Type | Description |
|---|---|---|
| hosts? | array | |
| nameservers? | array | Nameserver IPs or hostnames for the zone. Empty discovers the zone's |
| replaceZones? | boolean | Replace every managed zone with this run's zone instead of merging. |
| removeZone? | boolean | Remove this zone's override instead of adding/updating it. |
| manageResolver? | boolean | |
| installPackages? | boolean | |
| parallel? | number | |
| dryRun? | boolean | Validate and report the plan without changing any host. |
| listenPort? | number | |
| nixosRebuild? | boolean |
| Argument | Type | Description |
|---|---|---|
| hosts? | array | |
| nameservers? | array | Nameserver IPs or hostnames for the zone. Empty discovers the zone's |
| replaceZones? | boolean | Replace every managed zone with this run's zone instead of merging. |
| removeZone? | boolean | Remove this zone's override instead of adding/updating it. |
| manageResolver? | boolean | |
| installPackages? | boolean | |
| parallel? | number | |
| dryRun? | boolean | Validate and report the plan without changing any host. |
| listenPort? | number | |
| nixosRebuild? | boolean |
| Argument | Type | Description |
|---|---|---|
| hosts? | array | |
| manageResolver? | boolean | |
| parallel? | number |
| Argument | Type | Description |
|---|---|---|
| hosts? | array | |
| nameservers? | array | Nameserver IPs or hostnames for the zone. Empty discovers the zone's |
| replaceZones? | boolean | Replace every managed zone with this run's zone instead of merging. |
| removeZone? | boolean | Remove this zone's override instead of adding/updating it. |
| manageResolver? | boolean | |
| installPackages? | boolean | |
| parallel? | number | |
| dryRun? | boolean | Validate and report the plan without changing any host. |
| listenPort? | number | |
| nixosRebuild? | boolean |
Resources
Keep the DHCP-provided DNS for everything, and send one zone (or sub-zone) to that zone's authoritative nameservers — or to an explicit list — so the records you manage update as fast as the zone's nameservers publish them. This workflow is host-local. Run it on the machine it configures. It never calls sudo itself: `stage` writes the apply/rollback/verify scripts into an unprivileged staging dir, and the `elevate` step runs the apply script through @svendowideit/sudo's `runScript` operation, s
Bring an existing split-DNS install up to date with this extension. Run this on the host that owns the install. It re-runs the host-local stage with no zone, which means "reconcile every zone already stored on the host": it re-reads the host, refreshes the DHCP upstream view, re-renders and validates the dnsmasq config, stages the apply/rollback/verify scripts, and runs the apply script through @svendowideit/sudo's `runScript` operation. The script restarts only when something changed (or when
Remove the split-DNS forwarder from this host and restore the pre-install resolver, with the privileged removal delegated to @svendowideit/sudo. Run it on the host that owns the install. `stageRemove` writes remove.sh and its verification gate into an unprivileged staging dir; the `elevate` step runs remove.sh through @svendowideit/sudo's `runScript` operation; `verify` (expect=removed) confirms the service and generated files are gone and normal DNS answers. The remove script restores the firs
Keep the DHCP-provided DNS for everything, and send one zone (or sub-zone) to that zone's authoritative nameservers — or to an explicit list — so the records you manage update as fast as the zone's nameservers publish them. This is the worker variant: every step is dispatched to `worker`, so the staging, @svendowideit/sudo elevation and verification all run on that host. It never calls sudo itself: `stage` writes the apply/rollback/verify scripts into an unprivileged staging dir, and the `eleva
Bring an existing split-DNS install up to date with this extension. This is the worker variant: every step is dispatched to `worker`. It re-runs the host-local stage with no zone, which means "reconcile every zone already stored on the host": it re-reads the host, refreshes the DHCP upstream view, re-renders and validates the dnsmasq config, stages the apply/rollback/verify scripts, and runs the apply script through @svendowideit/sudo's `runScript` operation. The script restarts only when somet
Remove the split-DNS forwarder from this host and restore the pre-install resolver, with the privileged removal delegated to @svendowideit/sudo. This is the worker variant: every step is dispatched to `worker`. `stageRemove` writes remove.sh and its verification gate into an unprivileged staging dir; the `elevate` step runs remove.sh through @svendowideit/sudo's `runScript` operation; `verify` (expect=removed) confirms the service and generated files are gone and normal DNS answers. The remove
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit1/2partial
- @std/assert--jsr:@std/assert@1 is a range; audited 1.0.19, the version the registry resolved
1 dependency found
- jsr:@std/assert@1 -- audited 1.0.19
- Docs keep secrets in a vault1/1earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License2/2earned
- Verified public repository2/2earned
Spawns a subprocess with Deno.Command().
- models/split_dns.ts:1097 — only ssh/sh are used; args are built from typed host specs and the payload is piped via stdin instead of being interpolated into a shell string.
- files/split_dns_test.ts:645 — no reason given