fix: удален ненужный параметр SING_BOX_CONFIG из конфигурации сервиса
All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 6s

This commit is contained in:
2026-05-08 21:42:45 +03:00
parent eeec4359b0
commit 499d2d3367
2 changed files with 75 additions and 36 deletions

View File

@@ -20,7 +20,6 @@ services:
- .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

View File

@@ -31,35 +31,37 @@ const trafficBuffer = [];
const trafficSubscribers = new Set();
// Паттерны для парсинга трафика из логов sing-box.
// sing-box пишет строки вида:
// [router] match[N][rule-name] => outbound/direct[direct]
// Форматы логов sing-box:
// [TCP] 192.168.1.1:PORT --> example.com:443 outbound/direct[direct]
// [UDP] 192.168.1.1:PORT --> 8.8.8.8:53 outbound/direct[direct]
// [router] match[N][rule-name] => outbound/direct[tag]
// outbound/direct[tag]: dial tcp connection to host:port
// [TCP] DIRECT host:port --> direct
const TRAFFIC_OUTBOUND_RE =
/outbound[/\\]([a-z0-9_\-]+)|\boutbound[:\s]+([a-z0-9_\-]+)/i;
const TRAFFIC_DEST_RE =
/(?:to|dial|connect(?:ion)?\s+to|accepted\s+(?:from\s+[^\s]+\s+to\s+)?)([a-zA-Z0-9._\-]+|\d{1,3}(?:\.\d{1,3}){3}):(\d{1,5})(?!\d)/i;
const TRAFFIC_DOMAIN_RE = /\bdomain:\s*([a-zA-Z0-9._\-]+)/i;
const TRAFFIC_RULE_RE =
/matched?\s+rule\s+#?\d+\s*\[([^\]]+)\]|matched?\s*\[([^\]]+)\]|\busing\s+rule\s*\[([^\]]+)\]/i;
// Строка роутера: [router] match[N][rule-name] => outbound/direct[tag]
// Назначение после --> (основной формат sing-box)
const DEST_ARROW_RE = /-->\s*([\w.\-]+):(\d{1,5})/;
// Назначение в старом словесном стиле
const DEST_WORD_RE =
/(?:connection\s+to|dial(?:ing)?|connect(?:ing)?\s+to)\s+([\w.\-]+):(\d{1,5})/i;
// Тип аутбаунда: outbound/TYPE[tag] или outbound/TYPE
const OUTBOUND_RE = /outbound\/([a-z0-9_\-]+)/i;
// Строка роутера: [router] match[N][rule-name] => outbound/TYPE[tag]
const ROUTER_MATCH_LINE_RE =
/\[router\].*\bmatch\[\d+\]\[([^\]]+)\].*outbound[/\\]([a-z0-9_\-]+)/i;
/\[router\].*\bmatch\[\d+\]\[([^\]]+)\].*outbound\/([a-z0-9_\-]+)/i;
// Хранит имя последнего правила из [router] строки (для следующей строки с dest)
let _pendingRuleName = null;
let _pendingRuleAt = 0;
const RULE_CONTEXT_TTL_MS = 300;
const RULE_CONTEXT_TTL_MS = 500;
function parseTrafficLine(line) {
const clean = line.replace(/\x1b\[\d+m/g, "").trim();
// Детектируем строку роутера — она содержит имя правила и outbound, но не host:port
// Детектируем строку роутера — содержит правило, но не dest
const routerM = clean.match(ROUTER_MATCH_LINE_RE);
if (routerM) {
_pendingRuleName = routerM[1];
_pendingRuleAt = Date.now();
return null; // не выводим отдельную запись в трафик
return null;
}
// Берём накопленное имя правила, если свежее
@@ -70,37 +72,35 @@ function parseTrafficLine(line) {
_pendingRuleName = null;
_pendingRuleAt = 0;
const obMatch = clean.match(TRAFFIC_OUTBOUND_RE);
if (!obMatch) return null;
const outboundRaw = (obMatch[1] || obMatch[2] || "").toLowerCase();
if (!outboundRaw) return null;
// Ищем outbound
const obM = clean.match(OUTBOUND_RE);
if (!obM) return null;
const outboundRaw = obM[1].toLowerCase();
let category = "other";
if (outboundRaw === "direct" || outboundRaw.startsWith("direct"))
// Пропускаем DNS-аутбаунды
if (outboundRaw === "dns-out" || outboundRaw === "dns") return null;
let category;
if (outboundRaw === "direct" || outboundRaw.startsWith("direct-"))
category = "direct";
else if (outboundRaw === "block" || outboundRaw === "reject")
category = "block";
else if (outboundRaw !== "dns-out" && outboundRaw !== "dns") category = "vpn";
else return null; // пропускаем DNS-аутбаунды
else category = "vpn";
const domainMatch = clean.match(TRAFFIC_DOMAIN_RE);
const destMatch = clean.match(TRAFFIC_DEST_RE);
// Ищем назначение: --> (основной формат), потом словесный
const destM = clean.match(DEST_ARROW_RE) || clean.match(DEST_WORD_RE);
if (!destM) return null;
const host = domainMatch?.[1] || destMatch?.[1] || null;
const port = destMatch?.[2] ? parseInt(destMatch[2], 10) : null;
if (!host && !port) return null;
const ruleMatch = clean.match(TRAFFIC_RULE_RE);
const matchedRule =
ruleMatch?.[1] || ruleMatch?.[2] || ruleMatch?.[3] || inheritedRule || null;
const host = destM[1];
const port = parseInt(destM[2], 10);
return {
ts: new Date().toISOString(),
outbound: outboundRaw,
category,
host: host || "",
host,
port,
matchedRule,
matchedRule: inheritedRule,
};
}
@@ -126,10 +126,23 @@ function pushLog(level, line) {
// Парсим трафик из info/debug строк
if (level === "info" || level === "debug") {
const traffic = parseTrafficLine(line);
if (traffic) pushTrafficEntry(traffic);
if (traffic) {
pushTrafficEntry(traffic);
} else if (
(level === "info" || level === "debug") &&
_debugUnparsed < 30 &&
/\bTCP\b|\bUDP\b|\boutbound\b/i.test(line.replace(/\x1b\[\d+m/g, ""))
) {
_debugUnparsed++;
process.stdout.write(
`[traffic:unmatched] ${line.replace(/\x1b\[\d+m/g, "").trim()}\n`,
);
}
}
}
let _debugUnparsed = 0;
// Sing-box пишет все логи в stderr, поэтому парсим уровень из содержимого строки.
// Формат: ESC[<n>m LEVEL ESC[0m, где ESC = \x1b
const SINGBOX_LEVEL_RE =
@@ -1068,6 +1081,33 @@ if (existingPid && isPidAlive(existingPid)) {
attachExistingSingbox(existingPid);
} else {
removeSingboxPid();
// Если конфиг отсутствует (например после передплоя), пробуем пересобрать из кэша
if (!fs.existsSync(settings.configPath)) {
const stateData = readJson(settings.statePath, {});
const cached = readJson(settings.subscriptionCachePath, null);
if (stateData.selectedTag && cached?.config) {
try {
const customRules = readJson(settings.customRulesPath, []);
const generated = buildGatewayConfig(
{ ...cached.config, customRules },
stateData.selectedTag,
{ bypassAll: Boolean(stateData.bypassMode) },
);
writeSingboxConfig(generated);
pushLog(
"info",
`Конфиг sing-box восстановлен из кэша (сервер: ${stateData.selectedTag})`,
);
} catch (err) {
pushLog(
"error",
`Не удалось восстановить конфиг sing-box: ${err.message}`,
);
}
}
}
await startSingbox().catch((error) => {
console.warn(`[control] sing-box не запущен: ${error.message}`);
pushLog("error", `sing-box не запущен при старте: ${error.message}`);