Skip to content

Host DNS and domain resolution for Pigsty on Barn

An explicit hosts workflow, Pigsty-owned name semantics, source replacement safeguards, and an optional split-DNS upgrade path for macOS and Linux.
Note

Scope narrowed on 2026-11-11: the first delivery is now fixed core names and Pigsty CA trust. This page retains the broader exporter and resolver research; its general inventory and split-DNS work is optional expansion, not a prerequisite for the fixed profile.

Recommend explicit, opt-in managed hosts publication first. Pigsty computes the names and service targets; Barn previews and installs one neutral address/name snapshot through its existing helper. This is worthwhile for opening portals, addressing nodes and connecting to database or VIP names in a static Linux lab. Fixed addresses make repeated synchronization cheap: unchanged records need no privileged operation. Root is still required for every actual system hosts change, including removal. Setup does not grant perpetual update permission, and a cached or passwordless sudo authorization eliminates a password prompt, not privilege. Keep per-command authorization; consider suffix forwarding when a stable guest DNS endpoint and a complete namespace exist. A host daemon needs demonstrated demand beyond these two options.

Status: design dated 2026-11-10; evidence gathering and synthesis crossed into 2026-11-11, Asia/Shanghai. The checked Barn v1.1.0 source at b2cc8da has applied-node hosts publication. vm_hosts, hosts install -f, case normalization, and Pigsty bin/sandbox-dns are dirty, unpublished prototypes audited on those dates. Source selection guards, exporter modes, stronger transactions and DNS adapters below are proposals. Release binaries and real-platform behavior were not validated. The migration article provides broader context; this design specifies the DNS work independently.

Choose the smallest useful publication mechanism

Alternative Coverage and daily operation Privilege and failure behavior Decision
Managed hosts Finite short names and FQDNs; explicit complete synchronization; resolution survives guest shutdown, although connections fail Every changed write/removal is privileged; identical snapshot is a no-op; no resident service Recommended first implementation, always opt-in
Suffix forwarding to guest DNS Records actually served within an agreed zone; guest-side changes propagate without rewriting the host route Privileged route installation, endpoint changes and removal; stable routes need no host privilege for record updates; guest DNS becomes a dependency Optional next step, with separate macOS/Linux adapters and short-name handling
Host-local DNS daemon Can support frequent record changes, wildcard or additional RR types; user-owned data can update independently of fixed system routing Privileged service/routing setup and changes; daemon identity, ports, startup, crashes and upgrades become maintenance obligations Defer until measured demand justifies it
Zero automatic configuration / manual IPs, existing DNS or explicitly reviewed application/system overrides Barn makes no system changes; user owns integration and cleanup Always available; normal setup and VM lifecycle default to this policy

With no automation, Barn’s SSH aliases and direct IP access remain usable. Multi-portal Host/TLS names, browser URLs and application settings still need existing DNS or manual overrides. Per-application overrides do not provide system-wide resolution, and manually copied mappings drift as the inventory changes.

The boundary remains one Linux deployment per user, one inventory format and one host-wide Barn block. A different BARN_HOME does not create another hosts namespace. Pigsty owns portal, PostgreSQL, VIP and MinIO meaning; Barn neither discovers Patroni leaders nor learns Pigsty roles. This preserves the Barn contribution contract.

flowchart LR
  I["Pigsty inventory + context"] --> P["Pigsty rules + selected view"]
  P --> S["all.hosts + vm_hosts snapshot"]
  S --> B["Barn offline plan"]
  B --> H["Privileged helper"]
  H --> F["One managed hosts block"]

Define names and views before choosing a resolver

The default proposed export is host-access intent, a useful union of selected portal ingress names, node identities, explicit records and service aliases. It can intentionally contain names absent from guest dnsmasq. Keep separate categories—vm-identity, guest-hosts, infra-dns, portal-access, service-access—and retain their source, view and target type in the export report. A guest-dns view instead reproduces the selected infra DNS output and its task conditions. Shared computation must unify identity and target rules while allowing these different output policies.

This table is the authoritative mapping policy. The source column describes audited Pigsty behavior; the final column is the proposed host-access policy.

