diff --git a/Dockerfile b/Dockerfile index 4229b72..17081de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,29 +2,17 @@ FROM golang:1.24-bookworm AS builder RUN apt-get update && apt-get install -y git -# Клонируем репозиторий и собираем приложение WORKDIR /outline-sdk RUN git clone https://github.com/OutlineFoundation/outline-sdk.git . WORKDIR /outline-sdk/x/examples/outline-cli RUN CGO_ENABLED=1 go build -o /outline-cli -ldflags="-extldflags=-static" . -FROM debian:bookworm -RUN apt-get update && apt-get install -y curl wget -# Устанавливаем необходимые утилиты для работы сети и создаем пустой resolv.conf.head -RUN apt-get update && apt-get install -y iproute2 iptables && \ - rm -rf /var/lib/apt/lists/* && \ - touch /etc/resolv.conf.head +FROM lscr.io/linuxserver/wireguard:latest -# Копируем собранный бинарный файл из этапа сборки COPY --from=builder /outline-cli /usr/local/bin/outline-cli +COPY root/ / -# Обертка-энтрипоинт: готовит DNS и запускает outline-cli -COPY entrypoint.sh /usr/local/bin/outline-entrypoint.sh - -# Делаем бинарный файл исполняемым -RUN chmod +x /usr/local/bin/outline-cli /usr/local/bin/outline-entrypoint.sh - -# Точка входа -ENTRYPOINT ["/usr/local/bin/outline-entrypoint.sh"] - +RUN chmod +x /usr/local/bin/outline-cli \ + /custom-cont-init.d/* \ + /etc/s6-overlay/s6-rc.d/svc-outline/run diff --git a/docker-compose.yml b/docker-compose.yml index 285bae0..fd6fcb6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,27 @@ services: - outline-client: + wg2outline: build: . - container_name: outline-client + container_name: wg2outline privileged: true - # WireGuard UDP port is published from this service because - # the wireguard container shares this network namespace. ports: - "51821:51820/udp" environment: - # Replace with your Outline access key. OUTLINE_TRANSPORT: "${OUTLINE_TRANSPORT}" INTERNAL_SUBNET: "${INTERNAL_SUBNET}" + PUID: 1000 + PGID: 1000 + TZ: Etc/UTC + SERVERURL: ${SERVERURL} + SERVERPORT: 51821 + PEERS: ${PEERS:-1} + PEERDNS: 1.1.1.1 + PERSISTENTKEEPALIVE_PEERS: 25 + MTU: 1280 + LOG_CONFS: true cap_add: - NET_ADMIN - SYS_ADMIN + - SYS_MODULE devices: - /dev/net/tun:/dev/net/tun security_opt: @@ -26,45 +34,9 @@ services: - net.ipv6.conf.all.forwarding=0 - net.ipv6.conf.all.disable_ipv6=1 - net.ipv6.conf.default.disable_ipv6=1 - restart: unless-stopped - logging: - driver: json-file - options: - max-size: "50m" - max-file: "5" - - wireguard: - image: lscr.io/linuxserver/wireguard:latest - container_name: wg2outline - depends_on: - - outline-client - # Important: share network stack with outline-client so all - # traffic from WG peers leaves through Outline routing/policy. - network_mode: "service:outline-client" - cap_add: - - NET_ADMIN - - SYS_MODULE - environment: - - PUID=1000 - - PGID=1000 - - TZ=Etc/UTC - # Public IP or domain of this server for peers. - - SERVERURL=${SERVERURL} - - SERVERPORT=51821 - - PEERS=${PEERS:-1} - - PEERDNS=1.1.1.1 - - INTERNAL_SUBNET=${INTERNAL_SUBNET} - # Keepalive helps on NAT/mobile networks. - - PERSISTENTKEEPALIVE_PEERS=25 - # Optional for enterprise networks that block MTU/fragmentation. - - MTU=1280 - # Firewall/NAT is handled by the host/namespace setup. - - LOG_CONFS=true volumes: - ./wireguard-config:/config - /lib/modules:/lib/modules:ro - sysctls: - - net.ipv4.conf.all.src_valid_mark=1 restart: unless-stopped logging: driver: json-file diff --git a/root/custom-cont-init.d/10-outline-dns b/root/custom-cont-init.d/10-outline-dns new file mode 100644 index 0000000..ff8072c --- /dev/null +++ b/root/custom-cont-init.d/10-outline-dns @@ -0,0 +1,6 @@ +#!/usr/bin/with-contenv bash + +# Some distros mount /etc/resolv.conf as a file from the host. +# Ensure DNS works reliably inside the VPN network namespace. +umount /etc/resolv.conf 2>/dev/null || true +[ -f /etc/resolv.conf ] || printf 'nameserver 1.1.1.1\n' > /etc/resolv.conf diff --git a/root/etc/s6-overlay/s6-rc.d/svc-outline/dependencies.d/init-services b/root/etc/s6-overlay/s6-rc.d/svc-outline/dependencies.d/init-services new file mode 100644 index 0000000..e69de29 diff --git a/root/etc/s6-overlay/s6-rc.d/svc-outline/run b/root/etc/s6-overlay/s6-rc.d/svc-outline/run new file mode 100644 index 0000000..70d4c25 --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/svc-outline/run @@ -0,0 +1,82 @@ +#!/usr/bin/with-contenv bash +set -eu + +if [ -z "${OUTLINE_TRANSPORT:-}" ]; then + echo "ERROR: OUTLINE_TRANSPORT is required (ss://...)" >&2 + exit 1 +fi + +if [ -z "${INTERNAL_SUBNET:-}" ]; then + echo "ERROR: INTERNAL_SUBNET is required (e.g. 10.13.13.0)" >&2 + exit 1 +fi + +WG_FWMARK=0x51820 +WG_SUBNET="${INTERNAL_SUBNET}/24" + +apply_bypass_rules() { + # WireGuard UDP replies go to the peer's public IP, not the WG subnet. + # Mark and bypass Outline policy routing for those packets. + ip rule add pref 50 fwmark "${WG_FWMARK}" lookup main 2>/dev/null || true + + # Inner IP traffic to WG peers must use the main table (replies via wg0). + ip rule add pref 100 to "${WG_SUBNET}" lookup main 2>/dev/null || true + + # Docker/custom bridge subnets differ per host (e.g. 172.17.0.0/16, 10.200.0.0/16). + local pref=101 + local cidr + for cidr in $(ip -4 route show proto kernel scope link 2>/dev/null | awk '$2 == "dev" { print $1 }'); do + ip rule add pref "${pref}" to "${cidr}" lookup main 2>/dev/null || true + pref=$((pref + 1)) + done +} + +setup_wg_mangle() { + local wg_port="$1" + if iptables -t mangle -C OUTPUT -p udp --sport "${wg_port}" -j MARK --set-mark "${WG_FWMARK}" 2>/dev/null; then + return 0 + fi + iptables -t mangle -A OUTPUT -p udp --sport "${wg_port}" -j MARK --set-mark "${WG_FWMARK}" +} + +wait_for_outline_routing() { + local i + for i in $(seq 1 30); do + if ip link show outline233 >/dev/null 2>&1 && ip rule show | grep -q 'lookup 233'; then + return 0 + fi + sleep 1 + done + echo "WARN: Outline routing not ready after 30s, applying bypass rules anyway" >&2 + return 1 +} + +/usr/local/bin/outline-cli -transport "${OUTLINE_TRANSPORT}" & +OUTLINE_PID=$! + +wait_for_outline_routing || true +apply_bypass_rules +setup_wg_mangle "${SERVERPORT:-51820}" + +# wg0 starts after this service; refresh rules once the interface and listen port are known. +( + for _ in $(seq 1 60); do + if ip link show wg0 >/dev/null 2>&1; then + wg_port="$(wg show wg0 listen-port 2>/dev/null || echo "${SERVERPORT:-51820}")" + setup_wg_mangle "${wg_port}" + apply_bypass_rules + break + fi + sleep 2 + done +) & + +# Ensure exactly one NAT rule for traffic leaving via Outline interface. +if iptables -t nat -C POSTROUTING -o outline+ -j MASQUERADE 2>/dev/null; then + while iptables -t nat -C POSTROUTING -o outline+ -j MASQUERADE 2>/dev/null; do + iptables -t nat -D POSTROUTING -o outline+ -j MASQUERADE || true + done +fi +iptables -t nat -A POSTROUTING -o outline+ -j MASQUERADE + +wait "$OUTLINE_PID" diff --git a/root/etc/s6-overlay/s6-rc.d/svc-outline/type b/root/etc/s6-overlay/s6-rc.d/svc-outline/type new file mode 100644 index 0000000..5883cff --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/svc-outline/type @@ -0,0 +1 @@ +longrun diff --git a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/dependencies.d/svc-outline b/root/etc/s6-overlay/s6-rc.d/svc-wireguard/dependencies.d/svc-outline new file mode 100644 index 0000000..e69de29 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-outline b/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-outline new file mode 100644 index 0000000..e69de29