- 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
94 lines
5.2 KiB
JavaScript
94 lines
5.2 KiB
JavaScript
// Сборка конфига 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);
|