Name source Audited meaning and destination Host-access publication policy
Barn node identity and vm_alias nodename, otherwise pg_cluster-pg_seq, otherwise node-lastOctet; aliases resolve to the node address Include resolved VM names/aliases. Use vm_hosts for new service names so service edits do not change VM specifications. Barn inventory
node_default_etc_hosts, node_etc_hosts Guest hosts entries, gated by node_write_etc_hosts; substitute that node’s effective ${admin_ip} Include the selected view’s enabled entries as guest-hosts; parse each physical line separately. Node-name exchange is a separate task context. Node DNS tasks
dns_records Infra hosts files with ${admin_ip} substituted; dns_enabled gates service startup, not all file generation or removal of a running service Include declared records with desired-config provenance; never infer DNS liveness from this switch. Guest dnsmasq uses no-hosts, so guest /etc/hosts is not automatically served. Infra tasks, dnsmasq template
infra_portal.*.domain/domains Nginx names, not automatic DNS records. Tasks require a nonempty domain; standard home/path/endpoint templates support aliases Map enabled virtualhosts to an explicitly selected ingress, typically its effective admin_ip. Never use the backend endpoint, which may be loopback. Reject unsupported custom-template semantics; omit a domains-only skipped virtualhost with a diagnostic. Portal task
PG instance pg_cluster-pg_seq Inventory address; PG DNS tasks are gated by the PG node’s dns_enabled. Instance names receive no pg_dns_suffix Include valid instance aliases as service-access even when guest DNS is disabled, explicitly labelled host convenience. This is not evidence of a deployed DNS record. PG DNS tasks
PG cluster pg_cluster + pg_dns_suffix Suffix concatenates directly, default empty. auto: enabled PG VIP, otherwise declared primary; primary: declared primary; vip: enabled VIP, otherwise no address; none: no address; explicit IP: configured target Apply those target rules for service-access, labelling DNS-disabled convenience aliases. Require a unique declared primary where needed. Reject explicit CIDR targets: current role output retains the mask, while the prototype silently strips it. PG VIP branches deliberately strip the VIP mask. PG defaults, PG identity
Node VIP node_cluster + vip_dns_suffix Enabled vip_address, independent of PG VIP; effective node identity rules matter Include validated bare VIP addresses. Reject CIDR here pending a shared role/export correction, because the current DNS task writes the value unchanged. Node monitor tasks
MinIO member names Expanded minio_node plus minio_cluster-minio_seq map to member IPs Include member identities. Publish a MinIO service name only when declared through dns_records, enabled node hosts, portal policy or explicit vm_hosts; minio_domain alone supplies certificate/client naming, not a DNS record. MinIO identity, MinIO configuration
Explicit all.vars.vm_hosts Prototype-only neutral {address, names} entries Include as explicit intent, with the same conflict checks; explicit entries do not silently override contradictory derived records

Barn VM names are short lowercase labels, at most 63 characters. Pigsty can rename the guest differently: node_id_from_pg: true can override an explicit nodename, while Barn’s fallback does not consult that switch. For matching identities, choose an explicit short nodename and disable Pigsty’s PG-derived override where appropriate; put FQDNs in aliases. See Pigsty node identity.

Multiple infra nodes can legitimately have different admin_ip and i.pigsty views. The host needs one explicit ingress/view, distinct names, or a stable shared entry point. Never choose the first infra node. DNS replicas qualify as redundant upstreams only after their relevant answers and published generation agree.

Export one existing inventory shape, with honest limits

The viable prototype boundary is this minimal projection of the existing inventory format. The .100 target below is illustrative and requires a separately configured, reachable VIP.

host-names.yml — prototype-compatible shape
all:
  hosts:
    10.10.10.10: {nodename: meta}
  vars:
    vm_hosts:
      - address: 10.10.10.10
        names: [i.pigsty, meta]
      - address: 10.10.10.100
        names: [pg-meta]

vm_hosts is allowed only under all.vars; entries accept exactly address and a nonempty string list names. Provenance stays in a separate report, never extra entry keys. At least one managed host is required: a vm_hosts-only document currently fails with inventory defines no hosts. all.hosts supplies node identity and /24 context. This generated projection is disposable output, not another user-maintained inventory. It can be published before VM creation; it does not prove that nodes or services exist.

The current Barn adapter accepts one canonical RFC1918 IPv4 address per name, inside the managed /24, with usable suffixes .9–.254. .1 belongs to the host; .2–.8 are reserved; .0 and .255 are unusable. Multiple A answers, external destinations, IPv6, wildcards and non-address RR types require an explicit alternative; hosts or DNS capabilities in general do not widen this adapter. Custom private /24s work only after Pigsty’s admin_ip, records and VIPs are adjusted consistently. DNS creates neither routes nor VIPs. Barn network layout

