Files
3X-UI_autoVPS/tools/lib/singbox.js
T
Claude 0a19858efa 1.1: подписанное автообновление, kit backup и --restore, случайная заглушка
- kit update ставит только релизы, у которых SHA256SUMS подписан ключом автора
  (ssh-keygen -Y verify), версия внутри подписи совпадает, файлы сошлись с хешами;
  откат на старую версию запрещён; после обновления проверяется подписка, иначе возврат
- автообновление: systemd-таймер раз в сутки ночью со случайной задержкой,
  kit update --manual / --auto, журнал /var/log/kit-update.log
- hy2 update тоже проверяет подпись
- tools/release.sh: проверка версий и хеша установщика 3X-UI, SHA256SUMS и подпись
- kit backup и 3x-ui.sh --restore: архив 600, строгая проверка архива, конфиги nginx
  и kit-sub собираются заново, смена IP в подключениях
- сайт-заглушка: случайный из шести шаблонов внутри скрипта
- zashboard закреплён на v3.29.1, короткие тире и 192.168.x.x в текстах и картинках
- CI: ключ подписи одинаковый в kit и hy2, проверка подписи релиза
- CHANGELOG, README, инструкция, SECURITY.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7GrBSRPVXhrz3RAprWDnt
2026-09-30 19:15:19 +00:00

94 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Сборка конфига sing-box (Hiddify, Karing, NekoBox, sing-box) из разобранных ссылок.
(function (root) {
'use strict';
function clean(o) {
Object.keys(o).forEach((k) => (o[k] === undefined || o[k] === '' || (Array.isArray(o[k]) && !o[k].length)) && delete o[k]);
return o;
}
function tls(p) {
const s = p.tls;
if (!s || s.security === 'none') return undefined;
const t = clean({ enabled: true, server_name: s.sni, alpn: s.alpn, insecure: s.insecure || undefined });
if (s.fp) t.utls = { enabled: true, fingerprint: s.fp };
if (s.security === 'reality') t.reality = clean({ enabled: true, public_key: s.pbk, short_id: s.sid });
return t;
}
function transport(p) {
const t = p.transport;
if (!t || t.network === 'tcp') {
if (t && t.headerType === 'http') return clean({ type: 'http', path: t.path, host: t.host ? [t.host] : undefined });
return undefined;
}
if (t.network === 'ws') {
const o = { type: 'ws', path: t.path };
if (t.host) o.headers = { Host: t.host };
return o;
}
if (t.network === 'httpupgrade') return clean({ type: 'httpupgrade', path: t.path, host: t.host });
if (t.network === 'grpc') return { type: 'grpc', service_name: t.serviceName };
throw new Error('sing-box не поддерживает транспорт ' + t.network);
}
// Возвращает { outbound } или { endpoint } (WireGuard в sing-box – endpoint).
function convert(p) {
const base = { tag: p.name, server: p.server, server_port: p.port };
switch (p.type) {
case 'vless':
return { outbound: clean(Object.assign(base, { type: 'vless', uuid: p.uuid, flow: p.flow, tls: tls(p), transport: transport(p) })) };
case 'vmess':
return { outbound: clean(Object.assign(base, { type: 'vmess', uuid: p.uuid, security: p.cipher, alter_id: p.alterId, tls: tls(p), transport: transport(p) })) };
case 'trojan':
return { outbound: clean(Object.assign(base, { type: 'trojan', password: p.password, tls: tls(p), transport: transport(p) })) };
case 'ss':
return { outbound: Object.assign(base, { type: 'shadowsocks', method: p.method, password: p.password }) };
case 'hysteria2': {
// sing-box не умеет закреплять отпечаток сертификата – при pinSHA256 проверку отключаем.
const t = clean({ enabled: true, server_name: p.sni, alpn: p.alpn, insecure: (p.insecure || !!p.pinSHA256) || undefined });
const o = clean(Object.assign(base, { type: 'hysteria2', password: p.password, tls: t }));
if (p.obfs) o.obfs = { type: p.obfs, password: p.obfsPassword };
return { outbound: o };
}
case 'tuic':
return { outbound: clean(Object.assign(base, { type: 'tuic', uuid: p.uuid, password: p.password, congestion_control: p.congestion,
udp_relay_mode: p.udpRelay, tls: clean({ enabled: true, server_name: p.sni, alpn: p.alpn, insecure: p.insecure || undefined }) })) };
case 'wireguard':
return { endpoint: clean({ type: 'wireguard', tag: p.name, address: p.address.map((a) => a + (a.includes(':') ? '/128' : '/32')),
private_key: p.privateKey, mtu: p.mtu,
peers: [clean({ address: p.server, port: p.port, public_key: p.publicKey, pre_shared_key: p.preSharedKey, allowed_ips: ['0.0.0.0/0', '::/0'], reserved: p.reserved || undefined })] }) };
case 'amneziawg':
throw new Error('sing-box не поддерживает AmneziaWG');
default:
throw new Error('sing-box не поддерживает ' + p.type);
}
}
// opts: { inbound: {type:'mixed', port} } – для тестов и десктопных клиентов.
function build(proxies, opts) {
opts = opts || {};
const outbounds = [], endpoints = [], skipped = [];
proxies.filter((p) => p.type !== 'mtproto').forEach((p) => {
try {
const r = convert(p);
if (r.outbound) outbounds.push(r.outbound); else endpoints.push(r.endpoint);
} catch (e) { skipped.push({ name: p.name, error: e.message }); }
});
const tags = outbounds.map((o) => o.tag).concat(endpoints.map((e) => e.tag));
if (!tags.length) throw new Error(skipped.length ? skipped[0].error : 'Нет подходящих серверов для sing-box.');
const cfg = { log: { level: 'warn' } };
if (opts.inbound) cfg.inbounds = [{ type: 'mixed', tag: 'in', listen: '127.0.0.1', listen_port: opts.inbound.port }];
cfg.outbounds = [{ type: 'selector', tag: 'Прокси', outbounds: ['Авто'].concat(tags) },
{ type: 'urltest', tag: 'Авто', outbounds: tags, url: 'https://www.gstatic.com/generate_204', interval: '5m' }]
.concat(outbounds, [{ type: 'direct', tag: 'direct' }]);
if (endpoints.length) cfg.endpoints = endpoints;
cfg.route = { final: opts.final || 'Прокси', auto_detect_interface: true };
return { config: cfg, skipped, count: tags.length };
}
const api = { buildSingbox: build, singboxConvert: convert };
if (typeof module === 'object' && module.exports) module.exports = api;
root.PM = Object.assign(root.PM || {}, api);
})(typeof window !== 'undefined' ? window : globalThis);