Skip to main content

Split Dns

@svendowideit/split-dnsv2026.10.26.2beta· MIT· 1d agoBEHAVIORSWORKFLOWSACCEPTED WARNINGS
01README

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-dns

DEPENDENCIES

@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-remove

CONFIGURE

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.

02Behaviors1
@svendowideit/split-dnsv2026.10.26.2split_dns.ts

Global Arguments

ArgumentTypeDescription
hosts?arrayDefault host targets. Empty means the local host. Accepts
connectTimeoutSec?numberSSH connect timeout in seconds.
commandTimeoutSec?numberPer-command timeout in seconds. `nixos-rebuild` can be slow.
parallel?numberMaximum hosts to process at once.
listenPort?numberPort dnsmasq listens on.
manageResolver?booleanPoint the host's client resolver at dnsmasq. Set false to install and
installPackages?booleanInstall `dnsmasq-base` with apt on Debian/Ubuntu when dnsmasq is missing.
nixosRebuild?booleanRun `nixos-rebuild switch` on NixOS so the generated module is active.
backupKeep?numberHow many pre-apply snapshots to keep for diagnostics.
fn plan(hosts?: array, nameservers?: array, replaceZones?: boolean, removeZone?: boolean, manageResolver?: boolean, installPackages?: boolean, parallel?: number, dryRun?: boolean, listenPort?: number, nixosRebuild?: boolean)
Inspect each host, discover the zone's authoritative nameservers
ArgumentTypeDescription
hosts?array
nameservers?arrayNameserver IPs or hostnames for the zone. Empty discovers the zone's
replaceZones?booleanReplace every managed zone with this run's zone instead of merging.
removeZone?booleanRemove this zone's override instead of adding/updating it.
manageResolver?boolean
installPackages?boolean
parallel?number
dryRun?booleanValidate and report the plan without changing any host.
listenPort?number
nixosRebuild?boolean
fn stage(hosts?: array, nameservers?: array, replaceZones?: boolean, removeZone?: boolean, manageResolver?: boolean, installPackages?: boolean, parallel?: number, dryRun?: boolean, listenPort?: number, nixosRebuild?: boolean)
Render the desired config and stage apply, rollback, remove and
ArgumentTypeDescription
hosts?array
nameservers?arrayNameserver IPs or hostnames for the zone. Empty discovers the zone's
replaceZones?booleanReplace every managed zone with this run's zone instead of merging.
removeZone?booleanRemove this zone's override instead of adding/updating it.
manageResolver?boolean
installPackages?boolean
parallel?number
dryRun?booleanValidate and report the plan without changing any host.
listenPort?number
nixosRebuild?boolean
fn status(hosts?: array, manageResolver?: boolean, parallel?: number)
Report the managed zones, upstream source, config, service and live
ArgumentTypeDescription
hosts?array
manageResolver?boolean
parallel?number
fn stageRemove(hosts?: array, nameservers?: array, replaceZones?: boolean, removeZone?: boolean, manageResolver?: boolean, installPackages?: boolean, parallel?: number, dryRun?: boolean, listenPort?: number, nixosRebuild?: boolean)
Render the remove script (and its verification gate) into the
ArgumentTypeDescription
hosts?array
nameservers?arrayNameserver IPs or hostnames for the zone. Empty discovers the zone's
replaceZones?booleanReplace every managed zone with this run's zone instead of merging.
removeZone?booleanRemove this zone's override instead of adding/updating it.
manageResolver?boolean
installPackages?boolean
parallel?number
dryRun?booleanValidate and report the plan without changing any host.
listenPort?number
nixosRebuild?boolean
fn verify()
Verify the live result on this host after the sudo step: the

Resources

hostResult(infinite)— One host's plan, applied state, verification and rollback outcome.
summary(infinite)— Per-run roll-up of host results.
staged(7d)— Scripts staged for @svendowideit/sudo to run on this host.
03Workflows6
@svendowideit/split-dns-apply2ae39005-79e6-446f-9f8e-4ebac0d3b7d1

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

applyPlan, stage, elevate through @svendowideit/sudo, and verify.
1.plan— Read this host, discover the zone's authoritative nameservers when none are given, render the config, run dnsmasq --test, and query a temporary resolver. Changes nothing.
2.stage— Write apply.sh, rollback.sh, remove.sh and verify.sh into stagingDir. No root is needed and nothing outside stagingDir is changed.
3.elevate— Run the staged apply script as root through @svendowideit/sudo's runScript operation. The script activates the forwarder, verifies the live resolver, and restores the pre-apply snapshot on failure.
4.verify— Confirm the live resolver returns the managed zone and the control name. Fails with the exact query output if the host did not converge.
@svendowideit/split-dns-upgradec64b27b4-0986-48d1-9448-0e7853cc3d19

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

upgradeReconcile every stored zone and verify the live resolver.
1.plan— Re-read this host and render the existing zone set. Read-only.
2.stage— Stage apply/rollback/remove/verify scripts for the stored zone set.
3.elevate— Run the staged apply script as root through @svendowideit/sudo. Skipped when the host has no managed zones.
4.verify— Confirm the stored zones still resolve through the live resolver. Skipped when the host has no managed zones.
@svendowideit/split-dns-remove22356a6d-ee0e-4953-833b-09e24a0269b6

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

removeStage, elevate through @svendowideit/sudo, and verify removal.
1.stage-remove— Write remove.sh and its verification gate into stagingDir. No root is needed and nothing outside stagingDir is changed.
2.elevate— Run the staged remove script as root through @svendowideit/sudo. Skipped when the host has no split-dns install.
3.verify— Confirm the service, config and markers are gone and normal DNS answers. Skipped when there was nothing to remove.
@svendowideit/split-dns-apply-worker1e417f9b-51c3-4363-9421-b41abd7cc008

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

applyPlan, stage, elevate through @svendowideit/sudo, and verify.
1.plan— Read this host, discover the zone's authoritative nameservers when none are given, render the config, run dnsmasq --test, and query a temporary resolver. Changes nothing.
2.stage— Write apply.sh, rollback.sh, remove.sh and verify.sh into stagingDir. No root is needed and nothing outside stagingDir is changed.
3.elevate— Run the staged apply script as root through @svendowideit/sudo's runScript operation. The script activates the forwarder, verifies the live resolver, and restores the pre-apply snapshot on failure.
4.verify— Confirm the live resolver returns the managed zone and the control name. Fails with the exact query output if the host did not converge.
@svendowideit/split-dns-upgrade-worker6c6f336b-f429-4c0a-a9d6-7bda4c525a74

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

upgradeReconcile every stored zone and verify the live resolver.
1.plan— Re-read this host and render the existing zone set. Read-only.
2.stage— Stage apply/rollback/remove/verify scripts for the stored zone set.
3.elevate— Run the staged apply script as root through @svendowideit/sudo. Skipped when the host has no managed zones.
4.verify— Confirm the stored zones still resolve through the live resolver. Skipped when the host has no managed zones.
@svendowideit/split-dns-remove-worker5023ba7f-7938-4763-9d67-f6058c019006

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

removeStage, elevate through @svendowideit/sudo, and verify removal.
1.stage-remove— Write remove.sh and its verification gate into stagingDir. No root is needed and nothing outside stagingDir is changed.
2.elevate— Run the staged remove script as root through @svendowideit/sudo. Skipped when the host has no split-dns install.
3.verify— Confirm the service, config and markers are gone and normal DNS answers. Skipped when there was nothing to remove.
04Previous Versions1
2026.10.26.1MIT
05Stats
A
93 / 100
Downloads
2
Archive size
68.6 KB
  • 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

07Platforms