chore: snapshot before production push
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# PowerShell counterpart to validate-config.sh
|
||||
# Run this in a PowerShell session; it will perform the same checks.
|
||||
|
||||
param(
|
||||
[string]$BaselineFile = "docker-compose.lock.yml"
|
||||
)
|
||||
|
||||
function Assert-Error($msg) {
|
||||
Write-Error $msg
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (Get-Command docker -ErrorAction SilentlyContinue) {
|
||||
Write-Host "[validate-config] generating normalized compose manifest"
|
||||
docker compose config | Set-Content -Path generated.yml -Encoding utf8
|
||||
if (Test-Path $BaselineFile) {
|
||||
Write-Host "[validate-config] comparing against baseline $BaselineFile"
|
||||
$diff = Compare-Object -ReferenceObject (Get-Content $BaselineFile) -DifferenceObject (Get-Content generated.yml) -SyncWindow 0
|
||||
if ($diff) {
|
||||
Write-Host "[validate-config] configuration drift detected!"
|
||||
Assert-Error "Please review and, if the change is intentional, update $BaselineFile."
|
||||
}
|
||||
Write-Host "[validate-config] docker-compose.yml matches baseline"
|
||||
} else {
|
||||
Write-Host "[validate-config] baseline file not found, creating $BaselineFile"
|
||||
Copy-Item generated.yml $BaselineFile
|
||||
Write-Host "[validate-config] please commit $BaselineFile to the repository"
|
||||
}
|
||||
} else {
|
||||
Write-Host "[validate-config] docker binary not found; skipping compose validation"
|
||||
}
|
||||
|
||||
# CoreDNS check
|
||||
if (Get-Command coredns -ErrorAction SilentlyContinue) {
|
||||
Write-Host "[validate-config] checking PopuraDNS/Corefile with local coredns"
|
||||
coredns -conf PopuraDNS/Corefile -dns.port=0
|
||||
$canCheckCorefile = $true
|
||||
} elseif (Get-Command docker -ErrorAction SilentlyContinue) {
|
||||
Write-Host "[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
|
||||
$canCheckCorefile = $true
|
||||
} else {
|
||||
Write-Host "[validate-config] cannot check Corefile syntax (no coredns or docker)"
|
||||
$canCheckCorefile = $false
|
||||
}
|
||||
|
||||
# ensure onion and i2p zones are present in Corefile
|
||||
if ($canCheckCorefile) {
|
||||
foreach ($zone in @('onion.:53','i2p.:53','eth.:53','bit.:53','alt.:53','loki.:53','zil.:53','web3.:53','exit.:53','onion4.:53','onion6.:53')) {
|
||||
if (-not (Select-String -Path PopuraDNS/Corefile -Pattern $zone -Quiet)) {
|
||||
Assert-Error "[validate-config] error: Corefile missing $zone block"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# verify i2pd DNS server is enabled in its config
|
||||
if (Select-String -Path 'i2pd_yggdrasil_docker/src/i2pd.conf' -Pattern '\[dns\]' -Quiet) {
|
||||
$dnsSection = Select-String -Path 'i2pd_yggdrasil_docker/src/i2pd.conf' -Pattern 'enabled' -Context 0,1 | Where-Object { $_.Line -match 'true' }
|
||||
if (-not $dnsSection) {
|
||||
Write-Host "[validate-config] warning: i2pd DNS section present but not enabled"
|
||||
}
|
||||
} else {
|
||||
Write-Host "[validate-config] warning: i2pd.conf missing [dns] section"
|
||||
}
|
||||
|
||||
# tor config should contain DNSPort
|
||||
if (-not (Select-String -Path 'tor_yggdrasil_docker/torrc' -Pattern '^DNSPort' -Quiet)) {
|
||||
Write-Host "[validate-config] warning: torrc does not specify DNSPort; onion DNS will not work"
|
||||
}
|
||||
|
||||
# optional live DNS smoke queries (requires dig)
|
||||
if (Get-Command dig -ErrorAction SilentlyContinue) {
|
||||
Write-Host "[validate-config] performing live DNS query for .onion via CoreDNS"
|
||||
$res = dig @10.5.0.4 -p 53 facebookcorewwwi.onion +short
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Assert-Error "[validate-config] onion query failed"
|
||||
}
|
||||
Write-Host "[validate-config] performing live DNS query for .i2p via CoreDNS"
|
||||
$res = dig @10.5.0.4 -p 53 stats.i2p +short
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Assert-Error "[validate-config] i2p query failed"
|
||||
}
|
||||
}
|
||||
|
||||
# ensure referenced secrets exist
|
||||
foreach ($s in @("./secrets/tz.txt", "./secrets/YGGDRASIL_GENERATE_KEYS.txt")) {
|
||||
if (-not (Test-Path $s)) {
|
||||
Assert-Error "[validate-config] error: secret file $s is missing"
|
||||
}
|
||||
}
|
||||
|
||||
# check for :latest tags
|
||||
if (Select-String -Path docker-compose.yml -Pattern 'image: .*:latest' -Quiet) {
|
||||
Write-Host "[validate-config] warning: some services use the ':latest' image tag;" `
|
||||
"pin to a specific version before deploying to production."
|
||||
}
|
||||
|
||||
# warn if no healthchecks defined
|
||||
if (-not (Select-String -Path docker-compose.yml -Pattern 'healthcheck:' -Quiet)) {
|
||||
Write-Host "[validate-config] warning: no healthcheck definitions found in docker-compose.yml"
|
||||
}
|
||||
|
||||
Write-Host "[validate-config] warning: some services use the ':latest' image tag;" `
|
||||
"pin to a specific version before deploying to production."
|
||||
}
|
||||
|
||||
# verify restart policies exist
|
||||
if (-not (Select-String -Path docker-compose.yml -Pattern 'restart:' -Quiet)) {
|
||||
Write-Host "[validate-config] warning: some services lack a restart policy."
|
||||
}
|
||||
|
||||
# detect duplicate host ports
|
||||
$ports = Select-String -Path docker-compose.yml -Pattern ':[0-9]+:' -AllMatches | ForEach-Object { $_.Matches } | ForEach-Object { $_.Value.Trim(':') } | Sort-Object
|
||||
$dups = $ports | Group-Object | Where-Object { $_.Count -gt 1 } | Select-Object -ExpandProperty Name
|
||||
if ($dups) {
|
||||
Write-Host "[validate-config] warning: duplicate host port mappings detected: $($dups -join ' ')"
|
||||
}
|
||||
|
||||
# unbound configuration syntax check
|
||||
if (Get-Command unbound-checkconf -ErrorAction SilentlyContinue) {
|
||||
Write-Host "[validate-config] checking unbound configuration locally"
|
||||
unbound-checkconf -c unbound/unbound.conf
|
||||
} elseif (Get-Command docker -ErrorAction SilentlyContinue) {
|
||||
Write-Host "[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 {
|
||||
Write-Host "[validate-config] cannot check unbound config (no unbound-checkconf or docker)"
|
||||
}
|
||||
|
||||
# basic 3proxy config sanity
|
||||
foreach ($cfg in @('3proxy/first-instanse.cfg','3proxy/second-instanse.cfg')) {
|
||||
if (-not (Test-Path $cfg)) {
|
||||
Assert-Error "[validate-config] error: missing 3proxy config $cfg"
|
||||
}
|
||||
if (-not (Select-String -Path $cfg -Pattern 'socks|proxy' -Quiet)) {
|
||||
Write-Host "[validate-config] warning: $cfg does not contain socks/proxy directives"
|
||||
}
|
||||
}
|
||||
|
||||
# simple sanity checks
|
||||
if (Select-String -Path docker-compose.yml -Pattern 'container_name: darkproxy' -Quiet) {
|
||||
Write-Host "[validate-config] warning: 'container_name: darkproxy' appears in compose; this may" `
|
||||
"conflict with network or project name."
|
||||
}
|
||||
|
||||
if (Select-String -Path docker-compose.yml -Pattern 'PIHOLE_DNS_' -Quiet) {
|
||||
if (Select-String -Path docker-compose.yml -Pattern 'PIHOLE_DNS_:' -Quiet) {
|
||||
Write-Host "[validate-config] warning: PIHOLE_DNS_ variable ends with underscore;" `
|
||||
"consider using PIHOLE_DNS_1, PIHOLE_DNS_2 etc."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[validate-config] all validations passed"
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user