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
This commit is contained in:
2026-07-28 05:16:01 -05:00
parent 291a7ebc27
commit 1044224d02
3 changed files with 393 additions and 17 deletions
+116
View File
@@ -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`.
+220
View File
@@ -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/<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`:
```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://<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`:
```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://<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
```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 |
+57 -17
View File
@@ -60,6 +60,10 @@ sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' 2>&1
echo echo
echo "### Pi-hole DNS (dig @127.0.0.1:53 pi.hole) ###" 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 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) ###" 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 curl -sk -o /dev/null -w 'http=%{http_code}\n' http://127.0.0.1:10002/admin/ 2>&1
echo 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) ###" echo "### Technitium web (curl :5380) ###"
curl -sk -o /dev/null -w 'http=%{http_code}\n' http://127.0.0.1:5380/ 2>&1 curl -sk -o /dev/null -w 'http=%{http_code}\n' http://127.0.0.1:5380/ 2>&1
echo echo
echo "### NTP on tailscale IP (ntpdig -p 1) ###" echo "### NTP service ###"
timeout 5 ntpdig -t1 -c1 -p1 $tsip 2>&1 || echo "(ntpdig not available or no response)" HOST_NTP=""
echo "### chrony container sources ###" for u in ntpsec ntp chrony openntpd; do
sudo docker exec tsys-ntp chronyc -n sources 2>&1 | head -8 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 EOF
} }
@@ -112,6 +124,12 @@ services:
cap_add: cap_add:
- SYS_NICE - SYS_NICE
restart: always restart: always
networks:
- default
- dnsnet
networks:
dnsnet:
external: true
YAML YAML
on_node "$node" "cat > $SVC_ROOT/ntp/docker-compose.yml" <<YAML on_node "$node" "cat > $SVC_ROOT/ntp/docker-compose.yml" <<YAML
@@ -139,6 +157,13 @@ services:
volumes: volumes:
- './config:/etc/dns' - './config:/etc/dns'
restart: always restart: always
networks:
default:
dnsnet:
ipv4_address: 10.53.0.53
networks:
dnsnet:
external: true
YAML YAML
# ---- 3. Relay Pi-hole config from netboot -> target --------------------- # ---- 3. Relay Pi-hole config from netboot -> 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" | on_node "$node" "sudo tar -xf - -C $SVC_ROOT/technitium && sudo mv $SVC_ROOT/technitium/_data $SVC_ROOT/technitium/config"
fi fi
# ---- 5. Adapt copied config: Pi-hole interface name --------------------- # ---- 5. Adapt copied config: interface + repoint knel.net to local Technitium
log "$node: adapt Pi-hole pihole.toml interface name to actual iface" log "$node: adapt Pi-hole pihole.toml (interface + revServer -> local Technitium)"
on_node "$node" "bash -s" <<'EOF' on_node "$node" "bash -s" <<'EOF'
set -e set -e
IFACE=$(ip -o -4 route show to default 2>/dev/null | awk '{print $5; exit}') 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 if sudo test -f "$TOML"; then
sudo sed -i "s|^ interface = .*| interface = \"$IFACE\" ### ADAPTED from eth0 on clone|" "$TOML" sudo sed -i "s|^ interface = .*| interface = \"$IFACE\" ### ADAPTED from eth0 on clone|" "$TOML"
echo "set interface=$IFACE" 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 else
echo "(pihole.toml not present; FTL will create it on first run)" echo "(pihole.toml not present; FTL will create it on first run)"
fi fi
# Strip netboot-specific primary upstream 192.168.3.16? -> keep, it is reachable on LAN.
EOF 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 ----------------------------------------------------- # ---- 6. Pull images -----------------------------------------------------
log "$node: docker compose pull (pihole, ntp, technitium)" log "$node: docker compose pull (pihole, ntp, technitium)"
on_node "$node" "bash -s" <<EOF on_node "$node" "bash -s" <<EOF
@@ -203,20 +240,23 @@ EOF
# there does not pre-bind the specific tailscale-IP socket. On these targets # there does not pre-bind the specific tailscale-IP socket. On these targets
# ntpsec DOES bind the tailscale IP, so the container cannot claim it and is # ntpsec DOES bind the tailscale IP, so the container cannot claim it and is
# redundant anyway. We therefore keep the host ntpsec as the NTP service. # redundant anyway. We therefore keep the host ntpsec as the NTP service.
log "$node: NTP -- check whether host already serves UDP/123" log "$node: NTP -- detect host NTP service"
on_node "$node" "bash -s" <<'EOF' on_node "$node" "bash -s" <<'EOF'
set +e set +e
HOST_NTP=$(sudo ss -lun 2>/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 if [ -n "$HOST_NTP" ]; then
echo "Host already serves UDP/123 ($HOST_NTP); host daemon:" echo "Host NTP daemon '$HOST_NTP' is active -- it serves NTP on all local"
for u in ntpsec ntp chrony openntpd systemd-timesyncd; do echo "addresses (incl. the Tailscale IP). This is the same daemon family as"
systemctl is-active "$u" 2>/dev/null | grep -q active && echo " -> $u active" echo "netboot's bare-metal ntpsec; the netboot chrony container is redundant"
done echo "here and CANNOT bind the Tailscale IP (the host daemon already owns it)."
echo "Skipping chrony container (host NTP provides the service)." echo "-> Keeping host NTP. Removing any stale chrony container (tsys-ntp)."
echo "Removing any stale tsys-ntp container..." sudo docker rm -f tsys-ntp 2>/dev/null && echo " (removed tsys-ntp)" || echo " (no tsys-ntp to remove)"
sudo docker rm -f tsys-ntp 2>/dev/null || true echo " host peers:"; ntpq -pn 2>/dev/null | head -12 || true
else 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 sudo docker compose -f /home/localuser/services/ntp/docker-compose.yml up -d
fi fi
EOF EOF