From 007afd3b83c40d8c2b56573cc315d126fc89da4a Mon Sep 17 00:00:00 2001 From: Den Piligrim <89912505+vasiljevdenis@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:34:24 +0300 Subject: [PATCH 1/6] fix errors --- client/src/components/Footer.tsx | 2 +- client/src/pages/SettingsPage.tsx | 76 +++++++++++++++++-- install.sh | 8 +- server/src/rotation/rotation.service.ts | 9 ++- server/src/settings/settings.controller.ts | 8 ++ server/src/settings/settings.module.ts | 3 +- .../subscriptions/subscriptions.service.ts | 1 - server/src/xui/xui.service.ts | 23 ++++++ 8 files changed, 113 insertions(+), 17 deletions(-) diff --git a/client/src/components/Footer.tsx b/client/src/components/Footer.tsx index 2bc8c9a..0b2fc2f 100644 --- a/client/src/components/Footer.tsx +++ b/client/src/components/Footer.tsx @@ -42,7 +42,7 @@ export default function Footer() { (''); + const [loadingRotate, setLoadingRotate] = useState(false); useEffect(() => { loadSettings(); @@ -37,6 +38,42 @@ export default function SettingsPage() { } }, [settings.rotation_interval]); + const cleanData = () => { + const cleaned = { ...settings }; + + if (cleaned.xui_url) { + cleaned.xui_url = cleaned.xui_url.replace(/\/+$/, ''); + } + + if (cleaned.xui_login) cleaned.xui_login = cleaned.xui_login.trim(); + if (cleaned.xui_password) cleaned.xui_password = cleaned.xui_password.trim(); + + setSettings(prev => ({ ...prev, ...cleaned })); + + return cleaned; + }; + + const handleCheckConnection = async () => { + const data = cleanData(); // Сначала чистим + + try { + setMsg({ open: true, type: 'success', text: 'Проверка...' }); + const res = await api.post('/settings/check', { + xui_url: data.xui_url, + xui_login: data.xui_login, + xui_password: data.xui_password + }); + + if (res.data.success) { + setMsg({ open: true, type: 'success', text: 'Подключение успешно!' }); + } else { + setMsg({ open: true, type: 'error', text: 'Ошибка: Неверные данные или нет доступа' }); + } + } catch (e) { + setMsg({ open: true, type: 'error', text: 'Ошибка сети при проверке' }); + } + }; + const loadSettings = async () => { try { const { data } = await api.get('/settings'); @@ -64,8 +101,10 @@ export default function SettingsPage() { return; } + const data = cleanData(); + try { - await api.post('/settings', settings); + await api.post('/settings', data); setMsg({ open: true, type: 'success', text: 'Настройки сохранены!' }); } catch (e) { setMsg({ open: true, type: 'error', text: 'Ошибка сохранения' }); @@ -86,13 +125,25 @@ export default function SettingsPage() { } }; - const handleForceRotate = async () => { +const handleForceRotate = async () => { if (confirm('ВНИМАНИЕ: Это немедленно обновит конфиги в подписках.\n\nИнтервал автоматической ротации НЕ будет сброшен.\n\nПродолжить?')) { try { - await api.post('/rotation/rotate-all'); - setMsg({ open: true, type: 'success', text: 'Ротация успешно выполнена!' }); + setLoadingRotate(true); + const res = await api.post('/rotation/rotate-all'); + + setLoadingRotate(false); + if (res.data && res.data.success) { + setMsg({ open: true, type: 'success', text: res.data.message || 'Ротация успешно выполнена!' }); + } else { + setMsg({ + open: true, + type: 'error', + text: res.data?.message || 'Ошибка выполнения ротации' + }); + } } catch (e) { - setMsg({ open: true, type: 'error', text: 'Ошибка при запуске ротации' }); + setLoadingRotate(false); + setMsg({ open: true, type: 'error', text: 'Ошибка сети или сервера' }); } } }; @@ -111,7 +162,7 @@ export default function SettingsPage() { Сохранить подключение + {settings.xui_url && settings.xui_login && settings.xui_password && ( + + )} @@ -162,6 +223,7 @@ export default function SettingsPage() { + diff --git a/server/src/inbounds/inbound-builder.service.ts b/server/src/inbounds/inbound-builder.service.ts index 06eb580..7883583 100644 --- a/server/src/inbounds/inbound-builder.service.ts +++ b/server/src/inbounds/inbound-builder.service.ts @@ -35,7 +35,7 @@ export class InboundBuilderService { }, tcpSettings: { acceptProxyProtocol: false, header: { type: 'none' } } }), - sniffing: JSON.stringify({ enabled: true, destOverride: ['http', 'tls', 'quic', 'fakedns'], metadataOnly: false, routeOnly: false }) + sniffing: JSON.stringify({ enabled: false, destOverride: ['http', 'tls', 'quic', 'fakedns'], metadataOnly: false, routeOnly: false }) }; } @@ -47,7 +47,7 @@ export class InboundBuilderService { protocol: 'vless', remark: `vless-xhttp-reality`, settings: JSON.stringify({ - clients: [{ id: uuid, flow: 'xtls-rprx-vision', email: uuid, enable: true, limitIp: 0, totalGB: 0, expiryTime: 0, tgId: '', subId: '', reset: 0 }], + clients: [{ id: uuid, flow: '', email: uuid, enable: true, limitIp: 0, totalGB: 0, expiryTime: 0, tgId: '', subId: '', reset: 0 }], decryption: 'none', encryption: 'none', fallbacks: [] @@ -66,9 +66,23 @@ export class InboundBuilderService { shortIds: [crypto.randomBytes(4).toString('hex'), crypto.randomBytes(4).toString('hex')], settings: { publicKey: publicKey, fingerprint: 'random', serverName: '', spiderX: '/' } }, - xhttpSettings: { path: '/', mode: 'auto' } + xhttpSettings: { + host: domain, + path: "/", + mode: "auto", + noSSEHeader: false, + scMaxBufferedPosts: 30, + scMaxEachPostBytes: "1000000", + scStreamUpServerSecs: "20-80", + xPaddingBytes: "100-1000" + } }), - sniffing: JSON.stringify({ enabled: true, destOverride: ['http', 'tls', 'quic', 'fakedns'], metadataOnly: false, routeOnly: false }) + sniffing: JSON.stringify({ + enabled: false, + destOverride: ["http", "tls", "quic", "fakedns"], + metadataOnly: false, + routeOnly: false + }) }; } @@ -77,17 +91,28 @@ export class InboundBuilderService { return { enable: true, port, - protocol: 'vless', - remark: `vless-grpc-reality`, + protocol: "vless", + remark: "vless-reality-grpc", settings: JSON.stringify({ - clients: [{ id: uuid, flow: '', email: uuid, enable: true, limitIp: 0, totalGB: 0, expiryTime: 0, tgId: '', subId: '', reset: 0 }], - decryption: 'none', - encryption: 'none', + clients: [{ + id: uuid, + email: uuid, + enable: true, + flow: "", + limitIp: 0, + totalGB: 0, + expiryTime: 0, + tgId: "", + subId: "", + reset: 0 + }], + decryption: "none", + encryption: "none", fallbacks: [] }), streamSettings: JSON.stringify({ - network: 'grpc', - security: 'reality', + network: "grpc", + security: "reality", externalProxy: [], realitySettings: { show: false, @@ -99,9 +124,18 @@ export class InboundBuilderService { shortIds: [crypto.randomBytes(4).toString('hex')], settings: { publicKey: publicKey, fingerprint: 'random', serverName: '', spiderX: '/' } }, - grpcSettings: { serviceName: 'grpc', multiMode: false } + grpcSettings: { + serviceName: "myservice", + authority: domain, + multiMode: false, + } }), - sniffing: JSON.stringify({ enabled: true, destOverride: ['http', 'tls', 'quic', 'fakedns'] }) + sniffing: JSON.stringify({ + enabled: false, + destOverride: ["http", "tls", "quic", "fakedns"], + metadataOnly: false, + routeOnly: false + }) }; } @@ -113,18 +147,39 @@ export class InboundBuilderService { protocol: 'vless', remark: `vless-ws`, settings: JSON.stringify({ - clients: [{ id: uuid, flow: '', email: uuid, enable: true, limitIp: 0, totalGB: 0, expiryTime: 0, tgId: '', subId: '', reset: 0 }], - decryption: 'none', - encryption: 'none', + clients: [{ + id: uuid, + email: uuid, + enable: true, + flow: "", + limitIp: 0, + totalGB: 0, + expiryTime: 0, + tgId: "", + subId: "", + reset: 0 + }], + decryption: "none", + encryption: "none", fallbacks: [] }), streamSettings: JSON.stringify({ - network: 'ws', - security: 'none', + network: "ws", + security: "none", externalProxy: [], - wsSettings: { path: '/', headers: { Host: domain } } + wsSettings: { + host: domain, + path: "/", + acceptProxyProtocol: false, + heartbeatPeriod: 0, + } }), - sniffing: JSON.stringify({ enabled: true, destOverride: ['http', 'tls', 'quic', 'fakedns'] }) + sniffing: JSON.stringify({ + enabled: false, + destOverride: ["http", "tls", "quic", "fakedns"], + metadataOnly: false, + routeOnly: false + }) }; } @@ -136,29 +191,77 @@ export class InboundBuilderService { protocol: 'vmess', remark: 'vmess-tcp', settings: JSON.stringify({ - clients: [{ id: uuid, alterId: 0, email: uuid, limitIp: 0, totalGB: 0, expiryTime: 0, enable: true, tgId: '', subId: '', reset: 0 }], - disableInsecureEncryption: false + clients: [{ + id: uuid, + flow: "", + email: uuid, + enable: true, + limitIp: 0, + totalGB: 0, + expiryTime: 0, + tgId: "", + subId: "0", + alterId: "0", + reset: 0 + }], }), - streamSettings: JSON.stringify({ network: 'tcp', security: 'none', tcpSettings: { header: { type: 'http', request: { method: 'GET', path: ['/'], headers: { Host: [] } } } } }), - sniffing: JSON.stringify({ enabled: true, destOverride: ['http', 'tls', 'quic', 'fakedns'] }) + streamSettings: JSON.stringify({ + network: "tcp", + security: "none", + tcpSettings: { + acceptProxyProtocol: false, + header: { type: "none" } + } + }), + sniffing: JSON.stringify({ + enabled: false, + destOverride: ["http", "tls", "quic", "fakedns"], + metadataOnly: false, + routeOnly: false + }) }; } buildShadowsocksTcp(params: { port: number; uuid: string }) { - const { port, uuid } = params; + const { port, uuid } = params; return { enable: true, port, protocol: 'shadowsocks', remark: 'shadowsocks-tcp', settings: JSON.stringify({ - method: 'aes-256-gcm', - password: uuid, - network: 'tcp,udp', - clients: [] + clients: [{ + id: "", + flow: "", + email: uuid, + password: crypto.randomBytes(32).toString("base64"), + enable: true, + limitIp: 0, + totalGB: 0, + expiryTime: 0, + tgId: "", + subId: "", + reset: 0 + }], + ivCheck: false, + method: "2022-blake3-aes-256-gcm", + network: "tcp", + password: crypto.randomBytes(32).toString("base64") }), - streamSettings: JSON.stringify({ network: 'tcp', security: 'none' }), - sniffing: JSON.stringify({ enabled: true, destOverride: ['http', 'tls', 'quic', 'fakedns'] }) + streamSettings: JSON.stringify({ + network: "tcp", + security: "none", + tcpSettings: { + acceptProxyProtocol: false, + header: { type: "none" } + } + }), + sniffing: JSON.stringify({ + enabled: false, + destOverride: ["http", "tls", "quic", "fakedns"], + metadataOnly: false, + routeOnly: false + }) }; } @@ -170,12 +273,24 @@ export class InboundBuilderService { protocol: 'trojan', remark: `trojan-tcp-reality`, settings: JSON.stringify({ - clients: [{ password: uuid, email: uuid, limitIp: 0, totalGB: 0, expiryTime: 0, enable: true, tgId: '', subId: '', reset: 0 }], + clients: [{ + id: uuid, + email: uuid, + password: crypto.randomBytes(8).toString("hex"), + enable: true, + flow: "", + limitIp: 0, + totalGB: 0, + expiryTime: 0, + tgId: "", + subId: "", + reset: 0 + }], fallbacks: [] }), streamSettings: JSON.stringify({ - network: 'tcp', - security: 'reality', + network: "tcp", + security: "reality", externalProxy: [], realitySettings: { show: false, @@ -184,11 +299,34 @@ export class InboundBuilderService { dest: `${domain}:443`, serverNames: [domain], privateKey: privateKey, - shortIds: [crypto.randomBytes(4).toString('hex')], - settings: { publicKey: publicKey, fingerprint: 'random', serverName: '', spiderX: '/' } + shortIds: [ + crypto.randomBytes(4).toString("hex"), + crypto.randomBytes(3).toString("hex"), + crypto.randomBytes(8).toString("hex"), + crypto.randomBytes(2).toString("hex"), + crypto.randomBytes(2).toString("hex"), + crypto.randomBytes(2).toString("hex"), + crypto.randomBytes(2).toString("hex"), + crypto.randomBytes(4).toString("hex") + ], + settings: { + publicKey: publicKey, + fingerprint: "random", + serverName: "", + spiderX: "/" + } + }, + tcpSettings: { + acceptProxyProtocol: false, + header: { type: "none" } } }), - sniffing: JSON.stringify({ enabled: true, destOverride: ['http', 'tls', 'quic', 'fakedns'] }) + sniffing: JSON.stringify({ + enabled: false, + destOverride: ["http", "tls", "quic", "fakedns"], + metadataOnly: false, + routeOnly: false + }) }; } @@ -280,7 +418,7 @@ export class InboundBuilderService { const vmessObj = { add: domain, - aid: '', + aid: '0', alpn: "", fp: "", host: "", @@ -308,9 +446,9 @@ export class InboundBuilderService { const method = settings.method; const serverPassword = settings.password; - const finalPass = serverPassword || idOrPass; + const clientPassword = settings.clients[0].password; - const userInfo = `${method}:${finalPass}`; + const userInfo = `${method}:${serverPassword}:${clientPassword}`; const base64 = Buffer .from(userInfo, "utf8") diff --git a/server/src/rotation/rotation.service.ts b/server/src/rotation/rotation.service.ts index 8636089..cefaa1c 100644 --- a/server/src/rotation/rotation.service.ts +++ b/server/src/rotation/rotation.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -13,7 +13,7 @@ import { InboundBuilderService } from '../inbounds/inbound-builder.service'; import { v4 as uuidv4 } from 'uuid'; @Injectable() -export class RotationService { +export class RotationService implements OnModuleInit { private readonly logger = new Logger(RotationService.name); constructor( @@ -25,6 +25,26 @@ export class RotationService { private inboundBuilder: InboundBuilderService, ) {} + async onModuleInit() { + await this.initDefaultSettings(); + } + + private async initDefaultSettings() { + const key = 'rotation_status'; + const existing = await this.settingRepo.findOne({ where: { key } }); + + if (!existing) { + this.logger.log(`Инициализация настройки: ${key} = active`); + const newSetting = this.settingRepo.create({ + key: key, + value: 'active', + }); + await this.settingRepo.save(newSetting); + } else { + this.logger.log(`Текущий статус ротации: ${existing.value}`); + } + } + @Cron(CronExpression.EVERY_MINUTE) async handleTicker() { const intervalSetting = await this.settingRepo.findOne({ where: { key: 'rotation_interval' } }); @@ -35,8 +55,10 @@ export class RotationService { const now = Date.now(); const diffMinutes = (now - lastRun) / 1000 / 60; + const statusSetting = await this.settingRepo.findOne({ where: { key: 'rotation_status' } }); + const isStopped = statusSetting?.value === 'stopped'; - if (diffMinutes < intervalMinutes) { + if (diffMinutes < intervalMinutes || isStopped) { return; } @@ -52,7 +74,7 @@ export class RotationService { await this.settingRepo.save(s); } - async performRotation() { + async performRotation() { this.logger.log('Запуск плановой ротации...'); const isLoginSuccess = await this.xuiService.login(); diff --git a/update.sh b/update.sh index 458a457..a35c343 100644 --- a/update.sh +++ b/update.sh @@ -17,17 +17,10 @@ need_root() { [[ $EUID -eq 0 ]] || die "Запускать только от root" } -if ! command -v curl >/dev/null 2>&1; then - echo "❌ curl не установлен. Установите curl и повторите попытку" - echo " apt install -y curl" - exit 1 -fi - ################################# # CONFIG ################################# PROJECT_DIR="/opt/3dp-manager" -REPO_RAW="https://raw.githubusercontent.com/denpiligrim/3dp-manager/main" ################################# # START @@ -46,53 +39,21 @@ cd "$PROJECT_DIR" command -v docker >/dev/null 2>&1 || die "Docker не установлен" docker compose version >/dev/null 2>&1 || die "docker compose v2 недоступен" -################################# -# DOWNLOAD FILES -################################# -log "Загружаем обновлённые файлы из репозитория" - -mkdir -p app - -curl -fsSL "$REPO_RAW/app/Dockerfile" -o app/Dockerfile -curl -fsSL "$REPO_RAW/app/package.json" -o app/package.json -curl -fsSL "$REPO_RAW/app/index.js" -o app/index.js -curl -fsSL "$REPO_RAW/app/rotate.js" -o app/rotate.js -curl -fsSL "$REPO_RAW/app/builders/buildVlessRealityTcp.js" -o app/builders/buildVlessRealityTcp.js -curl -fsSL "$REPO_RAW/app/builders/buildVlessRealityXhttp.js" -o app/builders/buildVlessRealityXhttp.js -curl -fsSL "$REPO_RAW/app/builders/buildTrojanRealityTcp.js" -o app/builders/buildTrojanRealityTcp.js -curl -fsSL "$REPO_RAW/app/builders/buildShadowsocksTcp.js" -o app/builders/buildShadowsocksTcp.js -curl -fsSL "$REPO_RAW/app/builders/buildVmessTcp.js" -o app/builders/buildVmessTcp.js -curl -fsSL "$REPO_RAW/app/builders/buildVlessRealityGrpc.js" -o app/builders/buildVlessRealityGrpc.js -curl -fsSL "$REPO_RAW/app/builders/buildVlessWs.js" -o app/builders/buildVlessWs.js -curl -fsSL "$REPO_RAW/app/builders/buildInboundLink.js" -o app/builders/buildInboundLink.js -curl -fsSL "$REPO_RAW/whitelist.txt" -o whitelist.txt - -log "Файлы обновлены" - ################################# # REBUILD BACKEND ################################# -log "Пересобираем backend" -docker compose build node - -################################# -# RESTART CONTAINERS -################################# -log "Перезапускаем контейнеры" -docker compose up -d - -if [ -f "app/my_whitelist.txt" ]; then - log "✔ Копируем my_whitelist.txt в контейнер..." - docker cp app/my_whitelist.txt node:/app/my_whitelist.txt +log "Скачивание последних версий Docker-образов..." +if docker compose pull; then + log "Образы успешно загружены." +else + error "Ошибка при скачивании образов. Проверьте подключение к интернету или доступность GitHub Container Registry." fi -################################# -# HEALTH CHECK -################################# -sleep 2 +log "Пересоздание контейнеров..." +docker compose up -d -docker compose ps | grep node >/dev/null || die "Backend не запущен" -docker compose ps | grep nginx >/dev/null || die "Nginx не запущен" +log "Очистка старых Docker-образов (освобождение места)..." +docker image prune -f ################################# # DONE From 9aff2e78d87e9886214d75096b855f0379b72c73 Mon Sep 17 00:00:00 2001 From: Den Piligrim <89912505+vasiljevdenis@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:08:28 +0300 Subject: [PATCH 5/6] fix install --- install.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/install.sh b/install.sh index 02c61f7..b457e39 100644 --- a/install.sh +++ b/install.sh @@ -4,7 +4,6 @@ set -euo pipefail ################################# # КОНФИГУРАЦИЯ И ПЕРЕМЕННЫЕ ################################# -REPO_URL="https://github.com/denpiligrim/3dp-manager/archive/refs/heads/dp-gui.tar.gz" PROJECT_DIR="/opt/3dp-manager" DOCKER_USER="denpiligrim" DOCKER_TAG="dp-gui" @@ -108,9 +107,6 @@ fi log "Подготовка директории $PROJECT_DIR..." mkdir -p "$PROJECT_DIR" -log "Скачивание последней версии проекта..." -curl -L "$REPO_URL" | tar xz -C "$PROJECT_DIR" --strip-components=1 - cd "$PROJECT_DIR" ################################# From 9861b874655149c15eb79a1450c3609f3e8799c7 Mon Sep 17 00:00:00 2001 From: Den Piligrim <89912505+vasiljevdenis@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:09:33 +0300 Subject: [PATCH 6/6] fix install --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index b457e39..7628ab7 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ set -euo pipefail ################################# PROJECT_DIR="/opt/3dp-manager" DOCKER_USER="denpiligrim" -DOCKER_TAG="dp-gui" +DOCKER_TAG="latest" IMAGE_SERVER="ghcr.io/${DOCKER_USER}/3dp-manager-server:${DOCKER_TAG}" IMAGE_CLIENT="ghcr.io/${DOCKER_USER}/3dp-manager-client:${DOCKER_TAG}"