Ship a limited literal mode before general Ansible support

Proposed modes: literal produces desired host-access intent for declared static profiles; rendered computes a selected view using the normal Pigsty controller’s effective variables and role rules; observed explicitly collects a chosen infra node’s deployed DNS data and service observations. Rendered intent can differ from installed files after skipped/failed tasks; observed files can differ from active answers. Reports must state which evidence was collected and when. Neither mode means live Patroni authority.

Phase 1 literal support is deliberately narrow: one YAML source, known defaults kept in their role contexts, unambiguous inheritance, supported standard portal templates and the documented ${admin_ip}/MinIO substitutions. Detect and reject adjacent/controller group_vars or host_vars, multiple sources/plugins, DNS-relevant Jinja/lookups/facts, ambiguous sibling overrides and unsupported task contexts. Never silently ignore a variable source. Parse escaped and block newlines as separate records; reject malformed types and CIDR cases rather than repairing them into different role semantics.

Compute identities and targets across all relevant Pigsty hosts, including vm_skip external primary/infra nodes, before applying Barn’s address boundary. Until that calculation is supported, refuse affected profiles rather than omit their roles. Include all known external addresses in VIP collision checks. An unrelated skipped host is not permission to discard DNS-relevant configuration.

Phase 2 introduces shared Pigsty pure rules plus an effective-variable adapter, with role-render equivalence tests within each selected view. ansible-inventory --list handles inventory variables, not every role default, set_fact, task condition or controller template. General support needs explicit context for those inputs and refusal of unresolved values. Controlled read-only export can run on the normal Pigsty controller and transfer the sanitized projection to the host; host users need not install Ansible. Complete pre-VM export remains limited to profiles whose inputs are available without guest facts. Ansible variable merging

Bind validation and extraction to one input generation, checking relevant source/default files for changes before publication. Emit only node identities and names into a mode-0600 temporary file; never persist full hostvars, credentials or share settings. Proposed --mode, --view and --export options belong to Pigsty, not Barn. Default --view host-access requires an unambiguous ingress; host-access:<infra-node> explicitly selects its view, while guest-dns:<infra-node> selects DNS output. Phase 1 rejects unsupported selections. observed initially supports only guest-dns, without inventing convenience records. --export FILE writes only the projection and is mutually exclusive with applying it. Existing --json retains its Barn-plan meaning.

Make source replacement explicit and preserve service names

Evidence What it establishes What it cannot establish
Current desired inventory/export Names to publish, including before VM creation Applied nodes, successful Pigsty tasks or runtime DNS
Barn applied node state Committed node addresses, names and vm_alias, including stopped nodes vm_hosts, portals, service aliases or current leader
Installed block and metadata Current host file contents and the recorded source kind A current source pathname, live guests or fresh deployed DNS

The prototype replaces the entire block. Consequently, running bare hosts install after hosts install -f can remove inventory-only service aliases. Proposed minimal guard: store one stable ordinary comment, # source-kind: inventory or # source-kind: applied, inside the existing block. Do not invent a # barn: marker: the current parser rejects unknown markers. This metadata is a CLI source safeguard, independent of OS authorization.

Installed block Bare hosts install under the proposal Explicit source selection
Absent, legacy unmarked, or applied Preserve tagged behavior: derive applied-node snapshot, preview/confirm as usual -f FILE selects inventory; --from-applied selects applied state
inventory Fail before sudo, including with --yes; explain how to retain or replace services -f FILE replaces with a complete inventory snapshot; proposed --from-applied explicitly authorizes node-only replacement, showing removals
Unknown/duplicate source metadata or malformed block Fail with repair guidance No automatic overwrite

-f and proposed --from-applied are mutually exclusive. A different -f is itself an explicit source switch. Remember no source pathname, particularly no Pigsty temporary path; introduce no publisher registry or project scopes. Omitted records disappear on the next complete synchronization. vm_hosts: [] leaves that source’s node names/aliases. Failed extraction or an invalid empty inventory must never clear the block; complete removal uses hosts uninstall. Removing a name never destroys a VM.

Fingerprint published canonical records and source kind for semantic no-op detection. Keep input hashes, rules version, selected view and detailed provenance in the unprivileged report, not timestamped block comments. Password, CPU, file-path or irrelevant inventory edits must not force root writes. Still compare actual target bytes: a matching record digest cannot hide a damaged marker or external edit. Adding the source comment to an old block is a one-time privileged migration.

