From dee1c3ab1663026b11d3b7a9f78fd208c2ebb294 Mon Sep 17 00:00:00 2001 From: soheil khaledabadi Date: Sat, 6 Jun 2026 19:30:25 +0330 Subject: [PATCH] feat(proxy): add VLESS/Xray proxy configuration and update Docker setup --- .dockerignore | 1 + .env.production.example | 18 +++ .gitignore | 1 + Dockerfile | 24 +++ README.md | 44 ++++- docker-compose.yml | 45 +++++- docker/apt/configure-arvan-mirror.sh | 27 +++- docker/xray/config.example.json | 234 +++++++++++++++++++++++++++ 8 files changed, 383 insertions(+), 11 deletions(-) create mode 100644 docker/xray/config.example.json diff --git a/.dockerignore b/.dockerignore index cd1f67e..b6d6ad4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,6 +7,7 @@ .env.* !.env.example !.env.production.example +docker/xray/config.local.json node_modules vendor diff --git a/.env.production.example b/.env.production.example index bba4bfe..eb5c3cb 100644 --- a/.env.production.example +++ b/.env.production.example @@ -5,6 +5,24 @@ APP_DEBUG=false APP_URL=https://example.com HTTP_PORT=8080 +DOCKER_REGISTRY=docker.arvancloud.ir + +# Optional VLESS/Xray proxy fallback. +# Start with: docker compose --env-file .env.production --profile proxy up -d vless-proxy +XRAY_IMAGE=ghcr.io/xtls/xray-core:latest +XRAY_CONFIG_FILE=./docker/xray/config.local.json +VLESS_PROXY_HTTP_PORT=2080 +VLESS_PROXY_SOCKS_PORT=2081 +DOCKER_BUILD_HTTP_PROXY= +DOCKER_BUILD_HTTPS_PROXY= +DOCKER_BUILD_NO_PROXY=localhost,127.0.0.1,::1 +DEBIAN_APT_MIRROR= +DEBIAN_SECURITY_APT_MIRROR= +DEBIAN_APT_DISABLE_UPDATES= +APP_HTTP_PROXY= +APP_HTTPS_PROXY= +APP_NO_PROXY=localhost,127.0.0.1,::1,mariadb,nginx,app,queue,scheduler,vless-proxy + APP_LOCALE=fa APP_FALLBACK_LOCALE=en APP_FAKER_LOCALE=fa_IR diff --git a/.gitignore b/.gitignore index 7be55e2..c223a28 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ /.vscode /.zed /auth.json +/docker/xray/config.local.json /node_modules /public/build /public/fonts-manifest.dev.json diff --git a/Dockerfile b/Dockerfile index 630eaf5..62276fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,13 @@ ARG PHP_VERSION=8.4 FROM ${DOCKER_REGISTRY}/node:24-alpine AS assets WORKDIR /app +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY +ARG http_proxy +ARG https_proxy +ARG no_proxy + RUN sed -i 's|https://dl-cdn.alpinelinux.org|https://mirror.arvancloud.ir/alpine|g' /etc/apk/repositories COPY package.json package-lock.json ./ @@ -21,6 +28,16 @@ ARG PHP_VERSION FROM ${DOCKER_REGISTRY}/php:${PHP_VERSION}-fpm-bookworm AS app WORKDIR /var/www/html +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY +ARG http_proxy +ARG https_proxy +ARG no_proxy +ARG DEBIAN_APT_MIRROR +ARG DEBIAN_SECURITY_APT_MIRROR +ARG DEBIAN_APT_DISABLE_UPDATES + ENV COMPOSER_ALLOW_SUPERUSER=1 COPY docker/php/install-php-extensions /usr/local/bin/install-php-extensions @@ -90,6 +107,13 @@ CMD ["php-fpm"] ARG DOCKER_REGISTRY FROM ${DOCKER_REGISTRY}/nginx:1.27-alpine AS nginx +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY +ARG http_proxy +ARG https_proxy +ARG no_proxy + RUN sed -i 's|https://dl-cdn.alpinelinux.org|https://mirror.arvancloud.ir/alpine|g' /etc/apk/repositories COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf diff --git a/README.md b/README.md index f23dcab..8421a66 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,48 @@ docker compose --env-file .env.production restart app queue scheduler docker compose --env-file .env.production build ``` +### حالت fallback با VLESS/Xray + +کانفیگ VLESS داخل `docker/xray/config.local.json` قرار دارد و این فایل در git ignore شده است. فایل نمونه‌ی بدون اطلاعات اتصال هم در `docker/xray/config.example.json` موجود است. اگر این فایل local را نداشتید، از نمونه کپی بگیرید و مقدارهای اتصال را پر کنید. + +برای وقتی که اینترنت مستقیم یا mirrorها درست جواب نمی‌دهند، اول پروکسی را با profile جدا بالا بیاورید: + +```bash +docker compose --env-file .env.production --profile proxy up -d vless-proxy +``` + +پروکسی HTTP روی سیستم میزبان از `127.0.0.1:2080` و SOCKS از `127.0.0.1:2081` در دسترس است. برای build stepهای داخل Dockerfile از این دستور استفاده کنید: + +```bash +DOCKER_BUILD_HTTP_PROXY=http://host.docker.internal:2080 \ +DOCKER_BUILD_HTTPS_PROXY=http://host.docker.internal:2080 \ +docker compose --env-file .env.production build +``` + +اگر خود pull کردن imageهای Docker مشکل داشت، این پروکسی را در Docker Desktop هم تنظیم کنید: + +```text +HTTP proxy: http://127.0.0.1:2080 +HTTPS proxy: http://127.0.0.1:2080 +``` + +برای Debian apt، پیش‌فرض روی repository رسمی Debian می‌ماند چون mirror آروان ممکن است با نسخه‌ی base image sync نباشد و dependency conflict بدهد. اگر عمداً خواستید apt را هم به mirror دیگری ببرید، این متغیرها را هنگام build تنظیم کنید: + +```bash +DEBIAN_APT_MIRROR=http://mirror.example/debian \ +DEBIAN_SECURITY_APT_MIRROR=http://mirror.example/debian-security \ +DEBIAN_APT_DISABLE_UPDATES=true \ +docker compose --env-file .env.production build +``` + +برای اینکه درخواست‌های خروجی خود اپلیکیشن هم از پروکسی رد شوند: + +```bash +APP_HTTP_PROXY=http://vless-proxy:8080 \ +APP_HTTPS_PROXY=http://vless-proxy:8080 \ +docker compose --env-file .env.production --profile proxy up -d +``` + اول دیتابیس را بالا بیاورید: ```bash @@ -104,4 +146,4 @@ docker compose --env-file .env.production down -v ```bash docker compose --env-file .env.production exec app php artisan config:clear docker compose --env-file .env.production restart app queue scheduler -``` \ No newline at end of file +``` diff --git a/docker-compose.yml b/docker-compose.yml index bd457be..33970f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,51 @@ +x-build-args: &build-args + DOCKER_REGISTRY: ${DOCKER_REGISTRY:-docker.arvancloud.ir} + HTTP_PROXY: ${DOCKER_BUILD_HTTP_PROXY:-} + HTTPS_PROXY: ${DOCKER_BUILD_HTTPS_PROXY:-${DOCKER_BUILD_HTTP_PROXY:-}} + NO_PROXY: ${DOCKER_BUILD_NO_PROXY:-localhost,127.0.0.1,::1} + http_proxy: ${DOCKER_BUILD_HTTP_PROXY:-} + https_proxy: ${DOCKER_BUILD_HTTPS_PROXY:-${DOCKER_BUILD_HTTP_PROXY:-}} + no_proxy: ${DOCKER_BUILD_NO_PROXY:-localhost,127.0.0.1,::1} + DEBIAN_APT_MIRROR: ${DEBIAN_APT_MIRROR:-} + DEBIAN_SECURITY_APT_MIRROR: ${DEBIAN_SECURITY_APT_MIRROR:-} + DEBIAN_APT_DISABLE_UPDATES: ${DEBIAN_APT_DISABLE_UPDATES:-} + +x-app-proxy-environment: &app-proxy-environment + HTTP_PROXY: ${APP_HTTP_PROXY:-} + HTTPS_PROXY: ${APP_HTTPS_PROXY:-${APP_HTTP_PROXY:-}} + NO_PROXY: ${APP_NO_PROXY:-localhost,127.0.0.1,::1,mariadb,nginx,app,queue,scheduler,vless-proxy} + services: + vless-proxy: + image: ${XRAY_IMAGE:-ghcr.io/xtls/xray-core:latest} + restart: unless-stopped + profiles: + - proxy + command: ["run", "-config", "/usr/local/etc/xray/config.json"] + volumes: + - type: bind + source: ${XRAY_CONFIG_FILE:-./docker/xray/config.local.json} + target: /usr/local/etc/xray/config.json + read_only: true + bind: + create_host_path: false + ports: + - "127.0.0.1:${VLESS_PROXY_HTTP_PORT:-2080}:8080" + - "127.0.0.1:${VLESS_PROXY_SOCKS_PORT:-2081}:1080" + networks: + - hoshpoint + app: build: context: . target: app - args: - DOCKER_REGISTRY: docker.arvancloud.ir + args: *build-args image: hoshpoint-backend-app:production restart: unless-stopped env_file: - ${APP_ENV_FILE:-.env.production} environment: + <<: *app-proxy-environment APP_ENV: production APP_DEBUG: "false" LOG_CHANNEL: stderr @@ -27,8 +63,7 @@ services: build: context: . target: nginx - args: - DOCKER_REGISTRY: docker.arvancloud.ir + args: *build-args image: hoshpoint-backend-nginx:production restart: unless-stopped depends_on: @@ -46,6 +81,7 @@ services: env_file: - ${APP_ENV_FILE:-.env.production} environment: + <<: *app-proxy-environment APP_ENV: production APP_DEBUG: "false" LOG_CHANNEL: stderr @@ -66,6 +102,7 @@ services: env_file: - ${APP_ENV_FILE:-.env.production} environment: + <<: *app-proxy-environment APP_ENV: production APP_DEBUG: "false" LOG_CHANNEL: stderr diff --git a/docker/apt/configure-arvan-mirror.sh b/docker/apt/configure-arvan-mirror.sh index 4e06bb9..62916e2 100644 --- a/docker/apt/configure-arvan-mirror.sh +++ b/docker/apt/configure-arvan-mirror.sh @@ -7,10 +7,25 @@ if [ ! -f "$sources" ]; then exit 0 fi -sed -i \ - -e 's|https\?://deb\.debian\.org/debian|http://mirror.arvancloud.ir/debian|g' \ - -e 's|https\?://security\.debian\.org/debian-security|http://mirror.arvancloud.ir/debian|g' \ - -e 's| bookworm-updates||g' \ - "$sources" +debian_mirror="${DEBIAN_APT_MIRROR:-}" +security_mirror="${DEBIAN_SECURITY_APT_MIRROR:-}" +disable_updates="${DEBIAN_APT_DISABLE_UPDATES:-false}" -sed -i '/mirror\.arvancloud\.ir\/debian-security/d' "$sources" +if [ -n "$debian_mirror" ]; then + sed -i \ + -e "s|^URIs: https\\?://deb\\.debian\\.org/debian$|URIs: $debian_mirror|g" \ + "$sources" +fi + +if [ -n "$security_mirror" ]; then + sed -i \ + -e "s|^URIs: https\\?://deb\\.debian\\.org/debian-security$|URIs: $security_mirror|g" \ + -e "s|^URIs: https\\?://security\\.debian\\.org/debian-security$|URIs: $security_mirror|g" \ + "$sources" +fi + +case "$disable_updates" in + 1|true|TRUE|yes|YES) + sed -i -e '/^Suites:/ s| bookworm-updates||g' "$sources" + ;; +esac diff --git a/docker/xray/config.example.json b/docker/xray/config.example.json new file mode 100644 index 0000000..3a0a93c --- /dev/null +++ b/docker/xray/config.example.json @@ -0,0 +1,234 @@ +{ + "log": {}, + "inbounds": [ + { + "tag": "socks", + "settings": { + "userLevel": 8, + "auth": "noauth", + "udp": true + }, + "protocol": "socks", + "port": 55869, + "listen": "127.0.0.1", + "sniffing": { + "enabled": true, + "destOverride": [ + "tls", + "http", + "quic" + ], + "domainsExcluded": [ + "courier.push.apple.com" + ] + } + }, + { + "tag": "directSocks", + "protocol": "socks", + "settings": { + "userLevel": 8, + "auth": "noauth", + "udp": true + }, + "port": 1087, + "listen": "127.0.0.1" + }, + { + "tag": "api", + "protocol": "dokodemo-door", + "settings": { + "address": "[::1]" + }, + "port": 55870, + "listen": "[::1]" + } + ], + "outbounds": [ + { + "mux": { + "concurrency": 50, + "enabled": false, + "xudpConcurrency": 128, + "xudpProxyUDP443": "allow" + }, + "streamSettings": { + "xHttpSettings": { + "mode": "auto", + "path": "/", + "host": "main.treenix-ping.com", + "extra": { + "scMinPostsIntervalMs": "30", + "xPaddingBytes": "100-1000", + "scMaxEachPostBytes": "1000000" + } + }, + "network": "xhttp", + "security": "tls", + "tlsSettings": { + "allowInsecure": false, + "serverName": "main.treenix-ping.com", + "alpn": [ + "h2", + "http/1.1" + ], + "fingerprint": "chrome" + } + }, + "protocol": "vless", + "settings": { + "vnext": [ + { + "port": 443, + "address": "01d.ir", + "users": [ + { + "flow": "", + "level": 8, + "encryption": "mlkem768x25519plus.native.0rtt.Bf9R7vF7RcUZMTcA2Xa3RG0L85xr8QLVLbnzZaHcylA", + "email": "", + "id": "b7387661-1c9d-4f0b-9862-801cb5fa5a2c" + } + ] + } + ] + }, + "tag": "proxy" + }, + { + "streamSettings": { + "sockopt": { + "tcpNoDelay": true + } + }, + "protocol": "freedom", + "settings": { + "fragment": { + "length": "80-250", + "interval": "10-100", + "packets": "tlshello" + }, + "userLevel": 8 + }, + "tag": "fragment" + } + ], + "api": { + "tag": "api", + "services": [ + "StatsService" + ] + }, + "dns": { + "disableFallbackIfMatch": true, + "hosts": { + "dns.quad9.net": [ + "9.9.9.9", + "149.112.112.112", + "2620:fe::fe", + "2620:fe::9" + ], + "dns.cloudflare.com": [ + "104.16.132.229", + "104.16.133.229", + "2606:4700::6810:84e5", + "2606:4700::6810:85e5" + ], + "dns.google": [ + "8.8.8.8", + "8.8.4.4", + "2001:4860:4860::8888", + "2001:4860:4860::8844" + ], + "common.dot.dns.yandex.net": [ + "77.88.8.8", + "77.88.8.1", + "2a02:6b8::feed:0ff", + "2a02:6b8:0:1::feed:0ff" + ], + "cloudflare-dns.com": [ + "104.16.248.249", + "104.16.249.249", + "2606:4700::6810:f8f9", + "2606:4700::6810:f9f9" + ], + "dns.alidns.com": [ + "223.5.5.5", + "223.6.6.6", + "2400:3200::1", + "2400:3200:baba::1" + ], + "one.one.one.one": [ + "1.1.1.1", + "1.0.0.1", + "2606:4700:4700::1111", + "2606:4700:4700::1001" + ], + "dot.pub": [ + "1.12.12.12", + "120.53.53.53" + ] + }, + "disableCache": true, + "tag": "dnsQuery", + "disableFallback": true, + "queryStrategy": "UseIP", + "servers": [ + { + "address": "8.8.8.8", + "skipFallback": false + } + ] + }, + "stats": {}, + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "outboundTag": "api", + "type": "field", + "inboundTag": [ + "api" + ], + "ruleTag": "rule-0" + }, + { + "outboundTag": "proxy", + "type": "field", + "inboundTag": [ + "dnsQuery" + ], + "ruleTag": "rule-1" + }, + { + "outboundTag": "direct", + "type": "field", + "inboundTag": [ + "directSocks" + ], + "ruleTag": "rule-2" + } + ], + "balancers": [] + }, + "policy": { + "system": { + "statsInboundUplink": true, + "statsInboundDownlink": true, + "statsOutboundDownlink": true, + "statsOutboundUplink": true + }, + "levels": { + "8": { + "uplinkOnly": 1, + "statsUserUplink": false, + "handshake": 4, + "bufferSize": 0, + "connIdle": 30, + "downlinkOnly": 1, + "statsUserDownlink": false + } + } + }, + "transport": {} +} \ No newline at end of file