beta release v1.0

This commit is contained in:
Denis Vasilev
2025-12-20 22:15:57 +03:00
commit df6d311131
23 changed files with 2458 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
[English](https://github.com/denpiligrim/test_3dp-manager/blob/main/README_EN.md)
# 3DP-MANAGER
> [!WARNING]
> **Это бета-версия!**
>
> Программа находится в активной разработке. Возможны баги, нестабильность и изменения API.
> Используйте с осторожностью.
3DP-MANAGER — утилита, которая позволяет регулярно генерировать входящие подключения для панели 3X-UI на основе списка разрешённых доменов (whitelist). Белый список общий, но вы также можете добавить свой собственный список, назвав его `my_whitelist.txt` и поместив его в папку `/opt/3dp-manager/app` на вашем сервере.
Обсуждения доступны в Телеграм канале: [@denpiligrim_web](https://t.me/denpiligrim_web/719)
### Установка
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/install.sh)
```
### Обновление
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/update.sh)
```
### Удаление
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/delete.sh)
```
---
Сервис подписок и перенаправления входящего трафика работает совместно с **3DP-MANAGER** и позволяет перенаправлять весь входящий трафик с промежуточного сервера на основной сервер. Перенаправляются те же порты: `443`, `8443` и диапазон `10000-60000`. Сервис также создаёт ссылку на подписку, автоматически заменяя IP-адрес или домен в конфигурациях. Перенаправления настраиваются добавлением правил iptables в конфигурационный файл ufw, что обеспечивает стабильную работу в сочетании с файрволом. Рекомендуется устанавливать сервис на чистый сервер без ранее настроенных правил.
### Установка перенаправления
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/forwarding_install.sh)
```
### Удаление перенаправления
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/forwarding_delete.sh)
```
---
### Показать URL подписки
```bash
cd /opt/3dp-manager && docker compose exec node env | grep SUB_URL | cut -d'=' -f2
```
---
### Утилита для сбора доменов из мульти-подписок
Позволяет собирать домены из подписок с несколькими конфигурациями. В интернете встречается много подобных списков конфигураций, использующих белый SNI. Инструмент позволяет извлечь домены и подготовить готовый список для дальнейшего использования в генераторе входящих подключений.
Вставьте ссылку на подписку в скрипт и запустите команду в окружении `Node.js`.
```bash
node get_domains.js
```
### Использовать свой белый список
Файл должен иметь структуру аналогичную `whitelist.txt`. Переименуйте имя файла на `my_whitelist.txt`. Загрузите файл в папку `/opt/3dp-manager/app` на сервере и выполните команду:
```bash
cd /opt/3dp-manager && docker cp ./app/my_whitelist.txt node:/app/my_whitelist.txt
```
+62
View File
@@ -0,0 +1,62 @@
[Русский](https://github.com/denpiligrim/test_3dp-manager/blob/main/README.md)
# 3DP-MANAGER
> [!WARNING]
> **This is a beta version!**
>
> The program is currently under active development. Bugs, instability, and API changes are possible.
> Use with caution.
3DP-MANAGER is a utility that allows you to regularly generate inbound connections for the 3X-UI panel based on a whitelist of domains. The whitelist is shared, but you can also add your own list by naming it `my_whitelist.txt` and adding it to the `/opt/3dp-manager/app` folder on your server.
Discussions are available on the Telegram channel: [@denpiligrim_web](https://t.me/denpiligrim_web/719)
### Install
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/install.sh)
```
### Update
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/update.sh)
```
### Delete
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/delete.sh)
```
---
The subscription and inbound redirection service works in conjunction with **3DP-MANAGER** and allows you to redirect all inbound traffic from the intermediate server to the main server. The same ports are redirected, namely `443`, `8443`, and the range `10000-60000`. The service also creates a link to the subscription, automatically replacing the IP address or domain in the configurations. Redirects are configured by adding iptables rules to the ufw configuration file, which ensures stable operation in conjunction with the firewall. It is recommended to install the service on a clean server without any previously installed rules.
### Install forwarding
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/forwarding_install.sh)
```
### Delete forwarding
```bash
bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main/forwarding_delete.sh)
```
---
### Show sub URL
```bash
cd /opt/3dp-manager && docker compose exec node env | grep SUB_URL | cut -d'=' -f2
```
---
### Utility for collecting domains from multi-subscriptions
Allows you to collect domains from subscriptions with multiple configurations. There are many similar configuration lists circulating on the internet that use white SNI. The tool allows you to extract domains and prepare a ready-made list for further use in the inbound generator.
Insert a subscription link into the script and run the command in the `Node.js` environment.
```bash
node get_domains.js
```
### Use your whitelist
The file should have a structure similar to `whitelist.txt`. Rename the file to `my_whitelist.txt`. Upload the file to the `/opt/3dp-manager/app` folder on the server and run the command:
```bash
cd /opt/3dp-manager && docker cp ./app/my_whitelist.txt node:/app/my_whitelist.txt
```
+9
View File
@@ -0,0 +1,9 @@
SUB_TOKEN=$SUB_TOKEN
UI_URL=$UI_URL
UI_LOGIN=$UI_LOGIN
UI_PASSWORD=$UI_PASSWORD
NGINX_PORT=$NGINX_PORT
UI_HOST=$UI_HOST
UI_PROTO=$UI_PROTO
ROTATE_INTERVAL=$ROTATE_INTERVAL
SUB_URL=$SUB_URL
+11
View File
@@ -0,0 +1,11 @@
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --production
COPY index.js .
COPY builders ./builders
CMD ["node", "index.js"]
+160
View File
@@ -0,0 +1,160 @@
export function buildInboundLink(inbound, domain, idOrPass) {
let link = "";
switch (inbound.protocol) {
case "vless": {
link = buildVlessLink(inbound, domain, idOrPass);
break;
}
case "vmess": {
link = buildVmessLink(inbound, domain, idOrPass);
break;
}
case "shadowsocks":
link = buildSsLink(inbound, domain, idOrPass);
break;
case "trojan":
link = buildTrojanLink(inbound, domain, idOrPass);
break;
}
return link;
}
function buildVlessLink(inbound, domain, uuid) {
const stream = JSON.parse(inbound.streamSettings);
const settings = JSON.parse(inbound.settings);
const network = stream.network;
const security = stream.security || "none";
const params = new URLSearchParams();
// Базовые параметры
params.set("type", network);
params.set("encryption", "none");
params.set("security", security);
/**
* ===== REALITY =====
*/
if (security === "reality") {
const r = stream.realitySettings;
params.set("pbk", r.settings.publicKey);
params.set("fp", r.settings.fingerprint || "random");
params.set("sni", r.serverNames?.[0] || "");
params.set("sid", r.shortIds?.[0] || "");
params.set("spx", '%2F');
// TCP Reality flow
if (network === "tcp") {
const client = settings.clients?.[0];
if (client?.flow) {
params.set("flow", client.flow);
}
}
// XHTTP Reality
if (network === "xhttp") {
const x = stream.xhttpSettings || {};
params.set("path", x.path || "/");
params.set("host", x.host || r.serverNames?.[0]);
params.set("mode", x.mode || "auto");
}
// gRPC Reality
if (network === "grpc") {
const g = stream.grpcSettings || {};
params.set("serviceName", g.serviceName || "grpc");
params.set("authority", g.authority || r.serverNames?.[0]);
}
}
/**
* ===== WS NONE =====
*/
if (network === "ws") {
const ws = stream.wsSettings || {};
params.set("path", ws.path || "/");
if (ws.headers?.Host) {
params.set("host", ws.headers.Host);
}
}
return (
`vless://${uuid}@${domain}:${inbound.port}` +
`?${params.toString()}` +
`#${encodeURIComponent(inbound.remark)}`
);
}
function buildVmessLink(inbound, domain, uuid) {
const stream = JSON.parse(inbound.streamSettings);
const vmessObj = {
add: domain,
aid: '',
alpn: "",
fp: "",
host: "",
id: uuid,
net: stream.network || "tcp",
path: "/",
port: inbound.port,
ps: inbound.remark,
scy: "",
sni: "",
tls: stream.security || "none",
type: "none",
v: "2"
};
const base64 = Buffer
.from(JSON.stringify(vmessObj), "utf8")
.toString("base64");
return `vmess://${base64}`;
}
function buildSsLink(inbound, domain) {
const settings = JSON.parse(inbound.settings);
const method = settings.method;
const serverPassword = settings.password;
const clientPassword = settings.clients[0].password;
// method:serverPassword:clientPassword
const userInfo = `${method}:${serverPassword}:${clientPassword}`;
const base64 = Buffer
.from(userInfo, "utf8")
.toString("base64");
return `ss://${base64}@${domain}:${inbound.port}?type=tcp#${inbound.remark}`;
}
function buildTrojanLink(inbound, domain, password) {
const stream = JSON.parse(inbound.streamSettings);
const reality = stream.realitySettings;
const pbk = reality.settings.publicKey;
const sni = reality.serverNames?.[0] || domain;
const sid = reality.shortIds?.[0] || "";
const spx = '%2F';
return (
`trojan://${password}@${domain}:${inbound.port}` +
`?type=tcp` +
`&security=reality` +
`&pbk=${pbk}` +
`&fp=random` +
`&sni=${sni}` +
`&sid=${sid}` +
`&spx=${spx}` +
`#${inbound.remark}-${password}`
);
}
+43
View File
@@ -0,0 +1,43 @@
import crypto from "crypto";
export function buildShadowsocksTcp({ port, uuid }) {
return {
enable: true,
port,
protocol: "shadowsocks",
remark: "shadowsocks-tcp",
settings: JSON.stringify({
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",
tcpSettings: {
acceptProxyProtocol: false,
header: { type: "none" }
}
}),
sniffing: JSON.stringify({
enabled: false,
destOverride: ["http", "tls", "quic", "fakedns"],
metadataOnly: false,
routeOnly: false
})
};
}
+65
View File
@@ -0,0 +1,65 @@
import crypto from "crypto";
export function buildTrojanRealityTcp({ port, uuid, domain, keys }) {
return {
enable: true,
port,
protocol: "trojan",
remark: "trojan-reality-tcp",
settings: JSON.stringify({
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",
externalProxy: [],
realitySettings: {
show: false,
xver: 0,
target: `${domain}:443`,
dest: `${domain}:443`,
serverNames: [domain],
privateKey: keys.privateKey,
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: keys.publicKey,
fingerprint: "random",
serverName: "",
spiderX: "/"
}
},
tcpSettings: {
acceptProxyProtocol: false,
header: { type: "none" }
}
}),
sniffing: JSON.stringify({
enabled: false,
destOverride: ["http", "tls", "quic", "fakedns"],
metadataOnly: false,
routeOnly: false
})
};
}
+67
View File
@@ -0,0 +1,67 @@
import crypto from "crypto";
export function buildVlessRealityGrpc({ port, uuid, domain, keys }) {
return {
enable: true,
port,
protocol: "vless",
remark: "vless-reality-grpc",
settings: JSON.stringify({
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",
externalProxy: [],
realitySettings: {
show: false,
xver: 0,
target: `${domain}:443`,
dest: `${domain}:443`,
serverNames: [domain],
privateKey: keys.privateKey,
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: keys.publicKey,
fingerprint: "random",
serverName: "",
spiderX: "/"
}
},
grpcSettings: {
serviceName: "myservice",
authority: domain,
multiMode: false,
}
}),
sniffing: JSON.stringify({
enabled: false,
destOverride: ["http", "tls", "quic", "fakedns"],
metadataOnly: false,
routeOnly: false
})
};
}
+66
View File
@@ -0,0 +1,66 @@
import crypto from "crypto";
export function buildVlessRealityTcp({ port, uuid, domain, keys }) {
return {
enable: true,
port,
protocol: "vless",
remark: "vless-reality-tcp",
settings: JSON.stringify({
clients: [{
id: uuid,
flow: "xtls-rprx-vision",
email: uuid,
enable: true,
limitIp: 0,
totalGB: 0,
expiryTime: 0,
tgId: "",
subId: "",
reset: 0
}],
decryption: "none",
encryption: "none",
fallbacks: []
}),
streamSettings: JSON.stringify({
network: "tcp",
security: "reality",
externalProxy: [],
realitySettings: {
show: false,
xver: 0,
target: `${domain}:443`,
dest: `${domain}:443`,
serverNames: [domain],
privateKey: keys.privateKey,
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: keys.publicKey,
fingerprint: "random",
serverName: "",
spiderX: "/"
}
},
tcpSettings: {
acceptProxyProtocol: false,
header: { type: "none" }
}
}),
sniffing: JSON.stringify({
enabled: false,
destOverride: ["http", "tls", "quic", "fakedns"],
metadataOnly: false,
routeOnly: false
})
};
}
+72
View File
@@ -0,0 +1,72 @@
import crypto from "crypto";
export function buildVlessRealityXhttp({ port, uuid, domain, keys }) {
return {
enable: true,
port,
protocol: "vless",
remark: "vless-reality-xhttp",
settings: JSON.stringify({
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: "xhttp",
security: "reality",
externalProxy: [],
realitySettings: {
show: false,
xver: 0,
target: `${domain}:443`,
dest: `${domain}:443`,
serverNames: [domain],
privateKey: keys.privateKey,
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: keys.publicKey,
fingerprint: "random",
serverName: "",
spiderX: "/"
}
},
xhttpSettings: {
host: domain,
path: "/",
mode: "auto",
noSSEHeader: false,
scMaxBufferedPosts: 30,
scMaxEachPostBytes: "1000000",
scStreamUpServerSecs: "20-80",
xPaddingBytes: "100-1000"
}
}),
sniffing: JSON.stringify({
enabled: false,
destOverride: ["http", "tls", "quic", "fakedns"],
metadataOnly: false,
routeOnly: false
})
};
}
+44
View File
@@ -0,0 +1,44 @@
import crypto from "crypto";
export function buildVlessWs({ port, uuid, domain }) {
return {
enable: true,
port,
protocol: "vless",
remark: "vless-reality-ws",
settings: JSON.stringify({
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",
externalProxy: [],
wsSettings: {
host: domain,
path: "/",
acceptProxyProtocol: false,
heartbeatPeriod: 0,
}
}),
sniffing: JSON.stringify({
enabled: false,
destOverride: ["http", "tls", "quic", "fakedns"],
metadataOnly: false,
routeOnly: false
})
};
}
+36
View File
@@ -0,0 +1,36 @@
export function buildVmessTcp({ port, uuid }) {
return {
enable: true,
port,
protocol: "vmess",
remark: "vmess-tcp",
settings: JSON.stringify({
clients: [{
id: uuid,
flow: "",
email: uuid,
enable: true,
limitIp: 0,
totalGB: 0,
expiryTime: 0,
tgId: "",
subId: "",
reset: 0
}],
}),
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
})
};
}
+17
View File
@@ -0,0 +1,17 @@
services:
node:
build: ./app
env_file: .env
volumes:
- ./subscriptions:/subscriptions
- ./whitelist.txt:/app/whitelist.txt:ro
restart: unless-stopped
nginx:
image: nginx:alpine
depends_on: [node]
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./subscriptions:/subscriptions
+189
View File
@@ -0,0 +1,189 @@
import axios from "axios";
import cron from "node-cron";
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 cookieJar = {};
const api = axios.create({ baseURL: UI_URL, timeout: 15000, withCredentials: true });
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/test_3dp-manager/main/whitelist.txt";
// Вспомогательные функции
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)) 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 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?.id || null;
} catch (e) {
console.error("Add inbound failed:", e.message);
return null;
}
}
// Основная ротация inbound'ов
async function rotate() {
await login();
await updateWhitelist();
const whitelist = loadWhitelist();
const usedPorts = new Set();
const subs = [];
const existing = await getInbounds();
for (const i of existing) await deleteInbound(i.id);
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() }),
];
for (const b of builders) {
const domain = pickDomain(whitelist);
const inbound = await b(domain);
usedPorts.add(inbound.port);
const idOrPass = inbound.settings ? JSON.parse(inbound.settings).clients?.[0]?.id || JSON.parse(inbound.settings).clients?.[0]?.password : "";
// Формируем ссылку в зависимости от протокола
const link = buildInboundLink(inbound, UI_HOST, idOrPass);
if (link) subs.push(link);
await addInbound(inbound);
}
fs.writeFileSync(SUB_FILE, subs.join("\n") + "\n", "utf8");
console.log("✔ 10 inbound created, подписка обновлена");
}
// Первый запуск
rotate();
let interval = parseInt(process.env.ROTATE_INTERVAL) || 30;
if (interval < 10) interval = 10;
const cronExpression = `*/${interval} * * * *`;
cron.schedule(cronExpression, rotate);
+16
View File
@@ -0,0 +1,16 @@
events {}
http {
server {
listen $NGINX_PORT;
server_name $UI_HOST;
location = /bus/$SUB_TOKEN {
alias /subscriptions/list.txt;
default_type text/plain;
}
location / {
return 404;
}
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"type": "module",
"dependencies": {
"axios": "^1.13.2",
"node-cron": "^4.2.1"
}
}
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
#################################
# TRAP
#################################
trap 'echo -e "\033[1;31m[ERROR]\033[0m Ошибка в строке $LINENO"; exit 1' ERR
#################################
# HELPERS
#################################
log() { echo -e "\033[1;32m[INFO]\033[0m $1"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
die() { echo -e "\033[1;31m[ERROR]\033[0m $1"; exit 1; }
need_root() {
[[ $EUID -eq 0 ]] || die "Запускать только от root"
}
#################################
# START
#################################
need_root
PROJECT_DIR="/opt/3dp-manager"
log "Начинаем удаление 3dp-manager"
if [[ ! -d "$PROJECT_DIR" ]]; then
warn "Директория $PROJECT_DIR не найдена — удалять нечего"
exit 0
fi
cd "$PROJECT_DIR"
#################################
# DOCKER COMPOSE DOWN
#################################
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
if [[ -f docker-compose.yml ]]; then
log "Останавливаем контейнеры 3dp-manager"
docker compose down --volumes --remove-orphans || warn "Ошибка при docker compose down"
else
warn "docker-compose.yml не найден"
fi
else
warn "Docker или docker compose не установлен — пропуск"
fi
#################################
# CLEAN IMAGES (ТОЛЬКО НАШИ)
#################################
log "Удаляем образы 3dp-manager (если есть)"
docker images --format '{{.Repository}} {{.ID}}' \
| grep 3dp-manager \
| awk '{print $2}' \
| xargs -r docker rmi -f || true
#################################
# REMOVE DIRECTORY
#################################
log "Удаляем $PROJECT_DIR"
rm -rf "$PROJECT_DIR"
#################################
# DONE
#################################
log "✔ 3dp-manager полностью удалён"
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -euo pipefail
#################################
# TRAP
#################################
trap 'echo -e "\033[1;31m[ERROR]\033[0m Ошибка в строке $LINENO"; exit 1' ERR
#################################
# HELPERS
#################################
log() { echo -e "\033[1;32m[INFO]\033[0m $1"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
[[ $EUID -eq 0 ]] || { echo "Запускать нужно от root"; exit 1; }
#################################
# PATHS
#################################
BASE_DIR="/opt/3dp-manager"
UFW_BEFORE="/etc/ufw/before.rules"
UFW_NAT_MARKER="3dp-manager NAT"
NGINX_PORT=$(cd "$BASE_DIR" && docker compose exec -T node printenv ORIGIN_PORT | tr -d '\r')
#################################
# DOCKER CLEAN
#################################
if [[ -d "$BASE_DIR" ]]; then
log "Остановка Docker"
cd "$BASE_DIR"
docker compose down --remove-orphans || true
else
warn "Каталог проекта не найден"
fi
#################################
# REMOVE FILES
#################################
log "Удаление файлов проекта"
rm -rf "$BASE_DIR"
#################################
# UFW NAT CLEAN
#################################
echo "--- 1. Удаление оптимизаций ядра ---"
rm -f /etc/sysctl.d/99-relay-optimization.conf
# Применяем стандартные настройки (отключаем BBR и forward, если они не были включены до этого)
sysctl net.ipv4.ip_forward=0
sysctl net.core.default_qdisc=pfifo_fast
sysctl net.ipv4.tcp_congestion_control=cubic
echo "--- 2. Восстановление правил UFW из бэкапа ---"
if [ -f /etc/ufw/before.rules.bak ]; then
mv /etc/ufw/before.rules.bak /etc/ufw/before.rules
echo "Файл before.rules восстановлен из бэкапа."
else
echo "ВНИМАНИЕ: Бэкап before.rules.bak не найден. Правила NAT придется удалять вручную."
fi
echo "--- 3. Удаление разрешающих правил портов ---"
ufw delete allow 443/tcp
ufw delete allow 8443/tcp
ufw delete allow "$NGINX_PORT"/tcp
ufw delete allow 10000:60000/tcp
ufw delete allow 10000:60000/udp
echo "--- 4. Возврат политики FORWARD по умолчанию (DROP) ---"
sed -i 's/DEFAULT_FORWARD_POLICY="ACCEPT"/DEFAULT_FORWARD_POLICY="DROP"/' /etc/default/ufw
echo "--- 5. Перезапуск фаервола ---"
ufw reload
#################################
# RESULT
#################################
echo "Docker контейнеры удалены"
echo "UFW NAT очищен"
echo "ip_forward отключён"
log "Откат завершён. Для применения изменений перезагрузите систему!"
+435
View File
@@ -0,0 +1,435 @@
#!/usr/bin/env bash
set -euo pipefail
#################################
# TRAP
#################################
trap 'echo -e "\033[1;31m[ERROR]\033[0m Ошибка в строке $LINENO"; exit 1' ERR
#################################
# HELPERS
#################################
log() { echo -e "\033[1;32m[INFO]\033[0m $1"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
die() { echo -e "\033[1;31m[ERROR]\033[0m $1"; exit 1; }
[[ $EUID -eq 0 ]] || die "Запускать нужно от root"
# --- ASCII-баннер ---
echo "==================================================="
echo " ____ ____ _ ___ _ "
echo " / __ \___ ____ / __ \(_) (_)___ ______(_)___ ___ "
echo " / / / / _ \/ __ \/ /_/ / / / / __ \/ ___/ / __ \`__ \ "
echo " / /_/ / __/ / / / ____/ / / / /_/ / / / / / / / / /"
echo "/_____/\___/_/ /_/_/ /_/_/_/\__, /_/ /_/_/ /_/ /_/ "
echo " /____/ "
echo ""
echo " 3DP-MANAGER SUBSCRIPTION FORWARDER "
echo "==================================================="
echo ""
# --------------------
#################################
# INPUT
#################################
read -rp "URL исходной подписки: " ORIGIN_SUB_URL
[[ "$ORIGIN_SUB_URL" =~ ^https?:// ]] || die "Некорректный URL подписки"
read -rp "Домен или IP этого сервера (Enter = авто IP): " LOCAL_HOST
if [[ -z "$LOCAL_HOST" ]]; then
LOCAL_HOST=$(hostname -I | awk '{print $1}' | tr -d '[:space:]')
fi
#################################
# PARSE URL
#################################
ORIGIN_PROTO=$(echo "$ORIGIN_SUB_URL" | awk -F: '{print $1}')
ORIGIN_HOST=$(echo "$ORIGIN_SUB_URL" | awk -F[/:] '{print $4}')
ORIGIN_PORT=$(echo "$ORIGIN_SUB_URL" | awk -F[:] '{print $3}' | awk -F/ '{print $1}')
SUB_PATH="/$(echo "$ORIGIN_SUB_URL" | cut -d/ -f4-)"
#################################
# Определяем ORIGIN_IP
#################################
if [[ "$ORIGIN_HOST" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
# Это уже IP-адрес
ORIGIN_IP="$ORIGIN_HOST"
log "Используем прямой IP из URL: $ORIGIN_IP"
else
# Это домен — разрешаем в IP
log "Разрешаем домен $ORIGIN_HOST в IP..."
# Пробуем getent ahosts (IPv4 приоритет)
ORIGIN_IP=$(getent ahosts "$ORIGIN_HOST" | awk '/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {print $1; exit}')
# Если не вышло — fallback на hosts
[[ -z "$ORIGIN_IP" ]] && ORIGIN_IP=$(getent hosts "$ORIGIN_HOST" | awk '{print $1; exit}')
# Последний шанс — dig (если установлен)
if [[ -z "$ORIGIN_IP" ]] && command -v dig >/dev/null; then
ORIGIN_IP=$(dig +short "$ORIGIN_HOST" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1)
fi
[[ -n "$ORIGIN_IP" ]] || die "Не удалось разрешить домен в IP: $ORIGIN_HOST. Проверьте DNS или укажите IP вручную."
log "Домен $ORIGIN_HOST разрешён в IP: $ORIGIN_IP"
fi
# Финальная проверка
[[ -n "$ORIGIN_IP" && -n "$ORIGIN_PORT" && -n "$SUB_PATH" ]] || die "Ошибка парсинга URL подписки"
#################################
# HTTPS CHECK
#################################
USE_HTTPS=false
CERT_PATH=""
KEY_PATH=""
if [[ "$LOCAL_HOST" != "$(hostname -I | awk '{print $1}' | tr -d '[:space:]')" ]]; then
if [[ -d "/etc/letsencrypt/live/$LOCAL_HOST" ]]; then
CERT_PATH="/etc/letsencrypt/live/$LOCAL_HOST/fullchain.pem"
KEY_PATH="/etc/letsencrypt/live/$LOCAL_HOST/privkey.pem"
USE_HTTPS=true
log "Найдены сертификаты Let's Encrypt"
else
warn "Сертификаты Let's Encrypt не найдены"
read -rp "Путь к fullchain.pem (Enter = HTTP): " CERT_PATH
if [[ -n "$CERT_PATH" ]]; then
read -rp "Путь к privkey.pem: " KEY_PATH
[[ -f "$CERT_PATH" && -f "$KEY_PATH" ]] || die "Файлы сертификатов не найдены"
USE_HTTPS=true
fi
fi
fi
#################################
# PROJECT DIR
#################################
BASE_DIR="/opt/3dp-manager"
NODE_DIR="$BASE_DIR/node"
mkdir -p "$NODE_DIR"
cd "$BASE_DIR"
#################################
# DOWNLOAD FILES
#################################
REPO="https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main"
NGINX_PORT=$ORIGIN_PORT
SUB_URL="$ORIGIN_PROTO://$LOCAL_HOST:$NGINX_PORT$SUB_PATH"
#################################
# ENV
#################################
cat > .env <<EOF
ORIGIN_SUB_URL=$ORIGIN_SUB_URL
ORIGIN_HOST=$ORIGIN_HOST
ORIGIN_PORT=$ORIGIN_PORT
LOCAL_HOST=$LOCAL_HOST
SUB_URL=$SUB_URL
EOF
#################################
# node files
#################################
cat > node/package.json <<EOF
{
"type": "module",
"dependencies": {
"axios": "^1.13.2",
"express": "^5.2.1"
}
}
EOF
cat > node/Dockerfile <<EOF
FROM node:20-alpine
WORKDIR /app
COPY package.json .
RUN npm install --production
COPY index.js .
CMD ["node", "index.js"]
EOF
cat > node/index.js <<'EOF'
import express from "express";
import axios from "axios";
const app = express();
const {
ORIGIN_SUB_URL,
LOCAL_HOST
} = process.env;
app.get("/bus/:token", async (req, res) => {
try {
const url = ORIGIN_SUB_URL;
const r = await axios.get(url, { timeout: 15000 });
let data = r.data;
// vmess base64
const lines = data.split("\n").map(l => {
if (l.startsWith("vmess://")) {
const obj = JSON.parse(Buffer.from(l.slice(8), "base64").toString());
obj.add = LOCAL_HOST;
return "vmess://" + Buffer.from(JSON.stringify(obj)).toString("base64");
}
// остальные протоколы — замена хоста
return l.replace(/@([^:/?#]+)/, `@${LOCAL_HOST}`);
});
res.type("text/plain").send(lines.join("\n"));
} catch (e) {
res.status(500).send("subscription error");
}
});
app.listen(3000, () => {
console.log("sub-forwarder started");
});
EOF
#################################
# NGINX & DOCKER COMPOSE
#################################
if $USE_HTTPS; then
cat > nginx.conf <<EOF
events {}
http {
server {
listen $NGINX_PORT ssl;
server_name $LOCAL_HOST;
ssl_certificate $CERT_PATH;
ssl_certificate_key $KEY_PATH;
location $SUB_PATH {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
}
EOF
cat > docker-compose.yml <<EOF
services:
node:
build: ./node
env_file: .env
restart: unless-stopped
network_mode: host
container_name: node
nginx:
image: nginx:alpine
container_name: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- $CERT_PATH:$CERT_PATH:ro
- $KEY_PATH:$KEY_PATH:ro
network_mode: host
depends_on:
- node
EOF
else
cat > nginx.conf <<EOF
events {}
http {
server {
listen $NGINX_PORT;
server_name $LOCAL_HOST;
location $SUB_PATH {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
}
EOF
cat > docker-compose.yml <<EOF
services:
node:
build: ./node
env_file: .env
restart: unless-stopped
network_mode: host
container_name: node
nginx:
image: nginx:alpine
container_name: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
network_mode: host
depends_on:
- node
EOF
fi
#################################
# DOCKER
#################################
log "Проверка Docker"
if command -v docker >/dev/null 2>&1; then
log "Docker уже установлен"
else
log "Docker не найден, будет установлен"
apt update
apt install -y docker.io || die "Ошибка установки docker.io"
systemctl enable docker
systemctl restart docker
fi
# Проверка docker compose v2
if docker compose version >/dev/null 2>&1; then
log "docker compose v2 доступен"
else
log "Устанавливаем docker-compose-v2"
# решаем конфликт с docker-compose-plugin
apt install -y --allow-downgrades --allow-remove-essential --allow-change-held-packages \
docker-compose-v2 || warn "docker-compose-v2 не установлен, возможно уже есть плагин"
fi
#################################
# START
#################################
docker compose up -d --build
#################################
# UFW NAT
#################################
LOCAL_IP=$(hostname -I | awk '{print $1}')
# Проверка и установка UFW, если не установлен
if ! command -v ufw >/dev/null 2>&1; then
echo "UFW не установлен. Устанавливаю..."
apt update -qq && apt install -y ufw
fi
# Проверка статуса UFW (независимо от локали, на английском)
if LC_ALL=C ufw status 2>/dev/null | grep -q "Status: active"; then
echo "UFW уже активен."
else
echo "ВНИМАНИЕ: UFW выключен или не настроен. Включаю..."
# Разрешаем SSH (или другой порт, если используете не 22)
# OpenSSH — стандартный профиль, обычно порт 22
ufw allow OpenSSH >/dev/null 2>&1 || true
# Если SSH на другом порту, добавьте: ufw allow 2222/tcp
# Включаем UFW без подтверждения
ufw --force enable >/dev/null 2>&1
# Проверяем, успешно ли включён
if LC_ALL=C ufw status 2>/dev/null | grep -q "Status: active"; then
echo "UFW успешно включён."
else
echo "ОШИБКА: Не удалось включить UFW. Проверьте вручную!"
exit 1
fi
fi
echo "--- 1. Оптимизация сетевого стека ядра ---"
cat <<EOF > /etc/sysctl.d/99-relay-optimization.conf
net.ipv4.ip_forward = 1
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.netfilter.nf_conntrack_max = 2000000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_mtu_probing = 1
net.ipv4.conf.all.accept_local = 1
net.ipv4.conf.all.route_localnet = 1
net.core.netdev_max_backlog=250000
net.ipv4.tcp_slow_start_after_idle=0
net.ipv4.tcp_tw_reuse=1
EOF
sysctl --system
echo "--- 2. Настройка правил перенаправления (before.rules) ---"
cp /etc/ufw/before.rules /etc/ufw/before.rules.bak
# Создаем временный файл с вашими правилами
cat <<EOF > /tmp/ufw_nat_rules
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# Исключаем порт Nginx менеджера
-A PREROUTING -p tcp --dport $NGINX_PORT -j RETURN
# Проброс портов
-A PREROUTING -p tcp -m multiport --dports 443,8443,10000:60000 -j DNAT --to-destination $ORIGIN_IP
-A PREROUTING -p udp -m multiport --dports 443,8443,10000:60000 -j DNAT --to-destination $ORIGIN_IP
# Маскировка под локальный IP сервера
-A POSTROUTING -p tcp -d $ORIGIN_IP -j SNAT --to-source $LOCAL_IP
-A POSTROUTING -p udp -d $ORIGIN_IP -j SNAT --to-source $LOCAL_IP
COMMIT
*filter
:FORWARD ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
# Разрешаем пересылку для уже установленных соединений
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
# Явно разрешаем прохождение трафика на твой VPN сервер
-A FORWARD -d $ORIGIN_IP -j ACCEPT
-A FORWARD -s $ORIGIN_IP -j ACCEPT
COMMIT
*mangle
:FORWARD ACCEPT [0:0]
-A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
COMMIT
EOF
# Удаляем старые блоки *nat и *mangle из оригинала, если они там были (от прошлых запусков)
# И создаем чистую копию без них
sed -i '/\*nat/,/COMMIT/d' /etc/ufw/before.rules
sed -i '/\*mangle/,/COMMIT/d' /etc/ufw/before.rules
# Склеиваем: Сначала NAT/Mangle, потом остаток оригинального before.rules
cat /tmp/ufw_nat_rules /etc/ufw/before.rules > /etc/ufw/before.rules.new
mv /etc/ufw/before.rules.new /etc/ufw/before.rules
echo "--- 3. Открытие портов в самом фаерволе ---"
# Разрешаем входящий трафик на эти порты
ufw allow 443/tcp
ufw allow 8443/tcp
ufw allow "$NGINX_PORT"/tcp
ufw allow 10000:60000/tcp
ufw allow 10000:60000/udp
# Разрешаем FORWARD (пересылку)
# Это критически важный шаг для корректной работы DNAT в UFW
sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw
echo "--- 4. Перезапуск ---"
ufw reload
echo "Готово! Система оптимизирована, порты открыты, трафик перенаправлен."
#################################
# RESULT
#################################
echo
log "Готово"
echo "Подписка:"
echo "$SUB_URL"
+62
View File
@@ -0,0 +1,62 @@
const fs = require('fs');
// Вставьте сюда вашу ссылку на подписку
const SUBSCRIPTION_URL = '';
// Регулярное выражение для проверки домена (минимум одна точка, без спецсимволов)
const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i;
async function processSubscription() {
try {
console.log('Загрузка данных подписки...');
const response = await fetch(SUBSCRIPTION_URL);
if (!response.ok) {
throw new Error(`Ошибка загрузки: ${response.statusText}`);
}
let rawData = await response.text();
// Если подписка в Base64 (часто для vless), раскомментируйте строку ниже:
// if (!rawData.startsWith('vless://')) rawData = Buffer.from(rawData, 'base64').toString('utf8');
const links = rawData.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0);
const domains = new Set();
links.forEach(link => {
try {
const url = new URL(link);
const params = url.searchParams;
// Извлекаем значения из sni и host
const candidates = [params.get('sni'), params.get('host')];
candidates.forEach(val => {
if (val) {
const cleanVal = val.trim().toLowerCase();
// Валидация: проверяем через Regex и исключаем пустые строки
if (cleanVal && domainRegex.test(cleanVal)) {
domains.add(cleanVal);
}
}
});
} catch (e) {
// Пропускаем строки, которые не являются ссылками
}
});
const result = Array.from(domains).sort(); // Сортируем для удобства
if (result.length > 0) {
fs.writeFileSync('domains.txt', result.join('\n'), 'utf8');
console.log(`Успешно! Сохранено уникальных доменов: ${result.length}`);
} else {
console.log('Валидные домены не найдены.');
}
} catch (error) {
console.error('Произошла ошибка:', error.message);
}
}
processSubscription();
+330
View File
@@ -0,0 +1,330 @@
#!/usr/bin/env bash
set -euo pipefail
#################################
# TRAP ДЛЯ ОТЛАДКИ
#################################
trap 'echo -e "\033[1;31m[ERROR]\033[0m Ошибка в строке $LINENO"; exit 1' ERR
#################################
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
#################################
log() { echo -e "\033[1;32m[INFO]\033[0m $1"; }
die() { echo -e "\033[1;31m[ERROR]\033[0m $1"; exit 1; }
need_root() {
[[ $EUID -eq 0 ]] || die "Запускать только от root"
}
validate_url() {
[[ "$1" =~ ^https?://[^/]+:[0-9]+/.+ ]]
}
#################################
# ПРОВЕРКИ
#################################
need_root
# --- ASCII-баннер ---
echo "==================================================="
echo " ____ ____ _ ___ _ "
echo " / __ \___ ____ / __ \(_) (_)___ ______(_)___ ___ "
echo " / / / / _ \/ __ \/ /_/ / / / / __ \/ ___/ / __ \`__ \ "
echo " / /_/ / __/ / / / ____/ / / / /_/ / / / / / / / / /"
echo "/_____/\___/_/ /_/_/ /_/_/_/\__, /_/ /_/_/ /_/ /_/ "
echo " /____/ "
echo ""
echo " 3DP-MANAGER FOR 3X-UI "
echo "==================================================="
echo ""
# --------------------
#################################
# ВВОД ДАННЫХ
#################################
read -rp "URL панели 3x-ui (с портом и путем): " UI_URL
UI_URL="${UI_URL%/}"
validate_url "$UI_URL" || die "Некорректный URL 3x-ui"
read -rp "Логин 3x-ui: " UI_LOGIN
read -rsp "Пароль 3x-ui: " UI_PASSWORD
echo
[[ -z "$UI_LOGIN" || -z "$UI_PASSWORD" ]] && die "Логин/пароль обязательны"
# Проверка логина
LOGIN_RESPONSE=$(curl -s -X POST "$UI_URL/login" -H "Content-Type: application/json" -d "{\"username\":\"$UI_LOGIN\",\"password\":\"$UI_PASSWORD\"}")
# Проверка успешного логина по ключевому полю "success" или наличию куки
if ! echo "$LOGIN_RESPONSE" | grep -q '"success":true'; then
echo "Не удалось залогиниться в 3x-ui. Проверьте URL, логин и пароль."
exit 1
fi
echo "✔ Успешный логин в 3x-ui"
# Разбор UI_URL
UI_HOST=$(echo "$UI_URL" | awk -F[/:] '{print $4}') # домен или IP
UI_PROTO=$(echo "$UI_URL" | awk -F: '{print $1}') # http или https
if [[ "$UI_PROTO" == "https" ]]; then
log "HTTPS панель обнаружена, проверяем SSL сертификаты"
DEFAULT_CERT="/etc/letsencrypt/live/$UI_HOST/fullchain.pem"
DEFAULT_KEY="/etc/letsencrypt/live/$UI_HOST/privkey.pem"
if [[ -f "$DEFAULT_CERT" && -f "$DEFAULT_KEY" ]]; then
CERT_PATH="$DEFAULT_CERT"
KEY_PATH="$DEFAULT_KEY"
log "Найдены сертификаты Let's Encrypt для $UI_HOST"
else
log "⚠ Сертификаты Let's Encrypt для $UI_HOST не найдены"
read -rp "Введите полный путь к SSL сертификату (fullchain.pem): " CERT_PATH
read -rp "Введите полный путь к SSL ключу (privkey.pem): " KEY_PATH
[[ -f "$CERT_PATH" ]] || die "Файл сертификата не найден: $CERT_PATH"
[[ -f "$KEY_PATH" ]] || die "Файл ключа не найден: $KEY_PATH"
fi
fi
# Ввод интервала ротации
read -rp "Интервал генерации инбаундов в минутах (от 10, по умолчанию 30): " ROTATE_INTERVAL
ROTATE_INTERVAL="${ROTATE_INTERVAL:-30}" # по умолчанию 30
# Проверка, что это число и ≥10
if ! [[ "$ROTATE_INTERVAL" =~ ^[0-9]+$ ]] || [ "$ROTATE_INTERVAL" -lt 10 ]; then
echo "Неверное значение. Используется значение по умолчанию 30 минут."
ROTATE_INTERVAL=30
fi
echo "Интервал ротации установлен: $ROTATE_INTERVAL минут"
PROJECT_DIR="/opt/3dp-manager"
log "Используется директория проекта: $PROJECT_DIR"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
#################################
# Белый лист
#################################
REPO_BASE="https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main"
curl -fsSL "$REPO_BASE/whitelist.txt" -o whitelist.txt
log "whitelist.txt скопирован"
#################################
# ГЕНЕРАЦИЯ ТОКЕНА
#################################
SUB_TOKEN="$(openssl rand -hex 24)"
log "Сгенерирован токен подписки"
#################################
# DOCKER
#################################
log "Проверка Docker"
if command -v docker >/dev/null 2>&1; then
log "Docker уже установлен"
else
log "Docker не найден, будет установлен"
apt update
apt install -y docker.io || die "Ошибка установки docker.io"
systemctl enable docker
systemctl restart docker
fi
# Проверка docker compose v2
if docker compose version >/dev/null 2>&1; then
log "docker compose v2 доступен"
else
log "Устанавливаем docker-compose-v2"
# решаем конфликт с docker-compose-plugin
apt install -y --allow-downgrades --allow-remove-essential --allow-change-held-packages \
docker-compose-v2 || warn "docker-compose-v2 не установлен, возможно уже есть плагин"
fi
#################################
# СТРУКТУРА
#################################
mkdir -p app app/builders subscriptions
# Генерация случайного свободного порта для подписки/Nginx
get_random_port() {
while :; do
PORT=$((RANDOM % 50000 + 10000)) # диапазон 10000-60000
if ! ss -ltn | awk '{print $4}' | grep -q ":$PORT\$"; then
echo "$PORT"
return
fi
done
}
NGINX_PORT=$(get_random_port)
# Генерация URL подписки
SUB_URL="$UI_PROTO://$UI_HOST:$NGINX_PORT/bus/$SUB_TOKEN"
#################################
# ENV
#################################
cat > .env <<EOF
SUB_TOKEN=$SUB_TOKEN
UI_URL=$UI_URL
UI_LOGIN=$UI_LOGIN
UI_PASSWORD=$UI_PASSWORD
NGINX_PORT=$NGINX_PORT
UI_HOST=$UI_HOST
UI_PROTO=$UI_PROTO
ROTATE_INTERVAL=$ROTATE_INTERVAL
SUB_URL=$SUB_URL
EOF
#################################
# Dockerfile
#################################
curl -fsSL "$REPO_BASE/app/Dockerfile" -o app/Dockerfile
#################################
# package.json
#################################
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/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
curl -fsSL "$REPO_BASE/app/builders/buildShadowsocksTcp.js" -o app/builders/buildShadowsocksTcp.js
curl -fsSL "$REPO_BASE/app/builders/buildVmessTcp.js" -o app/builders/buildVmessTcp.js
curl -fsSL "$REPO_BASE/app/builders/buildVlessRealityGrpc.js" -o app/builders/buildVlessRealityGrpc.js
curl -fsSL "$REPO_BASE/app/builders/buildVlessWs.js" -o app/builders/buildVlessWs.js
curl -fsSL "$REPO_BASE/app/builders/buildInboundLink.js" -o app/builders/buildInboundLink.js
#################################
# NGINX & DOCKER COMPOSE
#################################
# Генерация nginx.conf
if [[ "$UI_PROTO" == "https" ]]; then
cat > docker-compose.yml <<EOF
services:
node:
build: ./app
env_file: .env
container_name: node
volumes:
- ./subscriptions:/subscriptions
- ./whitelist.txt:/app/whitelist.txt
restart: unless-stopped
nginx:
image: nginx:alpine
depends_on: [node]
ports:
- "$NGINX_PORT:$NGINX_PORT"
container_name: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./subscriptions:/subscriptions
- $CERT_PATH:$CERT_PATH:ro
- $KEY_PATH:$KEY_PATH:ro
EOF
cat > nginx.conf <<EOF
events {}
http {
server {
listen $NGINX_PORT ssl;
server_name $UI_HOST;
ssl_certificate $CERT_PATH;
ssl_certificate_key $KEY_PATH;
location = /bus/$SUB_TOKEN {
alias /subscriptions/list.txt;
default_type text/plain;
}
location / {
return 404;
}
}
}
EOF
else
cat > docker-compose.yml <<EOF
services:
node:
build: ./app
env_file: .env
container_name: node
volumes:
- ./subscriptions:/subscriptions
- ./whitelist.txt:/app/whitelist.txt
restart: unless-stopped
nginx:
image: nginx:alpine
depends_on: [node]
ports:
- "$NGINX_PORT:$NGINX_PORT"
container_name: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./subscriptions:/subscriptions
EOF
cat > nginx.conf <<EOF
events {}
http {
server {
listen $NGINX_PORT;
server_name $UI_HOST;
location = /bus/$SUB_TOKEN {
alias /subscriptions/list.txt;
default_type text/plain;
}
location / {
return 404;
}
}
}
EOF
fi
#################################
# ЗАПУСК
#################################
log "Сборка контейнеров"
docker compose build
log "Запуск контейнеров"
docker compose up -d
docker compose ps | grep node >/dev/null || die "Backend не запущен"
docker compose ps | grep nginx >/dev/null || die "Nginx не запущен"
#################################
# РЕЗУЛЬТАТ
#################################
log "✔ Установка завершена"
echo
echo "URL подписки:"
echo "$SUB_URL"
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -euo pipefail
#################################
# TRAP
#################################
trap 'echo -e "\033[1;31m[ERROR]\033[0m Ошибка в строке $LINENO"; exit 1' ERR
#################################
# HELPERS
#################################
log() { echo -e "\033[1;32m[INFO]\033[0m $1"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
die() { echo -e "\033[1;31m[ERROR]\033[0m $1"; exit 1; }
need_root() {
[[ $EUID -eq 0 ]] || die "Запускать только от root"
}
#################################
# CONFIG
#################################
PROJECT_DIR="/opt/3dp-manager"
REPO_RAW="https://raw.githubusercontent.com/denpiligrim/test_3dp-manager/main"
#################################
# START
#################################
need_root
log "Обновление 3dp-manager"
[[ -d "$PROJECT_DIR" ]] || die "3dp-manager не установлен ($PROJECT_DIR не найден)"
cd "$PROJECT_DIR"
#################################
# CHECK DOCKER
#################################
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/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
#################################
# HEALTH CHECK
#################################
sleep 2
docker compose ps | grep node >/dev/null || die "Backend не запущен"
docker compose ps | grep nginx >/dev/null || die "Nginx не запущен"
#################################
# DONE
#################################
log "3dp-manager успешно обновлён ✅"
+452
View File
@@ -0,0 +1,452 @@
2gis.com
2gis.ru
300.ya.ru
3475482542.mc.yandex.ru
742231.ms.ok.ru
a.auth-nsdi.ru
a.res-nsdi.ru
a.wb.ru
adm.digital.gov.ru
adm.mp.rzd.ru
admin.cs7777.vk.ru
admin.tau.vk.ru
ads.vk.ru
ads.x5.ru
adv.ozon.ru
ai-gpt1.vepene.site
ai4.vepene.site
akashi.vk-portal.net
alfabank.ru
aliusfilms.ru
ams2-cdn.2gis.com
an.yandex.ru
analytics.vk.ru
api-maps.yandex.ru
api.2gis.ru
api.avito.ru
api.browser.yandex.com
api.browser.yandex.ru
api.cs7777.vk.ru
api.events.plus.yandex.net
api.fastload.site
api.furypay.ru
api.max.ru
api.nodesecure.ru
api.ok.ru
api.photo.2gis.com
api.plus.kinopoisk.ru
api.reviews.2gis.com
api.s3.yandex.net
api.speedload.online
api.speedload.ru
api.tau.vk.ru
api.uxfeedback.yandex.net
api.vk.ru
apiok.ru
apple.com
at.ultima.foundation
auto.ru
autodiscover.ord.ozon.ru
avatars.mds.yandex.com
avatars.mds.yandex.net
avito.ru
avito.st
away.cs7777.vk.ru
away.tau.vk.ru
b.auth-nsdi.ru
b.res-nsdi.ru
bank.ozon.ru
banners-website.wildberries.ru
bfds.sberbank.ru
bg.fasssst.ru
bot.gosuslugi.ru
botapi.max.ru
bro-bg-store.s3.yandex.com
bro-bg-store.s3.yandex.net
bro-bg-store.s3.yandex.ru
brontp-pre.yandex.ru
browser.yandex.com
browser.yandex.ru
business.vk.ru
cargo.rzd.ru
catalog.api.2gis.com
cdn-de.maviks.ru
cdn.fastload.site
cdn.glorypay.ru
cdn.kickdown.click
cdn.lemanapro.ru
cdn.m1space.ru
cdn.s3.yandex.net
cdn.speedload.ru
cdn.tbank.ru
cdn.yandex.ru
cdncloudone.serverstats.ru
cdncloudthree.serverstats.ru
cdncloudtwo.serverstats.ru
cdnfive.serverstats.ru
cdnmsk.serverstats.ru
cdnrhkgfkkpupuotntfj.svc.cdn.yandex.net
cdnsix.vk-com.ru
cdntwo.dnsttlocate.ru
cdntwo.speedstream.ru
ch.fjdjdjfjd.icu
chat-prod.wildberries.ru
chat.speedload.ru
chat3.vtb.ru
cloud.cdn.yandex.com
cloud.cdn.yandex.net
cloud.cdn.yandex.ru
cloud.vk.com
cloud.vk.ru
cloudcdn-ams19.cdn.yandex.net
cloudcdn-m9-10.cdn.yandex.net
cloudcdn-m9-12.cdn.yandex.net
cloudcdn-m9-13.cdn.yandex.net
cloudcdn-m9-14.cdn.yandex.net
cloudcdn-m9-15.cdn.yandex.net
cloudcdn-m9-2.cdn.yandex.net
cloudcdn-m9-3.cdn.yandex.net
cloudcdn-m9-4.cdn.yandex.net
cloudcdn-m9-5.cdn.yandex.net
cloudcdn-m9-6.cdn.yandex.net
cloudcdn-m9-7.cdn.yandex.net
cloudcdn-m9-9.cdn.yandex.net
cloudlane.xyz
cms-res-web.online.sberbank.ru
cobrowsing.tbank.ru
collections.yandex.com
collections.yandex.ru
company.rzd.ru
connect.cs7777.vk.ru
contacts.rzd.ru
contract.gosuslugi.ru
crowdtest.payment-widget-smarttv.plus.tst.kinopoisk.ru
crowdtest.payment-widget.plus.tst.kinopoisk.ru
cs.avito.ru
cs7777.vk.ru
csp.yandex.net
d-assets.2gis.ru
dating.ok.ru
de1.vepene.site
dev.cs7777.vk.ru
dev.max.ru
dev.tau.vk.ru
digital.gov.ru
disk.2gis.com
disk.rzd.ru
dmp.dmpkit.lemanapro.ru
dnd.wb.ru
download.max.ru
dr.yandex.net
dr2.yandex.net
dropbox.com
dzen.ru
edgeport.xyz
egress.yandex.net
eh.vk.com
eh.vk.ru
ekmp-a-51.rzd.ru
enterprise.api-maps.yandex.ru
esa-res.online.sberbank.ru
esia.gosuslugi.ru
event.yandex.ru
expert.vk.ru
external-api.mediabilling.kinopoisk.ru
external-api.plus.kinopoisk.ru
fastsync.xyz
favicon.yandex.com
favicon.yandex.net
favicon.yandex.ru
favorites.api.2gis.com
filekeeper-vod.2gis.com
finance.wb.ru
fr.connectvpn.tech
frontend.vh.yandex.ru
fw.wb.ru
gbr.fasssst.online
germany1.cdn.yandexdns.ru
germany2.cdn.yandexdns.ru
gos9.portal-guard.com
gosuslugi.ru
gosweb.gosuslugi.ru
gotraf.nodesecure.ru
goya.rutube.ru
gpt-plus.vepene.site
gpt3.vepene.site
graphql-web.kinopoisk.ru
graphql.kinopoisk.ru
greone.dnsttlocate.ru
gretwo.dnsttlocate.ru
gu-st.ru
hd.kinopoisk.ru
hello.opengate.tech
help.max.ru
hkg.tunnelguard.ru
hlb.tunnelguard.ru
hls-svod.itunes.apple.com
hrc.tbank.ru
http-check-headers.yandex.ru
i.max.ru
i0.photo.2gis.com
i1.photo.2gis.com
i1ugrwg2dj.a.trbcdn.net
i2.photo.2gis.com
i3.photo.2gis.com
i4.photo.2gis.com
i5.photo.2gis.com
i6.photo.2gis.com
i7.photo.2gis.com
i8.photo.2gis.com
i9.photo.2gis.com
id.cs7777.vk.ru
id.sber.ru
id.tau.vk.ru
id.tbank.ru
id.vk.ru
ietf.org
images.apple.com
imgproxy.cdn-tinkoff.ru
informer.yandex.ru
invest.ozon.ru
io.ozone.ru
ir.ozone.ru
izbirkom.ru
jam.api.2gis.com
jitsi.wb.ru
keys.api.2gis.com
kiks.yandex.com
kiks.yandex.ru
kinopoisk.ru
knix.fasssst.online
le.tbank.ru
learning.ozon.ru
legal.max.ru
lemanapro.ru
link.max.ru
link.mp.rzd.ru
live.com
live.ok.ru
lk.gosuslugi.ru
log.strm.yandex.ru
login.cs7777.vk.ru
login.tau.vk.ru
login.vk.com
login.vk.ru
lv2.ultima.foundation
lva.tunnelguard.ru
m.47news.ru
m.avito.ru
m.cs7777.vk.ru
m.ok.ru
m.tau.vk.ru
m.vk.com
m.vk.ru
m.vkvideo.cs7777.vk.ru
ma.kinopoisk.ru
mail.yandex.com
mail.yandex.ru
map.gosuslugi.ru
mapgl.2gis.com
mapi.learning.ozon.ru
market.rzd.ru
maviks.ru
max.ru
mc.yandex.com
mc.yandex.ru
me.cs7777.vk.ru
mediafeeds.yandex.com
mediafeeds.yandex.ru
mediarush.xyz
megafon.ru
metrics.alfabank.ru
microapps.kinopoisk.ru
mp.rzd.ru
ms.cs7777.vk.ru
msk.t2.ru
mts.ru
multitest.ok.ru
music.vk.ru
music.yandex.ru
my.oversecure.pro
my.rzd.ru
mynode.nodesecure.ru
mynoderu.nodesecure.ru
neself.nodesecure.ru
neuro.translate.yandex.ru
nl.ultima.foundation
nl3.truservice.top
nla.tunnelguard.ru
novorossiya.gosuslugi.ru
nr9.strelkavpn.ru
oauth.cs7777.vk.ru
oauth.tau.vk.ru
oauth2.cs7777.vk.ru
ok.ufavpn.ru
oneclick-payment.kinopoisk.ru
online.sberbank.ru
ord.ozon.ru
ord.vk.ru
oversecure.pro
owa.ozon.ru
ozon.ru
partners.gosuslugi.ru
partners.lemanapro.ru
pay.ozon.ru
payment-widget-smarttv.plus.kinopoisk.ru
payment-widget.kinopoisk.ru
payment-widget.plus.kinopoisk.ru
pic.rutubelist.ru
pl-res.online.sberbank.ru
playself.nodesecure.ru
plb.tunnelguard.ru
plr.strelkavpn.ru
pos.gosuslugi.ru
powodzenia.xyz
pptest.userapi.com
preview.rutube.ru
prodvizhenie.rzd.ru
public-api.reviews.2gis.com
pulse.mp.rzd.ru
push.vk.ru
queuev4.vk.com
quiz.kinopoisk.ru
r.vk.ru
r0.mradx.net
receive-sentry.lmru.tech
reserve.ayagut.ovh
rontgen.fasssst.ru
ru.ultima.foundation
runstream.xyz
rutube.ru
rzd.ru
s.vtb.ru
s0.bss.2gis.com
s1.bss.2gis.com
s3.t2.ru
s3.yandex.net
sba.yandex.com
sba.yandex.net
sba.yandex.ru
sberbank.ru
secure-cloud.rzd.ru
secure.rzd.ru
securepay.ozon.ru
selfpl.speedload.online
seller.ozon.ru
sfd.gosuslugi.ru
shiftline.xyz
smartcaptcha.yandexcloud.net
sntr.avito.ru
softpipe.xyz
speller.yandex.net
sso-app4.vtb.ru
sso-app5.vtb.ru
sso.auto.ru
sso.dzen.ru
sso.passport.yandex.ru
ssp.rutube.ru
st-im.kinopoisk.ru
st-ok.cdn-vk.ru
st.avito.ru
st.kinopoisk.ru
st.max.ru
st.okcdn.ru
st.ozone.ru
st.vk.com
static-mon.yandex.net
static.lemanapro.ru
static.rutube.ru
stats.avito.ru
stats.vk-portal.net
sto.eu-ffast.com
storage.ape.yandex.net
streamport.xyz
strm-rad-23.strm.yandex.net
strm.yandex.net
strm.yandex.ru
styles.api.2gis.com
suggest.dzen.ru
suggest.sso.dzen.ru
sun6-20.userapi.com
sun6-21.userapi.com
sun6-22.userapi.com
sun9-101.userapi.com
sun9-38.userapi.com
surveys.yandex.ru
sync.browser.yandex.net
tamtam.ok.ru
target.vk.ru
team.rzd.ru
tech.vk.ru
tgju.org
ticket.rzd.ru
tickets.widget.kinopoisk.ru
tile0.maps.2gis.com
tile1.maps.2gis.com
tile2.maps.2gis.com
tile3.maps.2gis.com
tile4.maps.2gis.com
tradingview.com
travel.rzd.ru
travel.yandex.ru
travel.yastatic.net
ui.cs7777.vk.ru
ui.tau.vk.ru
usa1.vepene.site
user-geo-data.wildberries.ru
uslugi.yandex.ru
uxfeedback-cdn.s3.yandex.net
uxfeedback.yandex.ru
vk-portal.net
vkvideo.cs7777.vk.ru
vkvideo.ru
vless.hubp.de
vless.muzheng.top
voter.gosuslugi.ru
vt-1.ozone.ru
wap.yandex.com
wap.yandex.ru
wb.ru
web.max.ru
wechat.com
welcome.rzd.ru
widgets.kinopoisk.ru
wikiportal.su
ws-api.oneme.ru
ws.seller.ozon.ru
wtvp7ujn9v.a.trbcdn.net
www.abingdon.org.uk
www.apple.com
www.avito.ru
www.avito.st
www.bing.com
www.cikrf.ru
www.gosuslugi.ru
www.kinopoisk.ru
www.microsoft.com
www.ok.ru
www.ozon.ru
www.rutube.ru
www.rzd.ru
www.sberbank.ru
www.t2.ru
www.trustnav.com
www.vk.com
www.vtb.ru
xapi.ozon.ru
ya.ru
yabro-wbplugin.edadeal.yandex.ru
yabs.yandex.ru
yahoo.com
yandex.com
yandex.net
yandex.ru
yandexcdn.yandexdns.ru
yandexcdn2.yandexdns.ru
yastatic.net
yt.fasssst.ru
zen-yabro-morda.mediascope.mc.yandex.ru
zen.yandex.com
zen.yandex.net
zen.yandex.ru
zoomzoom.xyz