small-package/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc

786 lines
26 KiB
Plaintext

#!/usr/bin/ucode
'use strict';
import { readfile, writefile } from 'fs';
import { connect } from 'ubus';
import { cursor } from 'uci';
import { urldecode, urlencode } from 'luci.http';
import {
isEmpty, strToBool, strToInt, bytesizeToByte, durationToSecond,
arrToObj, removeBlankAttrs,
HM_DIR, RUN_DIR, PRESET_OUTBOUND, RULES_LOGICAL_TYPE
} from 'fchomo';
const ubus = connect();
/* UCI config START */
const uci = cursor();
const uciconf = 'fchomo';
uci.load(uciconf);
const ucifchm = 'config',
ucires = 'resources',
uciroute = 'routing';
const uciglobal = 'global',
uciinbound = 'inbound',
ucitls = 'tls',
uciapi = 'api',
ucisniffer = 'sniffer',
ucidns = 'dns',
uciexpr = 'experimental';
const ucisniff = 'sniff',
ucidnser = 'dns_server',
ucidnspoli = 'dns_policy',
ucipgrp = 'proxy_group',
ucinode = 'node',
uciprov = 'provider',
ucichain = 'dialer_proxy',
ucirule = 'ruleset',
ucirout = 'rules',
ucisubro = 'subrules';
/* Hardcode options */
const port_presets = {
common_tcpport: uci.get(uciconf, ucifchm, 'common_tcpport') || '20-21,22,53,80,110,143,443,465,853,873,993,995,5222,8080,8443,9418',
common_udpport: uci.get(uciconf, ucifchm, 'common_udpport') || '20-21,22,53,80,110,143,443,853,993,995,8080,8443,9418',
stun_port: uci.get(uciconf, ucifchm, 'stun_port') || '3478,19302',
turn_port: uci.get(uciconf, ucifchm, 'turn_port') || '5349',
steam_client_port: uci.get(uciconf, ucifchm, 'steam_client_port') || '27015-27050',
steam_p2p_udpport: uci.get(uciconf, ucifchm, 'steam_p2p_udpport') || '3478,4379,4380,27000-27100',
},
tun_name = uci.get(uciconf, ucifchm, 'tun_name') || 'hmtun0',
tun_addr4 = uci.get(uciconf, ucifchm, 'tun_addr4') || '198.19.0.1/30',
tun_addr6 = uci.get(uciconf, ucifchm, 'tun_addr6') || 'fdfe:dcba:9877::1/126',
route_table_id = strToInt(uci.get(uciconf, ucifchm, 'route_table_id')) || 2022, // global.js
route_rule_pref = strToInt(uci.get(uciconf, ucifchm, 'route_rule_pref')) || 9000, // global.js
redirect_gate_mark = strToInt(uci.get(uciconf, ucifchm, 'redirect_gate_mark')) || 2023,
redirect_pass_mark = strToInt(uci.get(uciconf, ucifchm, 'redirect_pass_mark')) || 2024,
self_mark = strToInt(uci.get(uciconf, ucifchm, 'self_mark')) || 200, // global.js
tproxy_mark = strToInt(uci.get(uciconf, ucifchm, 'tproxy_mark')) || 201, // global.js
tun_mark = strToInt(uci.get(uciconf, ucifchm, 'tun_mark')) || 202, // global.js
posh = 'c2luZ2JveA'; // Yes. Is true.
const listen_interfaces = uci.get(uciconf, uciroute, 'listen_interfaces') || null,
bind_interface = uci.get(uciconf, uciroute, 'bind_interface') || null,
lan_filter = uci.get(uciconf, uciroute, 'lan_filter') || null,
lan_direct_ipv4_ips = uci.get(uciconf, uciroute, 'lan_direct_ipv4_ips') || null,
lan_direct_ipv6_ips = uci.get(uciconf, uciroute, 'lan_direct_ipv6_ips') || null,
lan_direct_mac_addrs = uci.get(uciconf, uciroute, 'lan_direct_mac_addrs') || null,
lan_proxy_ipv4_ips = uci.get(uciconf, uciroute, 'lan_proxy_ipv4_ips') || null,
lan_proxy_ipv6_ips = uci.get(uciconf, uciroute, 'lan_proxy_ipv6_ips') || null,
lan_proxy_mac_addrs = uci.get(uciconf, uciroute, 'lan_proxy_mac_addrs') || null,
proxy_router = (uci.get(uciconf, uciroute, 'proxy_router') === '0') ? null : true,
client_enabled = uci.get(uciconf, uciroute, 'client_enabled') || '0',
routing_tcpport = uci.get(uciconf, uciroute, 'routing_tcpport') || [],
routing_udpport = uci.get(uciconf, uciroute, 'routing_udpport') || [],
routing_mode = uci.get(uciconf, uciroute, 'routing_mode') || null,
routing_domain = strToBool(uci.get(uciconf, uciroute, 'routing_domain')),
routing_dscp_mode = uci.get(uciconf, uciroute, 'routing_dscp_mode') || null,
routing_dscp_list = uci.get(uciconf, uciroute, 'routing_dscp_list') || null,
tposh = 'c2luZ2JveA';
/* WAN DNS server array */
let wan_dns = ubus.call('network.interface', 'status', {'interface': 'wan'})?.['dns-server'];
if (length(wan_dns) === 0)
wan_dns = ['223.5.5.5'];
/* All DNS server object */
const dnsservers = {};
uci.foreach(uciconf, ucidnser, (cfg) => {
if (cfg.enabled === '0')
return;
dnsservers[cfg['.name']] = {
label: cfg.label,
address: cfg.address
};
});
/* UCI config END */
/* Config helper START */
function parse_filter(cfg) {
if (isEmpty(cfg))
return null;
if (type(cfg) === 'array')
return join('|', cfg);
else
return cfg;
}
function get_proxynode(cfg) {
if (isEmpty(cfg))
return null;
const label = uci.get(uciconf, cfg, 'label');
if (isEmpty(label))
die(sprintf("%s's label is missing, please check your configuration.", cfg));
else
return label;
}
function get_proxygroup(cfg) {
if (isEmpty(cfg))
return null;
if (cfg in PRESET_OUTBOUND)
return cfg;
const label = uci.get(uciconf, cfg, 'label');
if (isEmpty(label))
die(sprintf("%s's label is missing, please check your configuration.", cfg));
else
return label;
}
function get_nameserver(cfg, detour) {
if (isEmpty(cfg))
return [];
if ('block-dns' in cfg)
//https://github.com/MetaCubeX/mihomo/blob/0128a0bb1fce17d39158c745a912d7b2b87cf975/config/config.go#L1131
return 'rcode://name_error';
let servers = [];
for (let k in cfg) {
if (k === 'system-dns') {
push(servers, 'system');
} else if (k === 'default-dns') {
map(wan_dns, (dns) => {
push(servers, dns + '#DIRECT');
});
} else
push(servers, replace(dnsservers[k]?.address || '', /#detour=([^&]+)/, (m, c1) => {
return '#' + urlencode(get_proxygroup(detour || c1));
}));
}
return servers;
}
function parse_entry(cfg) {
if (isEmpty(cfg))
return null;
let rule = json(cfg);
if (rule.detour)
rule.detour = get_proxygroup(rule.detour);
function _payloadStrategy(payload) {
// LOGIC_TYPE,((payload1),(payload2))
if (payload.factor === null || (type(payload.factor) in ['bool', 'int', 'string'])) {
return sprintf(payload.deny ? 'NOT,((%s))' : '%s', join(',', [payload.type, payload.factor ?? '']));
} else if (type(payload.factor) === 'array') {
return sprintf(`${payload.type},(%s)`, join(',', map(payload.factor, p => `(${_payloadStrategy(p)})`)));
} else if (type(payload.factor) === 'object') {
die(sprintf(`Factor type cannot be an object: '%J'`, payload.factor));
} else
die(`Factor type is incorrect: '${payload.factor}'`);
}
// toMihomo
let logical = (rule.type in RULES_LOGICAL_TYPE);
let payload = _payloadStrategy(logical ? {type: rule.type, factor: rule.payload} : rule.payload[0]);
if (rule.subrule)
return sprintf('SUB-RULE,(%s),%s', payload, rule.subrule);
else
if (rule.type === 'MATCH')
return join(',', [rule.type, rule.detour]);
else
return join(',', [...[payload, rule.detour],
...(rule.params ? keys(rule.params) : [])
]);
}
/* Config helper END */
/* Main */
const config = {};
/* All Proxy chain object */
const dialerproxy = {};
uci.foreach(uciconf, ucichain, (cfg) => {
if (cfg.enabled === '0')
return;
let identifier = '';
if (cfg.type === 'provider')
identifier = cfg.chain_head_sub;
else if (cfg.type === 'node')
identifier = cfg.chain_head;
else
return;
dialerproxy[identifier] = {
detour: get_proxygroup(cfg.chain_tail_group) || get_proxynode(cfg.chain_tail)
};
});
/* General START */
/* General settings */
config["global-ua"] = 'clash.meta';
config.mode = uci.get(uciconf, uciglobal, 'mode') || 'rule';
config["find-process-mode"] = uci.get(uciconf, uciglobal, 'find_process_mode') || 'off';
config["log-level"] = uci.get(uciconf, uciglobal, 'log_level') || 'warning';
config["etag-support"] = (uci.get(uciconf, uciglobal, 'etag_support') === '0') ? false : true;
config.ipv6 = (uci.get(uciconf, uciglobal, 'ipv6') === '0') ? false : true;
config["unified-delay"] = strToBool(uci.get(uciconf, uciglobal, 'unified_delay')) || false;
config["tcp-concurrent"] = strToBool(uci.get(uciconf, uciglobal, 'tcp_concurrent')) || false;
config["keep-alive-interval"] = durationToSecond(uci.get(uciconf, uciglobal, 'keep_alive_interval')) || 30;
config["keep-alive-idle"] = durationToSecond(uci.get(uciconf, uciglobal, 'keep_alive_idle')) || 600;
/* ACL settings */
config["interface-name"] = bind_interface;
config["routing-mark"] = self_mark;
/* Global Authentication */
config.authentication = uci.get(uciconf, uciglobal, 'authentication');
config["skip-auth-prefixes"] = uci.get(uciconf, uciglobal, 'skip_auth_prefixes');
/* General END */
/* GEOX START */
/* GEOX settings */
config["geodata-mode"] = true;
config["geodata-loader"] = 'memconservative';
config["geo-auto-update"] = false;
/* GEOX END */
/* TLS START */
/* TLS settings */
config["global-client-fingerprint"] = uci.get(uciconf, ucitls, 'global_client_fingerprint');
config.tls = {
"certificate": uci.get(uciconf, ucitls, 'tls_cert_path'),
"private-key": uci.get(uciconf, ucitls, 'tls_key_path')
};
/* TLS END */
/* API START */
const api_port = uci.get(uciconf, uciapi, 'external_controller_port');
const api_tls_port = uci.get(uciconf, uciapi, 'external_controller_tls_port');
/* API settings */
config["external-controller-cors"] = {
"allow-origins": uci.get(uciconf, uciapi, 'external_controller_cors_allow_origins') || ['*'],
"allow-private-network" : (uci.get(uciconf, uciapi, 'external_controller_cors_allow_private_network') === '0') ? false : true
};
config["external-controller"] = api_port ? '[::]:' + api_port : null;
config["external-controller-tls"] = api_tls_port ? '[::]:' + api_tls_port : null;
config["external-doh-server"] = uci.get(uciconf, uciapi, 'external_doh_server');
config.secret = uci.get(uciconf, uciapi, 'secret') || trim(readfile('/proc/sys/kernel/random/uuid'));
config["external-ui"] = RUN_DIR + '/ui';
config["external-ui-url"] = `https://codeload.github.com/${uci.get(uciconf, uciapi, 'dashboard_repo')}/zip/refs/heads/gh-pages`;
/* API END */
/* Cache START */
/* Cache settings */
config.profile = {
"store-selected": true,
"store-fake-ip": false
};
/* Cache END */
/* Experimental START */
/* Experimental settings */
config.experimental = {
"quic-go-disable-gso": strToBool(uci.get(uciconf, uciexpr, 'quic_go_disable_gso')),
"quic-go-disable-ecn": strToBool(uci.get(uciconf, uciexpr, 'quic_go_disable_ecn')),
"dialer-ip4p-convert": strToBool(uci.get(uciconf, uciexpr, 'dialer_ip4p_convert'))
};
/* Experimental END */
/* Sniffer START */
/* Sniffer settings */
config.sniffer = {
enable: true,
"force-dns-mapping": true,
"parse-pure-ip": true,
"override-destination": (uci.get(uciconf, ucisniffer, 'override_destination') === '0') ? false : true,
sniff: {},
"force-domain": uci.get(uciconf, ucisniffer, 'force_domain'),
"skip-domain": uci.get(uciconf, ucisniffer, 'skip_domain'),
"skip-src-address": uci.get(uciconf, ucisniffer, 'skip_src_address'),
"skip-dst-address": uci.get(uciconf, ucisniffer, 'skip_dst_address')
};
/* Sniff protocol settings */
uci.foreach(uciconf, ucisniff, (cfg) => {
if (cfg.enabled === '0')
return null;
config.sniffer.sniff[cfg.protocol] = {
ports: map(cfg.ports, ports => strToInt(ports) || null), // DEBUG ERROR data type *utils.IntRanges[uint16]
"override-destination": (cfg.override_destination === '0') ? false : true
};
});
/* Sniffer END */
/* Inbound START */
const proxy_mode = uci.get(uciconf, uciinbound, 'proxy_mode') || 'redir_tproxy';
/* Listen ports */
config.listeners = [];
push(config.listeners, {
name: 'mixed-in',
type: 'mixed',
port: strToInt(uci.get(uciconf, uciinbound, 'mixed_port')) || 7890,
listen: '::',
udp: true
});
if (match(proxy_mode, /redir/))
push(config.listeners, {
name: 'redir-in',
type: 'redir',
port: strToInt(uci.get(uciconf, uciinbound, 'redir_port')) || 7891,
listen: '::'
});
if (match(proxy_mode, /tproxy/))
push(config.listeners, {
name: 'tproxy-in',
type: 'tproxy',
port: strToInt(uci.get(uciconf, uciinbound, 'tproxy_port')) || 7892,
listen: '::',
udp: true
});
push(config.listeners, {
name: 'dns-in',
type: 'tunnel',
port: strToInt(uci.get(uciconf, uciinbound, 'tunnel_port')) || 7893,
listen: '::',
network: ['tcp', 'udp'],
target: '1.1.1.1:53'
}); // Not required for v1.19.2+
/* Tun settings */
if (match(proxy_mode, /tun/))
push(config.listeners, {
name: 'tun-in',
type: 'tun',
device: tun_name,
stack: uci.get(uciconf, uciinbound, 'tun_stack') || 'system',
"dns-hijack": ['udp://[::]:53', 'tcp://[::]:53'],
"inet4-address": [ tun_addr4 ],
"inet6-address": [ tun_addr6 ],
mtu: strToInt(uci.get(uciconf, uciinbound, 'tun_mtu')) || 9000,
gso: strToBool(uci.get(uciconf, uciinbound, 'tun_gso')) || false,
"gso-max-size": strToInt(uci.get(uciconf, uciinbound, 'tun_gso_max_size')) || 65536,
"auto-route": false,
"iproute2-table-index": route_table_id,
"iproute2-rule-index": route_rule_pref,
"auto-redirect": false,
"auto-redirect-input-mark": redirect_gate_mark,
"auto-redirect-output-mark": redirect_pass_mark,
"strict-route": false,
"route-address": [
"0.0.0.0/1",
"128.0.0.0/1",
"::/1",
"8000::/1"
],
"route-exclude-address": [
"192.168.0.0/16",
"fc00::/7"
],
"route-address-set": [],
"route-exclude-address-set": [],
"include-interface": [],
"exclude-interface": [],
"udp-timeout": durationToSecond(uci.get(uciconf, uciinbound, 'tun_udp_timeout')) || 300,
"endpoint-independent-nat": strToBool(uci.get(uciconf, uciinbound, 'tun_endpoint_independent_nat')),
"auto-detect-interface": true
});
/* Inbound END */
/* DNS START */
/* DNS settings */
config.dns = {
enable: true,
"prefer-h3": false,
listen: '[::]:' + (uci.get(uciconf, ucidns, 'dns_port') || '7853'),
ipv6: (uci.get(uciconf, ucidns, 'ipv6') === '0') ? false : true,
"enhanced-mode": 'redir-host',
"use-hosts": true,
"use-system-hosts": true,
"respect-rules": true,
"default-nameserver": get_nameserver(uci.get(uciconf, ucidns, 'boot_server')),
"proxy-server-nameserver": get_nameserver(uci.get(uciconf, ucidns, 'bootnode_server')),
nameserver: get_nameserver(uci.get(uciconf, ucidns, 'default_server')),
fallback: get_nameserver(uci.get(uciconf, ucidns, 'fallback_server')),
"nameserver-policy": {},
"fallback-filter": {
geoip: false
}
};
/* DNS policy */
uci.foreach(uciconf, ucidnspoli, (cfg) => {
if (cfg.enabled === '0')
return null;
let key;
if (cfg.type === 'domain') {
key = isEmpty(cfg.domain) ? null : join(',', cfg.domain);
} else if (cfg.type === 'geosite') {
key = isEmpty(cfg.geosite) ? null : 'geosite:' + join(',', cfg.geosite);
} else if (cfg.type === 'rule_set') {
key = isEmpty(cfg.rule_set) ? null : 'rule-set:' + join(',', cfg.rule_set);
};
if (!key)
return null;
config.dns["nameserver-policy"][key] = get_nameserver(cfg.server, cfg.proxy);
});
/* Fallback filter */
if (!isEmpty(config.dns.fallback))
config.dns["fallback-filter"] = {
geoip: (uci.get(uciconf, ucidns, 'fallback_filter_geoip') === '0') ? false : true,
"geoip-code": uci.get(uciconf, ucidns, 'fallback_filter_geoip_code') || 'cn',
geosite: uci.get(uciconf, ucidns, 'fallback_filter_geosite') || [],
ipcidr: uci.get(uciconf, ucidns, 'fallback_filter_ipcidr') || [],
domain: uci.get(uciconf, ucidns, 'fallback_filter_domain') || [],
};
/* DNS END */
/* Hosts START */
/* Hosts */
config.hosts = {};
/* Hosts END */
/* Proxy Node START */
/* Proxy Node */
config.proxies = [
/*{
name: 'direct-out',
type: 'direct',
udp: true,
"ip-version": undefined,
"interface-name": undefined,
"routing-mark": undefined
},*/
{
name: 'dns-out',
type: 'dns'
}
];
uci.foreach(uciconf, ucinode, (cfg) => {
if (cfg.enabled === '0')
return null;
push(config.proxies, {
name: cfg.label,
type: cfg.type,
server: cfg.server,
port: strToInt(cfg.port) || null,
/* Dial fields */
tfo: strToBool(cfg.tfo),
mptcp: strToBool(cfg.mptcp),
"dialer-proxy": dialerproxy[cfg['.name']]?.detour,
"interface-name": cfg.interface_name,
"routing-mark": strToInt(cfg.routing_mark) || null,
"ip-version": cfg.ip_version,
/* HTTP / SOCKS / Shadowsocks / VMess / VLESS / Trojan / hysteria2 / TUIC / SSH / WireGuard */
username: cfg.username,
uuid: cfg.vmess_uuid || cfg.uuid,
cipher: cfg.vmess_chipher || cfg.shadowsocks_chipher,
password: cfg.shadowsocks_password || cfg.password,
headers: cfg.headers ? json(cfg.headers) : null,
"private-key": cfg.wireguard_private_key || cfg.ssh_priv_key,
/* Hysteria / Hysteria2 */
ports: isEmpty(cfg.hysteria_ports) ? null : join(',', cfg.hysteria_ports),
up: cfg.hysteria_up_mbps ? cfg.hysteria_up_mbps + ' Mbps' : null,
down: cfg.hysteria_down_mbps ? cfg.hysteria_down_mbps + ' Mbps' : null,
obfs: cfg.hysteria_obfs_type,
"obfs-password": cfg.hysteria_obfs_password,
/* SSH */
"private-key-passphrase": cfg.ssh_priv_key_passphrase,
"host-key-algorithms": cfg.ssh_host_key_algorithms,
"host-key": cfg.ssh_host_key,
/* Shadowsocks */
/* Mieru */
"port-range": cfg.mieru_port_range,
transport: cfg.mieru_transport,
multiplexing: cfg.mieru_multiplexing,
/* Snell */
psk: cfg.snell_psk,
version: cfg.snell_version,
"obfs-opts": cfg.type === 'snell' ? {
mode: cfg.plugin_opts_obfsmode,
host: cfg.plugin_opts_host,
} : null,
/* TUIC */
ip: cfg.tuic_ip,
"congestion-controller": cfg.tuic_congestion_controller,
"udp-relay-mode": cfg.tuic_udp_relay_mode,
"udp-over-stream": strToBool(cfg.tuic_udp_over_stream),
"udp-over-stream-version": cfg.tuic_udp_over_stream_version,
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size) || null,
"reduce-rtt": strToBool(cfg.tuic_reduce_rtt),
"heartbeat-interval": strToInt(cfg.tuic_heartbeat) || null,
"request-timeout": strToInt(cfg.tuic_request_timeout) || null,
// fast-open: true
// max-open-streams: 20
/* Trojan */
"ss-opts": cfg.trojan_ss_enabled === '1' ? {
enabled: true,
method: cfg.trojan_ss_chipher,
password: cfg.trojan_ss_password
} : null,
/* AnyTLS */
"idle-session-check-interval": durationToSecond(cfg.anytls_idle_session_check_interval),
"idle-session-timeout": durationToSecond(cfg.anytls_idle_session_timeout),
"min-idle-session": strToInt(cfg.anytls_min_idle_session),
/* VMess / VLESS */
flow: cfg.vless_flow,
alterId: strToInt(cfg.vmess_alterid),
"global-padding": cfg.type === 'vmess' ? (cfg.vmess_global_padding === '0' ? false : true) : null,
"authenticated-length": strToBool(cfg.vmess_authenticated_length),
"packet-encoding": cfg.vmess_packet_encoding,
/* WireGuard */
ip: cfg.wireguard_ip,
ipv6: cfg.wireguard_ipv6,
"public-key": cfg.wireguard_peer_public_key,
"pre-shared-key": cfg.wireguard_pre_shared_key,
"allowed-ips": cfg.wireguard_allowed_ips,
reserved: cfg.wireguard_reserved,
mtu: strToInt(cfg.wireguard_mtu) || null,
"remote-dns-resolve": strToBool(cfg.wireguard_remote_dns_resolve),
dns: cfg.wireguard_dns,
/* Plugin fields */
plugin: cfg.plugin,
"plugin-opts": cfg.plugin ? {
mode: cfg.plugin_opts_obfsmode,
host: cfg.plugin_opts_host,
password: cfg.plugin_opts_thetlspassword,
version: strToInt(cfg.plugin_opts_shadowtls_version),
"version-hint": cfg.plugin_opts_restls_versionhint,
"restls-script": cfg.plugin_opts_restls_script
} : null,
/* Extra fields */
udp: strToBool(cfg.udp),
"udp-over-tcp": strToBool(cfg.uot),
"udp-over-tcp-version": cfg.uot_version,
/* TLS fields */
tls: (cfg.type in ['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic']) ? null : strToBool(cfg.tls),
"disable-sni": strToBool(cfg.tls_disable_sni),
...arrToObj([[(cfg.type in ['vmess', 'vless']) ? 'servername' : 'sni', cfg.tls_sni]]),
fingerprint: cfg.tls_fingerprint,
alpn: cfg.tls_alpn, // Array
"skip-cert-verify": strToBool(cfg.tls_skip_cert_verify),
"client-fingerprint": cfg.tls_client_fingerprint,
"reality-opts": cfg.tls_reality === '1' ? {
"public-key": cfg.tls_reality_public_key,
"short-id": cfg.tls_reality_short_id
} : null,
/* Transport fields */
// https://github.com/muink/mihomo/blob/3e966e82c793ca99e3badc84bf3f2907b100edae/adapter/outbound/vmess.go#L74
...(cfg.transport_enabled === '1' ? {
network: cfg.transport_type,
"http-opts": cfg.transport_type === 'http' ? {
method: cfg.transport_http_method,
path: isEmpty(cfg.transport_paths) ? ['/'] : cfg.transport_paths, // Array
headers: cfg.transport_http_headers ? json(cfg.transport_http_headers) : null,
} : null,
"h2-opts": cfg.transport_type === 'h2' ? {
host: cfg.transport_hosts, // Array
path: cfg.transport_path || '/',
} : null,
"grpc-opts": cfg.transport_type === 'grpc' ? {
"grpc-service-name": cfg.transport_grpc_servicename
} : null,
"ws-opts": cfg.transport_type === 'ws' ? {
path: cfg.transport_path || '/',
headers: cfg.transport_http_headers ? json(cfg.transport_http_headers) : null,
"max-early-data": strToInt(cfg.transport_ws_max_early_data) || null,
"early-data-header-name": cfg.transport_ws_early_data_header,
"v2ray-http-upgrade": strToBool(cfg.transport_ws_v2ray_http_upgrade),
"v2ray-http-upgrade-fast-open": strToBool(cfg.transport_ws_v2ray_http_upgrade_fast_open)
} : null
} : {}),
/* Multiplex fields */
smux: cfg.smux_enabled === '1' ? {
enabled: true,
protocol: cfg.smux_protocol,
"max-connections": strToInt(cfg.smux_max_connections) || null,
"min-streams": strToInt(cfg.smux_min_streams) || null,
"max-streams": strToInt(cfg.smux_max_streams) || null,
statistic: strToBool(cfg.smux_statistic),
"only-tcp": strToBool(cfg.smux_only_tcp),
padding: strToBool(cfg.smux_padding),
"brutal-opts": cfg.smux_brutal === '1' ? {
enabled: true,
up: strToInt(cfg.smux_brutal_up) || null, // Mbps
down: strToInt(cfg.smux_brutal_down) || null // Mbps
} : null
} : null
});
});
/* Proxy Node END */
/* Proxy Group START */
/* Proxy Group */
config["proxy-groups"] = [];
uci.foreach(uciconf, ucipgrp, (cfg) => {
if (cfg.enabled === '0')
return null;
push(config["proxy-groups"], {
name: cfg.label,
type: cfg.type,
proxies: [
...map(cfg.groups || [], cfg => get_proxygroup(cfg)),
...map(cfg.proxies || [], cfg => get_proxynode(cfg))
],
use: cfg.use,
"include-all": strToBool(cfg.include_all),
"include-all-proxies": strToBool(cfg.include_all_proxies),
"include-all-providers": strToBool(cfg.include_all_providers),
// Url-test fields
tolerance: (cfg.type === 'url-test') ? strToInt(cfg.tolerance) ?? 150 : null,
// Load-balance fields
strategy: cfg.strategy,
// Override fields
"disable-udp": strToBool(cfg.disable_udp) || false,
// Health fields
url: cfg.url,
interval: cfg.url ? durationToSecond(cfg.interval) ?? 600 : null,
timeout: cfg.url ? strToInt(cfg.timeout) || 5000 : null,
lazy: (cfg.lazy === '0') ? false : null,
"expected-status": cfg.url ? cfg.expected_status || '204' : null,
"max-failed-times": cfg.url ? strToInt(cfg.max_failed_times) ?? 5 : null,
// General fields
filter: parse_filter(cfg.filter),
"exclude-filter": parse_filter(cfg.exclude_filter),
"exclude-type": parse_filter(cfg.exclude_type),
hidden: strToBool(cfg.hidden),
icon: cfg.icon
});
});
/* Proxy Group END */
/* Provider START */
/* Provider settings */
config["proxy-providers"] = {};
uci.foreach(uciconf, uciprov, (cfg) => {
if (cfg.enabled === '0')
return null;
config["proxy-providers"][cfg['.name']] = {
type: cfg.type,
...(cfg.type === 'inline' ? {
"dialer-proxy": dialerproxy[cfg['.name']]?.detour,
payload: trim(cfg.payload)
} : {
path: HM_DIR + '/provider/' + cfg['.name'],
url: cfg.url,
"size-limit": bytesizeToByte(cfg.size_limit) || null,
interval: (cfg.type === 'http') ? durationToSecond(cfg.interval) ?? 86400 : null,
proxy: get_proxygroup(cfg.proxy),
header: cfg.header ? json(cfg.header) : null,
/* Health fields */
"health-check": cfg.health_enable === '0' ? {enable: false} : {
enable: true,
url: cfg.health_url,
interval: durationToSecond(cfg.health_interval) ?? 600,
timeout: strToInt(cfg.health_timeout) || 5000,
lazy: (cfg.health_lazy === '0') ? false : null,
"expected-status": cfg.health_expected_status || '204'
},
/* Override fields */
override: {
"additional-prefix": cfg.override_prefix,
"additional-suffix": cfg.override_suffix,
"proxy-name": isEmpty(cfg.override_replace) ? null : map(cfg.override_replace, (obj) => json(obj)),
// Configuration Items
tfo: strToBool(cfg.override_tfo),
mptcp: strToBool(cfg.override_mptcp),
udp: (cfg.override_udp === '0') ? false : true,
"udp-over-tcp": strToBool(cfg.override_uot),
up: cfg.override_up ? cfg.override_up + ' Mbps' : null,
down: cfg.override_down ? cfg.override_down + ' Mbps' : null,
"skip-cert-verify": strToBool(cfg.override_skip_cert_verify) || false,
"dialer-proxy": dialerproxy[cfg['.name']]?.detour,
"interface-name": cfg.override_interface_name,
"routing-mark": strToInt(cfg.override_routing_mark) || null,
"ip-version": cfg.override_ip_version
},
/* General fields */
filter: parse_filter(cfg.filter),
"exclude-filter": parse_filter(cfg.exclude_filter),
"exclude-type": parse_filter(cfg.exclude_type)
})
};
});
/* Provider END */
/* Rule set START */
/* Rule set settings */
config["rule-providers"] = {};
uci.foreach(uciconf, ucirule, (cfg) => {
if (cfg.enabled === '0')
return null;
config["rule-providers"][cfg['.name']] = {
type: cfg.type,
format: cfg.format,
behavior: cfg.behavior,
...(cfg.type === 'inline' ? {
payload: trim(cfg.payload)
} : {
path: HM_DIR + '/ruleset/' + cfg['.name'],
url: cfg.url,
"size-limit": bytesizeToByte(cfg.size_limit) || null,
interval: (cfg.type === 'http') ? durationToSecond(cfg.interval) ?? 259200 : null,
proxy: get_proxygroup(cfg.proxy)
})
};
});
/* Rule set END */
/* Routing rules START */
/* Routing rules */
config.rules = [
"IN-NAME,dns-in,dns-out", // Not required for v1.19.2+
"DST-PORT,53,dns-out"
];
uci.foreach(uciconf, ucirout, (cfg) => {
if (cfg.enabled === '0')
return null;
push(config.rules, parse_entry(cfg.entry));
});
/* Routing rules END */
/* Sub rules START */
/* Sub rules */
config["sub-rules"] = {};
uci.foreach(uciconf, ucisubro, (cfg) => {
if (cfg.enabled === '0')
return null;
if (!config["sub-rules"][cfg.group])
config["sub-rules"][cfg.group] = [];
push(config["sub-rules"][cfg.group], parse_entry(cfg.entry));
});
/* Sub rules END */
printf('%.J\n', removeBlankAttrs(config));