An unmarked prototype block cannot reliably reveal whether it contained inventory services. Upgrade by explicitly reinstalling the complete -f snapshot before using bare install. Require a CLI implementing this guard and its matching helper; the minimum release number remains to be assigned. Old CLIs can overwrite the block despite its comment, so a downgrade is not safe. The wrapper must check required capabilities, not merely a version string.

Preview offline, then authorize the actual change

The audited prototype already has the following interfaces; their existence does not satisfy the implementation gates below.

Existing interface Exact behavior
bin/sandbox-dns -f FILE --json Barn plan JSON, not a record export; default file is pigsty.yml
bin/sandbox-dns -f FILE --apply Wrapper previews, then invokes Barn with --yes
barn hosts install -f FILE --json Inventory preview, no application
barn hosts install -f FILE --yes Apply inventory snapshot; Barn has no --apply flag
barn hosts install -f FILE --json --yes Apply and return structured output; JSON does not imply read-only
barn hosts uninstall --json / --yes Preview / explicitly remove the whole Barn block

Proposed operator workflow after Phase 1 acceptance: preview and explicitly apply from Pigsty, repeating the same process after portal, node or VIP changes. These two command spellings exist in the prototype; the safer semantics are proposed.

Proposed routine workflow
bin/sandbox-dns -f pigsty.yml --json
bin/sandbox-dns -f pigsty.yml --apply

For review or controller-to-host transfer, the proposed exporter produces the same disposable projection. Only --mode, --view and --export below are new options.

Proposed explicit export workflow
bin/sandbox-dns -f pigsty.yml --mode literal --view host-access --export host-names.yml
barn hosts install -f host-names.yml --json
barn hosts install -f host-names.yml --yes

Returning to node-only publication intentionally removes service aliases. The proposed --from-applied makes that replacement explicit; uninstall already exists and needs neither inventory nor deployment state.

Proposed source switch, followed by independent cleanup examples
barn hosts install --from-applied --json
barn hosts install --from-applied --yes
barn hosts uninstall --json
barn hosts uninstall --yes

Every applying invocation must build and display its own complete plan before requesting root: added, changed, removed and retained names; old/new addresses; category, source kind and provenance; conflicts; native target; record generation and byte preconditions. Proposed diffs must account for names removed from the previous block even when their old field-level provenance is unavailable. Plans use local files/state only—no DNS lookup, guest connection, downloads or helper installation. An observed export is collected separately before planning. Do not print secrets or full hostvars.

Proposed outcome contract Result
Preview (--json without --yes, or no terminal without --yes) Exit 0 on a valid plan, applied: false; no sudo, even if changes exist
Unchanged semantic records and valid installed bytes Exit 0, changed: false, no helper installation or sudo
Interactive text without --yes Show plan and ask; Barn install defaults Yes and uninstall No; wrapper remains preview-only without --apply
Explicit apply without a terminal Use nonblocking authorization; lack of permission is a capability error, nonzero exit, no password wait and no hosts write
Invalid input, source switch violation, conflict or stale generation Nonzero error before write; require a fresh plan, never silently regenerate and commit different data
Verified write / post-write failure Exit 0 only after verification; otherwise report failure and any partial state, without claiming a successful rollback

Keep Barn’s {plan, applied} report shape. Its current plan has schema, action, target, changed, before/after hashes and optional lines/helper digest; source/diff details are additions. The current Python wrapper does not guarantee JSON errors; structured failure parity is a Phase 1 task.

Strengthen the existing privileged boundary

Current CLI planning limits normal edits to its block. The fixed helper validates the native target, caller/staging ownership, permissions and hashes, uses atomic replacement and verifies resulting bytes plus uid/gid/mode. It does not independently prove that the staged file changes only the Barn block, and no transaction lock spans check and rename. Passing tests do not cover those gaps. A caller-supplied digest proves byte identity, not allowed scope; atomic rename does not provide compare-and-swap. Tagged helper implementation

Proposed application keeps the same boundary: the helper acquires a root-owned host-wide cooperative lock, rereads the target, checks the reviewed digest, validates bounded records/source metadata and conflicts, reconstructs the permitted replacement itself, then compares, writes atomically and verifies. Uninstall may only remove that block. Preserve outside bytes, with the documented first-append newline exception; test additional filesystem metadata before promising its preservation. Retain stale-byte checks because unrelated root editors need not honor Barn’s lock. Freeze the generated source through application; the wrapper must bind its displayed plan to the apply request, or let one Barn invocation display and apply the authoritative plan, rather than discard a preview between subprocesses.

