diff --git a/.env.example b/.env.example index 7f08584..975bf03 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ PORT=3456 +BASE_IMAGE=debian:bookworm-slim +SINGBOX_VERSION=1.12.13 PROXY_PORT=8080 PROXY_BIND_IP=0.0.0.0 TPROXY_PORT=7895 diff --git a/Dockerfile b/Dockerfile index 32efb2d..2e216b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM debian:bookworm-slim +ARG BASE_IMAGE=debian:bookworm-slim +FROM ${BASE_IMAGE} ARG SINGBOX_VERSION=1.12.13 COPY dist /app/dist diff --git a/README.md b/README.md index e728262..60cf6a8 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,15 @@ npm install && npm run build docker compose -f docker-compose.gateway.yml up -d ``` +Если Docker Hub отвечает таймаутом на `debian:bookworm-slim`, можно собрать через read-through mirror: + +```bash +BASE_IMAGE=mirror.gcr.io/library/debian:bookworm-slim \ +docker compose -f docker-compose.gateway.yml build + +docker compose -f docker-compose.gateway.yml up -d +``` + UI доступен на `http://:3456`. На роутере указать шлюз по умолчанию (или нужные подсети) на IP контейнера. @@ -282,6 +291,8 @@ UI доступен на `http://:3456`. | Переменная | По умолчанию | Описание | | ------------------- | -------------------- | -------------------------------------- | | `PORT` | `3456` | Порт веб-интерфейса | +| `BASE_IMAGE` | `debian:bookworm-slim` | Базовый Docker image для сборки; можно заменить на mirror | +| `SINGBOX_VERSION` | `1.12.13` | Версия sing-box для Docker build | | `PROXY_PORT` | `8080` | HTTP/SOCKS mixed inbound | | `TPROXY_PORT` | `7895` | TProxy inbound sing-box | | `DATA_DIR` | `/var/lib/vpn-proxy` | Директория данных (volume) | diff --git a/docker-compose.gateway.yml b/docker-compose.gateway.yml index 39a4fc3..2251763 100644 --- a/docker-compose.gateway.yml +++ b/docker-compose.gateway.yml @@ -3,6 +3,9 @@ services: build: context: . dockerfile: Dockerfile + args: + BASE_IMAGE: ${BASE_IMAGE:-debian:bookworm-slim} + SINGBOX_VERSION: ${SINGBOX_VERSION:-1.12.13} container_name: vpn-proxy-gateway network_mode: host cap_add: diff --git a/src/server/index.js b/src/server/index.js index 968d544..10b3d51 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -21,9 +21,12 @@ import { matchRoute, detectRuleConflicts } from "./routeMatcher.js"; import { tcpPing, resolveHost } from "./ping.js"; const APPLY_HISTORY_LIMIT = 10; +const RULE_SET_TAG_RE = /^[a-z0-9][a-z0-9_.@!-]*$/i; const FALLBACK_RULE_SET_CATALOG = { geosite: [ "geosite-category-ru", + "geosite-category-ai-!cn", + "geosite-geolocation-!cn", "geosite-google", "geosite-youtube", "geosite-telegram", @@ -648,7 +651,7 @@ function normalizeCustomRules(input) { ["tcp", "udp"].includes(network), ), ruleSets: normalizeList(rule.ruleSets).filter((tag) => - /^[a-z0-9][a-z0-9-]*$/i.test(tag), + RULE_SET_TAG_RE.test(tag), ), })); } @@ -938,7 +941,7 @@ async function handleApi(req, res) { url: String(rs.url).trim(), format: rs.format === "source" ? "source" : "binary", })) - .filter((rs) => /^[a-z0-9][a-z0-9-]*$/i.test(rs.tag)); + .filter((rs) => RULE_SET_TAG_RE.test(rs.tag)); writeJson(settings.customRuleSetsPath, normalized); return sendJson(res, 200, { success: true, ruleSets: normalized }); } diff --git a/src/web/components/RuleEditorDrawer.jsx b/src/web/components/RuleEditorDrawer.jsx index 51b06e3..fb3b52b 100644 --- a/src/web/components/RuleEditorDrawer.jsx +++ b/src/web/components/RuleEditorDrawer.jsx @@ -4,7 +4,7 @@ import { isValidCidr, isValidPort, ruleErrors, hasErrors } from '../utils/valida import { api } from '../api.js'; const DOMAIN = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i; -const RULE_SET_TAG = /^[a-z0-9][a-z0-9-]*$/i; +const RULE_SET_TAG = /^[a-z0-9][a-z0-9_.@!-]*$/i; const validDomain = (v) => DOMAIN.test(String(v).trim()); const validRuleSetTag = (v) => RULE_SET_TAG.test(String(v).trim()); diff --git a/src/web/components/SettingsPage.jsx b/src/web/components/SettingsPage.jsx index 25a0fee..ec76953 100644 --- a/src/web/components/SettingsPage.jsx +++ b/src/web/components/SettingsPage.jsx @@ -601,8 +601,8 @@ function RuleSetsCard({ pushToast }) { const tag = newTag.trim(); const url = newUrl.trim(); if (!tag || !url) return; - if (!/^[a-z0-9][a-z0-9-]*$/i.test(tag)) { - pushToast({ kind: 'danger', title: 'Невалидный тег', message: 'Только буквы, цифры, дефис' }); + if (!/^[a-z0-9][a-z0-9_.@!-]*$/i.test(tag)) { + pushToast({ kind: 'danger', title: 'Невалидный тег', message: 'Буквы, цифры и символы - _ . @ !' }); return; } if (ruleSets.some((rs) => rs.tag === tag)) {