diff --git a/3proxy/Dockerfile b/3proxy/Dockerfile index 1e357f4..37e2c49 100644 --- a/3proxy/Dockerfile +++ b/3proxy/Dockerfile @@ -1,25 +1,15 @@ # Stage 1: Build FROM --platform=$TARGETPLATFORM 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/* +RUN apt-get update && apt-get install -y --no-install-recommends \ + wget \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* -# Download and extract appropriate 3proxy package based on architecture +# Download and extract appropriate 3proxy package based on architecture. RUN set -eux; \ if [ "$TARGETARCH" = "amd64" ]; then \ pkg_url="https://github.com/3proxy/3proxy/releases/download/0.9.4/3proxy-0.9.4.x86_64.deb"; \ @@ -37,45 +27,59 @@ RUN set -eux; \ mkdir -p /usr/local/3proxy; \ cp -a /tmp/3proxy-root/usr/local/3proxy/. /usr/local/3proxy/ || 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 1b: Build Yggdrasil tooling for target arch +FROM --platform=$BUILDPLATFORM golang:1.22-bookworm AS builder_yggdrasil + +ARG TARGETARCH +ARG YGGDRASIL_VERSION=v0.5.12 + +RUN git clone https://github.com/yggdrasil-network/yggdrasil-go.git /src/yggdrasil-go \ + && cd /src/yggdrasil-go \ + && git checkout ${YGGDRASIL_VERSION} + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) goarch=amd64 ;; \ + arm64) goarch=arm64 ;; \ + arm) goarch=arm ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}"; exit 1 ;; \ + esac; \ + cd /src/yggdrasil-go; \ + mkdir -p /out; \ + CGO_ENABLED=0 GOOS=linux GOARCH=${goarch} go build -o /out/yggdrasil ./cmd/yggdrasil; \ + CGO_ENABLED=0 GOOS=linux GOARCH=${goarch} go build -o /out/yggdrasilctl ./cmd/yggdrasilctl; \ + CGO_ENABLED=0 GOOS=linux GOARCH=${goarch} go build -o /out/genkeys ./cmd/genkeys # Stage 2: Runtime FROM ubuntu:22.04 -ARG TARGETARCH - -RUN apt-get update && apt-get install -y \ - libqt5core5a \ - libqt5network5 \ - libqt5xml5 \ - libqt5sql5 \ - libqt5sql5-sqlite \ +RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ + iproute2 \ + iputils-ping \ && rm -rf /var/lib/apt/lists/* # Copy binaries using the correct absolute paths from Stage 1 COPY --from=builder /usr/local/bin/3proxy /usr/bin/3proxy -COPY --from=builder /src/3proxy-eagle/src/3proxy-eagle /usr/bin/3proxy-eagle COPY --from=builder /usr/local/3proxy /usr/local/3proxy +COPY --from=builder_yggdrasil /out/yggdrasil /usr/bin/yggdrasil +COPY --from=builder_yggdrasil /out/yggdrasilctl /usr/bin/yggdrasilctl +COPY --from=builder_yggdrasil /out/genkeys /usr/bin/genkeys + +# Copy local runtime helpers/config +COPY ./entrypoint.sh /entrypoint.sh +COPY ./yggdrasil.conf /etc/yggdrasil/yggdrasil.conf # Setup config directories -RUN mkdir -p /etc/3proxy /etc/3proxy-eagle/data +RUN mkdir -p /etc/3proxy /etc/yggdrasil \ + && chmod +x /entrypoint.sh # 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 +EXPOSE 1080 3128 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"] +# Start Yggdrasil first, then run plain 3proxy instances from entrypoint. +ENTRYPOINT ["/entrypoint.sh"] +CMD [] diff --git a/3proxy/entrypoint.sh b/3proxy/entrypoint.sh new file mode 100644 index 0000000..c9011a5 --- /dev/null +++ b/3proxy/entrypoint.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +set -euo pipefail + +YGG_CONF="${YGG_CONF:-/etc/yggdrasil/yggdrasil.conf}" +YGG_CONNECT_WAIT_SECONDS="${YGG_CONNECT_WAIT_SECONDS:-8}" +YGG_RUNTIME_CONF="${YGG_RUNTIME_CONF:-/run/yggdrasil.conf}" +PROXY_CFG_PRIMARY="${PROXY_CFG_PRIMARY:-/etc/3proxy/first-instanse.cfg}" +PROXY_CFG_SECONDARY="${PROXY_CFG_SECONDARY:-/etc/3proxy/second-instanse.cfg}" + +ygg_pid="" +proxy_primary_pid="" +proxy_secondary_pid="" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" +} + +cleanup() { + if [[ -n "${proxy_primary_pid}" ]]; then + kill -TERM "${proxy_primary_pid}" 2>/dev/null || true + fi + if [[ -n "${proxy_secondary_pid}" ]]; then + kill -TERM "${proxy_secondary_pid}" 2>/dev/null || true + fi + if [[ -n "${ygg_pid}" ]]; then + kill -TERM "${ygg_pid}" 2>/dev/null || true + fi + wait || true +} + +trap cleanup TERM INT + +if [[ ! -f "${YGG_CONF}" ]]; then + log "Missing Yggdrasil config at ${YGG_CONF}" + exit 1 +fi + +if [[ ! -f "${PROXY_CFG_PRIMARY}" ]]; then + log "Missing primary 3proxy config at ${PROXY_CFG_PRIMARY}" + exit 1 +fi + +if [[ ! -f "${PROXY_CFG_SECONDARY}" ]]; then + log "Missing secondary 3proxy config at ${PROXY_CFG_SECONDARY}" + exit 1 +fi + +cp "${YGG_CONF}" "${YGG_RUNTIME_CONF}" + +# Populate keys once if config still has empty key fields. +if ! grep -Eq '^[[:space:]]*PrivateKey:[[:space:]]+[0-9a-fA-F]+' "${YGG_RUNTIME_CONF}"; then + log "Generating Yggdrasil keys for dark3proxy" + key_output="$(timeout 8 /usr/bin/genkeys 2>/dev/null || true)" + priv_key="$(printf '%s\n' "${key_output}" | awk '/^Priv:/{print $2; exit}')" + pub_key="$(printf '%s\n' "${key_output}" | awk '/^Pub:/{print $2; exit}')" + + if [[ -z "${priv_key}" || -z "${pub_key}" ]]; then + log "Failed to parse generated Yggdrasil keys" + exit 1 + fi + + sed -i -E \ + -e "s|^([[:space:]]*PublicKey:).*|\1 ${pub_key}|" \ + -e "s|^([[:space:]]*PrivateKey:).*|\1 ${priv_key}|" \ + "${YGG_RUNTIME_CONF}" +fi + +sysctl net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true + +log "Starting Yggdrasil" +/usr/bin/yggdrasil -useconffile "${YGG_RUNTIME_CONF}" & +ygg_pid=$! + +sleep "${YGG_CONNECT_WAIT_SECONDS}" +if ! kill -0 "${ygg_pid}" 2>/dev/null; then + log "Yggdrasil exited during startup" + exit 1 +fi + +log "Starting secondary 3proxy instance (${PROXY_CFG_SECONDARY})" +/usr/bin/3proxy "${PROXY_CFG_SECONDARY}" & +proxy_secondary_pid=$! + +sleep 1 +if ! kill -0 "${proxy_secondary_pid}" 2>/dev/null; then + log "Secondary 3proxy instance exited during startup" + exit 1 +fi + +log "Starting primary 3proxy instance (${PROXY_CFG_PRIMARY})" +/usr/bin/3proxy "${PROXY_CFG_PRIMARY}" & +proxy_primary_pid=$! + +sleep 1 +if ! kill -0 "${proxy_primary_pid}" 2>/dev/null; then + log "Primary 3proxy instance exited during startup" + exit 1 +fi + +wait -n "${proxy_secondary_pid}" "${proxy_primary_pid}" "${ygg_pid}" +exit_code=$? +log "A managed process exited, shutting down" +cleanup +exit "${exit_code}" diff --git a/3proxy/first-instanse.cfg b/3proxy/first-instanse.cfg index 5a31c7a..50e15dc 100644 --- a/3proxy/first-instanse.cfg +++ b/3proxy/first-instanse.cfg @@ -1,42 +1,50 @@ # regular 3proxy configuration flush +nserver 10.5.0.7/9053 nserver 10.5.0.6 nscache 65536 auth none -admin -p8161 -a127.0.0.1 -# strict required by 3proxy-eagle log lines +# Expose admin interface to host via docker port mapping (2002 -> 8161). +admin -p8161 +# Keep compact per-request accounting in container logs. 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}} +# Blocklist (plain 3proxy syntax). +deny * * vk.com +deny * * ok.ru +deny * * mail.ru +deny * * gosuslugi.ru # 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 +allow * * .i2p #parent 1000 http 127.0.0.1 4444 parent 1000 socks5+ 10.5.0.2 4447 +flush ## yggdrasil (127.0.0.1 1085 is second instanse) allow * * 200::/7 parent 1000 socks5+ 127.0.0.1 1085 +flush + ## ygg domains resolving by alfis -allow * * *.ygg +allow * * .ygg parent 1000 socks5+ 127.0.0.1 1085 +flush + ## meshnames domains resolving by meshnamed -allow * * *.meshname +allow * * .meshname parent 1000 socks5+ 127.0.0.1 1085 -allow * * *.meship +flush + +allow * * .meship parent 1000 socks5+ 127.0.0.1 1085 flush ## onion sites -allow * * *.onion +allow * * .onion parent 1000 socks5+ 10.5.0.7 9050 flush diff --git a/3proxy/yggdrasil.conf b/3proxy/yggdrasil.conf new file mode 100644 index 0000000..20b341f --- /dev/null +++ b/3proxy/yggdrasil.conf @@ -0,0 +1,27 @@ +{ + # Keep an explicit peer to darki2p so dark3proxy always has a Ygg path. + Peers: [ + tls://10.5.0.2:10654 + ] + + InterfacePeers: {} + + # No public listener needed for dark3proxy use-case. + Listen: [] + + AdminListen: unix:///var/run/yggdrasil.sock + + # Disable multicast discovery for deterministic container behavior. + MulticastInterfaces: [] + + AllowedPublicKeys: [] + + PublicKey: + PrivateKey: + + IfName: auto + IfMTU: 65535 + + NodeInfoPrivacy: false + NodeInfo: {} +} diff --git a/docker-compose.lock.yml b/docker-compose.lock.yml index 07a71e8..9b5316a 100644 --- a/docker-compose.lock.yml +++ b/docker-compose.lock.yml @@ -4,17 +4,25 @@ services: build: context: ./3proxy/. platform: linux/amd64 + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun dns: - "10.5.0.6" dns_search: internal.namespace #namespace used in internal DNS + environment: + YGG_CONNECT_WAIT_SECONDS: "8" volumes: - "./3proxy/first-instanse.cfg:/etc/3proxy/first-instanse.cfg" - "./3proxy/second-instanse.cfg:/etc/3proxy/second-instanse.cfg" + - "./3proxy/yggdrasil.conf:/etc/yggdrasil/yggdrasil.conf" restart: unless-stopped sysctls: - "net.ipv6.conf.all.disable_ipv6=0" ports: - - "2002:8161" # 3proxy-eagle web page + - "2000:1080" # primary host SOCKS via dark3proxy (externally reachable) + - "2002:8161" # 3proxy admin page networks: darkproxy: ipv4_address: 10.5.0.8 @@ -87,7 +95,6 @@ services: volumes: - "./tor_yggdrasil_docker/torrc:/etc/tor/torrc:ro" ports: - - "2000:9050/tcp" # primary host SOCKS (direct Tor, onion-capable, externally reachable) - "127.0.0.1:2006:9053/udp" # direct Tor DNSPort restart: unless-stopped # Auto-restart unless manually stopped dns: diff --git a/docker-compose.yml b/docker-compose.yml index 541b1b4..51840a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,12 +4,19 @@ services: build: context: ./3proxy/. platform: linux/amd64 + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun dns: - "10.5.0.6" dns_search: internal.namespace #namespace used in internal DNS + environment: + YGG_CONNECT_WAIT_SECONDS: "8" volumes: - "./3proxy/first-instanse.cfg:/etc/3proxy/first-instanse.cfg" - "./3proxy/second-instanse.cfg:/etc/3proxy/second-instanse.cfg" + - "./3proxy/yggdrasil.conf:/etc/yggdrasil/yggdrasil.conf" restart: unless-stopped cpus: "1.0" mem_reservation: 64m @@ -17,7 +24,8 @@ services: sysctls: - "net.ipv6.conf.all.disable_ipv6=0" ports: - - "2002:8161" # 3proxy-eagle web page (localhost only) + - "2000:1080" # primary host SOCKS via dark3proxy (externally reachable) + - "2002:8161" # 3proxy admin page networks: darkproxy: ipv4_address: 10.5.0.8 @@ -87,7 +95,6 @@ services: volumes: - "./tor_yggdrasil_docker/torrc:/etc/tor/torrc:ro" ports: - - "2000:9050/tcp" # primary host SOCKS (direct Tor, onion-capable, externally reachable) - "127.0.0.1:2006:9053/udp" # direct Tor DNSPort restart: unless-stopped # Auto-restart unless manually stopped cpus: "2.0"