Per-command sudo remains primary. Setup installs a root-owned, digest-matched helper; actual writes still execute with privilege. --yes bypasses Barn confirmation only. Current noninteractive authorization uses sudo -n, while an interactive invocation can ask for authentication. Persistent narrow sudo authorization is optional future work after helper-side scope validation and locking; never authorize arbitrary configuration includes or expand to unrestricted sudo. A helper-only rule also needs a scoped capability probe: today’s sudo -v/true probes can reject such a rule. Sudo behavior, sudoers command arguments

Own cleanup separately from VM lifecycle

Event Proposed ownership and residual state
First setup / boot / up Prepare networking/helper as appropriate; offer the explicit DNS workflow. Publish only on opt-in; inventory intent may precede VMs. No implicit hosts synchronization
Repeat sync / guest provisioning Pigsty regenerates the complete selected view; Barn compares and publishes. Provisioning does not silently elevate the host
stop, start or restart Keep names. Report that a resolved address may be offline; no root request just for lifecycle
Node removal, destroy or deployment purge Keep the installed block; display lingering names and the explicit cleanup command. With a desired source, report its diff; with only applied state, diagnose absent nodes without guessing service intent
hosts uninstall Remove only the Barn block; preserve current outside content, VMs, disks and guest DNS
Delete BARN_HOME / reinstall tool Cleanup remains possible from the native block. Reinstall a compatible CLI/helper and run hosts uninstall; neither deleted state nor remembered source is required
Uninstall Barn software Explain that removing binaries leaves host records. Offer cleanup first as a separate explicit operation; retain a documented compatible-reinstall or administrator block-edit recovery path

Proposed diagnostics compare source kind, canonical record generation and current bytes, rather than merely noticing a begin marker. They distinguish stale desired exports, unavailable applied state and stopped guests. A fresh publication is not a service health check.

Old Pigsty Vagrant DNS uses per-line # pigsty dns comments, not a paired block, and maps a fixed alias list to the first VM. Barn must preserve these foreign lines: same-address duplicates remain; different addresses conflict. Migration first inventories the marked lines, compares them against the complete new view and highlights old-only names. An explicit Pigsty migration step or administrator then removes only reviewed old records, with a fresh file comparison, before Barn publishes. Do not delete by .pigsty suffix or reuse the old script’s broad substring deletion. Barn’s narrow helper must not acquire Vagrant-line ownership. Verify duplicates are gone so later Barn uninstall really removes the intended names.

Treat service reachability and HA as separate contracts

pg_primary_ip is the inventory’s unique declared primary, including relevant external hosts; it is not the current Patroni leader. The audited failover callback does not update DNS. Direct PostgreSQL connections to that address retain static-node semantics. HAProxy role ports at that same address can route to the current backend role, but the proxy node’s address must itself remain reachable. A reachable VIP or redundant proxy entry point provides a stable publication target; vip-manager can move the PG VIP according to the DCS leader. DNS server redundancy, correct service target and successful client reconnection need separate tests; existing TCP connections do not migrate when DNS changes.

Nine audited configuration paths have reserved PG VIP conflicts: demo/debian, demo/el, fat, ha/dual, ha/full, ha/octo, ha/safe, ha/simu, ha/trio. The current pigsty.yml separately has .2 conflicts. build/dev and build/oss need explicit host-facing i.pigsty selection; demo/demo needs a short Barn name instead of its FQDN nodename. Correct all dependent records, URLs and connection settings together.

Candidate .100/.101/.102 addresses had zero literal matches in raw and YAML-decoded scans of 54 configuration paths, current pigsty.yml and 7 role defaults: 62 paths total. This only supports allocation within that audited corpus. It is not an IPAM reservation or a check of DHCP leases, ARP, external nodes, generated expressions or the live network. VIP collision and failover tests remain required on each target platform; relaxing Barn’s reserved-address validator cannot solve a network conflict.

Upgrade to split DNS only with a complete namespace

Keep legacy .pigsty unchanged by default. For an optional new zone, prefer the narrowly scoped pigsty.test; .test is reserved for testing, whereas .pigsty has no special-use reservation. Avoid .local, which belongs to mDNS. Changing zones also requires coordinated records, portal URLs and certificates; a routing rule alone cannot rename an application. IANA registry, RFC 6761, RFC 6762

