Files
PFVCluster/netinfra/pfv-netinfra-setup.md
T
mrcharles 1044224d02 feat: wire Pi-hole to forward knel.net to local Technitium + document setup
Add a shared Docker network (dnsnet, 10.53.0.0/24) connecting Pi-hole and
Technitium containers so Pi-hole can conditionally forward knel.net and
Tailscale-reverse queries to the local authoritative Technitium instance
(10.53.0.53) instead of netboot's upstream 192.168.3.16. Also adds
end-to-end documentation for both the reference node (pfv-netboot) and
the replicated nodes (pfv-netinfra-01/02).

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-28 05:16:01 -05:00

9.2 KiB

pfv-netinfra-01 / pfv-netinfra-02 — Network Services Setup

These two nodes replicate the network-infrastructure services of pfv-netboot (Pi-hole, Technitium DNS, NTP). They were deployed by setup-netinfra.sh, which reads config from pfv-netboot (read-only) and relays it to each target.

1. Nodes

pfv-netinfra-01 pfv-netinfra-02
OS Debian 13 (trixie), kernel 6.12.96+deb13 Debian 13 (trixie)
LAN ens18 192.168.3.252/24 ens18 192.168.3.253/24
Tailscale 100.70.181.72 100.93.194.82
RAM / Disk 1.9 GiB / 30 GB (27 GB free) 3.7 GiB / 30 GB (27 GB free)
Resolver Tailscale MagicDNS (100.100.100.100) same
Docker 29.6.2 (pre-installed, enabled) 29.6.2
Access ssh localuser@pfv-netinfra-0X, passwordless sudo; localuser not in docker group → use sudo docker same

2. Service layout

All services live under /home/localuser/services/<svc>/ (owned by localuser so the compose files are directly editable; data dirs keep container uids):

/home/localuser/services/
├── pihole/
│   ├── docker-compose.yml
│   └── etc-pihole/          # copied from netboot /root/pihole/etc-pihole
│       ├── pihole.toml      # Pi-hole v6 config (upstreams, etc.)
│       ├── gravity.db       # adlists / domainlists / clients / groups
│       ├── adlists.list
│       ├── dnsmasq.conf
│       ├── tls.{crt,pem,crt_ca}
│       └── versions
├── ntp/
│   └── docker-compose.yml   # chrony container (see §5 — not used; host ntpsec serves)
└── technitium/
    ├── docker-compose.yml
    └── config/              # copied from netboot orphaned volume dns_tsys-dns-config/_data
        ├── dns.config
        ├── auth.config
        ├── scopes/Default.scope
        ├── self-signed-cert.pfx
        └── zones/           # knel.net.zone + 12 Tailscale reverse zones

3. Pi-hole (container pihole)

Image pihole/pihole:latest; restart: always; cap_add: [SYS_NICE].

Host port Container Purpose
53/tcp, 53/udp 53 DNS (the LAN/Tailscale recursive resolver)
10002/tcp 80 Web admin (HTTP)
10003/tcp 443 Web admin (HTTPS)

docker-compose.yml:

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    hostname: pihole
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "10002:80/tcp"
      - "10003:443/tcp"
    environment:
      TZ: 'America/Chicago'
      FTLCONF_webserver_api_password: 'REDACTED_PASSWORD'
      FTLCONF_dns_listeningMode: 'all'
    volumes:
      - './etc-pihole:/etc/pihole'
    cap_add:
      - SYS_NICE
    restart: always
  • Upstream DNS (from copied pihole.toml): 192.168.3.16, 8.8.8.8, 2001:4860:4860::8888.
  • Adlist: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts.
  • pihole.toml interface was adapted from netboot's eth0 to the target's ens18.
  • Web admin: http://<node>:10002/admin/ — password REDACTED_PASSWORD (same as netboot).
  • Web UI URL per node: http://100.70.181.72:10002/admin/ (-01), http://100.93.194.82:10002/admin/ (-02).

4. Technitium DNS (container tsys-dns)

Image technitium/dns-server; restart: always. Authoritative DNS for knel.net (and Tailscale reverse zones), config copied verbatim from netboot's orphaned dns_tsys-dns-config volume.

Host port Container Purpose
5300/tcp, 5300/udp 53 DNS (remapped — see note)
5380/tcp 5380 Web console (HTTP)
53443/tcp 53443 Web console (HTTPS)

docker-compose.yml:

services:
  technitium:
    image: technitium/dns-server
    container_name: tsys-dns
    ports:
      - "5300:53/tcp"
      - "5300:53/udp"
      - "5380:5380/tcp"
      - "53443:53443/tcp"
    volumes:
      - './config:/etc/dns'
    restart: always
  • Zones loaded (verified): knel.net SOA → dns.knel.net. hostadmin.knel.net. 2025062313 900 300 604800 900, plus 12 Tailscale reverse zones.
  • Web console: http://<node>:5380/ → user admin + the original Technitium password (carried over via auth.config). If the password is unknown, reset it from the console or by removing config/auth.config and recreating the container.
  • Port note: Technitium's native DNS port (53) is remapped to host 5300 because Pi-hole already owns host :53 (they cannot both bind 0.0.0.0:53). To query the authoritative server: dig -p 5300 @<node> knel.net SOA. To make Pi-hole resolve knel.net via Technitium, add a conditional/local upstream in Pi-hole pointing to the container (e.g. 127.0.0.1#5300 is not host-reachable from Pi-hole's netns — use the docker bridge IP of tsys-dns, or add knel.net A-records directly in Pi-hole's Local DNS).

