🎁 Sync 2026-07-31 00:21:49

This commit is contained in:
github-actions[bot] committed 2026-07-31 00:21:49 +08:00
1 parent 2660b1fce8
commit ae7b9f4d28
204 files changed
+187145 -10038

No files matched your search

@@ -7,7 +7,6 @@
'use strict';
import { md5 } from 'digest';
import { readfile, writefile } from 'fs';
import { isnan } from 'math';
import { connect } from 'ubus';
@@ -30,10 +29,7 @@ uci.load(uciconfig);
const uciinfra = 'infra',
ucimain = 'config',
ucicontrol = 'control',
uciclashapi = 'clash_api',
ucintp = 'ntp',
ucicache = 'cache';
ucicontrol = 'control';
const ucidnssetting = 'dns',
ucidnsserver = 'dns_server',
@@ -45,63 +41,23 @@ const uciroutingsetting = 'routing',
const ucinode = 'node';
const uciruleset = 'ruleset';
const RULESET_DIR = HP_DIR + '/ruleset';
const routing_mode = uci.get(uciconfig, ucimain, 'routing_mode') || 'bypass_mainland_china';
function normalize_list(value) {
if (isEmpty(value))
return [];
if (type(value) === 'array')
return value;
return [value];
}
function ruleset_file_name(tag, format) {
let filename = replace(tag || 'ruleset', /[^A-Za-z0-9_.-]+/g, '_');
if (isEmpty(filename))
filename = 'ruleset';
return filename + ((format === 'source') ? '.json' : '.srs');
}
function ruleset_default_path(tag, format) {
return RULESET_DIR + '/' + ruleset_file_name(tag, format);
}
function ruleset_remote_path(path, tag, format) {
if (isEmpty(path))
return ruleset_default_path(tag, format);
if (match(path, /\/$/))
return path + ruleset_file_name(tag, format);
return path;
}
let wan_dns = ubus.call('network.interface', 'status', {'interface': 'wan'})?.['dns-server']?.[0];
if (!wan_dns)
wan_dns = (routing_mode in ['proxy_mainland_china', 'global']) ? '9.9.9.9' : '223.5.5.5';
wan_dns = (routing_mode in ['proxy_mainland_china', 'global']) ? '8.8.8.8' : '223.5.5.5';
const dns_port = uci.get(uciconfig, uciinfra, 'dns_port') || '5333';
let ntp_enabled = uci.get(uciconfig, ucintp, 'enabled'),
ntp_server = uci.get(uciconfig, ucintp, 'server'),
ntp_server_port = uci.get(uciconfig, ucintp, 'server_port'),
ntp_interval = uci.get(uciconfig, ucintp, 'interval');
if (isEmpty(ntp_enabled) && isEmpty(ntp_server)) {
ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server');
ntp_enabled = !isEmpty(ntp_server) ? '1' : '0';
}
const ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server') || 'time.apple.com';
const ipv6_support = uci.get(uciconfig, ucimain, 'ipv6_support') || '0';
let cache_file_enabled, cache_file_path, cache_store_rdrc, cache_rdrc_timeout,
main_node, main_udp_node, dedicated_udp_node, default_outbound, default_outbound_dns,
let main_node, main_udp_node, dedicated_udp_node, default_outbound, default_outbound_dns,
domain_strategy, sniff_override, dns_server, china_dns_server, dns_default_strategy,
dns_default_server, dns_disable_cache, dns_disable_cache_expire, dns_independent_cache,
dns_client_subnet, direct_domain_list,
dns_client_subnet, cache_file_store_rdrc, cache_file_rdrc_timeout, direct_domain_list,
proxy_domain_list;
if (routing_mode !== 'custom') {
@@ -137,6 +93,8 @@ if (routing_mode !== 'custom') {
dns_disable_cache_expire = uci.get(uciconfig, ucidnssetting, 'disable_cache_expire');
dns_independent_cache = uci.get(uciconfig, ucidnssetting, 'independent_cache');
dns_client_subnet = uci.get(uciconfig, ucidnssetting, 'client_subnet');
cache_file_store_rdrc = uci.get(uciconfig, ucidnssetting, 'cache_file_store_rdrc'),
cache_file_rdrc_timeout = uci.get(uciconfig, ucidnssetting, 'cache_file_rdrc_timeout');
/* Routing settings */
default_outbound = uci.get(uciconfig, uciroutingsetting, 'default_outbound') || 'nil';
@@ -152,7 +110,7 @@ const mixed_port = uci.get(uciconfig, uciinfra, 'mixed_port') || '5330';
let self_mark, redirect_port, tproxy_port, tun_name,
tun_addr4, tun_addr6, tun_mtu, tcpip_stack,
udp_timeout;
endpoint_independent_nat, udp_timeout;
if (routing_mode === 'custom')
udp_timeout = uci.get(uciconfig, uciroutingsetting, 'udp_timeout');
@@ -172,18 +130,13 @@ if (match(proxy_mode, /tun/)) {
tun_addr6 = uci.get(uciconfig, uciinfra, 'tun_addr6') || 'fdfe:dcba:9876::1/126';
tun_mtu = uci.get(uciconfig, uciinfra, 'tun_mtu') || '9000';
tcpip_stack = 'system';
if (routing_mode === 'custom')
if (routing_mode === 'custom') {
tcpip_stack = uci.get(uciconfig, uciroutingsetting, 'tcpip_stack') || 'system';
endpoint_independent_nat = uci.get(uciconfig, uciroutingsetting, 'endpoint_independent_nat');
}
}
const log_level = uci.get(uciconfig, ucimain, 'log_level') || 'warn';
cache_file_enabled = uci.get(uciconfig, ucicache, 'enabled');
cache_file_path = uci.get(uciconfig, ucicache, 'path');
cache_store_rdrc = uci.get(uciconfig, ucicache, 'store_rdrc');
cache_rdrc_timeout = uci.get(uciconfig, ucicache, 'rdrc_timeout');
let clash_api = null;
/* UCI config end */
/* Config helper start */
@@ -215,31 +168,6 @@ function parse_dnsserver(server_addr, default_protocol) {
}
}
function parse_custom_dnsserver(cfg) {
let server = cfg.server,
server_port = (cfg.type in ['https', 'h3']) ? null : strToInt(cfg.server_port),
path = cfg.path;
if (!isEmpty(server) && match(server, /:\/\//)) {
const server_url = parseURL(server);
server = server_url.hostname;
if (isEmpty(server_port))
server_port = strToInt(server_url.port);
if ((cfg.type in ['https', 'h3']) && isEmpty(path) && server_url.pathname !== '/')
path = server_url.pathname;
}
if ((cfg.type in ['https', 'h3']) && isEmpty(path))
path = '/dns-query';
return {
type: cfg.type,
server,
server_port,
path
};
}
function parse_dnsquery(strquery) {
if (type(strquery) !== 'array' || isEmpty(strquery))
return null;
@@ -252,149 +180,13 @@ function parse_dnsquery(strquery) {
}
function filter_existing_nodes(nodes) {
if (type(nodes) !== 'array' || isEmpty(nodes))
return [];
return filter(nodes, (k) => {
const node = uci.get_all(uciconfig, k) || {};
return !isEmpty(node);
});
}
function collect_group_nodes(groups, subscription_nodes, nodes, legacy_nodes) {
let result = [];
for (let group in normalize_list(groups))
uci.foreach(uciconfig, ucinode, (cfg) => {
if (cfg.grouphash === group && !~index(result, cfg['.name']))
push(result, cfg['.name']);
});
for (let node in filter_existing_nodes(normalize_list(subscription_nodes)))
if (!~index(result, node))
push(result, node);
for (let node in filter_existing_nodes(normalize_list(nodes)))
if (!~index(result, node))
push(result, node);
for (let node in filter_existing_nodes(normalize_list(legacy_nodes)))
if (!~index(result, node))
push(result, node);
return result;
}
function collect_policy_nodes(nodes, current) {
let result = [];
for (let node in normalize_list(nodes)) {
if (node === current || ~index(result, node))
continue;
const outbound = uci.get_all(uciconfig, node) || {};
if (!isEmpty(outbound) && outbound['.type'] === uciroutingnode && outbound.enabled === '1' && outbound.node in ['urltest', 'selector'])
push(result, node);
}
return result;
}
const reserved_outbound_tags = {
'GLOBAL': true,
'main-out': true,
'main-udp-out': true,
'direct-out': true,
'block-out': true
};
let outbound_tag_map = {},
outbound_tag_used = {};
function normalize_outbound_tag(tag) {
if (isEmpty(tag))
return null;
tag = trim(sprintf('%s', tag));
tag = replace(tag, /[\r\n\t]+/g, ' ');
tag = replace(tag, /[\/\\?#%]+/g, '-');
tag = replace(tag, /\s+/g, ' ');
return tag || null;
}
function section_display_name(section) {
if (type(section) !== 'object' || isEmpty(section))
return null;
let label = normalize_outbound_tag(section.label);
if (!isEmpty(label))
return label;
if (section['.type'] === ucinode) {
const address = trim(section.override_address || section.address || '');
const port = trim(section.override_port || section.port || '');
if (address && port)
return normalize_outbound_tag(`${address}:${port}`);
}
return null;
}
function register_outbound_tag(section_id, display_name) {
if (isEmpty(section_id))
return null;
let tag = outbound_tag_map[section_id];
if (!isEmpty(tag))
return tag;
const fallback_tag = `cfg-${section_id}-out`;
const base_tag = normalize_outbound_tag(display_name) || fallback_tag;
tag = base_tag;
let suffix = 2;
while (reserved_outbound_tags[tag] || outbound_tag_used[tag]) {
tag = `${base_tag} (${suffix})`;
suffix++;
}
outbound_tag_map[section_id] = tag;
outbound_tag_used[tag] = true;
return tag;
}
function get_section_outbound_tag(section_id) {
if (isEmpty(section_id))
return null;
let tag = outbound_tag_map[section_id];
if (!isEmpty(tag))
return tag;
const section = uci.get_all(uciconfig, section_id) || {};
return register_outbound_tag(section_id, section_display_name(section));
}
uci.foreach(uciconfig, ucinode, (cfg) => {
register_outbound_tag(cfg['.name'], section_display_name(cfg));
});
uci.foreach(uciconfig, uciroutingnode, (cfg) => {
if (cfg.enabled === '1')
register_outbound_tag(cfg['.name'], section_display_name(cfg));
});
function generate_endpoint(node) {
if (type(node) !== 'object' || isEmpty(node))
return null;
const endpoint = {
type: node.type,
tag: get_section_outbound_tag(node['.name']),
tag: 'cfg-' + node['.name'] + '-out',
address: node.wireguard_local_address,
mtu: strToInt(node.wireguard_mtu),
private_key: node.wireguard_private_key,
@@ -425,14 +217,13 @@ function generate_outbound(node) {
if (type(node) !== 'object' || isEmpty(node))
return null;
const tls_utls_value = (node.type === 'anytls' && isEmpty(node.tls_utls)) ? 'chrome' : node.tls_utls;
const outbound = {
type: node.type,
tag: get_section_outbound_tag(node['.name']),
tag: 'cfg-' + node['.name'] + '-out',
routing_mark: strToInt(self_mark),
server: (node.type !== 'direct') ? node.address : null,
server_port: (node.type !== 'direct') ? strToInt(node.port) : null,
server: node.address,
server_port: strToInt(node.port),
/* Hysteria(2) */
server_ports: node.hysteria_hopping_port,
@@ -440,6 +231,10 @@ function generate_outbound(node) {
user: (node.type === 'ssh') ? node.username : null,
password: node.password,
/* Direct */
override_address: node.override_address,
override_port: strToInt(node.override_port),
proxy_protocol: strToInt(node.proxy_protocol),
/* AnyTLS */
idle_session_check_interval: strToTime(node.anytls_idle_session_check_interval),
idle_session_timeout: strToTime(node.anytls_idle_session_timeout),
@@ -511,9 +306,9 @@ function generate_outbound(node) {
config: node.tls_ech_config,
config_path: node.tls_ech_config_path
} : null,
utls: !isEmpty(tls_utls_value) ? {
utls: !isEmpty(node.tls_utls) ? {
enabled: true,
fingerprint: tls_utls_value
fingerprint: node.tls_utls
} : null,
reality: (node.tls_reality === '1') ? {
enabled: true,
@@ -569,10 +364,10 @@ function get_outbound(cfg) {
const node = uci.get(uciconfig, cfg, 'node');
if (isEmpty(node))
die(sprintf("%s's node is missing, please check your configuration.", cfg));
else if (node in ['urltest', 'selector'])
return get_section_outbound_tag(cfg);
else if (node === 'urltest')
return 'cfg-' + cfg + '-out';
else
return get_section_outbound_tag(node);
return 'cfg-' + node + '-out';
}
}
}
@@ -595,117 +390,10 @@ function get_ruleset(cfg) {
return null;
let rules = [];
for (let i in cfg) {
if (isEmpty(i)) {
push(rules, null);
continue;
}
const ruleset = uci.get_all(uciconfig, i);
push(rules, !isEmpty(ruleset?.tag) ? ruleset.tag : ('cfg-' + i + '-rule'));
}
for (let i in cfg)
push(rules, isEmpty(i) ? null : 'cfg-' + i + '-rule');
return rules;
}
function subscription_provider_name(name) {
name = trim(name || '');
return name;
}
function unique_provider_name(name, used) {
const base = name;
let suffix = 2;
while (used[name]) {
name = sprintf('%s (%d)', base, suffix);
suffix++;
}
used[name] = true;
return name;
}
function get_subscription_info(group_hash) {
const value = uci.get(uciconfig, 'subscription', 'subinfo_' + substr(group_hash, 0, 16));
if (isEmpty(value))
return null;
try {
const info = json(value);
if (type(info) !== 'object')
return null;
const has_subscription_info = ('upload' in info) || ('download' in info) ||
('total' in info) || ('expire' in info);
return {
updated_at: info.updated_at,
subscription_info: has_subscription_info ? {
upload: info.upload,
download: info.download,
total: info.total,
expire: info.expire
} : null
};
} catch (e) {
return null;
}
}
function build_proxy_providers() {
let providers = [],
used_names = {},
subscription_names = normalize_list(uci.get(uciconfig, 'subscription', 'subscription_name')),
sub_index = 0;
for (let suburl in normalize_list(uci.get(uciconfig, 'subscription', 'subscription_url'))) {
const subscription_name = subscription_names[sub_index];
sub_index++;
if (isEmpty(suburl) || isEmpty(trim(subscription_name || '')))
continue;
const url = replace(suburl, /#.*$/, ''),
group_hash = md5(url);
let proxies = [];
uci.foreach(uciconfig, ucinode, (cfg) => {
if (cfg.grouphash !== group_hash)
return;
const tag = get_section_outbound_tag(cfg['.name']);
if (!isEmpty(tag) && !~index(proxies, tag))
push(proxies, tag);
});
if (isEmpty(proxies))
continue;
const subscription_info = get_subscription_info(group_hash);
push(providers, {
name: unique_provider_name(subscription_provider_name(subscription_name), used_names),
type: 'Proxy',
vehicle_type: 'HTTP',
proxies: proxies,
update_id: (routing_mode === 'custom') ? group_hash : null,
updated_at: subscription_info?.updated_at,
subscription_info: subscription_info?.subscription_info
});
}
return providers;
}
clash_api = {
external_controller: uci.get(uciconfig, uciclashapi, 'external_controller') || '127.0.0.1:9090',
external_ui: uci.get(uciconfig, uciclashapi, 'external_ui'),
external_ui_download_url: uci.get(uciconfig, uciclashapi, 'external_ui_download_url'),
external_ui_download_detour: get_outbound(uci.get(uciconfig, uciclashapi, 'external_ui_download_detour')),
secret: uci.get(uciconfig, uciclashapi, 'secret'),
default_mode: uci.get(uciconfig, uciclashapi, 'default_mode') || 'rule',
proxy_providers: build_proxy_providers()
};
/* Config helper end */
const config = {};
@@ -721,10 +409,8 @@ config.log = {
/* NTP */
if (!isEmpty(ntp_server))
config.ntp = {
enabled: ntp_enabled === '1',
enabled: true,
server: ntp_server,
server_port: strToInt(ntp_server_port),
interval: ntp_interval,
detour: 'direct-out',
domain_resolver: 'default-dns',
};
@@ -834,7 +520,10 @@ if (!isEmpty(main_node)) {
push(config.dns.servers, {
tag: 'cfg-' + cfg['.name'] + '-dns',
...parse_custom_dnsserver(cfg),
type: cfg.type,
server: cfg.server,
server_port: strToInt(cfg.server_port),
path: cfg.path,
headers: cfg.headers,
tls: cfg.tls_sni ? {
enabled: true,
@@ -876,6 +565,7 @@ if (!isEmpty(main_node)) {
user: cfg.user,
rule_set: get_ruleset(cfg.rule_set),
rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source),
rule_set_ip_cidr_accept_empty: strToBool(cfg.rule_set_ip_cidr_accept_empty),
invert: strToBool(cfg.invert),
outbound: get_outbound(cfg.outbound),
action: cfg.action,
@@ -916,6 +606,8 @@ push(config.inbounds, {
listen: '::',
listen_port: int(mixed_port),
udp_timeout: strToTime(udp_timeout),
sniff: true,
sniff_override_destination: strToBool(sniff_override),
set_system_proxy: false
});
@@ -925,7 +617,9 @@ if (match(proxy_mode, /redirect/))
tag: 'redirect-in',
listen: '::',
listen_port: int(redirect_port)
listen_port: int(redirect_port),
sniff: true,
sniff_override_destination: strToBool(sniff_override)
});
if (match(proxy_mode, /tproxy/))
push(config.inbounds, {
@@ -935,7 +629,9 @@ if (match(proxy_mode, /tproxy/))
listen: '::',
listen_port: int(tproxy_port),
network: 'udp',
udp_timeout: strToTime(udp_timeout)
udp_timeout: strToTime(udp_timeout),
sniff: true,
sniff_override_destination: strToBool(sniff_override)
});
if (match(proxy_mode, /tun/))
push(config.inbounds, {
@@ -946,8 +642,11 @@ if (match(proxy_mode, /tun/))
address: (ipv6_support === '1') ? [tun_addr4, tun_addr6] : [tun_addr4],
mtu: strToInt(tun_mtu),
auto_route: false,
endpoint_independent_nat: strToBool(endpoint_independent_nat),
udp_timeout: strToTime(udp_timeout),
stack: tcpip_stack
stack: tcpip_stack,
sniff: true,
sniff_override_destination: strToBool(sniff_override)
});
/* Inbound end */
@@ -972,16 +671,14 @@ if (!isEmpty(main_node)) {
let urltest_nodes = [];
if (main_node === 'urltest') {
const main_urltest_nodes = filter_existing_nodes(
normalize_list(uci.get(uciconfig, ucimain, 'main_urltest_nodes'))
);
const main_urltest_nodes = uci.get(uciconfig, ucimain, 'main_urltest_nodes') || [];
const main_urltest_interval = uci.get(uciconfig, ucimain, 'main_urltest_interval');
const main_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_urltest_tolerance');
push(config.outbounds, {
type: 'urltest',
tag: 'main-out',
outbounds: map(main_urltest_nodes, (k) => get_section_outbound_tag(k)),
outbounds: map(main_urltest_nodes, (k) => `cfg-${k}-out`),
interval: strToTime(main_urltest_interval),
tolerance: strToInt(main_urltest_tolerance),
idle_timeout: (strToInt(main_urltest_interval) > 1800) ? `${main_urltest_interval * 2}s` : null,
@@ -990,31 +687,23 @@ if (!isEmpty(main_node)) {
} else {
const main_node_cfg = uci.get_all(uciconfig, main_node) || {};
if (main_node_cfg.type === 'wireguard') {
const main_endpoint = generate_endpoint(main_node_cfg);
if (main_endpoint) {
main_endpoint.tag = 'main-out';
push(config.endpoints, main_endpoint);
}
push(config.endpoints, generate_endpoint(main_node_cfg));
config.endpoints[length(config.endpoints)-1].tag = 'main-out';
} else {
const main_outbound = generate_outbound(main_node_cfg);
if (main_outbound) {
main_outbound.tag = 'main-out';
push(config.outbounds, main_outbound);
}
push(config.outbounds, generate_outbound(main_node_cfg));
config.outbounds[length(config.outbounds)-1].tag = 'main-out';
}
}
if (main_udp_node === 'urltest') {
const main_udp_urltest_nodes = filter_existing_nodes(
normalize_list(uci.get(uciconfig, ucimain, 'main_udp_urltest_nodes'))
);
const main_udp_urltest_nodes = uci.get(uciconfig, ucimain, 'main_udp_urltest_nodes') || [];
const main_udp_urltest_interval = uci.get(uciconfig, ucimain, 'main_udp_urltest_interval');
const main_udp_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_udp_urltest_tolerance');
push(config.outbounds, {
type: 'urltest',
tag: 'main-udp-out',
outbounds: map(main_udp_urltest_nodes, (k) => get_section_outbound_tag(k)),
outbounds: map(main_udp_urltest_nodes, (k) => `cfg-${k}-out`),
interval: strToTime(main_udp_urltest_interval),
tolerance: strToInt(main_udp_urltest_tolerance),
idle_timeout: (strToInt(main_udp_urltest_interval) > 1800) ? `${main_udp_urltest_interval * 2}s` : null,
@@ -1023,37 +712,22 @@ if (!isEmpty(main_node)) {
} else if (dedicated_udp_node) {
const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {};
if (main_udp_node_cfg.type === 'wireguard') {
const main_udp_endpoint = generate_endpoint(main_udp_node_cfg);
if (main_udp_endpoint) {
main_udp_endpoint.tag = 'main-udp-out';
push(config.endpoints, main_udp_endpoint);
}
push(config.endpoints, generate_endpoint(main_udp_node_cfg));
config.endpoints[length(config.endpoints)-1].tag = 'main-udp-out';
} else {
const main_udp_outbound = generate_outbound(main_udp_node_cfg);
if (main_udp_outbound) {
main_udp_outbound.tag = 'main-udp-out';
push(config.outbounds, main_udp_outbound);
}
push(config.outbounds, generate_outbound(main_udp_node_cfg));
config.outbounds[length(config.outbounds)-1].tag = 'main-udp-out';
}
}
for (let i in urltest_nodes) {
const urltest_node = uci.get_all(uciconfig, i) || {};
if (isEmpty(urltest_node))
continue;
if (urltest_node.type === 'wireguard') {
const endpoint = generate_endpoint(urltest_node);
if (endpoint) {
endpoint.tag = get_section_outbound_tag(i);
push(config.endpoints, endpoint);
}
push(config.endpoints, generate_endpoint(urltest_node));
config.endpoints[length(config.endpoints)-1].tag = 'cfg-' + i + '-out';
} else {
const outbound = generate_outbound(urltest_node);
if (outbound) {
outbound.tag = get_section_outbound_tag(i);
push(config.outbounds, outbound);
}
push(config.outbounds, generate_outbound(urltest_node));
config.outbounds[length(config.outbounds)-1].tag = 'cfg-' + i + '-out';
}
}
} else if (!isEmpty(default_outbound)) {
@@ -1065,63 +739,37 @@ if (!isEmpty(main_node)) {
return;
if (cfg.node === 'urltest') {
const legacy_nodes = (isEmpty(cfg.subscription_groups) && isEmpty(cfg.subscription_nodes) && isEmpty(cfg.selected_nodes)) ? cfg.urltest_nodes : null;
const urltest_list = collect_group_nodes(cfg.subscription_groups, cfg.subscription_nodes, cfg.selected_nodes, legacy_nodes);
push(config.outbounds, {
type: 'urltest',
tag: get_section_outbound_tag(cfg['.name']),
outbounds: map(urltest_list, (k) => get_section_outbound_tag(k)),
tag: 'cfg-' + cfg['.name'] + '-out',
outbounds: map(cfg.urltest_nodes, (k) => `cfg-${k}-out`),
url: cfg.urltest_url,
interval: strToTime(cfg.urltest_interval),
tolerance: strToInt(cfg.urltest_tolerance),
idle_timeout: strToTime(cfg.urltest_idle_timeout),
interrupt_exist_connections: strToBool(cfg.urltest_interrupt_exist_connections)
});
urltest_nodes = [...urltest_nodes, ...filter(urltest_list, (l) => !~index(urltest_nodes, l))];
} else if (cfg.node === 'selector') {
const selector_node_list = collect_group_nodes(cfg.subscription_groups, cfg.subscription_nodes, cfg.selected_nodes, null);
const selector_policy_list = collect_policy_nodes(cfg.policy_nodes, cfg['.name']);
const selector_list = [...selector_node_list, ...filter(selector_policy_list, (l) => !~index(selector_node_list, l))];
const selector_default = (!isEmpty(cfg.selector_default) && ~index(selector_list, cfg.selector_default)) ? cfg.selector_default : null;
push(config.outbounds, {
type: 'selector',
tag: get_section_outbound_tag(cfg['.name']),
outbounds: map(selector_list, (k) => get_section_outbound_tag(k)),
default: selector_default ? get_section_outbound_tag(selector_default) : null,
interrupt_exist_connections: strToBool(cfg.selector_interrupt_exist_connections)
});
urltest_nodes = [...urltest_nodes, ...filter(selector_node_list, (l) => !~index(urltest_nodes, l))];
urltest_nodes = [...urltest_nodes, ...filter(cfg.urltest_nodes, (l) => !~index(urltest_nodes, l))];
} else {
const outbound = uci.get_all(uciconfig, cfg.node) || {};
if (isEmpty(outbound))
return;
if (outbound.type === 'wireguard') {
const endpoint = generate_endpoint(outbound);
if (!endpoint)
return;
endpoint.bind_interface = cfg.bind_interface;
endpoint.detour = get_outbound(cfg.outbound);
push(config.endpoints, generate_endpoint(outbound));
config.endpoints[length(config.endpoints)-1].bind_interface = cfg.bind_interface;
config.endpoints[length(config.endpoints)-1].detour = get_outbound(cfg.outbound);
if (cfg.domain_resolver)
endpoint.domain_resolver = {
config.endpoints[length(config.endpoints)-1].domain_resolver = {
server: get_resolver(cfg.domain_resolver),
strategy: cfg.domain_strategy
};
push(config.endpoints, endpoint);
} else {
const routed_outbound = generate_outbound(outbound);
if (!routed_outbound)
return;
routed_outbound.bind_interface = cfg.bind_interface;
routed_outbound.detour = get_outbound(cfg.outbound);
push(config.outbounds, generate_outbound(outbound));
config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface;
config.outbounds[length(config.outbounds)-1].detour = get_outbound(cfg.outbound);
if (cfg.domain_resolver)
routed_outbound.domain_resolver = {
config.outbounds[length(config.outbounds)-1].domain_resolver = {
server: get_resolver(cfg.domain_resolver),
strategy: cfg.domain_strategy
};
push(config.outbounds, routed_outbound);
}
push(routing_nodes, cfg.node);
}
@@ -1129,18 +777,10 @@ if (!isEmpty(main_node)) {
for (let i in filter(urltest_nodes, (l) => !~index(routing_nodes, l))) {
const urltest_node = uci.get_all(uciconfig, i) || {};
if (isEmpty(urltest_node))
continue;
if (urltest_node.type === 'wireguard') {
const endpoint = generate_endpoint(urltest_node);
if (endpoint)
push(config.endpoints, endpoint);
} else {
const outbound = generate_outbound(urltest_node);
if (outbound)
push(config.outbounds, outbound);
}
if (urltest_node.type === 'wireguard')
push(config.endpoints, generate_endpoint(urltest_node));
else
push(config.outbounds, generate_outbound(urltest_node));
}
}
@@ -1155,10 +795,13 @@ config.route = {
{
inbound: 'dns-in',
action: 'hijack-dns'
},
{
action: 'sniff'
}
/*
* leave for sing-box 1.13.0
* {
* action: 'sniff'
* }
*/
],
rule_set: [],
auto_detect_interface: isEmpty(default_interface) ? true : null,
@@ -1223,7 +866,6 @@ if (!isEmpty(main_node)) {
tag: 'geoip-cn',
format: 'binary',
url: 'https://fastly.jsdelivr.net/gh/1715173329/IPCIDR-CHINA@rule-set/cn.srs',
path: ruleset_default_path('geoip-cn', 'binary'),
download_detour: 'main-out'
});
push(config.route.rule_set, {
@@ -1231,7 +873,6 @@ if (!isEmpty(main_node)) {
tag: 'geosite-cn',
format: 'binary',
url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-cn.srs',
path: ruleset_default_path('geosite-cn', 'binary'),
download_detour: 'main-out'
});
push(config.route.rule_set, {
@@ -1239,7 +880,6 @@ if (!isEmpty(main_node)) {
tag: 'geosite-noncn',
format: 'binary',
url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-!cn.srs',
path: ruleset_default_path('geosite-noncn', 'binary'),
download_detour: 'main-out'
});
}
@@ -1284,25 +924,17 @@ if (!isEmpty(main_node)) {
user: cfg.user,
rule_set: get_ruleset(cfg.rule_set),
rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source),
rule_set_ip_cidr_accept_empty: strToBool(cfg.rule_set_ip_cidr_accept_empty),
invert: strToBool(cfg.invert),
action: cfg.action,
outbound: (cfg.action === 'route') ? get_outbound(cfg.outbound) : null,
server: (cfg.action === 'resolve') ? get_resolver(cfg.resolve_server) : null,
strategy: (cfg.action === 'resolve') ? cfg.resolve_strategy : null,
disable_cache: (cfg.action === 'resolve') ? strToBool(cfg.resolve_disable_cache) : null,
rewrite_ttl: (cfg.action === 'resolve') ? strToInt(cfg.resolve_rewrite_ttl) : null,
client_subnet: (cfg.action === 'resolve') ? cfg.resolve_client_subnet : null,
override_address: (cfg.action in ['route', 'route-options']) ? cfg.override_address : null,
override_port: (cfg.action in ['route', 'route-options']) ? strToInt(cfg.override_port) : null,
udp_disable_domain_unmapping: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.udp_disable_domain_unmapping) : null,
udp_connect: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.udp_connect) : null,
udp_timeout: (cfg.action in ['route', 'route-options']) ? strToTime(cfg.udp_timeout) : null,
tls_fragment: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.tls_fragment) : null,
tls_fragment_fallback_delay: (cfg.action in ['route', 'route-options']) ? strToTime(cfg.tls_fragment_fallback_delay) : null,
tls_record_fragment: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.tls_record_fragment) : null,
method: (cfg.action === 'reject') ? cfg.reject_method : null,
no_drop: (cfg.action === 'reject') ? strToBool(cfg.reject_no_drop) : null
outbound: get_outbound(cfg.outbound),
override_address: cfg.override_address,
override_port: strToInt(cfg.override_port),
udp_disable_domain_unmapping: strToBool(cfg.udp_disable_domain_unmapping),
udp_connect: strToBool(cfg.udp_connect),
udp_timeout: strToTime(cfg.udp_timeout),
tls_fragment: strToBool(cfg.tls_fragment),
tls_fragment_fallback_delay: strToTime(cfg.tls_fragment_fallback_delay),
tls_record_fragment: strToBool(cfg.tls_record_fragment)
});
});
@@ -1313,16 +945,14 @@ if (!isEmpty(main_node)) {
if (cfg.enabled !== '1')
return null;
const tag = !isEmpty(cfg.tag) ? cfg.tag : ('cfg-' + cfg['.name'] + '-rule');
push(config.route.rule_set, {
type: cfg.type,
tag: tag,
tag: 'cfg-' + cfg['.name'] + '-rule',
format: cfg.format,
path: cfg.path,
url: cfg.url,
path: (cfg.type === 'remote') ? ruleset_remote_path(cfg.remote_path || cfg.path, tag, cfg.format) : cfg.path,
download_detour: get_outbound(cfg.outbound),
update_interval: (cfg.type === 'remote') ? '87600h' : null
update_interval: cfg.update_interval
});
});
}
@@ -1331,17 +961,15 @@ if (!isEmpty(main_node)) {
/* Experimental start */
if (routing_mode in ['bypass_mainland_china', 'custom']) {
config.experimental = {
clash_api,
cache_file: {
enabled: cache_file_enabled !== '0',
path: cache_file_path || (HP_DIR + '/cache.db'),
store_rdrc: strToBool(cache_store_rdrc),
rdrc_timeout: strToTime(cache_rdrc_timeout),
enabled: true,
path: RUN_DIR + '/cache.db',
store_rdrc: strToBool(cache_file_store_rdrc),
rdrc_timeout: strToTime(cache_file_rdrc_timeout),
}
};
}
/* Experimental end */
system('mkdir -p ' + RUN_DIR);
system('mkdir -p ' + RULESET_DIR);
writefile(RUN_DIR + '/sing-box-c.json', sprintf('%.J\n', removeBlankAttrs(config)));
@@ -67,7 +67,7 @@ uci.foreach(uciconfig, uciserver, (cfg) => {
password: cfg.hysteria_obfs_password
} : cfg.hysteria_obfs_password,
recv_window_conn: strToInt(cfg.hysteria_recv_window_conn),
recv_window_client: strToInt(cfg.hysteria_recv_window_client),
recv_window_client: strToInt(cfg.hysteria_revc_window_client),
max_conn_client: strToInt(cfg.hysteria_max_conn_client),
disable_mtu_discovery: strToBool(cfg.hysteria_disable_mtu_discovery),
ignore_client_bandwidth: strToBool(cfg.hysteria_ignore_client_bandwidth),
+3 -22
View File
@@ -66,34 +66,15 @@ export function getTime(epoch) {
};
export function wGETResponse(url, ua) {
export function wGET(url, ua) {
if (!url || type(url) !== 'string')
return null;
if (!ua)
ua = 'Wget/1.21 (HomeProxy, like v2rayN)';
const output = executeCommand(`/usr/bin/wget -O- -S --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {};
return {
body: trim(output.stdout),
headers: output.stderr || '',
exitcode: output.exitcode
};
};
export function wGET(url, ua) {
return wGETResponse(url, ua)?.body;
};
export function wGETHeaders(url, ua) {
if (!url || type(url) !== 'string')
return null;
if (!ua)
ua = 'clash.meta';
const output = executeCommand(`/usr/bin/wget -O /dev/null -S --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {};
return output.stderr || '';
const output = executeCommand(`/usr/bin/wget -qO- --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {};
return trim(output.stdout);
};
/* Utilities end */
@@ -7,34 +7,14 @@
'use strict';
import { popen } from 'fs';
import { cursor } from 'uci';
import { isEmpty, parseURL } from 'homeproxy';
import { isEmpty, parseURL, validation } from 'homeproxy';
const uci = cursor();
const uciconfig = 'homeproxy';
uci.load(uciconfig);
function random_secret() {
const fd = popen('tr -dc "A-Za-z0-9" < /dev/urandom 2>/dev/null | head -c 16');
let random = '';
if (fd) {
random = trim(fd.read('all') || '');
fd.close();
}
if (isEmpty(random)) {
const fallback = popen('awk \'BEGIN{srand(); printf "%06d", int(rand() * 1000000)}\'');
if (fallback) {
random = trim(fallback.read('all') || '');
fallback.close();
}
}
return random;
}
const uciinfra = 'infra',
ucimigration = 'migration',
ucimain = 'config',
@@ -45,16 +25,8 @@ const uciinfra = 'infra',
ucirouting = 'routing',
uciroutingnode = 'routing_node',
uciroutingrule = 'routing_rule',
uciclashapi = 'clash_api',
ucintp = 'ntp',
ucicache = 'cache',
uciserver = 'server';
function is_update_cron(value) {
let matched = match(value || '', /^(\d{1,2})\s+(\d{1,2})\s+\*\s+\*\s+([0-7*])$/);
return !isEmpty(matched) && int(matched[1]) <= 59 && int(matched[2]) <= 23;
}
/* chinadns-ng has been removed */
if (uci.get(uciconfig, uciinfra, 'china_dns_port'))
uci.delete(uciconfig, uciinfra, 'china_dns_port');
@@ -64,7 +36,9 @@ const china_dns_server = uci.get(uciconfig, ucimain, 'china_dns_server');
if (type(china_dns_server) === 'array') {
uci.set(uciconfig, ucimain, 'china_dns_server', china_dns_server[0]);
} else {
if (match(china_dns_server, /,/))
if (china_dns_server === 'wan_114')
uci.set(uciconfig, ucimain, 'china_dns_server', '114.114.114.114');
else if (match(china_dns_server, /,/))
uci.set(uciconfig, ucimain, 'china_dns_server', split(china_dns_server, ',')[0]);
}
@@ -83,93 +57,6 @@ if (!uci.get(uciconfig, uciinfra, 'ntp_server'))
if (!isEmpty(uci.get(uciconfig, uciinfra, 'tun_gso')))
uci.delete(uciconfig, uciinfra, 'tun_gso');
/* endpoint_independent_nat was removed in sing-box 1.13 */
if (!isEmpty(uci.get(uciconfig, ucirouting, 'endpoint_independent_nat')))
uci.delete(uciconfig, ucirouting, 'endpoint_independent_nat');
/* direct outbound proxy_protocol was removed in sing-box 1.13 */
uci.foreach(uciconfig, ucinode, (cfg) => {
if (cfg.type === 'direct' && !isEmpty(cfg.proxy_protocol))
uci.delete(uciconfig, cfg['.name'], 'proxy_protocol');
});
const legacy_panel_url = 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip';
const legacy_panel_proxy_url = 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip';
const old_panel_url = 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip';
const old_panel_proxy_url = 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip';
const default_panel_url = 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip';
/* clash api panel options were introduced */
if (!uci.get(uciconfig, uciclashapi))
uci.set(uciconfig, uciclashapi, uciconfig);
if (isEmpty(uci.get(uciconfig, uciclashapi, 'external_ui')))
uci.set(uciconfig, uciclashapi, 'external_ui', '/etc/homeproxy/run/ui');
const panel_url = uci.get(uciconfig, uciclashapi, 'external_ui_download_url');
if (isEmpty(panel_url) || panel_url === legacy_panel_url || panel_url === legacy_panel_proxy_url || panel_url === old_panel_proxy_url)
uci.set(uciconfig, uciclashapi, 'external_ui_download_url', default_panel_url);
if (isEmpty(uci.get(uciconfig, uciclashapi, 'external_ui_download_detour')))
uci.set(uciconfig, uciclashapi, 'external_ui_download_detour', 'direct-out');
if (isEmpty(uci.get(uciconfig, uciclashapi, 'external_controller')))
uci.set(uciconfig, uciclashapi, 'external_controller', '0.0.0.0:9095');
if (isEmpty(uci.get(uciconfig, uciclashapi, 'secret')) || uci.get(uciconfig, uciclashapi, 'secret') === 'homeproxy') {
let random = random_secret();
if (!isEmpty(random))
uci.set(uciconfig, uciclashapi, 'secret', random);
}
if (isEmpty(uci.get(uciconfig, uciclashapi, 'default_mode')))
uci.set(uciconfig, uciclashapi, 'default_mode', 'rule');
/* ntp options were moved into a dedicated section */
if (!uci.get(uciconfig, ucintp))
uci.set(uciconfig, ucintp, uciconfig);
const legacy_ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server');
if (isEmpty(uci.get(uciconfig, ucintp, 'enabled')))
uci.set(uciconfig, ucintp, 'enabled', '1');
if (isEmpty(uci.get(uciconfig, ucintp, 'server')))
uci.set(uciconfig, ucintp, 'server', !isEmpty(legacy_ntp_server) ? legacy_ntp_server : 'ntp.aliyun.com');
if (isEmpty(uci.get(uciconfig, ucintp, 'server_port')))
uci.set(uciconfig, ucintp, 'server_port', '123');
if (isEmpty(uci.get(uciconfig, ucintp, 'interval')))
uci.set(uciconfig, ucintp, 'interval', '30m');
/* cache file options were moved into a dedicated section */
if (!uci.get(uciconfig, ucicache))
uci.set(uciconfig, ucicache, uciconfig);
if (isEmpty(uci.get(uciconfig, ucicache, 'enabled')))
uci.set(uciconfig, ucicache, 'enabled', '1');
const old_cache_path = '/var/run/homeproxy/cache.db';
const default_cache_path = '/etc/homeproxy/cache.db';
const cache_path = uci.get(uciconfig, ucicache, 'path');
if (isEmpty(cache_path) || cache_path === old_cache_path)
uci.set(uciconfig, ucicache, 'path', default_cache_path);
if (!isEmpty(uci.get(uciconfig, ucicache, 'store_fakeip')))
uci.delete(uciconfig, ucicache, 'store_fakeip');
if (isEmpty(uci.get(uciconfig, ucicache, 'store_rdrc')))
uci.set(uciconfig, ucicache, 'store_rdrc', isEmpty(uci.get(uciconfig, ucidns, 'cache_file_store_rdrc')) ? '1' : uci.get(uciconfig, ucidns, 'cache_file_store_rdrc'));
if (isEmpty(uci.get(uciconfig, ucicache, 'rdrc_timeout')) && !isEmpty(uci.get(uciconfig, ucidns, 'cache_file_rdrc_timeout')))
uci.set(uciconfig, ucicache, 'rdrc_timeout', uci.get(uciconfig, ucidns, 'cache_file_rdrc_timeout'));
if (!isEmpty(uci.get(uciconfig, ucidns, 'cache_file_store_rdrc')))
uci.delete(uciconfig, ucidns, 'cache_file_store_rdrc');
if (!isEmpty(uci.get(uciconfig, ucidns, 'cache_file_rdrc_timeout')))
uci.delete(uciconfig, ucidns, 'cache_file_rdrc_timeout');
/* create migration section */
if (!uci.get(uciconfig, ucimigration))
uci.set(uciconfig, ucimigration, uciconfig);
@@ -188,10 +75,6 @@ if (isEmpty(uci.get(uciconfig, ucimain, 'log_level')))
if (isEmpty(uci.get(uciconfig, uciserver, 'log_level')))
uci.set(uciconfig, uciserver, 'log_level', 'warn');
if (!isEmpty(uci.get(uciconfig, 'subscription', 'auto_update_time')) &&
!is_update_cron(uci.get(uciconfig, 'subscription', 'auto_update_time')))
uci.set(uciconfig, 'subscription', 'auto_update_time', '0 0 * * *');
/* empty value defaults to all ports now */
if (uci.get(uciconfig, ucimain, 'routing_port') === 'all')
uci.delete(uciconfig, ucimain, 'routing_port');
@@ -356,47 +239,6 @@ uci.foreach(uciconfig, uciroutingrule, (cfg) => {
}
});
/* routing node options */
uci.foreach(uciconfig, uciroutingnode, (cfg) => {
if (cfg.node === 'urltest' && !isEmpty(cfg.urltest_nodes)) {
let custom_nodes = [],
subscription_nodes = [];
for (let node in (type(cfg.urltest_nodes) === 'array' ? cfg.urltest_nodes : [ cfg.urltest_nodes ])) {
let node_cfg = uci.get_all(uciconfig, node);
if (isEmpty(node_cfg))
continue;
if (!isEmpty(node_cfg.grouphash))
push(subscription_nodes, node);
else
push(custom_nodes, node);
}
if (isEmpty(cfg.selected_nodes) && !isEmpty(custom_nodes))
uci.set(uciconfig, cfg['.name'], 'selected_nodes', custom_nodes);
if (isEmpty(cfg.subscription_nodes) && !isEmpty(subscription_nodes))
uci.set(uciconfig, cfg['.name'], 'subscription_nodes', subscription_nodes);
uci.delete(uciconfig, cfg['.name'], 'urltest_nodes');
}
});
/* rule set options */
uci.foreach(uciconfig, uciruleset, (cfg) => {
if (isEmpty(cfg.tag))
uci.set(uciconfig, cfg['.name'], 'tag', 'cfg-' + cfg['.name'] + '-rule');
if (cfg.type === 'remote') {
if (isEmpty(cfg.auto_update))
uci.set(uciconfig, cfg['.name'], 'auto_update', '1');
if (!is_update_cron(cfg.update_interval))
uci.set(uciconfig, cfg['.name'], 'update_interval', '0 0 * * *');
}
});
/* server options */
/* auto_firewall was moved into server options */
const auto_firewall = uci.get(uciconfig, uciserver, 'auto_firewall');
@@ -404,13 +246,6 @@ if (!isEmpty(auto_firewall))
uci.delete(uciconfig, uciserver, 'auto_firewall');
uci.foreach(uciconfig, uciserver, (cfg) => {
if (!isEmpty(cfg.hysteria_revc_window_client) && isEmpty(cfg.hysteria_recv_window_client)) {
uci.set(uciconfig, cfg['.name'], 'hysteria_recv_window_client', cfg.hysteria_revc_window_client);
}
if (!isEmpty(cfg.hysteria_revc_window_client))
uci.delete(uciconfig, cfg['.name'], 'hysteria_revc_window_client');
/* auto_firewall was moved into server options */
if (auto_firewall === '1')
uci.set(uciconfig, cfg['.name'], 'firewall' , '1');
@@ -1,46 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
set -eu
NAME="homeproxy"
RUN_DIR="/var/run/$NAME"
REQUEST_FILE="$RUN_DIR/provider-update-id"
LOCK_FILE="$RUN_DIR/provider-update-v2.lock"
LOG_FILE="$RUN_DIR/homeproxy.log"
PROVIDER_ID="${1:-}"
log() {
printf '%s [SUBSCRIBE] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE"
}
case "$PROVIDER_ID" in
*[!0-9a-f]*|'') exit 1 ;;
esac
[ "${#PROVIDER_ID}" -eq 32 ] || exit 1
mkdir -p "$RUN_DIR"
if ! lock -n "$LOCK_FILE" 2>/dev/null; then
log "Another provider update is running; waiting for it to finish..."
lock "$LOCK_FILE"
fi
umask 077
printf '%s\n' "$PROVIDER_ID" > "$REQUEST_FILE"
cleanup() {
rm -f "$REQUEST_FILE"
lock -u "$LOCK_FILE" 2>/dev/null || true
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# Give the Clash API response time to reach the panel before HomeProxy restarts.
log "Updating provider $PROVIDER_ID from Clash API..."
sleep 1
if /etc/homeproxy/scripts/update_subscriptions.uc; then
log "Provider $PROVIDER_ID update completed."
else
log "Provider $PROVIDER_ID update failed."
exit 4
fi
@@ -21,65 +21,64 @@ to_upper() {
}
check_list_update() {
local LIST_FILE="$1"
local REPO_NAME="$2"
local REPO_BRANCH="$3"
local REPO_FILE="$4"
local LOCK_FILE="$RUN_DIR/update_resources-$LIST_FILE.lock"
local GITHUB_TOKEN="$(uci -q get homeproxy.config.github_token)"
local listtype="$1"
local listrepo="$2"
local listref="$3"
local listname="$4"
local lock="$RUN_DIR/update_resources-$listtype.lock"
local github_token="$(uci -q get homeproxy.config.github_token)"
local wget="wget --timeout=10 -q"
if ! lock -n "$LOCK_FILE" 2>"/dev/null"; then
log "[$(to_upper "$LIST_FILE")] A task is already running."
exec 200>"$lock"
if ! flock -n 200 &> "/dev/null"; then
log "[$(to_upper "$listtype")] A task is already running."
return 2
fi
local NEW_VER=$(curl -sL ${GITHUB_TOKEN:+-H "Authorization: Bearer $GITHUB_TOKEN"} "https://api.github.com/repos/$REPO_NAME/releases/latest" | jsonfilter -e "@.tag_name")
if [ -z "$NEW_VER" ]; then
log "[$(to_upper "$LIST_FILE")] Failed to get the latest version, please retry later."
[ -z "$github_token" ] || github_token="--header=Authorization: Bearer $github_token"
local list_info="$($wget "${github_token:--q}" -O- "https://api.github.com/repos/$listrepo/commits?sha=$listref&path=$listname&per_page=1")"
local list_sha="$(echo -e "$list_info" | jsonfilter -qe "@[0].sha")"
local list_ver="$(echo -e "$list_info" | jsonfilter -qe "@[0].commit.message" | grep -Eo "[0-9-]+" | tr -d '-')"
if [ -z "$list_sha" ] || [ -z "$list_ver" ]; then
log "[$(to_upper "$listtype")] Failed to get the latest version, please retry later."
return 1
fi
local OLD_VER=$(cat "$RESOURCES_DIR/$LIST_FILE.ver" 2>/dev/null || echo "NOT FOUND")
if [ "$OLD_VER" = "$NEW_VER" ]; then
log "[$(to_upper "$LIST_FILE")] Current version: $NEW_VER."
log "[$(to_upper "$LIST_FILE")] You're already at the latest version."
local local_list_ver="$(cat "$RESOURCES_DIR/$listtype.ver" 2>"/dev/null" || echo "NOT FOUND")"
if [ "$local_list_ver" = "$list_ver" ]; then
log "[$(to_upper "$listtype")] Current version: $list_ver."
log "[$(to_upper "$listtype")] You're already at the latest version."
return 3
else
log "[$(to_upper "$LIST_FILE")] Local version: $OLD_VER, latest version: $NEW_VER."
log "[$(to_upper "$listtype")] Local version: $local_list_ver, latest version: $list_ver."
fi
if ! curl -sL -o "$RUN_DIR/$REPO_FILE" "https://cdn.jsdelivr.net/gh/$REPO_NAME@$REPO_BRANCH/$REPO_FILE" || [ ! -s "$RUN_DIR/$REPO_FILE" ]; then
rm -f "$RUN_DIR/$REPO_FILE"
log "[$(to_upper "$LIST_FILE")] Update failed."
if ! $wget "https://fastly.jsdelivr.net/gh/$listrepo@$list_sha/$listname" -O "$RUN_DIR/$listname" || [ ! -s "$RUN_DIR/$listname" ]; then
rm -f "$RUN_DIR/$listname"
log "[$(to_upper "$listtype")] Update failed."
return 1
fi
mv -f "$RUN_DIR/$REPO_FILE" "$RESOURCES_DIR/$LIST_FILE.${REPO_FILE##*.}"
echo -e "$NEW_VER" > "$RESOURCES_DIR/$LIST_FILE.ver"
log "[$(to_upper "$LIST_FILE")] Successfully updated."
mv -f "$RUN_DIR/$listname" "$RESOURCES_DIR/$listtype.${listname##*.}"
echo -e "$list_ver" > "$RESOURCES_DIR/$listtype.ver"
log "[$(to_upper "$listtype")] Successfully updated."
return 0
}
case "$1" in
"china_ip4")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "cncidr.txt" && \
sed -i "/IP-CIDR6,/d; s/IP-CIDR,//g" "$RESOURCES_DIR/china_ip4.txt"
check_list_update "$1" "1715173329/IPCIDR-CHINA" "master" "ipv4.txt"
;;
"china_ip6")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "cncidr.txt" && \
sed -i "/IP-CIDR,/d; s/IP-CIDR6,//g" "$RESOURCES_DIR/china_ip6.txt"
check_list_update "$1" "1715173329/IPCIDR-CHINA" "master" "ipv6.txt"
;;
"gfw_list")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "gfw.txt" && \
sed -i "s/^\.//g" "$RESOURCES_DIR/gfw_list.txt"
check_list_update "$1" "Loyalsoldier/v2ray-rules-dat" "release" "gfw.txt"
;;
"china_list")
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "direct.txt" && \
sed -i "s/^\.//g" "$RESOURCES_DIR/china_list.txt"
check_list_update "$1" "Loyalsoldier/v2ray-rules-dat" "release" "direct-list.txt" && \
sed -i -e "s/full://g" -e "/:/d" "$RESOURCES_DIR/china_list.txt"
;;
*)
echo -e "Usage: $0 <china_ip4 / china_ip6 / gfw_list / china_list>"
@@ -1,10 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2025 ImmortalWrt.org
CACHE_PATH="$(uci -q get homeproxy.cache.path)"
[ -n "$CACHE_PATH" ] || CACHE_PATH="/etc/homeproxy/cache.db"
rm -f "$CACHE_PATH"
/etc/init.d/homeproxy restart
File diff suppressed because it is too large. Load diff