#!/usr/bin/env bash # Utility to validate the Compose file and other service configurations. # Intended to be run locally or in CI; it exits non-zero on any failure. set -euo pipefail BASELINE_FILE="docker-compose.lock.yml" TEMP_FILE=$(mktemp) # validate docker-compose configuration if Docker is present if command -v docker >/dev/null 2>&1; then echo "[validate-config] generating normalized compose manifest" docker compose config > "$TEMP_FILE" if [ -f "$BASELINE_FILE" ]; then echo "[validate-config] comparing against baseline $BASELINE_FILE" if ! diff -u "$BASELINE_FILE" "$TEMP_FILE"; then echo "[validate-config] configuration drift detected!" echo "Please review and, if the change is intentional, update $BASELINE_FILE." exit 1 fi echo "[validate-config] docker-compose.yml matches baseline" else echo "[validate-config] baseline file not found, creating $BASELINE_FILE" cp "$TEMP_FILE" "$BASELINE_FILE" echo "[validate-config] please commit $BASELINE_FILE to the repository" fi else echo "[validate-config] docker binary not found; skipping compose validation" fi # CoreDNS syntax check (use local binary or container fallback) if command -v coredns >/dev/null 2>&1; then echo "[validate-config] checking PopuraDNS/Corefile with local coredns" coredns -conf PopuraDNS/Corefile -dns.port=0 elif command -v docker >/dev/null 2>&1; then echo "[validate-config] checking PopuraDNS/Corefile with container" docker run --rm -v "${PWD}/PopuraDNS/Corefile:/etc/coredns/Corefile:ro" \ coredns/coredns:latest -conf /etc/coredns/Corefile -dns.port=0 else echo "[validate-config] cannot check Corefile syntax (no coredns or docker)" fi # ensure onion and i2p zones are present in Corefile for zone in "onion.:53" "i2p.:53" "eth.:53" "bit.:53" "alt.:53" "loki.:53" "zil.:53" "web3.:53" "exit.:53" "onion4.:53" "onion6.:53"; do if ! grep -q "$zone" PopuraDNS/Corefile; then echo "[validate-config] error: Corefile missing $zone block" >&2 exit 1 fi done # verify i2pd DNS server is enabled in its config if grep -q "\[dns\]" i2pd_yggdrasil_docker/src/i2pd.conf; then if ! awk '/\[dns\]/,/\[/{if($0~/enabled/ && $0~/true/) ok=1} END{exit !ok}' i2pd_yggdrasil_docker/src/i2pd.conf; then echo "[validate-config] warning: i2pd DNS section present but not enabled" fi else echo "[validate-config] warning: i2pd.conf missing [dns] section" fi # tor configuration should expose DNSPort for onion resolution if ! grep -q '^DNSPort' tor_yggdrasil_docker/torrc; then echo "[validate-config] warning: torrc does not specify DNSPort; onion DNS will not work" fi # optional live DNS smoke queries (requires dig) if command -v dig >/dev/null 2>&1; then echo "[validate-config] performing live DNS query for .onion via CoreDNS" if ! dig @10.5.0.4 -p 53 facebookcorewwwi.onion +short >/dev/null; then echo "[validate-config] onion query failed" >&2 exit 1 fi echo "[validate-config] performing live DNS query for .i2p via CoreDNS" if ! dig @10.5.0.4 -p 53 stats.i2p +short >/dev/null; then echo "[validate-config] i2p query failed" >&2 exit 1 fi fi # ensure referenced secrets exist for s in ./secrets/tz.txt ./secrets/YGGDRASIL_GENERATE_KEYS.txt; do if [ ! -f "$s" ]; then echo "[validate-config] error: secret file $s is missing" >&2 exit 1 fi done # check for :latest tags (discouraged in prod) if grep -qE 'image: .*:latest' docker-compose.yml; then echo "[validate-config] warning: some services use the ':latest' image tag;" \ "pin to a specific version before deploying to production." fi # warn if there are no healthchecks at all if ! grep -q 'healthcheck:' docker-compose.yml; then echo "[validate-config] warning: no healthcheck definitions found in docker-compose.yml" fi echo "[validate-config] warning: some services use the ':latest' image tag;" \ "pin to a specific version before deploying to production." fi # verify every service has a restart policy if ! grep -q "restart:" docker-compose.yml; then echo "[validate-config] warning: some services lack a restart policy." fi # detect duplicate host ports ports=$(grep -oP ':[0-9]+:' docker-compose.yml | tr -d ':' | sort) if [ -n "$(echo "$ports" | uniq -d)" ]; then echo "[validate-config] warning: duplicate host port mappings detected:" $(echo "$ports" | uniq -d) fi # unbound configuration syntax check if command -v unbound-checkconf >/dev/null 2>&1; then echo "[validate-config] checking unbound configuration locally" unbound-checkconf -c unbound/unbound.conf elif command -v docker >/dev/null 2>&1; then echo "[validate-config] checking unbound configuration in container" docker run --rm -v "${PWD}/unbound/unbound.conf:/etc/unbound/unbound.conf:ro" \ mvance/unbound:latest unbound-checkconf -c /etc/unbound/unbound.conf else echo "[validate-config] cannot check unbound config (no unbound-checkconf or docker)" fi # very basic 3proxy config sanity for cfg in 3proxy/first-instanse.cfg 3proxy/second-instanse.cfg; do if [ ! -f "$cfg" ]; then echo "[validate-config] error: missing 3proxy config $cfg" >&2 exit 1 fi if ! grep -qE 'socks|proxy' "$cfg"; then echo "[validate-config] warning: $cfg does not contain socks/proxy directives" fi done # simple sanity checks if grep -q "container_name: darkproxy" docker-compose.yml; then echo "[validate-config] warning: 'container_name: darkproxy' appears in compose;" \ "this may conflict with network or project name." fi # ensure Pi-hole DNS environment is correctly formatted if grep -q "PIHOLE_DNS_" docker-compose.yml | grep -q "PIHOLE_DNS_:"; then echo "[validate-config] warning: PIHOLE_DNS_ variable ends with underscore;" \ "consider using PIHOLE_DNS_1, PIHOLE_DNS_2 etc." fi echo "[validate-config] all validations passed"