5. NTP (host ntpsec, not a container)

Both targets already run a bare-metal ntpsec daemon (active, enabled) that serves NTP on every local address — including the Tailscale IP — and keeps the system clock synced. This is the same daemon family as netboot's own bare-metal ntpsec.

  • Why no chrony container? netboot's chrony container (tsys-ntp) binds the Tailscale IP 100.103.64.82:123; on netboot that works only because its ntpsec does not pre-bind the specific Tailscale-IP socket. On these targets ntpsec does bind the Tailscale IP, so the container cannot claim it (address already in use) and would be a non-functional duplicate (verified: the container started but never synced — Stratum 0). It is therefore intentionally omitted; host ntpsec provides NTP. setup-netinfra.sh detects an active host NTP unit and removes any stale tsys-ntp container.
  • ntpsec config (/etc/ntpsec/ntp.conf): Debian NTP pool (0-3.debian.pool.ntp.org), restrict default kod nomodify noquery limited (serves time, blocks mgmt queries).
  • Verified sync: -01 stratum 2 (~2 ms offset), -02 stratum 3 (~0.2 ms offset), leap normal.

The ntp/docker-compose.yml is still written on each node for parity/reference (and in case the host NTP is ever disabled — then sudo docker compose -f /home/localuser/services/ntp/docker-compose.yml up -d brings up chrony).

6. Verification results (2026-07-28)

Check pfv-netinfra-01 pfv-netinfra-02
pihole health healthy healthy
dig @127.0.0.1:53 pi.hole 172.18.0.2 172.18.0.2
Pi-hole web :10002 HTTP 302 (→login) HTTP 302
dig @127.0.0.1:5300 knel.net SOA SOA answered SOA answered
Technitium web :5380 HTTP 200 HTTP 200
NTP daemon ntpsec, stratum 2, synced ntpsec, stratum 3, synced

7. Operating the services

# status
sudo docker ps

# Pi-hole
sudo docker compose -f /home/localuser/services/pihole/docker-compose.yml ps
sudo docker compose -f /home/localuser/services/pihole/docker-compose.yml logs -f
sudo docker exec pihole pihole -v          # version
sudo docker exec pihole pihole -g          # rebuild gravity
sudo docker exec pihole pihole -a -p       # set/change web password

# Technitium
sudo docker compose -f /home/localuser/services/technitium/docker-compose.yml logs -f
sudo docker exec tsys-dns sh               # explore /etc/dns

# NTP (host)
systemctl status ntpsec
ntpq -pn

8. Differences from pfv-netboot (intentional)

  1. Layout under /home/localuser/services/ instead of /root (so localuser can manage compose files); Pi-hole data dir still owned by localuser, as on netboot.
  2. Pi-hole interface set to ens18 (targets' NIC) instead of netboot's eth0.
  3. NTP: host ntpsec (Debian pool) used instead of netboot's chrony container (the container cannot bind the Tailscale IP here; see §5).
  4. Technitium DNS host port remapped 53 → 5300 to avoid clashing with Pi-hole on :53. The knel.net zone and all reverse zones are identical to netboot's.
  5. Pi-hole query logs (pihole-FTL.db*) and regenerable caches/backups are not copied (transient); gravity DB and all configuration are.

9. Re-running / reproducing

setup-netinfra.sh is idempotent — it skips re-copying config if already present and uses docker compose up -d (no-ops when unchanged). It reads pfv-netboot read-only and never mutates it.

./setup-netinfra.sh                       # deploy to both nodes
./setup-netinfra.sh pfv-netinfra-01       # deploy one node
./setup-netinfra.sh pfv-netinfra-01 verify   # verify only

Prerequisites: SSH key access to all three hosts as localuser with passwordless sudo; the targets reach 192.168.3.16/8.8.8.8 for Pi-hole upstream and the internet for image pulls.

10. Files in this directory

File Purpose
setup-netinfra.sh orchestrator: deploys + verifies the clone on -01/-02
audit-netboot.sh broad read-only audit of pfv-netboot
deep-audit-netboot.sh docker inspect / compose / volume deep audit (read-only)
gather-configs.sh targeted config pull (pihole.toml, technitium) (read-only)
baseline.sh read-only baseline of a target node
netboot-audit.txt, netboot-deep-audit.txt, netboot-configs.txt audit output
pfv-netboot-setup.md reference-node documentation
pfv-netinfra-setup.md this document