From 1044224d02f6bc5271eb31bdb811255ab69e81b3 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Tue, 28 Jul 2026 05:16:01 -0500 Subject: [PATCH] feat: wire Pi-hole to forward knel.net to local Technitium + document setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- netinfra/pfv-netboot-setup.md | 116 +++++++++++++++++ netinfra/pfv-netinfra-setup.md | 220 +++++++++++++++++++++++++++++++++ netinfra/setup-netinfra.sh | 74 ++++++++--- 3 files changed, 393 insertions(+), 17 deletions(-) create mode 100644 netinfra/pfv-netboot-setup.md create mode 100644 netinfra/pfv-netinfra-setup.md diff --git a/netinfra/pfv-netboot-setup.md b/netinfra/pfv-netboot-setup.md new file mode 100644 index 0000000..2899350 --- /dev/null +++ b/netinfra/pfv-netboot-setup.md @@ -0,0 +1,116 @@ +# pfv-netboot β€” Reference Network Infrastructure (READ-ONLY reference) + +> **Status:** REFERENCE SOURCE ONLY. This node is production infrastructure. +> Do **not** modify it. This document describes it as audited so its services can +> be replicated to `pfv-netinfra-01` / `pfv-netinfra-02`. All data below was +> collected by **read-only** audit scripts (`audit-netboot.sh`, +> `deep-audit-netboot.sh`, `gather-configs.sh`) on 2026-07-27/28. + +## 1. Host + +| Item | Value | +|---|---| +| Hostname / FQDN | `pfv-netboot` / `pfv-netboot.knel.net` | +| OS | Debian GNU/Linux 12 (bookworm), kernel 6.1.0-44-amd64 | +| Hardware | 2 vCPU, ~1.9 GiB RAM, 491 GB disk (18 GB used) | +| Timezone | `America/Chicago` (US/Central) | +| LAN | `eth0` static `192.168.3.250/22`, gw `192.168.3.254` (`/etc/network/interfaces`) | +| Tailscale | `100.103.64.82` (`tailscale0`) | +| DNS resolver | Tailscale MagicDNS β€” `/etc/resolv.conf` β†’ `100.100.100.100` | +| Docker | Docker Engine 29.6.2 (containerd v2.2.6, runc 1.3.6) | +| Access | `localuser` has passwordless sudo; **not** in `docker` group (uses `sudo docker`) | + +`eth1` is up but unconfigured; many docker bridges exist (`pihole_default`, +`ntp_default`, `dns_default`, and several stale ones). + +## 2. Services overview + +| Service | Form | Running? | +|---|---|---| +| **Pi-hole** (DNS sinkhole, recursive resolver) | Docker container `pihole` | βœ… healthy | +| **NTP** β€” overlay on Tailscale IP | Docker container `tsys-ntp` (`dockurr/chrony`) | βœ… healthy | +| **NTP** β€” system clock + LAN serving | bare-metal `ntpsec` (`ntpd`) | βœ… active, enabled | +| **Technitium DNS** (authoritative for `knel.net`) | Docker container | ❌ **not running**; config preserved in orphaned volume | + +## 3. Pi-hole (container) + +- **Compose:** `/root/pihole/docker-compose.yml` (compose project `pihole`) +- **Image:** `pihole/pihole:latest` β€” Core **v6.1.2**, Web v6.2.1, FTL v6.2.2 +- **Container:** `pihole`, `restart: always`, `cap_add: [SYS_NICE]`, network `pihole_default` +- **Ports (host):** + + | Host | Container | Purpose | + |---|---|---| + | `53/tcp`, `53/udp` | 53 | DNS | + | `10002/tcp` | 80 | Web admin (HTTP) | + | `10003/tcp` | 443 | Web admin (HTTPS, self-signed) | + +- **Environment:** `TZ=America/Chicago`, `FTLCONF_webserver_api_password=REDACTED_PASSWORD`, `FTLCONF_dns_listeningMode=all` +- **Data:** bind mount `/root/pihole/etc-pihole:/etc/pihole` (dir owned by `localuser`; files by container `pihole` uid) +- **Config (Pi-hole v6 TOML):** `pihole.toml`. Key settings: + - Upstream DNS: `192.168.3.16`, `8.8.8.8`, `2001:4860:4860::8888` + - `listeningMode = "ALL"`, `interface = "eth0"`, `dns.port = 53`, `dns.domain = "lan"` + - `queryLogging = true`, DNSSEC off +- **Adlists:** one entry β€” `https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts` (in `gravity.db`/`adlists.list`) +- **Gravity DB:** `/etc/pihole/gravity.db` (~5.5 MB) holds adlists/domainlists/clients/groups +- **Web admin:** `http://pfv-netboot:10002/admin/` (password `REDACTED_PASSWORD`) +- Note: query history `pihole-FTL.db` (~2.5 GB) is transient and **excluded** from replication. + +## 4. NTP (two layers) + +### 4a. chrony container (`tsys-ntp`) β€” overlay on the Tailscale IP +- **Compose:** `/root/NTP/docker-compose.yml` (project `ntp`) +- **Image:** `dockurr/chrony` +- **Env:** `NTP_SERVERS=pool.ntp.org` +- **Ports:** `100.103.64.82:123:123/udp` β€” bound specifically to the **Tailscale IP** +- `restart: always` +- chrony.conf (generated): `server pool.ntp.org iburst`, `allow all`, `rtcsync` +- On netboot this coexists with bare-metal ntpsec because ntpsec here does **not** pre-bind the specific Tailscale-IP socket, letting Docker claim it. + +### 4b. bare-metal `ntpsec` +- Unit `ntpsec.service` β€” active, enabled; `/usr/sbin/ntpd -c /etc/ntpsec/ntp.conf -g -N -u ntpsec:ntpsec` +- **Config** (`/etc/ntpsec/ntp.conf`): + ``` + driftfile /var/lib/ntp/ntp.drift + leapfile /usr/share/zoneinfo/leap-seconds.list + server pfvsvrpi.knel.net + restrict 127.0.0.1 + restrict ::1 + ``` +- Listens on all local addresses (incl. Tailscale) for UDP/123; serves LAN clients. + +## 5. Technitium DNS (currently stopped) + +- **Not running** β€” no container and **no compose file** exists for it. +- A previous deployment left an **orphaned Docker volume** `dns_tsys-dns-config` + (mountpoint `/var/lib/docker/volumes/dns_tsys-dns-config/_data`) whose contents + are intact (last activity 2025-06-23). A second typo'd volume + `dns_tyss-dns-config` is empty. +- Config files are **binary** (Technitium's own serialization), but copy verbatim: + `dns.config`, `auth.config`, `log.config`, `scopes/Default.scope`, + `self-signed-cert.pfx`, `cache.bin`, `zones/`, `stats/`, `logs/`. +- **Zones present** (12 reverse + 1 forward): + - `knel.net.zone` β€” forward zone; SOA `dns.knel.net. hostadmin.knel.net.` (serial `2025062313`). A-records for the internal fleet, including: `tsys1`, `rr-middleware`, `pfv-netboot`, `pfv-k8s-cnode1`…`cnode5`, `pfv-k8s-wnode3`, `tsys-k8scloud-netcup-1`, `tsys-kali-vptechops`, `tsys-kali-dev`; NS `dns.knel.net`. + - Reverse zones for Tailscale CGNAT ranges (`100.x.in-addr.arpa`): `199.86`, `145.105`, `181.103`, `184.108`, `194.67`, `2.108`, `211.114`, `46.96`, `64.103`, `75.110`, `97.82`, `119.127`. +- **Auth:** `auth.config` defines user `admin` (Administrators group) with a stored password hash; the plaintext password is whatever was set on the original Technitium instance. +- The compose project name historically was `dns` (network `dns_default` still exists). + +## 6. Firewall / misc + +- nftables/iptables: mostly Docker + Tailscale chains (`ts-input`, `ts-forward`, + `DOCKER`, `DOCKER-FORWARD`); default `INPUT ACCEPT`, `FORWARD DROP`, + `OUTPUT ACCEPT`. No UFW / firewalld. +- Also runs (out of scope for this replication): Samba (137/138/139, 445), NFS + (2049), rpcbind (111), Postfix (25), Cockpit (9090), Beszel agent, webmin/ + usermin (10000/10002/20000), Tailscale (41641). +- SELinux absent; AppArmor default docker profile. + +## 7. How it was audited (no changes made) + +```bash +ssh localuser@pfv-netboot 'bash -s' < audit-netboot.sh # broad read-only sweep +ssh localuser@pfv-netboot 'bash -s' < deep-audit-netboot.sh # docker inspect + compose +ssh localuser@pfv-netboot 'bash -s' < gather-configs.sh # pihole.toml + technitium +``` + +Artifacts: `netboot-audit.txt`, `netboot-deep-audit.txt`, `netboot-configs.txt`. diff --git a/netinfra/pfv-netinfra-setup.md b/netinfra/pfv-netinfra-setup.md new file mode 100644 index 0000000..1b65b2f --- /dev/null +++ b/netinfra/pfv-netinfra-setup.md @@ -0,0 +1,220 @@ +# 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//` (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`: +```yaml +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://: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`: +```yaml +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://: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 @ 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 + +```bash +# 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. + +```bash +./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 | diff --git a/netinfra/setup-netinfra.sh b/netinfra/setup-netinfra.sh index 155c95b..fdb53fb 100755 --- a/netinfra/setup-netinfra.sh +++ b/netinfra/setup-netinfra.sh @@ -60,6 +60,10 @@ sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' 2>&1 echo echo "### Pi-hole DNS (dig @127.0.0.1:53 pi.hole) ###" dig +time=3 +tries=1 +short @127.0.0.1 -p 53 pi.hole 2>&1 +echo "### Pi-hole -> Technitium (dig @53 knel.net SOA) ###" +dig +time=3 +tries=1 +short @127.0.0.1 -p 53 knel.net SOA 2>&1 +echo "### Pi-hole -> Technitium (dig @53 pfv-netboot.knel.net A) ###" +dig +time=3 +tries=1 +short @127.0.0.1 -p 53 pfv-netboot.knel.net A 2>&1 echo "### Pi-hole web (curl :10002) ###" curl -sk -o /dev/null -w 'http=%{http_code}\n' http://127.0.0.1:10002/admin/ 2>&1 echo @@ -68,10 +72,18 @@ dig +time=3 +tries=1 @127.0.0.1 -p 5300 knel.net SOA +short 2>&1 echo "### Technitium web (curl :5380) ###" curl -sk -o /dev/null -w 'http=%{http_code}\n' http://127.0.0.1:5380/ 2>&1 echo -echo "### NTP on tailscale IP (ntpdig -p 1) ###" -timeout 5 ntpdig -t1 -c1 -p1 $tsip 2>&1 || echo "(ntpdig not available or no response)" -echo "### chrony container sources ###" -sudo docker exec tsys-ntp chronyc -n sources 2>&1 | head -8 +echo "### NTP service ###" +HOST_NTP="" +for u in ntpsec ntp chrony openntpd; do + systemctl is-active --quiet "\$u" 2>/dev/null && { HOST_NTP="\$u"; break; } +done +echo "host daemon: \${HOST_NTP:-none}" +if [ -n "\$HOST_NTP" ]; then + ntpq -c "rv 0 leap,stratum,offset" 2>&1 | head -3 +else + echo "(no host NTP; chrony container:)" + sudo docker exec tsys-ntp chronyc -n tracking 2>&1 | head -6 +fi EOF } @@ -112,6 +124,12 @@ services: cap_add: - SYS_NICE restart: always + networks: + - default + - dnsnet +networks: + dnsnet: + external: true YAML on_node "$node" "cat > $SVC_ROOT/ntp/docker-compose.yml" < target --------------------- @@ -167,8 +192,8 @@ YAML | on_node "$node" "sudo tar -xf - -C $SVC_ROOT/technitium && sudo mv $SVC_ROOT/technitium/_data $SVC_ROOT/technitium/config" fi - # ---- 5. Adapt copied config: Pi-hole interface name --------------------- - log "$node: adapt Pi-hole pihole.toml interface name to actual iface" + # ---- 5. Adapt copied config: interface + repoint knel.net to local Technitium + log "$node: adapt Pi-hole pihole.toml (interface + revServer -> local Technitium)" on_node "$node" "bash -s" <<'EOF' set -e IFACE=$(ip -o -4 route show to default 2>/dev/null | awk '{print $5; exit}') @@ -177,12 +202,24 @@ TOML=/home/localuser/services/pihole/etc-pihole/pihole.toml if sudo test -f "$TOML"; then sudo sed -i "s|^ interface = .*| interface = \"$IFACE\" ### ADAPTED from eth0 on clone|" "$TOML" echo "set interface=$IFACE" + # Repoint knel.net conditional forward from netboot's upstream (192.168.3.16) + # to the LOCAL Technitium container at its fixed dnsnet IP 10.53.0.53. + # Subnet 100.64.0.0/10 = Tailscale CGNAT range (covers all Tailscale reverse zones). + if sudo grep -q 'revServers' "$TOML"; then + sudo sed -i 's|"true,[0-9./]*,192\.168\.3\.16,knel\.net"|"true,100.64.0.0/10,10.53.0.53,knel.net"|' "$TOML" + echo "revServer repointed to 10.53.0.53 (local Technitium)" + else + echo "(revServers not found; FTL will use defaults)" + fi else echo "(pihole.toml not present; FTL will create it on first run)" fi -# Strip netboot-specific primary upstream 192.168.3.16? -> keep, it is reachable on LAN. EOF + # ---- 5b. Create shared Docker network for Pi-hole <-> Technitium ---------- + log "$node: create dnsnet shared Docker network (10.53.0.0/24)" + on_node "$node" "sudo docker network create --subnet 10.53.0.0/24 dnsnet 2>/dev/null || true" + # ---- 6. Pull images ----------------------------------------------------- log "$node: docker compose pull (pihole, ntp, technitium)" on_node "$node" "bash -s" </dev/null | awk '$5 ~ /:123$/ {print}' | head -1) +HOST_NTP="" +for u in ntpsec ntp chrony openntpd; do + if systemctl is-active --quiet "$u" 2>/dev/null; then HOST_NTP="$u"; break; fi +done if [ -n "$HOST_NTP" ]; then - echo "Host already serves UDP/123 ($HOST_NTP); host daemon:" - for u in ntpsec ntp chrony openntpd systemd-timesyncd; do - systemctl is-active "$u" 2>/dev/null | grep -q active && echo " -> $u active" - done - echo "Skipping chrony container (host NTP provides the service)." - echo "Removing any stale tsys-ntp container..." - sudo docker rm -f tsys-ntp 2>/dev/null || true + echo "Host NTP daemon '$HOST_NTP' is active -- it serves NTP on all local" + echo "addresses (incl. the Tailscale IP). This is the same daemon family as" + echo "netboot's bare-metal ntpsec; the netboot chrony container is redundant" + echo "here and CANNOT bind the Tailscale IP (the host daemon already owns it)." + echo "-> Keeping host NTP. Removing any stale chrony container (tsys-ntp)." + sudo docker rm -f tsys-ntp 2>/dev/null && echo " (removed tsys-ntp)" || echo " (no tsys-ntp to remove)" + echo " host peers:"; ntpq -pn 2>/dev/null | head -12 || true else - echo "Nothing serving UDP/123; starting chrony container." + echo "No host NTP daemon active; starting chrony container." sudo docker compose -f /home/localuser/services/ntp/docker-compose.yml up -d fi EOF