Comprehensive documentation gardening across the merged repo: - tailscale.md: fully rewritten with current ground truth. The netinfra pair now runs production Technitium with all knel.net records replicated. Both LAN IPs resolve knel.net device names and recurse externally. The old "NXDOMAIN / zone is stale" findings are replaced with the resolved state and current recommendations. - AGENTS.md: rewritten with Gitea-compatible clickable relative links to all key scripts and docs. Autonomous commit/push policy prominently documented. SSH user corrected to localuser. - README.md: directory table and docs table now use clickable links. - All .md cross-references converted to Gitea-renderable relative links. - Stale path references (ProjectCode/, Project-Tests/, ProjectDocs/) updated to current names (provisioning/, tests/) across all docs. - Stale repo name "FetchApply" / "KNELServerBuild" updated to "PFVCluster" in actionable docs; historical AI-review docs tagged with an HTML comment notice. - REFACTORING-EXAMPLES.md: tagged as historical (pre-refactor patterns). - tests/README.md, dns-cluster-setup/README.md, docs/DEPLOYMENT.md, docs/SECURITY.md: path references fixed to current structure. 🤖 Generated with [Crush](https://github.com/charmassociates/crush) Assisted-by: GLM-5 via Crush <crush@charm.land>
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.tomlinterfacewas adapted from netboot'seth0to the target'sens18.- Web admin:
http://<node>:10002/admin/— passwordREDACTED_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.netSOA →dns.knel.net. hostadmin.knel.net. 2025062313 900 300 604800 900, plus 12 Tailscale reverse zones. - Web console:
http://<node>:5380/→ useradmin+ the original Technitium password (carried over viaauth.config). If the password is unknown, reset it from the console or by removingconfig/auth.configand 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 resolveknel.netvia Technitium, add a conditional/local upstream in Pi-hole pointing to the container (e.g.127.0.0.1#5300is not host-reachable from Pi-hole's netns — use the docker bridge IP oftsys-dns, or addknel.netA-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 IP100.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.shdetects an active host NTP unit and removes any staletsys-ntpcontainer. - 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)
- Layout under
/home/localuser/services/instead of/root(solocalusercan manage compose files); Pi-hole data dir still owned bylocaluser, as on netboot. - Pi-hole
interfaceset toens18(targets' NIC) instead of netboot'seth0. - NTP: host
ntpsec(Debian pool) used instead of netboot's chrony container (the container cannot bind the Tailscale IP here; see §5). - Technitium DNS host port remapped
53 → 5300to avoid clashing with Pi-hole on:53. Theknel.netzone and all reverse zones are identical to netboot's. - 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 |