commit 11b766c9062f3591a41ec22bae90db335219ef11 Author: auto-ci Date: Sun Mar 1 20:39:26 2026 -0500 chore: snapshot before production push diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ab8ce00 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,55 @@ +# Darkproxy AI Coding Guidelines + +## Project Overview +Darkproxy is a multi-container Docker stack enabling anonymous proxying across darknet (I2P, Tor) and clearnet. It chains SOCKS5 proxies through custom DNS resolvers, combining Yggdrasil networking, CoreDNS service orchestration, and blockchain-based TLD resolvers (Alfis, EmerCoin, meshname). + +## Architecture +**Fixed container IPs on `darkproxy` network (10.5.0.0/16)**: +- `10.5.0.2`: i2pd_yggdrasil - I2P + Yggdrasil IPv6 tunneling +- `10.5.0.3`: alfis - Blockchain resolver for .anon/.ygg/.btn/.conf TLDs +- `10.5.0.4`: coredns - CoreDNS (PopuraDNS) DNS orchestration hub +- `10.5.0.5`: unbound - Recursive clearnet DNS +- `10.5.0.6`: pihole - DNS filtering + upstream forwarder +- `10.5.0.7`: tor_yggdrasil - Tor + Yggdrasil tunneling +- `10.5.0.8`: dark3proxy - SOCKS5/HTTP proxy (main entrypoint) +- `10.5.0.9`: emc - EmerCoin resolver for .emc/.coin/.lib domains + +**Data flow**: Client → dark3proxy (10.5.0.8:1080 SOCKS5) → pihole DNS (10.5.0.6) → coredns (10.5.0.4) → TLD-specific resolvers → upstream SOCKS backends (i2pd/tor). + +## Key Workflows +**Build custom images** (with version pinning via build args): +- I2P: `cd i2pd_yggdrasil_docker/src && ./make_docker_image.sh` (sets I2PD_VERSION=2.58.0, YGGDRASIL_VERSION=v0.5.12, UBUNTU_VERSION=24.04) +- Tor: `cd tor_yggdrasil_docker && ./build_image.sh` (same versions via git describe fallback) +- 3proxy: No build script—uses GitHub release binary download in Dockerfile + +**Deploy/Debug**: +- Start: `docker-compose up --build` from root +- Logs: `docker-compose logs -f coredns` (or dark3proxy, i2pd_yggdrasil, tor_yggdrasil) +- Shell: `docker exec -it darkdns /bin/sh` (use container_name from compose) +- Health: Each service has `healthcheck` or ping-based validation in entrypoint + +**Add new TLD resolver**: +1. In docker-compose.yml: add new service with fixed IP in 10.5.0.x range, ensure internal network access +2. In PopuraDNS/Corefile: add block like `newtld.:53 { acl { allow net 10.0.0.0/8 ...; block }; forward . 10.5.0.X:53 }` +3. Rebuild coredns: `docker-compose up --build coredns` + +## Conventions +**Secrets/Environment**: Stored in `secrets/` directory (tz.txt for timezone, YGGDRASIL_GENERATE_KEYS.txt for key generation). Mounted as Docker secrets, referenced in environment via `/run/secrets/`. + +**Proxy Routing Logic** (in `3proxy/first-instanse.cfg`): +- Parent rules chain traffic: i2p domains → i2pd SOCKS (10.5.0.2:4447), onion → tor SOCKS (10.5.0.7:9050), Yggdrasil (200::/7) → local SOCKS, clearnet → tor fallback +- Blacklist format: `{{vk.com,ok.ru,...}}` (blocks these domains) + +**DNS Stacking**: pihole (ad-filtering) → coredns (routing) → specialists. pihole upstreams to coredns (10.5.0.4), coredns forwards TLDs to resolvers. ACLs in Corefile block external networks; only 10.0.0.0/8, 127.0.0.1, fc00::/7 allowed. + +**Yggdrasil Key Generation**: Services `i2pd_yggdrasil` and `tor_yggdrasil` run Python utils (yggdrasil_get_keys, yggdrasil_find_public_peers) in entrypoint—generates strong keys + finds optimal peers unless YGGDRASIL_GENERATE_KEYS=false. + +**Networking**: All services require `sysctls: net.ipv6.conf.all.disable_ipv6=0` for Yggdrasil IPv6. Tunneling services (i2pd_yggdrasil, tor_yggdrasil) need `privileged: true`, `cap_add: NET_ADMIN`, `/dev/net/tun:/dev/net/tun`. + +## Examples +- **Port Mapping** (via Yggdrasil IP 100.117.196.19): SOCKS on 2000→1080, 3proxy-eagle web 2002→8161, I2P console 2001→7070 +- **Config Mounting**: Read-only volumes for configs—e.g., `./unbound/unbound.conf:/opt/unbound/etc/unbound/unbound.conf` +- **Image Tagging**: Custom builds tag as `service_name:${TAG_VERSION}` (git tag) and `:latest`; published to registry (host60/ prefix in docker-compose) + +Reference files: [docker-compose.yml](docker-compose.yml), [PopuraDNS/Corefile](PopuraDNS/Corefile), [3proxy/first-instanse.cfg](3proxy/first-instanse.cfg), [i2pd_yggdrasil_docker/src/make_docker_image.sh](i2pd_yggdrasil_docker/src/make_docker_image.sh), [tor_yggdrasil_docker/build_image.sh](tor_yggdrasil_docker/build_image.sh) +/home/sommopfle/darkproxy/.github/copilot-instructions.md \ No newline at end of file diff --git a/.github/workflows/config-validation.yml b/.github/workflows/config-validation.yml new file mode 100644 index 0000000..ce34a83 --- /dev/null +++ b/.github/workflows/config-validation.yml @@ -0,0 +1,63 @@ +name: "Configuration validation" + +on: + push: + paths: + - 'docker-compose.yml' + - 'PopuraDNS/**' + - '.github/workflows/**' + pull_request: + paths: + - 'docker-compose.yml' + - 'PopuraDNS/**' + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Run full validation script + run: | + chmod +x scripts/validate-config.sh + scripts/validate-config.sh + + smoke: + needs: lint + runs-on: ubuntu-latest + services: + docker: + image: docker:24-dind + privileged: true + options: >- + --cpus=2 --memory=4g + steps: + - uses: actions/checkout@v4 + + - name: Start stack and wait for health + run: | + docker compose up -d --build + # wait up to 120 seconds for each container's healthcheck to pass + for svc in darkdns darkpihole darkunbound darkalfis darkemer dark3proxy darktor darkproxy; do + echo "waiting for $svc" + for i in {1..24}; do + status=$(docker inspect -f '{{.State.Health.Status}}' $svc 2>/dev/null || echo none) + if [ "$status" = "healthy" ]; then + echo "$svc healthy" && break + fi + sleep 5 + done + done + docker compose ps + + # quick DNS smoke tests via CoreDNS container + echo "testing onion resolution" + docker exec darkdns dig +short @127.0.0.1 -p 53 facebookcorewwwi.onion + echo "testing i2p resolution" + docker exec darkdns dig +short @127.0.0.1 -p 53 stats.i2p + + docker compose down diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..168e24b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +i2pd/data/ +tor/data/ + +# runtime secrets should not be committed +secrets/ + +generated.yml diff --git a/3proxy/Dockerfile b/3proxy/Dockerfile new file mode 100644 index 0000000..14d25bb --- /dev/null +++ b/3proxy/Dockerfile @@ -0,0 +1,74 @@ +# Stage 1: Build +FROM --platform=$BUILDPLATFORM ubuntu:22.04 AS builder + +ARG BUILDPLATFORM +ARG TARGETPLATFORM +ARG TARGETARCH + +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + qtbase5-dev \ + qtchooser \ + qt5-qmake \ + qtbase5-dev-tools \ + wget \ + git \ + build-essential \ + libqt5core5a \ + && rm -rf /var/lib/apt/lists/* + +# Download appropriate 3proxy binary based on architecture +RUN if [ "$TARGETARCH" = "amd64" ]; then \ + wget -O 3proxy.deb https://github.com/3proxy/3proxy/releases/download/0.9.4/3proxy-0.9.4.x86_64.deb; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + wget -O 3proxy.deb https://github.com/3proxy/3proxy/releases/download/0.9.4/3proxy-0.9.4.aarch64.deb || \ + { echo "ARM64 binary not available, using x86_64"; wget -O 3proxy.deb https://github.com/3proxy/3proxy/releases/download/0.9.4/3proxy-0.9.4.x86_64.deb; }; \ + elif [ "$TARGETARCH" = "arm" ]; then \ + wget -O 3proxy.deb https://github.com/3proxy/3proxy/releases/download/0.9.4/3proxy-0.9.4.armv7l.deb || \ + { echo "ARMv7 binary not available"; exit 1; }; \ + fi && \ + dpkg -i 3proxy.deb || true + +# Build 3proxy-eagle in a dedicated source directory +RUN git clone https://gitea.darkness.services/acetone/3proxy-eagle.git /src/3proxy-eagle \ + && cd /src/3proxy-eagle \ + && find . -name "*.cpp" -o -name "*.h" | xargs sed -i 's/127\.0\.0\.1/0.0.0.0/g' \ + && cd /src/3proxy-eagle/src \ + && qmake 3proxy-eagle.pro \ + && make -j$(nproc) + +# Stage 2: Runtime +FROM ubuntu:22.04 + +ARG TARGETARCH + +RUN apt-get update && apt-get install -y \ + libqt5core5a \ + libqt5network5 \ + libqt5xml5 \ + libqt5sql5 \ + libqt5sql5-sqlite \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Copy binaries using the correct absolute paths from Stage 1 +COPY --from=builder /usr/bin/3proxy /usr/bin/3proxy +COPY --from=builder /src/3proxy-eagle/src/3proxy-eagle /usr/bin/3proxy-eagle + +# Setup config directories +RUN mkdir -p /etc/3proxy /etc/3proxy-eagle/data + +# Ensure these files exist in your local directory where you run 'docker build' +#COPY first-instanse.cfg /etc/3proxy/ +#COPY second-instanse.cfg /etc/3proxy/ + +EXPOSE 8161 + +# Run in foreground +CMD ["/usr/bin/3proxy-eagle", \ + "-i", "/usr/bin/3proxy,/etc/3proxy/first-instanse.cfg", \ + "-i", "/usr/bin/3proxy,/etc/3proxy/second-instanse.cfg", \ + "-w", "/etc/3proxy-eagle/data", \ + "-t", "DarkProxy"] diff --git a/3proxy/first-instanse.cfg b/3proxy/first-instanse.cfg new file mode 100644 index 0000000..0db23f5 --- /dev/null +++ b/3proxy/first-instanse.cfg @@ -0,0 +1,45 @@ +# regular 3proxy configuration +fakeresolve +flush +auth iponly +admin -p8161 -a0.0.0.0 +# strict required by 3proxy-eagle log lines +log +logformat " type=%N destination=%n to=%O from=%I" + +# 3proxy-eagle black list format +{{vk.com,ok.ru,mail.ru,gosuslugi.ru,127.0.0.1}} + +# regular 3proxy configuration again + +# expose a lightweight status socket for Prometheus scraping +# the exporter will connect here and convert counters to metrics +monitor -p6800 + +## i2p sites +allow * * *.i2p +#parent 1000 http 127.0.0.1 4444 +parent 1000 socks5+ 10.5.0.2 4447 + +## yggdrasil (127.0.0.1 1085 is second instanse) +allow * * 200::/7 +parent 1000 socks5+ 127.0.0.1 1085 +## ygg domains resolving by alfis +allow * * *.ygg +parent 1000 socks5+ 127.0.0.1 1085 +## meshnames domains resolving by meshnamed +allow * * *.meshname +parent 1000 socks5+ 127.0.0.1 1085 +allow * * *.meship +parent 1000 socks5+ 127.0.0.1 1085 + +## onion sites +allow * * *.onion +parent 1000 socks5+ 10.5.0.7 9050 + +## clearnet via another proxy or change to tor (9050) +allow * +parent 1000 socks5+ 10.5.0.7 9050 + +socks -olSO_REUSEADDR,SO_REUSEPORT -ocTCP_TIMESTAMPS,TCP_NODELAY -osTCP_NODELAY -46 -i0.0.0.0 -p1080 +proxy -olSO_REUSEADDR,SO_REUSEPORT -ocTCP_TIMESTAMPS,TCP_NODELAY -osTCP_NODELAY -46 -a1 -i0.0.0.0 -p3128 diff --git a/3proxy/second-instanse.cfg b/3proxy/second-instanse.cfg new file mode 100644 index 0000000..98adf18 --- /dev/null +++ b/3proxy/second-instanse.cfg @@ -0,0 +1,8 @@ +auth iponly +# allow only Yggdrasil network subnet by this instanse +# otherwise all ygg/meshname/meship domains with +# non-yggdrasil addresses goes bypass any rules +# and potentially can deanonimaze outproxy machine +allow * * 200::/7 +deny * +socks -olSO_REUSEADDR,SO_REUSEPORT -ocTCP_TIMESTAMPS,TCP_NODELAY -osTCP_NODELAY -64 -i127.0.0.1 -p1085 diff --git a/PopuraDNS b/PopuraDNS new file mode 160000 index 0000000..749c8a6 --- /dev/null +++ b/PopuraDNS @@ -0,0 +1 @@ +Subproject commit 749c8a63004064e2bb68f5c4e384cceccdc33640 diff --git a/README.md b/README.md new file mode 100644 index 0000000..f382b02 --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +Welcome to darkproxy... + +This container runs the following vontainers in the stack. + +3proxy-eagle - Socks5/http proxy + - DNS + - pihole - ad blocking and metrics + - PopuraDNS - non-recursive DNS forwards requests to the following: + Alfis - Blockchain based yggdrasil resolver + EmerCoin - Resolves EMC domains + UnBound - Recursive DNS for Clearnet domains + * .onion and .i2p domain names are now resolved via the stack (see DNS section below) + - I2PD over Yggdrasil container (`container_name: darki2p`) + * Yggdrasil IP network stack + * Socks5 server for I2P + - Tor container + +The system offers a DNS that resolves many darknet and clearnet IP's. The +stack now handles: + +* `*.onion` – forwarded to Tor's DNSPort running in the `darktor` container at + 10.5.0.7:9053. +* `*.i2p` – served by the built-in DNS server inside the `darki2p` container + (10.5.0.2:53) which is enabled via the `[dns]` section in `i2pd.conf`. + +You can query these directly from any container on the `darkproxy` network, for +example: + +```sh +# Tor .onion domains +docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 facebookcorewwwi.onion + +# I2P .i2p domains +docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 stats.i2p + +# Ethereum Name Service (.eth) via Cloudflare fallback +docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 vitalik.eth + +# Other supported TLDs: .bit, .alt, .loki, .zil, .web3, .exit, .onion4, .onion6 +docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 example.bit +``` + +> **Note:** the CoreDNS service at `10.5.0.4` no longer enforces an ACL for +> the root zone – it will happily perform recursive lookups for any client. +> Queries from your host should now work without the “refused to do a recursive +> query” error. If you later reintroduce an ACL, update this note accordingly. + + +## Supported TLDs and resolvers + +The stack now provides DNS resolution for a comprehensive set of alternative TLDs: + +| TLD | Resolver | Purpose | +|-----|----------|---------| +| `.onion`, `.exit`, `.onion4`, `.onion6` | Tor DNSPort (10.5.0.7:9053) | Tor hidden services | +| `.i2p` | i2pd DNS (10.5.0.2:53) | I2P eepsite names | +| `.eth` | Cloudflare 1.1.1.1 | Ethereum Name Service | +| `.bit` | Google 8.8.8.8 | Namecoin names | +| `.alt` | alt-root 185.121.177.177 | Alternative DNS root | +| `.loki` | Cloudflare 1.1.1.1 | Lokinet privacy network | +| `.zil` | Cloudflare 1.1.1.1 | Zilliqa blockchain | +| `.web3` | Cloudflare 1.1.1.1 | General blockchain namespace | +| Alfis TLDs | Alfis (10.5.0.3) | `.anon`, `.btn`, `.conf`, `.index`, `.merch`, `.mirror`, `.mob`, `.screen`, `.srv`, `.ygg` | +| EmerCoin TLDs | EmerCoin (10.5.0.9) | `.emc`, `.coin`, `.lib`, `.bazar`, `.enum` | +| OpenNIC TLDs | OpenNIC public resolvers | `.bbs`, `.chan`, `.cyb`, `.dyn`, `.epic`, `.geek`, `.gopher`, `.indy`, `.libre`, `.neo`, `.null`, `.o`, `.oss`, `.oz`, `.parody`, `.pirate`, `.fur`, `.ku`, `.rm`, `.te`, `.ti`, `.uu`, `.ko` | +| Clearnet | Unbound (10.5.0.5) | All other domains via recursive resolution | + + +## Configuration validation and drift tests + +A simple automation is included to help detect accidental changes to the +compose and DNS configuration: + +1. **Local check** – run `scripts/validate-config.sh` (or its PowerShell equivalent). + - The script requires `docker` to be installed; it will generate a normalized + manifest and compare it against `docker-compose.lock.yml`. + - If the baseline file does not exist it will be created; commit the file after + review. + - CoreDNS syntax is also validated using either a local `coredns` binary or a + container. + +2. **CI workflow** – `.github/workflows/config-validation.yml` is triggered on + pushes or pull requests affecting `docker-compose.yml` or `PopuraDNS/`. + The job executes the same `docker compose config` comparison and runs the + Corefile check inside a container. + +3. **Updating the baseline** – when intentional changes are made to the + compose file, regenerate the baseline: + + ```sh + scripts/validate-config.sh # fails with drift + cp generated.yml docker-compose.lock.yml + git add docker-compose.lock.yml + ``` + +These steps ensure that configuration drift is caught early and reviewers can +see what has changed. + + +## Monitoring the proxy + +A lightweight Prometheus/Grafana stack is included to expose 3proxy metrics +and give you visibility into traffic volumes, connected sessions, etc. + +### how it works + +* `3proxy` is configured with `monitor -p6800`, which sets up a plain-text + status socket. Samples look like `PROXY CONNS 12` or `SOCKS IN 345`. +* `monitor/exporter.py` polls that socket every few seconds and exports + the counters on HTTP port **9100** in Prometheus format. +* The `docker-compose.yml` file now defines three new services: + `3proxy_exporter`, `prometheus` and `grafana`. + Prometheus scrapes the exporter and Grafana points at Prometheus as a data + source. + +### building & running + +The exporter lives in `monitor/`; build the image and start the stack: + +```sh +# build everything including the new services +docker-compose build 3proxy_exporter prometheus grafana + +docker-compose up -d +``` + +Prometheus will be accessible on port **9090**, Grafana on **3000** (admin +password `secret`). Use Grafana to create or import dashboards – a simple +example JSON is provided in `monitor/3proxy-dashboard.json`. You can import +that file directly or build your own panels using metrics such as +`proxy_conns` and `proxy_bytes_in`. + +### tips + +* Alerts can be added in Prometheus rules, e.g. fire when `proxy_conns` + exceeds a threshold for several minutes. +* If you don’t want the full stack, you can still query the monitor port + directly with `nc`; nothing in the proxy depends on the exporter. +* The `scripts/validate-config.sh` script warns if a 3proxy config lacks a + `monitor` line. + +## Production readiness checklist + +Make sure you never commit real secrets. The `secrets/` directory is now +included in `.gitignore`; both timezone and Yggdrasil key files live there and +are mounted into containers at runtime. + +Before deploying to a live environment, run the validation script and resolve +any warnings or errors. The script covers: + +* YAML syntax and drift against the committed baseline +* CoreDNS/PopuraDNS Corefile syntax +* Unbound configuration syntax +* Presence of required secret files +* Simple 3proxy configuration sanity (presence of `socks`/`proxy` rules) +* Detection of `:latest` image tags (pin to fixed versions) +* Verification that restart policies exist + +The GitHub Actions workflow also includes a **smoke test** job that spins up +all services in `docker-compose.yml` using a Docker‑in‑Docker service and +waits for each container’s healthcheck to report `healthy`. This gives an +additional layer of confidence that the stack can be built and started +successfully. + +You can also extend the script with additional service-specific checks such as +running `docker run ...` commands to exercise the images, scanning the +`3proxy` config more deeply, or linting other mounted configuration files. + +Run the script locally via `./scripts/validate-config.sh` (or +`.scripts/validate-config.ps1` on Windows) and review the output carefully. +CI will execute the same validations automatically on pull requests. + + diff --git a/docker-compose.arm.yml b/docker-compose.arm.yml new file mode 100644 index 0000000..a43307d --- /dev/null +++ b/docker-compose.arm.yml @@ -0,0 +1,43 @@ +# ARM-specific override for darkproxy stack +# Use together with the main compose file: +# +# docker compose -f docker-compose.yml -f docker-compose.arm.yml up --build +# +# This file adds `platform: linux/arm64` hints to services so the correct +# architecture is selected when running on an ARM host. It does **not** replace +# any images; if an image is not available for ARM (e.g. alfis, unbound, the +# prebuilt tor_yggdrasil) Docker will attempt emulation via QEMU or fall back to +# amd64. +# +# You can also modify this file to swap in ARM-specific images or build +# instructions if you have them. + +services: + dark3proxy: + platform: linux/arm64 + + i2pd_yggdrasil: + platform: linux/arm64 + + tor_yggdrasil: + platform: linux/arm64 + + alfis: + platform: linux/arm64 + + coredns: + platform: linux/arm64 + + unbound: + platform: linux/arm64 + + pihole: + platform: linux/arm64 + + emc: + platform: linux/arm64 + + geth: + platform: linux/arm64 + +# networks, volumes, and secrets are inherited from the base compose file diff --git a/docker-compose.lock.yml b/docker-compose.lock.yml new file mode 100644 index 0000000..3ab2406 --- /dev/null +++ b/docker-compose.lock.yml @@ -0,0 +1,269 @@ +services: + dark3proxy: + container_name: dark3proxy + image: host60/darkproxy-dark3proxy:v1.0 + build: + context: ./3proxy/. + dns: + - "10.5.0.6" + dns_search: internal.namespace #namespace used in internal DNS + volumes: + - "./3proxy/first-instanse.cfg:/etc/3proxy/first-instanse.cfg" + - "./3proxy/second-instanse.cfg:/etc/3proxy/second-instanse.cfg" + restart: unless-stopped + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + ports: + - 100.117.196.19:2000:1080 # socks port + - 100.117.196.19:2002:8161 # 3proxy-eagle web page + networks: + darkproxy: + ipv4_address: 10.5.0.8 + + i2pd_yggdrasil: + container_name: darki2p +# image: i2pd_yggdrasil:latest + image: host60/i2pd_yggdrasil:v1.0 + privileged: true + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + ports: + # I2P Service Ports +# - "2827:2827" # BOB Bridge +# - "4444:4444" # HTTP Proxy +# - "4447:4447" # SOCKS Proxy + - "100.117.196.19:2001:7070" # Webconsole +# - "7650:7650" # I2PControl +# - "7654:7654" # I2CP +# - "7656:7656" # SAM Bridge (TCP) + - "10765:10765" # Main I2P Listener + # [dns] section enabled in i2pd.conf; DNS port is 53 (accessible internally at 10.5.0.2:53) + # mapping to host is possible if desired, e.g. 10853:53/udp + # Yggdrasil Ports + - "10654:10654" # Yggdrasil Listener + environment: + tz: /run/secrets/tz + secrets: + - tz + restart: unless-stopped + healthcheck: + test: ["CMD", "bash", "-c", "pgrep i2pd && pgrep yggdrasil"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + mac_address: ce:22:b8:0e:6e:78 + networks: + darkproxy: + ipv4_address: 10.5.0.2 + + tor_yggdrasil: + image: host60/tor_yggdrasil:v1.0 +# image: tor_yggdrasil:latest +# container_name: tor_over_yggdrasil + container_name: darktor + # platform removed; image is amd64-only + cap_add: + - NET_ADMIN # Required for network tunnel management + security_opt: + - no-new-privileges:true # Security enhancement - prevent privilege esca> + devices: + - /dev/net/tun:/dev/net/tun # TUN device for Yggdrasil network interface + ports: +# - "127.0.0.1:9050:9050/tcp" # SOCKS5 proxy port (Tor) for application-l> + - "10655:10655/tcp" # Yggdrasil peer connections + secrets: + - YGGDRASIL_GENERATE_KEYS + environment: + YGGDRASIL_GENERATE_KEYS: /run/secrets/YGGDRASIL_GENERATE_KEYS # Generate new Yggdrasil keys on startup + restart: unless-stopped # Auto-restart unless manually stopped + dns: + - "10.5.0.6" + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + networks: + darkproxy: + ipv4_address: 10.5.0.7 + + alfis: + image: cofob/alfis + # platform removed; image is amd64 only + container_name: darkalfis + volumes: + - darkalfis_data:/storage + restart: unless-stopped + stop_grace_period: 10s + dns: + - "10.5.0.6" + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + networks: + darkproxy: + ipv4_address: 10.5.0.3 + + coredns: +# image: darkdns + image: host60/darkdns:v1.0 + build: + context: ./PopuraDNS + dockerfile: Dockerfile + container_name: darkdns + restart: unless-stopped + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + logging: + driver: "json-file" + options: + max-size: "10m" # Maximum log file size + max-file: "3" # Maximum of 3 rotating log files + networks: + darkproxy: + ipv4_address: 10.5.0.4 + + unbound: + container_name: darkunbound + image: mvance/unbound:latest + # platform removed; image is amd64-only + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + networks: + darkproxy: + ipv4_address: 10.5.0.5 + volumes: + - type: bind + read_only: true + source: ./unbound/unbound.conf + target: /opt/unbound/etc/unbound/unbound.conf + - "./unbound/forward-records.conf:/opt/unbound/etc/unbound/forward-records.conf" + - "./unbound/a-records.conf:/opt/unbound/etc/unbound/a-records.conf" +# ports: +# - "5053:5053/tcp" +# - "5053:5053/udp" +# healthcheck: +# disable: true + restart: unless-stopped + + pihole: + container_name: darkpihole + image: pihole/pihole:latest + platform: linux/arm64 + # For DHCP it is recommended to remove these ports and instead add: network_mode: "host" + ports: + # DNS Ports +# - "53:53/tcp" +# - "53:53/udp" + # Default HTTP Port +# - "80:80/tcp" + # Default HTTPs Port. FTL will generate a self-signed certificate +# - "443:443/tcp + # Uncomment the line below if you are using Pi-hole as your DHCP server +# - "67:67/udp" + # Uncomment the line below if you are using Pi-hole as your NTP server +# - "123:123/udp" + - "100.117.196.19:2003:80/tcp" # pihole web port + environment: + # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g: + TZ: 'America/Detroit' + # WEBPASSWORD: 'set a secure password here or it will be random' +# FTLCONF_webserver_api_password: 'darkproxy' + # If using Docker's default `bridge` network setting the dns listening mode should be set to 'all' +# FTLCONF_dns_listeningMode: 'all' + PIHOLE_DNS_: 10.5.0.4 + TEMPERATUREUNIT: f + WEBTHEME: lcars + WEBPASSWORD: darkproxy + FTLCONF_dns_upstreams: coredns + # Volumes store your data between container upgrades + volumes: + # For persisting Pi-hole's databases and common configuration file + - './pihole/etc-pihole:/etc/pihole' + # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most starting fresh with Pi-hole v6. If you're upgrading from v5 you and have used this directory before, you should keep it enabled for the first v6 container start to allow for a complete migration. It can be removed afterwards. Needs environment variable FTLCONF_misc_etc_dnsmasq_d: 'true' + - './pihole/etc-dnsmasq.d:/etc/dnsmasq.d' + cap_add: + # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities + # Required if you are using Pi-hole as your DHCP server, else not needed + - NET_ADMIN + # Required if you are using Pi-hole as your NTP client to be able to set the host's system time +# - SYS_TIME + # Optional, if Pi-hole should get some more processing time + - SYS_NICE + restart: unless-stopped # Recommended but not required (DHCP needs NET_ADMIN) + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + healthcheck: + test: ["CMD", "dig", "+short", "+norecurse", "+timeout=2", "@127.0.0.1", "google.com"] + interval: 1m + timeout: 10s + retries: 3 + start_period: 30s + networks: + darkproxy: + ipv4_address: 10.5.0.6 + + geth: + image: ethereum/client-go:latest + platform: linux/arm64 + container_name: darkgeth + command: + - --syncmode=light + - --http + - --http.addr=0.0.0.0 + - --http.port=8545 + - --http.api=eth,net,web3 + - --cache=256 + networks: + darkproxy: + ipv4_address: 10.5.0.10 + restart: unless-stopped + stop_grace_period: 30s + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8545"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 120s + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + + emc: + image: wg00/emercoin:0.8.4 + platform: linux/arm64 + container_name: darkemer + volumes: + - emc_data:/emc + - /etc/localtime:/etc/localtime:ro + networks: + darkproxy: + ipv4_address: 10.5.0.9 + restart: unless-stopped + stop_grace_period: 30s + +volumes: + darkalfis_data: + name: darkalfis_data + emc_data: + name: emc_data + tor-data: + driver: local + +networks: + darkproxy: +# name: darkproxy + enable_ipv6: true + driver: bridge + ipam: + config: + - subnet: 10.5.0.0/16 + gateway: 10.5.0.1 + - subnet: 2001:0BC5::/112 + gateway: 2001:0BC5::1 + +secrets: + tz: + file: ./secrets/tz.txt + YGGDRASIL_GENERATE_KEYS: + file: ./secrets/YGGDRASIL_GENERATE_KEYS.txt diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e93fb25 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,311 @@ +services: + dark3proxy: + container_name: dark3proxy + image: host60/darkproxy-dark3proxy:v1.0 + build: + context: ./3proxy/. + dns: + - "10.5.0.6" + dns_search: internal.namespace #namespace used in internal DNS + volumes: + - "./3proxy/first-instanse.cfg:/etc/3proxy/first-instanse.cfg" + - "./3proxy/second-instanse.cfg:/etc/3proxy/second-instanse.cfg" + restart: unless-stopped + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + ports: + - 2000:1080 # socks port + - 2002:8161 # 3proxy-eagle web page + networks: + darkproxy: + ipv4_address: 10.5.0.8 + + i2pd_yggdrasil: + container_name: darki2p + image: host60/i2pd_yggdrasil:v1.3 +# image: i2pd_yggdrasil:latest + privileged: true + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + ports: + # I2P Service Ports +# - "2827:2827" # BOB Bridge +# - "4444:4444" # HTTP Proxy +# - "4447:4447" # SOCKS Proxy + - "2001:7070" # Webconsole +# - "7650:7650" # I2PControl +# - "7654:7654" # I2CP +# - "7656:7656" # SAM Bridge (TCP) + - "10765:10765" # Main I2P Listener + # I2P name resolution occurs over the SOCKS proxy; there is no +# separate DNS listener in this build (the `dns` section in i2pd.conf +# is currently disabled because the shipped i2pd release doesn't +# support it). SOCKS remains on port 4447 and will resolve *.i2p names. + # mapping to host is possible if desired, e.g. 10853:53/udp + # Yggdrasil Ports + - "10654:10654" # Yggdrasil Listener + environment: + tz: /run/secrets/tz + secrets: + - tz + restart: unless-stopped + healthcheck: + test: ["CMD", "bash", "-c", "pgrep i2pd && pgrep yggdrasil"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + mac_address: ce:22:b8:0e:6e:78 + networks: + darkproxy: + ipv4_address: 10.5.0.2 + + tor_yggdrasil: + image: host60/tor_yggdrasil:v1.0 +# image: tor_yggdrasil:latest +# container_name: tor_over_yggdrasil + container_name: darktor + # platform removed; image is amd64-only + cap_add: + - NET_ADMIN # Required for network tunnel management + security_opt: + - no-new-privileges:true # Security enhancement - prevent privilege esca> + devices: + - /dev/net/tun:/dev/net/tun # TUN device for Yggdrasil network interface + ports: +# - "127.0.0.1:9050:9050/tcp" # SOCKS5 proxy port (Tor) for application-l> + - "10655:10655/tcp" # Yggdrasil peer connections + secrets: + - YGGDRASIL_GENERATE_KEYS + environment: + YGGDRASIL_GENERATE_KEYS: /run/secrets/YGGDRASIL_GENERATE_KEYS # Generate new Yggdrasil keys on startup + restart: unless-stopped # Auto-restart unless manually stopped + dns: + - "10.5.0.6" + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + networks: + darkproxy: + ipv4_address: 10.5.0.7 + + alfis: + image: cofob/alfis + # platform removed; image is amd64 only + container_name: darkalfis + volumes: + - darkalfis_data:/storage + restart: unless-stopped + stop_grace_period: 10s + dns: + - "10.5.0.6" + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + networks: + darkproxy: + ipv4_address: 10.5.0.3 + + coredns: +# image: darkdns + image: host60/darkdns:v1.0 + build: + context: ./PopuraDNS + dockerfile: Dockerfile + container_name: darkdns + restart: unless-stopped + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + logging: + driver: "json-file" + options: + max-size: "10m" # Maximum log file size + max-file: "3" # Maximum of 3 rotating log files + networks: + darkproxy: + ipv4_address: 10.5.0.4 + + unbound: + container_name: darkunbound + image: mvance/unbound:latest + # platform removed; image is amd64-only + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + networks: + darkproxy: + ipv4_address: 10.5.0.5 + volumes: + - type: bind + read_only: true + source: ./unbound/unbound.conf + target: /opt/unbound/etc/unbound/unbound.conf + - "./unbound/forward-records.conf:/opt/unbound/etc/unbound/forward-records.conf" + - "./unbound/a-records.conf:/opt/unbound/etc/unbound/a-records.conf" +# ports: +# - "5053:5053/tcp" +# - "5053:5053/udp" +# healthcheck: +# disable: true + restart: unless-stopped + + pihole: + container_name: darkpihole + image: pihole/pihole:latest + platform: linux/arm64 + # For DHCP it is recommended to remove these ports and instead add: network_mode: "host" + ports: + # DNS Ports +# - "53:53/tcp" +# - "53:53/udp" + # Default HTTP Port +# - "80:80/tcp" + # Default HTTPs Port. FTL will generate a self-signed certificate +# - "443:443/tcp + # Uncomment the line below if you are using Pi-hole as your DHCP server +# - "67:67/udp" + # Uncomment the line below if you are using Pi-hole as your NTP server +# - "123:123/udp" + - "2003:80/tcp" # pihole web port + environment: + # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g: + TZ: 'America/Detroit' + # WEBPASSWORD: 'set a secure password here or it will be random' +# FTLCONF_webserver_api_password: 'darkproxy' + # If using Docker's default `bridge` network setting the dns listening mode should be set to 'all' +# FTLCONF_dns_listeningMode: 'all' + PIHOLE_DNS_: 10.5.0.4 + TEMPERATUREUNIT: f + WEBTHEME: lcars + WEBPASSWORD: darkproxy + FTLCONF_dns_upstreams: coredns + # Volumes store your data between container upgrades + volumes: + # For persisting Pi-hole's databases and common configuration file + - './pihole/etc-pihole:/etc/pihole' + # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most starting fresh with Pi-hole v6. If you're upgrading from v5 you and have used this directory before, you should keep it enabled for the first v6 container start to allow for a complete migration. It can be removed afterwards. Needs environment variable FTLCONF_misc_etc_dnsmasq_d: 'true' + - './pihole/etc-dnsmasq.d:/etc/dnsmasq.d' + cap_add: + # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities + # Required if you are using Pi-hole as your DHCP server, else not needed + - NET_ADMIN + # Required if you are using Pi-hole as your NTP client to be able to set the host's system time +# - SYS_TIME + # Optional, if Pi-hole should get some more processing time + - SYS_NICE + restart: unless-stopped # Recommended but not required (DHCP needs NET_ADMIN) + sysctls: + - "net.ipv6.conf.all.disable_ipv6=0" + healthcheck: + test: ["CMD", "dig", "+short", "+norecurse", "+timeout=2", "@127.0.0.1", "google.com"] + interval: 1m + timeout: 10s + retries: 3 + start_period: 30s + networks: + darkproxy: + ipv4_address: 10.5.0.6 + + emc: + image: wg00/emercoin:0.8.4 + platform: linux/arm64 + container_name: darkemer + volumes: + - emc_data:/emc + - /etc/localtime:/etc/localtime:ro + networks: + darkproxy: + ipv4_address: 10.5.0.9 + restart: unless-stopped + stop_grace_period: 30s + + tailscale: + build: + context: ./tailscale + dockerfile: Dockerfile + container_name: darkscale + hostname: darkproxy-exit + cap_add: + - NET_ADMIN + - SYS_MODULE + devices: + - /dev/net/tun:/dev/net/tun + environment: + # ⚠️ Get your own auth key from https://login.tailscale.com/admin/settings/keys + - TS_AUTHKEY=${TS_AUTHKEY:-tskey-YOUR-AUTH-KEY-HERE} + - TS_EXTRA_ARGS=--accept-dns=false --advertise-exit-node + - TS_STATE_DIR=/var/lib/tailscale + volumes: + - tailscale_data:/var/lib/tailscale + networks: + darkproxy: + ipv4_address: 10.5.0.10 + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv6.conf.all.forwarding=1 + restart: unless-stopped + + # monitoring exporter for 3proxy metrics + 3proxy_exporter: + container_name: dark3proxy-exporter + build: + context: ./monitor + image: darkproxy-3proxy-exporter:latest + depends_on: + - dark3proxy + networks: + - darkproxy + + prometheus: + image: prom/prometheus:latest + container_name: darkprom + volumes: + - ./monitor/prometheus.yml:/etc/prometheus/prometheus.yml:ro + ports: + - "9090:9090" + networks: + - darkproxy + + grafana: + image: grafana/grafana:latest + container_name: darkgraf + ports: + - "2005:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=secret + volumes: + - grafana-data:/var/lib/grafana + networks: + - darkproxy + +volumes: + darkalfis_data: + name: darkalfis_data + emc_data: + name: emc_data + tor-data: + driver: local + tailscale_data: + name: tailscale_data + grafana-data: + name: grafana-data + +networks: + darkproxy: +# name: darkproxy + enable_ipv6: true + driver: bridge + ipam: + config: + - subnet: 10.5.0.0/16 + gateway: 10.5.0.1 + - subnet: 2001:0BC5::/112 + gateway: 2001:0BC5::1 + +secrets: + tz: + file: ./secrets/tz.txt + YGGDRASIL_GENERATE_KEYS: + file: ./secrets/YGGDRASIL_GENERATE_KEYS.txt + diff --git a/i2pd_yggdrasil_docker b/i2pd_yggdrasil_docker new file mode 160000 index 0000000..82f003e --- /dev/null +++ b/i2pd_yggdrasil_docker @@ -0,0 +1 @@ +Subproject commit 82f003e3276259e5c3311db79e769c2ff1087f14 diff --git a/pihole/etc-dnsmasq.d/01-pihole.conf b/pihole/etc-dnsmasq.d/01-pihole.conf new file mode 100644 index 0000000..8a106f1 --- /dev/null +++ b/pihole/etc-dnsmasq.d/01-pihole.conf @@ -0,0 +1,37 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2017 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# Dnsmasq config for Pi-hole's FTLDNS +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +############################################################################### +# FILE AUTOMATICALLY POPULATED BY PI-HOLE INSTALL/UPDATE PROCEDURE. # +# ANY CHANGES MADE TO THIS FILE AFTER INSTALL WILL BE LOST ON THE NEXT UPDATE # +# # +# IF YOU WISH TO CHANGE THE UPSTREAM SERVERS, CHANGE THEM IN: # +# /etc/pihole/setupVars.conf # +# # +# ANY OTHER CHANGES SHOULD BE MADE IN A SEPARATE CONFIG FILE # +# WITHIN /etc/dnsmasq.d/yourname.conf # +############################################################################### + +addn-hosts=/etc/pihole/local.list +addn-hosts=/etc/pihole/custom.list + + +localise-queries + + +no-resolv + +log-queries +log-facility=/var/log/pihole/pihole.log + +log-async +cache-size=10000 +server=172.30.0.9 +server=2001:db9::a +interface=eth0 diff --git a/pihole/etc-dnsmasq.d/06-rfc6761.conf b/pihole/etc-dnsmasq.d/06-rfc6761.conf new file mode 100644 index 0000000..fcdd001 --- /dev/null +++ b/pihole/etc-dnsmasq.d/06-rfc6761.conf @@ -0,0 +1,42 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2021 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# RFC 6761 config file for Pi-hole +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +############################################################################### +# FILE AUTOMATICALLY POPULATED BY PI-HOLE INSTALL/UPDATE PROCEDURE. # +# ANY CHANGES MADE TO THIS FILE AFTER INSTALL WILL BE LOST ON THE NEXT UPDATE # +# # +# CHANGES SHOULD BE MADE IN A SEPARATE CONFIG FILE # +# WITHIN /etc/dnsmasq.d/yourname.conf # +############################################################################### + +# RFC 6761: Caching DNS servers SHOULD recognize +# test, localhost, invalid +# names as special and SHOULD NOT attempt to look up NS records for them, or +# otherwise query authoritative DNS servers in an attempt to resolve these +# names. +server=/test/ +server=/localhost/ +server=/invalid/ + +# The same RFC requests something similar for +# 10.in-addr.arpa. 21.172.in-addr.arpa. 27.172.in-addr.arpa. +# 16.172.in-addr.arpa. 22.172.in-addr.arpa. 28.172.in-addr.arpa. +# 17.172.in-addr.arpa. 23.172.in-addr.arpa. 29.172.in-addr.arpa. +# 18.172.in-addr.arpa. 24.172.in-addr.arpa. 30.172.in-addr.arpa. +# 19.172.in-addr.arpa. 25.172.in-addr.arpa. 31.172.in-addr.arpa. +# 20.172.in-addr.arpa. 26.172.in-addr.arpa. 168.192.in-addr.arpa. +# Pi-hole implements this via the dnsmasq option "bogus-priv" (see +# 01-pihole.conf) because this also covers IPv6. + +# OpenWRT furthermore blocks bind, local, onion domains +# see https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob_plain;f=package/network/services/dnsmasq/files/rfc6761.conf;hb=HEAD +# and https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml +# We do not include the ".local" rule ourselves, see https://github.com/pi-hole/pi-hole/pull/4282#discussion_r689112972 +server=/bind/ +server=/onion/ diff --git a/scripts/validate-config.ps1 b/scripts/validate-config.ps1 new file mode 100644 index 0000000..6198963 --- /dev/null +++ b/scripts/validate-config.ps1 @@ -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" diff --git a/scripts/validate-config.sh b/scripts/validate-config.sh new file mode 100644 index 0000000..9a09b16 --- /dev/null +++ b/scripts/validate-config.sh @@ -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" diff --git a/tor_yggdrasil_docker b/tor_yggdrasil_docker new file mode 160000 index 0000000..4e14ce6 --- /dev/null +++ b/tor_yggdrasil_docker @@ -0,0 +1 @@ +Subproject commit 4e14ce6ed6fcc5a0913c9b771766f1b613fc627a diff --git a/tr.sh b/tr.sh new file mode 100755 index 0000000..db70e8f --- /dev/null +++ b/tr.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Most of this is credited to +# https://trac.torproject.org/projects/tor/wiki/doc/TransparentProxy +# With a few minor edits + +# to run iptables commands you need to be root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root." + exit 1 +fi + +### set variables +# destinations you don't want routed through Tor +_non_tor="192.168.1.0/24 192.168.0.0/24" + +# get the UID that Tor runs as +_tor_uid=$(docker exec -u tor tor id -u) + +# Tor's TransPort +_trans_port="9040" +_dns_port="5353" + +### set iptables *nat +iptables -t nat -A OUTPUT -m owner --uid-owner $_tor_uid -j RETURN +#iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports $_dns_port + +# allow clearnet access for hosts in $_non_tor +for _clearnet in $_non_tor 127.0.0.0/9 127.128.0.0/10; do + iptables -t nat -A OUTPUT -d $_clearnet -j RETURN +done + +# redirect all other output to Tor's TransPort +iptables -t nat -A OUTPUT -p tcp --syn -j REDIRECT --to-ports $_trans_port + +### set iptables *filter +iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + +# allow clearnet access for hosts in $_non_tor +for _clearnet in $_non_tor 127.0.0.0/8; do + iptables -A OUTPUT -d $_clearnet -j ACCEPT +done + +# allow only Tor output +iptables -A OUTPUT -m owner --uid-owner $_tor_uid -j ACCEPT +#iptables -A OUTPUT -j REJECT