add: self-signed cert, docker, cmd rotate

This commit is contained in:
Den Piligrim
2026-01-05 14:27:03 +03:00
parent b77e5560c8
commit 45f67d3848
10 changed files with 257 additions and 16 deletions
+19 -4
View File
@@ -2,7 +2,7 @@
<p><img src="https://denpiligrim.ru/storage/images/3dp-manager.png" alt="3dp-manager preview"></p>
![Version](https://img.shields.io/badge/version-1.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
![Version](https://img.shields.io/badge/version-1.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
# 3DP-MANAGER
@@ -61,6 +61,9 @@
## Установка
> [!WARNING]
> Утилита работает на основном сервере
У вас должны быть установлены пакеты на сервере `curl`, `jq` командой: `apt install curl jq` и панель управления `3x-ui`, которую можно установить командой: `bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)`
Установите проект на сервер командой:
@@ -70,7 +73,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/3dp-manager/main
<sup>Краткое описание: запускает скрипт установки и разворачивает контейнеры и сервисы.</sup>
Если панель 3x-ui находится в Docker контейнере или на другом сервере, установите командой:
Если панель 3x-ui находится в Docker контейнере, установите командой:
```bash
REMOTE_PANEL=true bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/3dp-manager/main/install.sh)
@@ -101,7 +104,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/3dp-manager/main
## Установка сервиса перенаправления (forwarding)
> [!WARNING]
> Сервис перенаправления работает на промежуточном сервере.
> Сервис перенаправления работает на промежуточном сервере
Сервис перенаправления позволяет проксировать входящие порты с промежуточного сервера на основной.
@@ -150,7 +153,19 @@ node get_domains.js
cd /opt/3dp-manager && docker cp ./app/my_whitelist.txt node:/app/my_whitelist.txt
```
<sup>Краткое описание: добавляет ваш файл доменов в контейнер приложения. Чтобы сразу же сгенерировать инбаунды с новым списком, выполните `docker exec -it node sh` и затем `node index.js`.</sup>
<sup>Краткое описание: добавляет ваш файл доменов в контейнер приложения. Чтобы сразу же сгенерировать инбаунды с новым списком, выполните `docker exec -it node sh` и затем `node rotate.js`.</sup>
## Ручной запуск генерации
Чтобы создать новые инбаунды, выполните команды поочередно:
```bash
cd /opt/3dp-manager
docker exec -it node sh
node rotate.js
```
<sup>Краткое описание: выполняет немедленную генерацию инбаундов, не влияя на заданный интервал ротации.</sup>
---
+1 -1
View File
@@ -2,7 +2,7 @@
<p><img src="https://denpiligrim.ru/storage/images/3dp-manager.png" alt="3dp-manager preview"></p>
![Version](https://img.shields.io/badge/version-1.0.1.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
![Version](https://img.shields.io/badge/version-1.0.2.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
# 3DP-MANAGER
+1 -1
View File
@@ -2,7 +2,7 @@
<p><img src="https://denpiligrim.ru/storage/images/3dp-manager.png" alt="3dp-manager preview"></p>
![Version](https://img.shields.io/badge/version-1.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
![Version](https://img.shields.io/badge/version-1.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
# 3DP-MANAGER
+1 -1
View File
@@ -2,7 +2,7 @@
<p><img src="https://denpiligrim.ru/storage/images/3dp-manager.png" alt="3dp-manager preview"></p>
![Version](https://img.shields.io/badge/version-1.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
![Version](https://img.shields.io/badge/version-1.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
# 3DP-MANAGER
+1 -1
View File
@@ -2,7 +2,7 @@
<p><img src="https://denpiligrim.ru/storage/images/3dp-manager.png" alt="3dp-manager preview"></p>
![Version](https://img.shields.io/badge/version-1.0.1-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
![Version](https://img.shields.io/badge/version-1.0.2-blue.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0) [![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=flat&logo=telegram&logoColor=white)](https://t.me/denpiligrim_web) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCOv2tFFYDY4mXOM60PVz8zw)](https://www.youtube.com/@denpiligrim)
# 3DP-MANAGER
+1
View File
@@ -6,6 +6,7 @@ COPY package.json ./
RUN npm install --production
COPY index.js .
COPY rotate.js .
COPY builders ./builders
CMD ["node", "index.js"]
+11 -8
View File
@@ -1,5 +1,5 @@
import axios from "axios";
import cron from "node-cron";
import https from "https";
import fs from "fs";
import net from "net";
import { buildVlessRealityTcp } from "./builders/buildVlessRealityTcp.js";
@@ -16,7 +16,10 @@ const SUB_FILE = "/subscriptions/list.txt";
const LAST_INBOUNDS = '/subscriptions/latestInbounds.json';
const cookieJar = {};
const api = axios.create({ baseURL: UI_URL, timeout: 15000, withCredentials: true });
const agent = new https.Agent({
rejectUnauthorized: false
});
const api = axios.create({ baseURL: UI_URL, timeout: 15000, withCredentials: true, httpsAgent: agent });
api.interceptors.request.use(config => {
if (cookieJar.value) config.headers['Cookie'] = cookieJar.value;
@@ -121,12 +124,12 @@ async function login() {
} catch (e) { console.error("Login failed:", e.message); throw e; }
}
async function getInbounds() {
try {
const res = await api.get("/panel/api/inbounds/list");
return res.status === 200 ? res.data.obj || [] : [];
} catch (e) { console.error("Get inbounds failed:", e.message); return []; }
}
// async function getInbounds() {
// try {
// const res = await api.get("/panel/api/inbounds/list");
// return res.status === 200 ? res.data.obj || [] : [];
// } catch (e) { console.error("Get inbounds failed:", e.message); return []; }
// }
async function deleteInbounds() {
try {
+216
View File
@@ -0,0 +1,216 @@
import axios from "axios";
import https from "https";
import fs from "fs";
import net from "net";
import { buildVlessRealityTcp } from "./builders/buildVlessRealityTcp.js";
import { buildVlessRealityXhttp } from "./builders/buildVlessRealityXhttp.js";
import { buildTrojanRealityTcp } from "./builders/buildTrojanRealityTcp.js";
import { buildShadowsocksTcp } from "./builders/buildShadowsocksTcp.js";
import { buildVmessTcp } from "./builders/buildVmessTcp.js";
import { buildVlessRealityGrpc } from "./builders/buildVlessRealityGrpc.js";
import { buildVlessWs } from "./builders/buildVlessWs.js";
import { buildInboundLink } from "./builders/buildInboundLink.js";
const { UI_URL, UI_LOGIN, UI_PASSWORD, UI_HOST } = process.env;
const SUB_FILE = "/subscriptions/list.txt";
const LAST_INBOUNDS = '/subscriptions/latestInbounds.json';
const cookieJar = {};
const agent = new https.Agent({
rejectUnauthorized: false
});
const api = axios.create({ baseURL: UI_URL, timeout: 15000, withCredentials: true, httpsAgent: agent });
api.interceptors.request.use(config => {
if (cookieJar.value) config.headers['Cookie'] = cookieJar.value;
return config;
});
const WHITELIST_FILE = "/app/whitelist.txt";
const WHITELIST_REPO_URL = "https://raw.githubusercontent.com/denpiligrim/3dp-manager/main/whitelist.txt";
// Helper functions
async function updateWhitelist() {
try {
const res = await axios.get(WHITELIST_REPO_URL, { timeout: 10000 });
fs.writeFileSync(WHITELIST_FILE, res.data, "utf8");
console.log("✔ whitelist.txt обновлен из репозитория");
} catch (err) {
console.warn("⚠ Не удалось обновить whitelist.txt, будет использоваться локальный файл:", err.message);
}
}
function loadWhitelist() {
let fileToUse = WHITELIST_FILE;
if (fs.existsSync("/app/my_whitelist.txt")) {
console.log('Найден кастомный whitelist: /app/my_whitelist.txt. Используется он.');
fileToUse = "/app/my_whitelist.txt";
} else {
console.log(`Кастомный whitelist не найден. Используется дефолтный: ${WHITELIST_FILE}`);
if (!fs.existsSync(WHITELIST_FILE)) {
throw new Error("Дефолтный whitelist.txt не найден");
}
}
return fs.readFileSync(fileToUse, "utf8")
.split("\n")
.map(v => v.trim())
.filter(Boolean);
}
function pickDomain(list) {
return list[Math.floor(Math.random() * list.length)];
}
async function isPortFree(port) {
return new Promise(resolve => {
const s = net.createServer()
.once("error", () => resolve(false))
.once("listening", () => {
s.close();
resolve(true);
})
.listen(port, "0.0.0.0");
});
}
async function getFreePort(used) {
while (true) {
const p = Math.floor(Math.random() * (60000 - 10000)) + 10000;
if (used.has(p)) continue;
if (await isPortFree(p)) {
used.add(p);
return p;
}
}
}
async function generateRealityKeys() {
try {
const res = await api.get("/panel/api/server/getNewX25519Cert");
if (res.data?.success && res.data?.obj) {
return {
privateKey: res.data.obj.privateKey,
publicKey: res.data.obj.publicKey
};
}
throw new Error("Invalid key response");
} catch (e) {
console.error("Failed to get Reality keys:", e.message);
throw e;
}
}
async function uuid() {
try {
const res = await api.get("/panel/api/server/getNewUUID");
if (res.data?.success && res.data?.obj?.uuid) {
return res.data.obj.uuid;
}
throw new Error("Invalid UUID response");
} catch (e) {
console.error("Failed to get UUID:", e.message);
throw e;
}
}
// API 3x-ui
async function login() {
try {
const res = await api.post("/login", { username: UI_LOGIN, password: UI_PASSWORD });
if (res.headers['set-cookie']) cookieJar.value = res.headers['set-cookie'].join('; ');
console.log("Login success");
} catch (e) { console.error("Login failed:", e.message); throw e; }
}
// async function getInbounds() {
// try {
// const res = await api.get("/panel/api/inbounds/list");
// return res.status === 200 ? res.data.obj || [] : [];
// } catch (e) { console.error("Get inbounds failed:", e.message); return []; }
// }
async function deleteInbounds() {
try {
const data = fs.readFileSync(LAST_INBOUNDS, 'utf-8');
const oldIds = JSON.parse(data);
if (Array.isArray(oldIds) && oldIds.length > 0) {
console.log(`Удаляем старые инбаунды: ${oldIds.length} шт.`);
for (const id of oldIds) {
try {
await deleteInbound(id);
} catch (err) {
console.error(`Ошибка при удалении инбаунда ${id}:`, err.message);
}
}
}
} catch (error) {
console.log("Старых ID не найдено, пропускаем удаление.");
}
}
async function deleteInbound(id) {
try { await api.post(`/panel/api/inbounds/del/${id}`); } catch { }
}
async function addInbound(config) {
try {
const res = await api.post("/panel/api/inbounds/add", config);
return res.data?.obj?.id || null;
} catch (e) {
console.error("Add inbound failed:", e.message);
return null;
}
}
// Main rotation of inbound entries
async function rotate() {
await login();
await updateWhitelist();
const whitelist = loadWhitelist();
const usedPorts = new Set();
const subs = [];
await deleteInbounds();
const builders = [
async (d) => buildVlessRealityTcp({ port: await isPortFree(8443) ? 8443 : await getFreePort(usedPorts), uuid: await uuid(), domain: d, keys: await generateRealityKeys() }),
async (d) => buildVlessRealityXhttp({ port: await isPortFree(443) ? 443 : await getFreePort(usedPorts), uuid: await uuid(), domain: d, keys: await generateRealityKeys() }),
async (d) => buildVlessRealityGrpc({ port: await getFreePort(usedPorts), uuid: await uuid(), domain: d, keys: await generateRealityKeys() }),
async (d) => buildVlessWs({ port: await getFreePort(usedPorts), uuid: await uuid(), domain: d }),
async (d) => buildVlessRealityTcp({ port: await getFreePort(usedPorts), uuid: await uuid(), domain: d, keys: await generateRealityKeys() }),
async (d) => buildVlessRealityTcp({ port: await getFreePort(usedPorts), uuid: await uuid(), domain: d, keys: await generateRealityKeys() }),
async (d) => buildVlessRealityTcp({ port: await getFreePort(usedPorts), uuid: await uuid(), domain: d, keys: await generateRealityKeys() }),
async (d) => buildVmessTcp({ port: await getFreePort(usedPorts), uuid: await uuid() }),
async (d) => buildShadowsocksTcp({ port: await getFreePort(usedPorts), uuid: await uuid() }),
async (d) => buildTrojanRealityTcp({ port: await getFreePort(usedPorts), uuid: await uuid(), domain: d, keys: await generateRealityKeys() }),
];
const inboundIds = [];
for (const b of builders) {
const domain = pickDomain(whitelist);
const inbound = await b(domain);
const idOrPass = inbound.settings ? JSON.parse(inbound.settings).clients?.[0]?.id || JSON.parse(inbound.settings).clients?.[0]?.password : "";
// Build the link depending on the protocol
const link = buildInboundLink(inbound, UI_HOST, idOrPass);
if (link) subs.push(link);
const id = await addInbound(inbound);
inboundIds.push(id);
}
if (inboundIds.length > 0) {
fs.writeFileSync(LAST_INBOUNDS, JSON.stringify(inboundIds, null, 2));
console.log(`Новые ID (${inboundIds.length} шт.) сохранены в ${LAST_INBOUNDS}`);
}
fs.writeFileSync(SUB_FILE, subs.join("\n") + "\n", "utf8");
console.log("✔ 10 inbound created, подписка обновлена");
}
// Initial run
rotate();
+1
View File
@@ -285,6 +285,7 @@ curl -fsSL "$REPO_BASE/app/package.json" -o app/package.json
# JS Files
#################################
curl -fsSL "$REPO_BASE/app/index.js" -o app/index.js
curl -fsSL "$REPO_BASE/app/rotate.js" -o app/rotate.js
curl -fsSL "$REPO_BASE/app/builders/buildVlessRealityTcp.js" -o app/builders/buildVlessRealityTcp.js
curl -fsSL "$REPO_BASE/app/builders/buildVlessRealityXhttp.js" -o app/builders/buildVlessRealityXhttp.js
curl -fsSL "$REPO_BASE/app/builders/buildTrojanRealityTcp.js" -o app/builders/buildTrojanRealityTcp.js
+5
View File
@@ -80,6 +80,11 @@ docker compose build node
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
fi
#################################
# HEALTH CHECK
#################################