Suffix forwarding does not solve current short names. Neither /etc/resolver/pigsty nor Linux ~pigsty automatically sends bare meta, pg-meta or pg-meta-1 to Pigsty. The audited cluster suffix defaults empty; instance records have no suffix. Before enabling a route, publish corresponding node/instance FQDNs and, for clusters, an explicit pg_dns_suffix, such as .pigsty.test. Keep finite hosts aliases for short-name compatibility by default. Search-domain installation is a separate opt-in and works only when the completed FQDN actually exists. A local high-port daemon has exactly the same routing prerequisite. systemd routing versus search

macOS uses one owned resolver file

Proposed configuration, not installed:

/private/etc/resolver/barn.pigsty.test
domain pigsty.test
nameserver 10.10.10.10
port 53

macOS supports domain-specific resolvers and explicit ports. The filename supplies the domain only when domain is absent; longest suffix matching applies. The proposal uses an explicit domain and no automatic search list. Refuse an existing same-zone route or owned-path collision, including VPN configuration. Create only the missing owned file/directory with appropriate root ownership; keep backups and staging content outside the resolver directory. Verify activation with scutil --dns, dscacheutil and system getaddrinfo(). Direct dig @server tests the DNS server, not macOS native routing. Reinitialization and cache convergence need target-version acceptance. Apple resolver manual, Apple configd parser

Installation, endpoint changes and removal require privilege; guest record updates with an unchanged route do not. Uninstall deletes only the still-matching Barn file and verifies route disappearance. Preserve VPN/default resolvers and later user edits; never restore a whole old DNS configuration.

Linux must detect the resolver and its owner

Detection is read-only: inspect resolved’s running state, NSS hosts: ordering, the /etc/resolv.conf target and the effective NetworkManager DNS plugin. A file listing upstreams directly can bypass resolved’s per-link routing. An active NetworkManager alone proves no split-DNS capability. Probe supported versions/features rather than changing the host’s DNS stack. resolved client paths, NetworkManager plugins

Supported adapter, proposed Persistent installation and activation Removal and limits
networkd + resolved, on an exclusively Barn-owned link Owned drop-in for the verified Barn .network: DNS=10.10.10.10, Domains=~pigsty.test, DNSDefaultRoute=no; reload/reconfigure only that link Remove matching own drop-in, reread effective state; preserve other files and settings
NetworkManager + resolved Address the Barn profile by UUID; set its DNS and ipv4.dns-search=~pigsty.test, reapply and verify resolved Restore only owned properties still matching applied values; never delete a shared profile or change global plugin
NetworkManager with existing dnsmasq plugin Use the Barn profile’s conditional forwarding; custom-port support requires separate detection Same profile ownership rules; do not enable a new global plugin automatically
Runtime resolved Only an active, exclusively Barn-owned non-loopback link; explicitly report lack of reboot persistence Whole-link revert only when all affected configuration is demonstrably Barn-owned; never on shared/VPN/physical links
Unsupported resolver or ambiguous ownership Explain the missing capability and retain hosts/manual mode No global nameserver or /etc/resolv.conf takeover

Never generate ~.. DNSDefaultRoute=no cannot cancel a catch-all domain. Preserve priorities and DHCP/VPN configuration; do not use negative NetworkManager priorities to win unrelated zones. Port 53 is the initial guest-forwarding baseline. Do not copy newer resolved custom-port syntax blindly into older NM profiles. The loopback interface is not a general resolved routing scope; a localhost daemon needs a supported routing mechanism. Last-guest shutdown can remove barn0 carrier or the link itself, invalidating its DNS scope and allowing queries to follow default routes. Strict private-zone non-leakage therefore needs a stable route, server-side negative policy and real tests. networkd DNS settings, NM priority/search, resolvectl reset scope, resolved link eligibility

Both platforms need a root-owned journal outside deployment state recording owned paths/profile UUID, prior existence, applied values and metadata. Prepare and verify upstream UDP/TCP service before enabling routes; compare the reviewed configuration again before writing; verify native resolution afterward. Roll back only still-owned deltas, reporting partial state on drift or interruption. A stored ifindex is not a durable identity. Switching from hosts requires removing overlapping Barn records explicitly and reporting foreign hosts overrides. NM profile persistence and runtime activation must both be checked. nmcli persistence and reapply

A daemon must earn its lifecycle cost

