mirror of
https://github.com/p1neappleXpress/OpenFlux.git
synced 2026-10-02 05:04:39 +08:00
Add Docker image and compose for local client / exit-node runs
One multi-stage image (golang:1.26-alpine -> alpine:3.22, CGO off) serving both roles via an env-driven entrypoint: - client: SOCKS5 proxy, no special privileges, published on 127.0.0.1:1080 - exit-node: raw sockets + the kernel-RST drop, confined to the container's network namespace (NET_RAW/NET_ADMIN via compose), so the iptables rule can never touch the host — the containerized variant of the scoped rule the README already recommends docker-compose.yml runs the two ends behind separate profiles; .env.example documents TRANSPORT (yandex|vyandex|oneme), DOC_URL, MAX_TOKEN/MAX_UID, etc.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Keep the build context lean: only Go sources + go.mod/go.sum are needed
|
||||
# (export_ios*.go compile out via //go:build ios; mobile app dirs are inert).
|
||||
.git
|
||||
.gitignore
|
||||
.idea
|
||||
.claude
|
||||
|
||||
# Prebuilt binaries — never belong in the image.
|
||||
openflux
|
||||
openflux-*
|
||||
output/
|
||||
|
||||
# Mobile client sources & scripts — not part of the linux binary.
|
||||
ios-app/
|
||||
build_android.sh
|
||||
build_ios.sh
|
||||
build_ios_app.sh
|
||||
|
||||
# Local config & docs
|
||||
.env
|
||||
.env.local
|
||||
README.md
|
||||
README.ru.md
|
||||
|
||||
# Captures / artifacts
|
||||
*.pcap
|
||||
*.pcapng
|
||||
capture.txt
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copy to .env (gitignored) or export inline. All optional except the
|
||||
# credentials of whichever transport you run.
|
||||
|
||||
# Transport channel: yandex (legacy docs editor) | vyandex (new volga editor) | oneme (MAX)
|
||||
TRANSPORT=yandex
|
||||
|
||||
# yandex / vyandex: public Yandex document URL shared by client and exit node.
|
||||
DOC_URL=
|
||||
|
||||
# oneme: MAX Web token + the *other* side's user id (client dials the exit node's uid).
|
||||
MAX_TOKEN=
|
||||
MAX_UID=
|
||||
|
||||
# Client only: SOCKS5 listen address inside the container (published as 127.0.0.1:1080).
|
||||
SOCKS5_LISTEN=:1080
|
||||
|
||||
# Exit node only: pin a dedicated egress/alias IP. Usually unnecessary inside
|
||||
# the container's own netns — leave empty for auto-detection.
|
||||
EXIT_LOCAL_IP=
|
||||
|
||||
# 1 enables verbose packet-level logging (noisy).
|
||||
DEBUG=0
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# OpenFlux (openflux) — one image, two roles:
|
||||
# client — SOCKS5 proxy, no special privileges
|
||||
# exit-node — raw sockets + RST-drop, needs NET_RAW/NET_ADMIN (see compose)
|
||||
# Role is selected at runtime by the entrypoint from ROLE=client|exit-node.
|
||||
|
||||
FROM golang:1.26-alpine AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Cache module downloads across builds.
|
||||
COPY go.mod go.sum ./
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download
|
||||
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/openflux .
|
||||
|
||||
FROM alpine:3.22
|
||||
# ca-certificates: all transports are TLS (wss/https) to Yandex/MAX endpoints.
|
||||
# iptables: the exit node must drop kernel RSTs inside its network namespace.
|
||||
RUN apk add --no-cache ca-certificates iptables
|
||||
|
||||
COPY --from=build /out/openflux /usr/local/bin/openflux
|
||||
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/openflux
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
@@ -0,0 +1,49 @@
|
||||
# OpenFlux local runner. One image, two mutually independent services —
|
||||
# the two ends never talk to each other directly; they meet on the transport
|
||||
# channel (the Yandex doc / the MAX call). Start only the end you need:
|
||||
#
|
||||
# docker compose --profile client up -d --build # SOCKS5 on 127.0.0.1:1080
|
||||
# docker compose --profile exit-node up -d --build
|
||||
#
|
||||
# Configure via .env (see .env.example) or inline env vars:
|
||||
# DOC_URL=... MAX_TOKEN=... MAX_UID=... TRANSPORT=vyandex docker compose --profile client up -d
|
||||
#
|
||||
# Privileges, deliberately asymmetric:
|
||||
# client — none. It dials out like a browser and serves SOCKS5 locally.
|
||||
# exit-node — NET_RAW (raw sockets) + NET_ADMIN (the RST-drop rule), confined
|
||||
# to this container's netns; the rule can never touch the host.
|
||||
|
||||
services:
|
||||
client:
|
||||
build: .
|
||||
image: openflux:local
|
||||
restart: unless-stopped
|
||||
profiles: [client]
|
||||
environment:
|
||||
ROLE: client
|
||||
TRANSPORT: ${TRANSPORT:-yandex}
|
||||
URL: ${DOC_URL:-}
|
||||
MAX_TOKEN: ${MAX_TOKEN:-}
|
||||
MAX_UID: ${MAX_UID:-}
|
||||
SOCKS5_LISTEN: ${SOCKS5_LISTEN:-:1080}
|
||||
DEBUG: ${DEBUG:-0}
|
||||
# No auth on the SOCKS5 server — keep it bound to the host loopback only.
|
||||
ports:
|
||||
- "127.0.0.1:1080:1080"
|
||||
|
||||
exit-node:
|
||||
build: .
|
||||
image: openflux:local
|
||||
restart: unless-stopped
|
||||
profiles: [exit-node]
|
||||
environment:
|
||||
ROLE: exit-node
|
||||
TRANSPORT: ${TRANSPORT:-yandex}
|
||||
URL: ${DOC_URL:-}
|
||||
MAX_TOKEN: ${MAX_TOKEN:-}
|
||||
MAX_UID: ${MAX_UID:-}
|
||||
LOCAL_IP: ${EXIT_LOCAL_IP:-}
|
||||
DEBUG: ${DEBUG:-0}
|
||||
cap_add:
|
||||
- NET_RAW
|
||||
- NET_ADMIN
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
# Maps environment variables to openflux flags and, for the exit node,
|
||||
# installs the kernel-RST-drop rule *inside this container's netns*.
|
||||
#
|
||||
# Why the rule: the exit node's TCP connections live in a userspace (gVisor)
|
||||
# stack, so the kernel has no socket for them and answers every inbound
|
||||
# SYN-ACK with an RST, tearing the tunnel down. Confining the DROP to the
|
||||
# container netns is the scoped variant of upstream's host-wide rule — it
|
||||
# cannot affect the host or other containers.
|
||||
set -eu
|
||||
|
||||
role="${ROLE:-client}"
|
||||
transport="${TRANSPORT:-yandex}"
|
||||
listen="${SOCKS5_LISTEN:-:1080}"
|
||||
|
||||
case "$role" in
|
||||
client|exit-node) ;;
|
||||
*)
|
||||
echo "ROLE must be 'client' or 'exit-node' (got '$role')" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$transport" in
|
||||
yandex|vyandex|oneme) ;;
|
||||
*)
|
||||
echo "TRANSPORT must be one of yandex, vyandex, oneme (got '$transport')" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
set -- "--$role" --transport "$transport"
|
||||
|
||||
if [ "$role" = client ]; then
|
||||
set -- "$@" --socks5 "$listen"
|
||||
fi
|
||||
|
||||
if [ -n "${URL:-}" ]; then
|
||||
set -- "$@" --url "$URL"
|
||||
fi
|
||||
if [ -n "${MAX_TOKEN:-}" ]; then
|
||||
set -- "$@" --maxToken "$MAX_TOKEN"
|
||||
fi
|
||||
if [ -n "${MAX_UID:-}" ]; then
|
||||
set -- "$@" --maxUid "$MAX_UID"
|
||||
fi
|
||||
if [ -n "${LOCAL_IP:-}" ]; then
|
||||
# Optional: pin the egress IP (alias IP) so the RST drop could be scoped
|
||||
# with `-s <ip>` too; inside a dedicated container netns it's usually
|
||||
# unnecessary.
|
||||
set -- "$@" --local-ip "$LOCAL_IP"
|
||||
fi
|
||||
case "${DEBUG:-0}" in
|
||||
1|true|yes) set -- "$@" --debug ;;
|
||||
esac
|
||||
|
||||
if [ "$role" = exit-node ]; then
|
||||
echo "[entrypoint] dropping outbound TCP RSTs inside the container netns"
|
||||
if ! iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP; then
|
||||
echo "[entrypoint] WARNING: iptables failed (missing NET_ADMIN?); kernel RSTs will kill tunnel connections" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[entrypoint] exec: openflux $*"
|
||||
exec openflux "$@"
|
||||
Reference in New Issue
Block a user