diff --git a/.gitea/workflows/gateway-build.yml b/.gitea/workflows/gateway-build.yml index 8c461b7..55de0ff 100644 --- a/.gitea/workflows/gateway-build.yml +++ b/.gitea/workflows/gateway-build.yml @@ -5,6 +5,9 @@ on: branches: [master] workflow_dispatch: +env: + DEPLOY_PATH: /opt/vpn-proxy + jobs: build: runs-on: ubuntu-latest @@ -26,3 +29,72 @@ jobs: docker build -t "${IMAGE}:latest" -t "${IMAGE}:${{ gitea.sha }}" . docker push "${IMAGE}:latest" docker push "${IMAGE}:${{ gitea.sha }}" + + deploy: + needs: build + runs-on: lxc-111 + steps: + - name: Deploy gateway to LXC 111 + run: | + set -euo pipefail + + REGISTRY_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||') + IMAGE="${REGISTRY_HOST}/${{ gitea.repository }}/gateway" + + echo "Logging into registry..." + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY_HOST" -u "${{ gitea.actor }}" --password-stdin + + echo "Preparing deploy directory: ${{ env.DEPLOY_PATH }}" + mkdir -p "${{ env.DEPLOY_PATH }}" + + cat > "${{ env.DEPLOY_PATH }}/docker-compose.server.yml" < "${{ env.DEPLOY_PATH }}/.env" <<'EOF' + PORT=3456 + PROXY_PORT=8080 + TPROXY_PORT=7895 + TPROXY_MARK=1 + TPROXY_TABLE=100 + TPROXY_CHAIN=VPN_PROXY_TPROXY + ROUTING_RU_DIRECT=true + LOG_LEVEL=info + EOF + echo "Created default .env. Existing deployments can edit ${{ env.DEPLOY_PATH }}/.env and it will be preserved." + else + echo "Preserving existing .env" + fi + + cd "${{ env.DEPLOY_PATH }}" + + echo "Pulling latest image..." + docker compose -f docker-compose.server.yml pull + + echo "Starting gateway..." + docker compose -f docker-compose.server.yml up -d + + echo "Current container:" + docker ps --filter "name=vpn-proxy-gateway" diff --git a/docker-compose.server.yml b/docker-compose.server.yml new file mode 100644 index 0000000..ed5faaf --- /dev/null +++ b/docker-compose.server.yml @@ -0,0 +1,22 @@ +services: + vpn-proxy-gateway: + image: ${GATEWAY_IMAGE} + container_name: vpn-proxy-gateway + network_mode: host + cap_add: + - NET_ADMIN + - NET_RAW + env_file: + - .env + environment: + DATA_DIR: /var/lib/vpn-proxy + SING_BOX_CONFIG: /etc/sing-box/config.json + SING_BOX_CACHE: /var/lib/sing-box/cache.db + volumes: + - vpn-proxy-data:/var/lib/vpn-proxy + - sing-box-cache:/var/lib/sing-box + restart: unless-stopped + +volumes: + vpn-proxy-data: + sing-box-cache: diff --git a/src/server/index.js b/src/server/index.js index daa8c1a..96f282e 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -184,8 +184,8 @@ async function handleApi(req, res) { if (req.method === 'POST' && req.url === '/api/apply') { const body = await readBody(req); - const selectedTag = String(body.selectedTag || '').trim(); - if (!selectedTag) return sendJson(res, 400, { success: false, error: 'selectedTag is required' }); + const selectedTag = String(body.selectedTag || ''); + if (!selectedTag.trim()) return sendJson(res, 400, { success: false, error: 'selectedTag is required' }); const cached = readJson(settings.subscriptionCachePath, null); if (!cached?.config) { diff --git a/src/server/singbox.js b/src/server/singbox.js index 37d279c..1ec0546 100644 --- a/src/server/singbox.js +++ b/src/server/singbox.js @@ -11,7 +11,11 @@ function clone(value) { function findOutbound(subscriptionConfig, selectedTag) { const outbounds = Array.isArray(subscriptionConfig?.outbounds) ? subscriptionConfig.outbounds : []; - return outbounds.find((outbound) => outbound.tag === selectedTag && PROXY_TYPES.has(outbound.type)); + const exact = outbounds.find((outbound) => outbound.tag === selectedTag && PROXY_TYPES.has(outbound.type)); + if (exact) return exact; + + const trimmedTag = String(selectedTag || '').trim(); + return outbounds.find((outbound) => String(outbound.tag || '').trim() === trimmedTag && PROXY_TYPES.has(outbound.type)); } function ruleSets() {