A future daemon should bind loopback only, preferably a supported high port; accept bounded records from the intended user; validate a complete generation before atomic activation; retain the last good generation on bad input. Fixed root-owned service/routing configuration must not execute user-writable programs or arbitrary includes as root. Installation, service upgrades and routing changes remain privileged. User agents end at logout; a system service adds installation and maintenance. Test startup order, restart backoff, port occupation and process identity. Remove routes and verify removal before stopping an intentionally uninstalled daemon. A crash should be diagnosable without silently forwarding private names publicly. Apple high-port implementation

Hosts has no wildcard behavior. Future wildcard DNS should cover only a deliberate shared ingress subzone, not all Pigsty names. Define NXDOMAIN for unknown names and NODATA for absent record types; dnsmasq domain matching and local-zone policy require careful configuration. Support UDP and TCP. An AAAA answer and the transport used to query DNS are independent: do not invent unreachable IPv6 addresses or ::1. Barn’s present export remains IPv4-only; dual-stack records require a new adapter contract. dnsmasq manual, RFC 3596, RFC 7766

System, application and negative caches can retain older results. Browser/SDK DoH may bypass native routing. Existing hosts entries can mask DNS, subject to the actual NSS path; VPNs can change both route reachability and zone selection. Test these paths and report measured convergence instead of promising instant updates or flushing every cache on every sync. DoH client policy

Evidence establishes a prototype, not platform acceptance

Recorded offline evidence Result and interpretation
Barn hosts/config/helper/CLI tests 16 top-level tests passed; temporary-file writes and preview coverage, including malformed blocks, conflicts, reserved addresses, helper pairing and no-deployment inventory preview
Pigsty extractor tests 11 passed; application subprocesses mocked, sanitized projection and preview behavior covered
Adversarial fixtures 21 observations reproduced, including four Ansible inventory comparisons; these expose gaps, not 21 correctness passes
Configuration corpus 54 paths, including a symlink alias; 53 Barn-VM-valid, 42 extractor-accepted. Parser acceptance does not certify role equivalence or deployment success
Actual prototype preview conf/meta.yml produced 10 names and applied: false; native hosts bytes remained unchanged

The adversarial evidence found ignored group_vars/host_vars, different sibling precedence, multiline/escaped-newline misaddressing, skipped virtualhosts exported, external-role omission and incomplete address collision checks. It also exposed mixed-view ambiguity, unrepresentable multiple addresses and incorrectly normalized explicit CIDR targets. PG aliases under dns_enabled: false require the explicit host-convenience policy above; their existence alone is not a defect. Silent wrong addresses and claims of deployed-DNS equivalence are defects.

Offline review retained fixtures, per-template results, commands and source fingerprints. No private configuration or native-hosts hashes are reproduced here. Barn DNS-related files matched those fingerprints during synthesis even though concurrent work moved local HEAD to 158786a1fee011edf3a1673400b8dd40ba9a7261; Pigsty’s checked baseline is 1ee6597dd806605b600e90cf01948277748b45f3. Unpublished evidence lives in Barn internal/hostconfig, internal/config, cmd/barn/hosts_inventory_test.go, and Pigsty bin/sandbox-dns / tests/test_sandbox_dns.py; these are paths, not claims that public baseline links contain the prototypes.

To reproduce the targeted tests with the same candidate, first set DNS_AUDIT_DIR to the absolute path of an existing disposable directory outside the checkout; the commands use it for all caches and state. These are reproduction instructions, not additional checks performed during this documentation synthesis:

Barn checkout — offline reproduction
export GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off GOENV=off GOWORK=off
export GOFLAGS=-mod=readonly
export GOCACHE="${DNS_AUDIT_DIR:?}/go-cache"
export GOTMPDIR="${DNS_AUDIT_DIR:?}/go-tmp" TMPDIR="${DNS_AUDIT_DIR:?}/tmp"
export BARN_HOME="${DNS_AUDIT_DIR:?}/barn-home"
mkdir -p "$GOCACHE" "$GOTMPDIR" "$TMPDIR" "$BARN_HOME"
go test -count=1 -v \
  -run '^(TestReconcile.*|TestApplyHelper.*|TestInstalledHelperValidation.*|TestCompanionHelperDigest.*|TestHelper.*|TestParseInventoryVMHosts.*|TestHostsInventoryPreviewNeedsNoDeployment)$' \
  ./internal/hostconfig ./internal/config ./cmd/barn-hosts-helper ./cmd/barn
Pigsty checkout — offline reproduction
python3 -B -m unittest discover -s tests -p test_sandbox_dns.py -v

The recorded environment used Go 1.27.3 on darwin/arm64, Python 3.14, PyYAML 6.0.3 and Ansible 2.21.5 for inventory comparisons. Reproduce corpus counts by enumerating sorted conf/**/*.yml paths including symlinks, running candidate barn validate --json, then the same extractor. Promote the saved adversarial fixtures into repository tests before release. No sudo, system configuration changes, VM operations or playbooks were executed in this research. Real resolver activation, VPN behavior, cache timing and HA remain pending; the main documentation workflow runs the site check separately.

Implement in stages with explicit acceptance gates

Phase and owner Work checklist Acceptance gate
0 — Pigsty semantics Separate output views; fix silent parser/override/virtualhost errors; handle or reject external roles; settle CIDR rules; correct reserved VIPs, multi-infra selection and VM identities Every supported static profile has reviewed expected records and provenance; unsupported inputs fail visibly; 21 adversarial observations become expected-result regressions
1 — Pigsty exporter + Barn hosts Limited literal export; exact existing projection; source-kind guard and explicit applied replacement; full offline diff; frozen input; structured failures; helper scope validation and cooperative lock Inventory services survive attempted bare install; no-op needs no sudo; every actual write verifies; targeted tests and required repository checks pass
2 — integration and HA Shared pure rules/effective-variable adapter; controller-side rendered/observed export; lifecycle diagnostics, migration and reinstall cleanup; real platform validation Per-view role equivalence and supported-profile coverage; host access and VIP failover on macOS arm64 and Linux amd64, extending to other advertised hosts
3 — optional split DNS FQDN publication, stable endpoint, capability detection, ownership journal and platform adapters Suffix isolation, persistence, rollback and failure matrix pass; short names retain their stated contract
Demand gate — local daemon Measure update frequency or RR requirements unsatisfied above; define service identity and lifecycle Demonstrated benefit exceeds service maintenance; outage, logout, port and upgrade tests pass
Required test area Concrete acceptance cases
Semantics Overrides, sibling groups, defaults/templates, multiline data, portal conditions, PG target branches, MinIO service-name origin, vm_skip, multi-infra and multi-address refusals
Source and file safety Inventory → bare rejection; explicit applied replacement/removals; legacy migration; case conflicts, trailing dots, duplicate/malformed markers; block-external edits; missing state; crash/race/stale-plan and helper scope tests
Idempotence Repeated snapshots; irrelevant credential/resource edits; generation drift; source-mode-only migration; no root prompt on no-op; noninteractive permission failure
Native access and HA Portal Host/TLS routing, node/PG short names and FQDNs, PG direct versus proxy ports, VIP reachability/failover, external-address collision, new versus existing connections
Resolver integration Native macOS lookup; Linux NSS/stub/bypass paths; UDP/TCP, A/AAAA/NODATA/NXDOMAIN, zone apex/subdomains, public/VPN isolation and identical upstream views
Persistence and recovery Reboot, logout, guest/DNS outage, last guest stopped, interface recreation, DHCP renewal, VPN reconnect, caches/DoH, partial apply, foreign edits, uninstall and reinstall

Keep remaining choices bounded with defaults

Product question Recommended default
Enable host changes automatically? No; explicit Pigsty preview/apply, lifecycle diagnostics only
Which view/source? Literal desired host-access for supported profiles; rendered/observed are explicit, labelled alternatives; no inferred deployed authority
Multiple infra or external endpoints? Reject ambiguity; explicitly choose ingress/view. Out-of-subnet addresses use manual/existing DNS until a separate capability is designed
Source migration/version? Preserve bare legacy/applied behavior; guard inventory blocks; capability-check the first release carrying the guard and matching helper; no safe-downgrade promise
Name canonicalization? Lowercase ASCII for comparison/output; deduplicate equal mappings, reject conflicting addresses; keep a node’s canonical name first where present. Reject trailing-dot input in Phase 1 and detect outside equivalent spellings conservatively; accept no silent normalization divergence
Namespace and short names? Keep .pigsty; optional pigsty.test for new routing; explicit hosts short aliases, no automatic search domain or certificate rename
Privilege and cache policy? Per-call sudo; no persistent grant or automatic global cache flush. Measure convergence before setting a documented target
VIP and service lifecycle? .100/.101/.102 are audited-template candidates only; verify allocation. Retain names across stop/destroy; explicit cleanup survives state deletion
General exporter and daemon timing? Ship a strict useful subset first; expand through per-view equivalence gates. Defer a daemon until demand and maintenance ownership are established

Table reader