⛄ Sync 2026-06-30 01:11:33

This commit is contained in:
github-actions[bot] committed 2026-06-30 01:11:33 +08:00
1 parent fb3826f4ad
commit a09508c01e
157 files changed
+78873 -73698

No files matched your search

@@ -56,6 +56,7 @@ config openclash 'config'
option yacd_type 'Meta'
option append_default_dns '0'
option enable_respect_rules '0'
option default_dashboard 'metacubexd'
option geo_custom_url 'https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb'
option geosite_custom_url 'https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat'
option geoip_custom_url 'https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat'
+124 -44
View File
@@ -102,6 +102,11 @@ load_ip_route_pass() {
if [ "$china_ip_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
if [ "$enable_redirect_dns" != "2" ]; then
mkdir -p ${DNSMASQ_CONF_DIR}
if [ "$settype" = "nftset" ]; then
nft add set inet fw4 china_ip_route_pass '{ type ipv4_addr; flags interval; auto-merge; }'
else
ipset -! create china_ip_route_pass hash:net family inet hashsize 1024 maxelem 1000000
fi
awk '!/^$/&&!/^#/&&!/(^([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])((\/[0-9][0-9])?)$/{printf("'${settype}'=/%s/'${nftflag}'china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute_pass.conf
for ip in $(uci_get_config "china_ip_route_pass"); do
[ -z "$ip" ] && continue
@@ -114,6 +119,11 @@ load_ip_route_pass() {
if [ "$china_ip6_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
if [ "$enable_redirect_dns" != "2" ]; then
mkdir -p ${DNSMASQ_CONF_DIR}
if [ "$settype" = "nftset" ]; then
nft add set inet fw4 china_ip6_route_pass '{ type ipv6_addr; flags interval; auto-merge; }'
else
ipset -! create china_ip6_route_pass hash:net family inet6 hashsize 1024 maxelem 1000000
fi
awk '!/^$/&&!/^#/&&/([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})/{printf("'${settype}'=/%s/'${nftflag}'china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute6_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute6_pass.conf
for ip in $(uci_get_config "china_ip6_route_pass"); do
[ -z "$ip" ] && continue
@@ -142,6 +152,7 @@ load_ip_route_pass() {
if [ "$china_ip_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
if [ "$enable_redirect_dns" != "2" ]; then
mkdir -p ${DNSMASQ_CONF_DIR}
ipset -! create china_ip_route_pass hash:net family inet hashsize 1024 maxelem 1000000
awk '!/^$/&&!/^#/&&!/(^([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])((\/[0-9][0-9])?)$/{printf("ipset=/%s/china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute_pass.conf
for ip in $(uci_get_config "china_ip_route_pass"); do
[ -z "$ip" ] && continue
@@ -154,6 +165,7 @@ load_ip_route_pass() {
if [ "$china_ip6_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
if [ "$enable_redirect_dns" != "2" ]; then
mkdir -p ${DNSMASQ_CONF_DIR}
ipset -! create china_ip6_route_pass hash:net family inet6 hashsize 1024 maxelem 1000000
awk '!/^$/&&!/^#/&&/([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})/{printf("ipset=/%s/china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute6_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute6_pass.conf
for ip in $(uci_get_config "china_ip6_route_pass"); do
[ -z "$ip" ] && continue
@@ -172,7 +184,7 @@ load_ip_route_pass() {
fi
}
change_dns() {
change_dnsmasq() {
if ! /etc/init.d/dnsmasq enabled; then
return
fi
@@ -184,13 +196,15 @@ change_dns() {
/usr/share/openclash/openclash_custom_domain_dns.sh
if [ "$1" -eq 1 ]; then
uci -q del openclash.config.dnsmasq_server
config_load "dhcp"
config_list_foreach "$(uci -q show dhcp.@dnsmasq[0].server |awk -F '.' '{print $2}')" "server" save_dnsmasq_server
if [ "$(uci_get_config "redirect_dns")" != "1" ]; then
uci -q del openclash.config.dnsmasq_server
config_load "dhcp"
config_list_foreach "$(uci -q show dhcp.@dnsmasq[0].server |awk -F '.' '{print $2}')" "server" save_dnsmasq_server
uci -q set openclash.config.dnsmasq_noresolv="$(uci -q get dhcp.@dnsmasq[0].noresolv)"
uci -q set openclash.config.dnsmasq_resolvfile="$(uci -q get dhcp.@dnsmasq[0].resolvfile)"
fi
uci -q del dhcp.@dnsmasq[-1].server
uci -q add_list dhcp.@dnsmasq[0].server=127.0.0.1#"$dns_port"
uci -q set openclash.config.dnsmasq_noresolv="$(uci -q get dhcp.@dnsmasq[0].noresolv)"
uci -q set openclash.config.dnsmasq_resolvfile="$(uci -q get dhcp.@dnsmasq[0].resolvfile)"
uci -q delete dhcp.@dnsmasq[0].resolvfile
uci -q set dhcp.@dnsmasq[0].noresolv=1
uci -q set dhcp.@dnsmasq[0].localuse=1
@@ -212,33 +226,44 @@ change_dns() {
uci -q set openclash.config.filter_aaaa_dns=0
fi
uci -q commit dhcp
uci -q commit openclash
uci -q commit dhcp
/etc/init.d/dnsmasq restart
} >/dev/null 2>&1
revert_dns() {
revert_dnsmasq()
{
if ! /etc/init.d/dnsmasq enabled; then
return
fi
redirect_dns=$(uci_get_config "redirect_dns")
dnsmasq_server=$(uci_get_config "dnsmasq_server")
dnsmasq_noresolv=$(uci_get_config "dnsmasq_noresolv")
dnsmasq_resolvfile=$(uci_get_config "dnsmasq_resolvfile")
cachesize_dns=$(uci_get_config "cachesize_dns")
dnsmasq_cachesize=$(uci_get_config "dnsmasq_cachesize")
filter_aaaa_dns=$(uci_get_config "filter_aaaa_dns")
dnsmasq_filter_aaaa=$(uci_get_config "dnsmasq_filter_aaaa")
default_resolvfile=$(uci_get_config "default_resolvfile")
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_custom_domain.conf
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute_pass.conf
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute6_pass.conf
[ "$1" -eq 1 ] && {
[ "$redirect_dns" -eq 1 ] && {
uci -q del dhcp.@dnsmasq[-1].server
[ -n "${10}" ] && {
[ -n "$dnsmasq_server" ] && {
config_load "openclash"
config_list_foreach "config" "dnsmasq_server" set_dnsmasq_server
}
if [ "$4" == "0" ] || [ -z "$4" ] || [ -z "$(uci -q show dhcp.@dnsmasq[0].server)" ]; then
if [ "$dnsmasq_noresolv" == "0" ] || [ -z "$dnsmasq_noresolv" ] || [ -z "$(uci -q show dhcp.@dnsmasq[0].server)" ]; then
uci -q set dhcp.@dnsmasq[0].noresolv=0
if [ -n "$5" ] && [ -n "$(grep nameserver $5)" ]; then
uci -q set dhcp.@dnsmasq[0].resolvfile="$5"
elif [ -n "$3" ] && [ -n "$(grep nameserver $3)" ]; then
uci -q set dhcp.@dnsmasq[0].resolvfile="$3"
if [ -n "$dnsmasq_resolvfile" ] && [ -n "$(grep nameserver $dnsmasq_resolvfile)" ]; then
uci -q set dhcp.@dnsmasq[0].resolvfile="$dnsmasq_resolvfile"
elif [ -n "$default_resolvfile" ] && [ -n "$(grep nameserver $default_resolvfile)" ]; then
uci -q set dhcp.@dnsmasq[0].resolvfile="$default_resolvfile"
elif [ -s "/tmp/resolv.conf.d/resolv.conf.auto" ] && [ -n "$(grep "nameserver" /tmp/resolv.conf.d/resolv.conf.auto)" ]; then
uci -q set dhcp.@dnsmasq[0].resolvfile=/tmp/resolv.conf.d/resolv.conf.auto
uci -q set openclash.config.default_resolvfile=/tmp/resolv.conf.d/resolv.conf.auto
@@ -253,19 +278,19 @@ revert_dns() {
fi
}
[ "$6" -eq 1 ] && {
uci -q set dhcp.@dnsmasq[0].cachesize="$7"
[ "$cachesize_dns" -eq 1 ] && {
uci -q set dhcp.@dnsmasq[0].cachesize="$dnsmasq_cachesize"
uci -q set openclash.config.cachesize_dns=0
uci -q delete openclash.config.dnsmasq_cachesize
}
[ "$8" -eq 1 ] && {
uci -q set dhcp.@dnsmasq[0].filter_aaaa="$9"
[ "$filter_aaaa_dns" -eq 1 ] && {
uci -q set dhcp.@dnsmasq[0].filter_aaaa="$dnsmasq_filter_aaaa"
uci -q set openclash.config.filter_aaaa_dns=0
uci -q delete openclash.config.dnsmasq_filter_aaaa
}
[ "$1" -eq 1 ] && {
[ "$redirect_dns" -eq 1 ] && {
uci -q set openclash.config.redirect_dns=0
uci -q del openclash.config.dnsmasq_server
}
@@ -273,8 +298,6 @@ revert_dns() {
uci -q commit dhcp
uci -q commit openclash
/etc/init.d/dnsmasq restart
masq_port=$(uci -q get dhcp.@dnsmasq[0].port)
if [ "$(nslookup www.apple.com 127.0.0.1:${masq_port} >/dev/null 2>&1 || echo $?)" = "1" ]; then
resolv_file=$(uci -q get dhcp.@dnsmasq[0].resolvfile)
@@ -302,9 +325,10 @@ nameserver 119.29.29.29
nameserver 8.8.8.8
EOF
fi
/etc/init.d/dnsmasq restart
fi
/etc/init.d/dnsmasq restart
} >/dev/null 2>&1
start_fail()
@@ -389,7 +413,7 @@ if [ -z "$RAW_CONFIG_FILE" ] || [ ! -f "$RAW_CONFIG_FILE" ]; then
uci -q commit openclash
RAW_CONFIG_FILE="/etc/openclash/config/$CONFIG_NAME"
CONFIG_FILE="/etc/openclash/$CONFIG_NAME"
TMP_CONFIG_FILE="/tmp/yaml_config_tmp_$CONFIG_NAME"
TMP_CONFIG_FILE="/tmp/$CONFIG_NAME"
LOG_ERROR "Config Not Found, Switch Config File to【$RAW_CONFIG_FILE】"
break
fi
@@ -512,12 +536,14 @@ do_run_file()
chnr6_path="/etc/openclash/china_ip6_route.ipset"
geosite_path="/etc/openclash/GeoSite.dat"
geoip_path="/etc/openclash/GeoIP.dat"
asn_path="/etc/openclash/ASN.mmdb"
lgbm_path="/etc/openclash/Model.bin"
mv "/tmp/etc/openclash/Country.mmdb" "$ipdb_path"
mv "/tmp/etc/openclash/china_ip_route.ipset" "$chnr_path"
mv "/tmp/etc/openclash/china_ip6_route.ipset" "$chnr6_path"
mv "/tmp/etc/openclash/GeoSite.dat" "$geosite_path"
mv "/tmp/etc/openclash/GeoIP.dat" "$geoip_path"
mv "/tmp/etc/openclash/ASN.mmdb" "$asn_path"
mv "/tmp/etc/openclash/Model.bin" "$lgbm_path"
mv "/tmp/etc/openclash/core/" "/etc/openclash"
if [ "$CACHE_PATH" != "/tmp/etc/openclash/cache.db" ]; then
@@ -530,12 +556,14 @@ do_run_file()
chnr6_path="/tmp/etc/openclash/china_ip6_route.ipset"
geosite_path="/tmp/etc/openclash/GeoSite.dat"
geoip_path="/tmp/etc/openclash/GeoIP.dat"
asn_path="/tmp/etc/openclash/ASN.mmdb"
lgbm_path="/tmp/etc/openclash/Model.bin"
[ ! -h "/etc/openclash/Country.mmdb" ] && mv "/etc/openclash/Country.mmdb" "$ipdb_path"
[ ! -h "/etc/openclash/china_ip_route.ipset" ] && mv "/etc/openclash/china_ip_route.ipset" "$chnr_path"
[ ! -h "/etc/openclash/china_ip6_route.ipset" ] && mv "/etc/openclash/china_ip6_route.ipset" "$chnr6_path"
[ ! -h "/etc/openclash/GeoSite.dat" ] && mv "/etc/openclash/GeoSite.dat" "$geosite_path"
[ ! -h "/etc/openclash/GeoIP.dat" ] && mv "/etc/openclash/GeoIP.dat" "$geoip_path"
[ ! -h "/etc/openclash/ASN.mmdb" ] && mv "/etc/openclash/ASN.mmdb" "$asn_path"
[ ! -h "/etc/openclash/Model.bin" ] && mv "/etc/openclash/Model.bin" "$lgbm_path"
mv "/etc/openclash/core/" "/tmp/etc/openclash"
fi
@@ -545,7 +573,9 @@ do_run_file()
ln -s "$meta_core_path" /etc/openclash/clash
if [ "$smart_enable" -eq 1 ] || [ "$core_type" == "Smart" ]; then
if [ -n "$OIX_TOKEN" ]; then
core_type="Oix"
elif [ "$smart_enable" -eq 1 ] || [ "$core_type" == "Smart" ]; then
core_type="Smart"
else
core_type="Meta"
@@ -556,8 +586,8 @@ do_run_file()
chown root:root "$CLASH"
fi
[ ! -f "$CLASH" ] || { [ "$core_type" = "Smart" ] && [ -z "$($CLASH -v | grep 'smart')" ]; } && {
LOG_TIP "Detected that the Core is not Installed, Ready to Download..."
[ ! -f "$CLASH" ] || { [ "$core_type" = "Smart" ] && [ -z "$($CLASH -v | grep 'smart')" ]; } || { [ "$core_type" = "Oix" ] && [ -z "$($CLASH -v | grep 'oix')" ]; } && {
LOG_TIP "【$core_type】Core is not Detected installed, Ready to Download..."
rm -rf "/tmp/clash_last_version"
/usr/share/openclash/openclash_core.sh "$core_type"
if [ ! -f "$meta_core_path" ]; then
@@ -618,6 +648,10 @@ do_run_file()
ln -s "$chnr6_path" /etc/openclash/china_ip6_route.ipset
}
[ -f "$asn_path" ] && [ "$small_flash_memory" = "1" ] && {
ln -s "$asn_path" /etc/openclash/ASN.mmdb
}
#Restore history cache
if [ -f "$HISTORY_PATH" ]; then
cmp -s "$CACHE_PATH" "$HISTORY_PATH"
@@ -762,7 +796,7 @@ check_core_status()
# redirect dns setting after core started, prevent core dns lookup failure
if [ "$1" == "start" ]; then
change_dns "$enable_redirect_dns"
change_dnsmasq "$enable_redirect_dns"
set_firewall
LOG_TIP "OpenClash Start Successful!"
else
@@ -787,6 +821,8 @@ start_run_core()
chown root:root "$CLASH"
procd_open_instance "openclash"
procd_set_param env SAFE_PATHS=/usr/share/openclash:/etc/ssl
procd_append_param env CLASH_AGE_SECRET_KEY="$SECRET_KEY"
procd_append_param env OIX_TOKEN="$OIX_TOKEN"
procd_set_param command /bin/sh -c "$CLASH -d $CLASH_CONFIG -f \"$CONFIG_FILE\" >> $LOG_FILE 2>&1"
procd_set_param user "root"
procd_set_param group "nogroup"
@@ -1370,6 +1406,11 @@ if [ -n "$FW4" ]; then
nft add rule inet fw4 openclash ip protocol tcp counter redirect to "$proxy_port"
nft 'add rule inet fw4 dstnat meta nfproto {ipv4} ip protocol tcp counter jump openclash'
# Accept redirected traffic in input chain (needed when zone input policy is REJECT without DNAT rules)
if [ -z "$(nft list chain inet fw4 input 2>/dev/null | grep 'ct status dnat accept')" ]; then
nft insert rule inet fw4 input position 0 ct status dnat accept comment \"OpenClash Redirect Accept\"
fi
if [ -z "$en_mode_tun" ]; then
#udp
if [ "$enable_udp_proxy" -eq 1 ]; then
@@ -1464,6 +1505,11 @@ if [ -n "$FW4" ]; then
fi
fi
# Accept TPROXY traffic in input chain (needed when zone input policy is REJECT)
if [ -z "$(nft list chain inet fw4 input 2>/dev/null | grep 'OpenClash TPROXY Accept')" ]; then
nft insert rule inet fw4 input position 0 meta mark "$PROXY_FWMARK" accept comment \"OpenClash TPROXY Accept\"
fi
#router self proxy tcp
if [ "$router_self_proxy" = "1" ] || ([ "$enable_redirect_dns" != "2" ] && [ "$en_mode" = "fake-ip" ]); then
nft 'add chain inet fw4 openclash_output'
@@ -2219,6 +2265,11 @@ if [ -z "$FW4" ]; then
iptables -t nat -A openclash -p tcp -j REDIRECT --to-ports "$proxy_port"
iptables -t nat -A PREROUTING -p tcp -j openclash
# Accept redirected traffic in input chain (needed when zone input policy is REJECT without DNAT rules)
if [ -z "$(iptables-save -t filter 2>/dev/null | grep 'OpenClash Redirect Accept')" ]; then
iptables -I INPUT -m conntrack --ctstate DNAT -j ACCEPT -m comment --comment "OpenClash Redirect Accept"
fi
if [ -z "$en_mode_tun" ]; then
#udp
if [ "$enable_udp_proxy" -eq 1 ]; then
@@ -2302,6 +2353,14 @@ if [ -z "$FW4" ]; then
iptables -t mangle -A OUTPUT -p udp -j openclash_output
fi
fi
# Accept TPROXY traffic in input chain (needed when zone input policy is REJECT)
if [ -z "$(iptables -nL INPUT 2>/dev/null | grep 'OpenClash TPROXY Accept')" ]; then
iptables -I INPUT -m mark --mark "$PROXY_FWMARK" -j ACCEPT -m comment --comment "OpenClash TPROXY Accept"
fi
if [ -z "$en_mode_tun" ]; then
#quic
if [ "$disable_udp_quic" -eq 1 ]; then
if [ "$china_ip_route" = "2" ]; then
@@ -2584,6 +2643,11 @@ if [ -z "$FW4" ]; then
ip6tables -t nat -A PREROUTING -p tcp -j openclash
fi
# Accept redirected IPv6 traffic in input chain (needed when zone input policy is REJECT without DNAT rules)
if [ -z "$(ip6tables-save -t filter 2>/dev/null | grep 'OpenClash Redirect Accept')" ]; then
ip6tables -I INPUT -m conntrack --ctstate DNAT -j ACCEPT -m comment --comment "OpenClash Redirect Accept"
fi
#TProxy & TUN & Redirect udp
if [ "$enable_v6_udp_proxy" -eq 1 ] || [ "$ipv6_mode" -ne 1 ]; then
ip6tables -t mangle -N openclash
@@ -2655,6 +2719,11 @@ if [ -z "$FW4" ]; then
ip6tables -t mangle -A PREROUTING -j openclash
# Accept TPROXY IPv6 traffic in input chain (needed when zone input policy is REJECT)
if [ -z "$(ip6tables-save -t filter 2>/dev/null | grep 'OpenClash TPROXY Accept')" ]; then
ip6tables -I INPUT -m mark --mark "$PROXY_FWMARK" -j ACCEPT -m comment --comment "OpenClash TPROXY Accept"
fi
#router self proxy
if [ "$router_self_proxy" = "1" ]; then
if [ "$ipv6_mode" -eq 1 ] || [ "$ipv6_mode" -eq 3 ]; then
@@ -2987,6 +3056,8 @@ overwrite_file()
EOF
allowed_keys_types="\
AGE_SECRET_KEY:string \
AGE_PUBLIC_KEY:string \
APPEND_DEFAULT_DNS:int_bool \
APPEND_WAN_DNS:int_bool \
AUTO_SMART_SWITCH:int_bool \
@@ -3118,6 +3189,11 @@ EOF
echo "# --- overwrite source: ${name} (sid=${sid}) ---" >> "$overwrite_script"
echo "export OPENCLASH_OVERWRITE_SID='${sid}'" >> "$overwrite_script"
cfg_name=$(basename "$(uci_get_config "config_path")" 2>/dev/null)
age_config_name="${cfg_name%.*}"
age_secret_key=""
age_public_key=""
param=$(uci -q get openclash."$sid".param 2>/dev/null || echo '')
if [ -n "$param" ]; then
OLD_IFS="$IFS"
@@ -3251,8 +3327,9 @@ EOF
uci -q set openclash.@overwrite[0].config_path="$RAW_CONFIG_FILE"
cfg_name=$(basename "$RAW_CONFIG_FILE" 2>/dev/null)
if [ -n "$cfg_name" ]; then
age_config_name="${cfg_name%.*}"
CONFIG_FILE="/etc/openclash/${cfg_name}"
TMP_CONFIG_FILE="/tmp/yaml_config_tmp_${cfg_name}"
TMP_CONFIG_FILE="/tmp/${cfg_name}"
fi
elif printf "%s" "$key_u" | grep -q "^SUB_INFO_URL$"; then
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
@@ -3265,6 +3342,16 @@ EOF
uci -q set openclash.@subscribe_info[-1].name="${cfg_name%.*}"
uci -q add_list openclash.@subscribe_info[-1].url="$url_key"
fi
elif printf "%s" "$key_u" | grep -q "^AGE_SECRET_KEY$"; then
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
val_key=$(eval "echo \"$val_clean\"")
[ -z "$val_key" ] && continue
age_secret_key="$val_key"
elif printf "%s" "$key_u" | grep -q "^AGE_PUBLIC_KEY$"; then
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
val_key=$(eval "echo \"$val_clean\"")
[ -z "$val_key" ] && continue
age_public_key="$val_key"
elif printf "%s" "$key_u" | grep -q "^RESTART$"; then
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
val_key=$(eval "echo \"$val_clean\"")
@@ -3295,6 +3382,10 @@ EOF
fi
done < "$file"
if [ -n "$age_config_name" ] && { [ -n "$age_secret_key" ] || [ -n "$age_public_key" ]; }; then
uci_set_age_keys_by_name "$age_config_name" "$age_secret_key" "$age_public_key"
fi
if [ -n "$yaml_content" ]; then
LOG_TIP "Load YAML Override Block【YAML Block => $name】"
@@ -3377,7 +3468,10 @@ get_config()
RAW_CONFIG_FILE=$(uci_get_config "config_path")
CFG_NAME=$(basename "$RAW_CONFIG_FILE" 2>/dev/null)
CONFIG_FILE="/etc/openclash/${CFG_NAME}"
TMP_CONFIG_FILE="/tmp/yaml_config_tmp_${CFG_NAME}"
TMP_CONFIG_FILE="/tmp/${CFG_NAME}"
CFG_NO_EXT_NAME="${CFG_NAME%.*}"
SECRET_KEY=$(uci_get_age_secret_keys "$CFG_NO_EXT_NAME")
OIX_TOKEN=$(uci_get_config "oix_token")
enable=$(uci_get_config "enable")
enable_custom_clash_rules=$(uci_get_config "enable_custom_clash_rules")
da_password=$(uci_get_config "dashboard_password")
@@ -3662,20 +3756,6 @@ stop_service()
echo "OpenClash Already Stop!"
}
revert_dnsmasq()
{
redirect_dns=$(uci_get_config "redirect_dns")
dnsmasq_server=$(uci_get_config "dnsmasq_server")
dnsmasq_noresolv=$(uci_get_config "dnsmasq_noresolv")
dnsmasq_resolvfile=$(uci_get_config "dnsmasq_resolvfile")
cachesize_dns=$(uci_get_config "cachesize_dns")
dnsmasq_cachesize=$(uci_get_config "dnsmasq_cachesize")
filter_aaaa_dns=$(uci_get_config "filter_aaaa_dns")
dnsmasq_filter_aaaa=$(uci_get_config "dnsmasq_filter_aaaa")
default_resolvfile=$(uci_get_config "default_resolvfile")
revert_dns "$redirect_dns" "$enable" "$default_resolvfile" "$dnsmasq_noresolv" "$dnsmasq_resolvfile" "$cachesize_dns" "$dnsmasq_cachesize" "$filter_aaaa_dns" "$dnsmasq_filter_aaaa" "$dnsmasq_server"
} >/dev/null 2>&1
restart()
{
echo "OpenClash Restart..."
File diff suppressed because it is too large. Load diff
@@ -123,6 +123,8 @@
# SKIP_PROXY_ADDRESS (跳过代理地址, 0: 禁用, 1: 启用)
# SUB_INFO_URL (订阅信息 URL)
# DOWNLOAD_FILE (下载外部文件到指定路径, need URL and path)
# AGE_PUBLIC_KEY (指定配置文件的 AGE 公钥)
# AGE_SECRET_KEY (指定配置文件的 AGE 私钥)
# RESTART (模块更新后重启插件, true: 启用, false: 禁用)
# =========================
@@ -25,6 +25,7 @@ uci -q commit firewall
mkdir -p /etc/openclash/config
mkdir -p /etc/openclash/proxy_provider
mkdir -p /etc/openclash/rule_provider
mkdir -p /etc/openclash/core
mkdir -p /etc/openclash/history
mkdir -p /usr/share/openclash/backup/overwrite
@@ -151,6 +152,7 @@ cp -f "/etc/openclash/custom/openclash_custom_overwrite.sh" "/usr/share/openclas
cp -f "/etc/openclash/china_ip_route.ipset" "/usr/share/openclash/backup/china_ip_route.ipset" >/dev/null 2>&1
cp -f "/etc/openclash/china_ip6_route.ipset" "/usr/share/openclash/backup/china_ip6_route.ipset" >/dev/null 2>&1
cp -f "/etc/openclash/overwrite/default" "/usr/share/openclash/backup/overwrite/default" >/dev/null 2>&1
cp -f "/etc/openclash/rule_provider/oc-cn-domain.mrs" "/usr/share/openclash/backup/oc-cn-domain.mrs" >/dev/null 2>&1
#Restore
if [ -f "/tmp/openclash.bak" ]; then
@@ -186,6 +188,10 @@ if [ -f "/tmp/openclash.bak" ]; then
cp -rf "/tmp/pac/." "/www/luci-static/resources/openclash/pac/" >/dev/null 2>&1
rm -rf "/tmp/pac/" >/dev/null 2>&1
fi
#oc-domain
if [ -f "/etc/openclash/rule_provider/oc-cn-domain.mrs" ]; then
cp -f "/usr/share/openclash/backup/oc-cn-domain.mrs" "/etc/openclash/rule_provider/oc-cn-domain.mrs" >/dev/null 2>&1
fi
rm -rf "/etc/openclash/openclash" >/dev/null 2>&1
rm -rf "/tmp/openclash" >/dev/null 2>&1
rm -rf "/tmp/openclash.bak" >/dev/null 2>&1
@@ -22,9 +22,47 @@ module YAML
end
def self.load_file(filename, *args, **kwargs)
yaml_content = File.read(filename)
processed_content = fix_short_id_quotes(yaml_content)
yaml_content = File.read(filename, mode: "r:bom|utf-8")
secret = nil
if kwargs.key?(:secret)
secret = kwargs.delete(:secret)
end
if secret && secret.to_s.strip != "" && yaml_content.include?("BEGIN AGE ENCRYPTED FILE")
begin
decrypted = decrypt_content_with_secret(secret.to_s, yaml_content)
if decrypted && !decrypted.empty? && !decrypted.include?("BEGIN AGE ENCRYPTED FILE")
processed = fix_short_id_quotes(decrypted)
return load(processed, *args, **kwargs)
else
LOG_WARN("【%s】Decrypted content empty or still encrypted" % [filename])
end
rescue => e
LOG_WARN("Decrypt attempt failed:【%s】" % [e.message])
end
end
if (secret.nil? || secret.to_s.strip == "") && yaml_content.include?("BEGIN AGE ENCRYPTED FILE")
keys = find_age_keys_for_filename(filename)
(keys[:secrets] || []).each do |sec|
begin
decrypted = decrypt_content_with_secret(sec, yaml_content)
if decrypted && !decrypted.empty? && !decrypted.include?("BEGIN AGE ENCRYPTED FILE")
processed = fix_short_id_quotes(decrypted)
return load(processed, *args, **kwargs)
end
rescue => e
LOG_WARN("Decrypt attempt failed for a found secret:【%s】" % [e.message])
end
end
end
if yaml_content.include?("BEGIN AGE ENCRYPTED FILE")
raise "Encrypted file: decryption failed for %s" % [filename]
end
processed_content = fix_short_id_quotes(yaml_content)
load(processed_content, *args, **kwargs)
end
@@ -32,6 +70,45 @@ module YAML
begin
yaml_content = original_dump(obj, **options)
processed = fix_short_id_quotes(yaml_content)
public_key = nil
fname = nil
if options.key?(:public)
public_key = options.delete(:public)
end
if (!public_key || public_key.to_s.strip == "")
if options.key?(:filename)
fname = options.delete(:filename)
elsif io && io.respond_to?(:path)
fname = io.path
elsif io && io.respond_to?(:to_path)
fname = io.to_path
end
if fname && fname.to_s.strip != ""
keys = find_age_keys_for_filename(fname)
public_key = keys[:publics].first if keys[:publics] && !keys[:publics].empty?
end
end
if public_key && public_key.to_s.strip != ""
begin
encrypted = encrypt_content_with_public(public_key.to_s, processed)
if encrypted && !encrypted.empty?
if io.nil?
return encrypted
elsif io.respond_to?(:write)
io.write(encrypted)
return io
else
return encrypted
end
end
rescue => e
LOG_WARN("Encrypt attempt failed:【%s】" % [e.message])
end
end
if io.nil?
processed
elsif io.respond_to?(:write)
@@ -46,6 +123,77 @@ module YAML
end
end
def self.popen_stream(cmd, input, chunk_size: 64 * 1024)
output = String.new
IO.popen(cmd, 'r+', err: [:child, :out]) do |io|
io.binmode
writer = Thread.new do
begin
input.bytesize.times do |i|
chunk = input.byteslice(i * chunk_size, chunk_size)
break if chunk.nil?
io.write(chunk)
end
io.close_write
rescue Errno::EPIPE
end
end
while chunk = io.read(chunk_size)
output << chunk
end
writer.join
end
[output, $?]
end
def self.decode64(input)
out, status = popen_stream(["base64", "-d"], input)
status.success? ? out : input
rescue Errno::ENOENT
input
end
def self.find_age_keys_for_filename(filename)
begin
basename = File.basename(filename)
basename_no_ext = File.basename(filename, File.extname(filename))
publics = []
secrets = []
[basename, basename_no_ext].uniq.each do |n|
cmd_public = ["/bin/sh", "-c", ". /usr/share/openclash/uci.sh; uci_get_age_public_keys \"$1\"", "sh", n]
IO.popen(cmd_public, "r") do |io|
io.each_line { |l| publics << l.strip unless l.nil? || l.strip == "" }
end
cmd_secret = ["/bin/sh", "-c", ". /usr/share/openclash/uci.sh; uci_get_age_secret_keys \"$1\"", "sh", n]
IO.popen(cmd_secret, "r") do |io|
io.each_line { |l| secrets << l.strip unless l.nil? || l.strip == "" }
end
end
{ publics: publics, secrets: secrets }
rescue => e
{ publics: [], secrets: [] }
end
end
def self.decrypt_content_with_secret(secret, content)
cmd = ['/etc/openclash/core/clash_meta', 'age', 'decrypt', secret, '-', '-']
out, status = popen_stream(cmd, content)
status.success? ? out : nil
rescue => e
nil
end
def self.encrypt_content_with_public(public, content)
cmd = ['/etc/openclash/core/clash_meta', 'age', 'encrypt', public, '-', '-']
out, status = popen_stream(cmd, content)
status.success? ? out : nil
rescue => e
nil
end
private
# fix_short_id_quotes:
@@ -65,6 +213,8 @@ module YAML
# Input: short-id: null -> Output: short-id: ""
def self.fix_short_id_quotes(yaml_content)
yaml_content = decode64(yaml_content)
return yaml_content unless yaml_content.include?('short-id:')
begin
@@ -17,18 +17,30 @@ set_lock
DOWNLOAD_FILE="/tmp/clash_last_version"
RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
CORE_TYPE=$(uci_get_config "core_type")
OIX_TOKEN=$(uci_get_config "oix_token")
if [ -n "$1" ]; then
github_address_mod="$1"
fi
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${RELEASE_BRANCH}/core_version"
if [ "$CORE_TYPE" = "Oix" ] || [ -n "$OIX_TOKEN" ]; then
OIX_VERSION_URL="https://github.com/vernesong/mihomo-oix/releases/download/Pre-Alpha/version.txt"
if [ "$github_address_mod" != "0" ] && [ "$github_address_mod" != "https://cdn.jsdelivr.net/" ] && [ "$github_address_mod" != "https://fastly.jsdelivr.net/" ] && [ "$github_address_mod" != "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}${OIX_VERSION_URL}"
else
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
DOWNLOAD_URL="$OIX_VERSION_URL"
fi
else
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${RELEASE_BRANCH}/core_version"
else
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
fi
else
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
fi
fi
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "$DOWNLOAD_FILE" "$DOWNLOAD_FILE"
@@ -49,7 +49,7 @@ config_test()
{
if [ -f "$CLASH" ]; then
LOG_OUT "Config File Download Successful, Test If There is Any Errors..."
test_info=$($CLASH -t -d $CLASH_CONFIG -f "$CFG_FILE")
test_info=$($CLASH -t -d $CLASH_CONFIG -f "$CFG_FILE" -age-secret-key "$SECRET_KEY")
local IFS=$'\n'
for i in $test_info; do
if [ -n "$(echo "$i" |grep "configuration file")" ]; then
@@ -72,15 +72,14 @@ config_download()
LOG_TIP "Config File【$name】Downloading User-Agent【$sub_ua】..."
if [ -n "$subscribe_url_param" ] && [ -n "$c_address" ]; then
LOG_INFO "Config File【$name】Downloading URL【$c_address$subscribe_url_param】..."
local DOWNLOAD_URL="${c_address}${subscribe_url_param}"
local DOWNLOAD_PARAM="$sub_ua"
DOWNLOAD_URL="${c_address}${subscribe_url_param}"
fi
if [ -z "$DOWNLOAD_URL" ]; then
LOG_INFO "Config File【$name】Downloading URL【$subscribe_url】..."
local DOWNLOAD_URL="${subscribe_url}"
local DOWNLOAD_PARAM="$sub_ua"
DOWNLOAD_URL="${subscribe_url}"
fi
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "$CFG_FILE" "$CONFIG_FILE" "$DOWNLOAD_PARAM"
DOWNLOAD_PARAM="$sub_ua"
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "$CFG_FILE" "$CONFIG_FILE" "$DOWNLOAD_PARAM" "$SECRET_KEY"
DOWNLOAD_RESULT=$?
}
@@ -317,7 +316,7 @@ convert_custom_param()
sub_info_get()
{
local section="$1" subscribe_url template_path subscribe_url_param template_path_encode key_match_param key_ex_match_param c_address de_ex_keyword sub_ua append_custom_params
local section="$1" subscribe_url template_path subscribe_url_param template_path_encode key_match_param key_ex_match_param c_address de_ex_keyword sub_ua append_custom_params SECRET_KEY DOWNLOAD_URL DOWNLOAD_PARAM
config_get_bool "enabled" "$section" "enabled" "1"
config_get "name" "$section" "name" "config"
config_get "sub_convert" "$section" "sub_convert" ""
@@ -335,6 +334,7 @@ sub_info_get()
config_get "custom_template_url" "$section" "custom_template_url" ""
config_get "de_ex_keyword" "$section" "de_ex_keyword" ""
config_get "sub_ua" "$section" "sub_ua" "clash-verge/v2.4.5"
SECRET_KEY=$(uci_get_age_secret_keys "$name")
CONFIG_FILE="/etc/openclash/config/$name.yaml"
CFG_FILE="/tmp/$name.yaml"
@@ -44,8 +44,8 @@ if [ -z "$CHNR_CUSTOM_URL" ]; then
else
DOWNLOAD_FILE_CURL "$CHNR_CUSTOM_URL" "/tmp/china_ip_route.txt" "$chnr_path"
fi
if [ "$?" -eq 0 ]; then
DOWNLOAD_RESULT=$?
if [ "$DOWNLOAD_RESULT" -eq 0 ]; then
LOG_OUT "Chnroute Cidr List Download Success, Check Updated..."
#预处理
if [ -n "$FW4" ]; then
@@ -69,7 +69,7 @@ if [ "$?" -eq 0 ]; then
else
LOG_OUT "Updated Chnroute Cidr List No Change, Do Nothing..."
fi
elif [ "$?" -eq 2 ]; then
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
LOG_OUT "Updated Chnroute Cidr List No Change, Do Nothing..."
else
LOG_OUT "Chnroute Cidr List Update Error, Please Try Again Later..."
@@ -35,7 +35,9 @@ fi
CORE_TYPE="$1"
C_CORE_TYPE=$(uci_get_config "core_type")
SMART_ENABLE=$(uci_get_config "smart_enable" || echo 0)
OIX_TOKEN=$(uci_get_config "oix_token")
[ "$SMART_ENABLE" -eq 1 ] && CORE_TYPE="Smart"
[ "$CORE_TYPE" = "Oix" ] || [ -n "$OIX_TOKEN" ] && CORE_TYPE="Oix"
[ -z "$CORE_TYPE" ] && CORE_TYPE="Meta"
small_flash_memory=$(uci_get_config "small_flash_memory")
CPU_MODEL=$(uci_get_config "core_version")
@@ -62,31 +64,45 @@ else
fi
CORE_CV=$($meta_core_path -v 2>/dev/null |awk -F ' ' '{print $3}' |head -1)
DOWNLOAD_FILE="/tmp/clash_meta.tar.gz"
TMP_FILE="/tmp/clash_meta"
TARGET_CORE_PATH="$meta_core_path"
if [ "$CORE_TYPE" = "Smart" ]; then
if [ "$CORE_TYPE" = "Oix" ]; then
CORE_URL_PATH=""
DOWNLOAD_FILE="/tmp/clash_meta.gz"
CORE_LV=$(sed -n 1p /tmp/clash_last_version 2>/dev/null)
elif [ "$CORE_TYPE" = "Smart" ]; then
CORE_URL_PATH="$RELEASE_BRANCH/smart"
DOWNLOAD_FILE="/tmp/clash_meta.tar.gz"
CORE_LV=$(sed -n 2p /tmp/clash_last_version 2>/dev/null)
else
CORE_URL_PATH="$RELEASE_BRANCH/meta"
DOWNLOAD_FILE="/tmp/clash_meta.tar.gz"
CORE_LV=$(sed -n 1p /tmp/clash_last_version 2>/dev/null)
fi
[ "$C_CORE_TYPE" = "$CORE_TYPE" ] || [ -z "$C_CORE_TYPE" ] && restart=1
[ "$C_CORE_TYPE" != "$CORE_TYPE" ] || [ -z "$C_CORE_TYPE" ] && restart=1
if [ "$CORE_CV" != "$CORE_LV" ] || [ -z "$CORE_CV" ]; then
if [ "$CPU_MODEL" != 0 ]; then
LOG_TIP "【$CORE_TYPE】Core Downloading, Please Try to Download and Upload Manually If Fails"
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
if [ "$CORE_TYPE" = "Oix" ]; then
OIX_CORE_URL="https://github.com/vernesong/mihomo-oix/releases/download/Pre-Alpha/mihomo-${CPU_MODEL}-${CORE_LV}.gz"
if [ "$github_address_mod" != "0" ] && [ "$github_address_mod" != "https://cdn.jsdelivr.net/" ] && [ "$github_address_mod" != "https://fastly.jsdelivr.net/" ] && [ "$github_address_mod" != "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}${OIX_CORE_URL}"
else
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
DOWNLOAD_URL="$OIX_CORE_URL"
fi
else
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
else
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
fi
else
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
fi
fi
retry_count=0
@@ -107,8 +123,12 @@ if [ "$CORE_CV" != "$CORE_LV" ] || [ -z "$CORE_CV" ]; then
LOG_TIP "【"$CORE_TYPE"】Core Download Successful, Start Update..."
extract_success=true
[ -s "$DOWNLOAD_FILE" ] && {
tar zxvfo "$DOWNLOAD_FILE" -C /tmp >/dev/null 2>&1 || extract_success=false
mv /tmp/clash "$TMP_FILE" >/dev/null 2>&1 || extract_success=false
if [ "$CORE_TYPE" = "Oix" ]; then
gzip -dc "$DOWNLOAD_FILE" > "$TMP_FILE" 2>/dev/null || extract_success=false
else
tar zxvfo "$DOWNLOAD_FILE" -C /tmp >/dev/null 2>&1 || extract_success=false
mv /tmp/clash "$TMP_FILE" >/dev/null 2>&1 || extract_success=false
fi
rm -rf "$DOWNLOAD_FILE" >/dev/null 2>&1
chmod 4755 "$TMP_FILE" >/dev/null 2>&1 || extract_success=false
"$TMP_FILE" -v >/dev/null 2>&1 || extract_success=false
@@ -8,28 +8,54 @@ DOWNLOAD_FILE_CURL() {
DOWNLOAD_PATH=$2
FILE_PATH=$3
DOWNLOAD_UA=$4
SECRET_KEY=$5
[ -z "$DOWNLOAD_UA" ] && DOWNLOAD_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
CURL_OUTPUT=$(curl -sLI --connect-timeout 5 -m 10 --speed-time 5 --speed-limit 1 --retry 2 \
-H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" 2>&1)
NEW_ETAG=$(echo "$CURL_OUTPUT" | grep -i "^etag:" | cut -d' ' -f2- | tr -d '\r\n' | sed 's/^"//;s/"$//')
HTTP_CODE=$(echo "$CURL_OUTPUT" | grep -i "^HTTP" | tail -1 | cut -d' ' -f2)
HEADER_TMP="/tmp/openclash_curl_header_$$"
DOWNLOAD_TMP="${DOWNLOAD_PATH}.download.$$"
CACHED_ETAG=$(GET_ETAG_BY_PATH "$FILE_PATH")
ETAG_HEADER=""
if [ "$HTTP_CODE" = "200" ] && [ -n "$NEW_ETAG" ] && [ "$NEW_ETAG" = "$CACHED_ETAG" ] && [ -e "$FILE_PATH" ]; then
return 2
if [ -n "$CACHED_ETAG" ] && [ -e "$FILE_PATH" ]; then
FILE_MTIME=$(date -r "$FILE_PATH" '+%Y-%m-%d %H:%M:%S' 2>/dev/null)
LAST_UPDATE=$(GET_ETAG_TIMESTAMP_BY_PATH "$FILE_PATH")
if [ -n "$LAST_UPDATE" ] && [ -n "$FILE_MTIME" ] && [ "$LAST_UPDATE" = "$FILE_MTIME" ]; then
ETAG_HEADER="If-None-Match: \"${CACHED_ETAG}\""
fi
fi
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
if [ "$SHOW_DOWNLOAD_PROGRESS" = "1" ] || [ "$SHOW_DOWNLOAD_PROGRESS" = "true" ]; then
TEMP_LOG="/tmp/curl_log_$$"
LOG_OUT "Downloading:【$(basename "$DOWNLOAD_PATH") - 0%】"
(
curl -# -L --connect-timeout 10 -m 60 --speed-time 20 --speed-limit 1 --retry 2 \
-H "User-Agent: ${DOWNLOAD_UA}" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_PATH" 2>"$TEMP_LOG"
if [ -n "$SECRET_KEY" ] && [ -n "$ETAG_HEADER" ]; then
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" \
-H "X-Age-Public-Key: ${SECRET_KEY}" \
-H "$ETAG_HEADER" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
elif [ -n "$SECRET_KEY" ]; then
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" \
-H "X-Age-Public-Key: ${SECRET_KEY}" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
elif [ -n "$ETAG_HEADER" ]; then
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" \
-H "$ETAG_HEADER" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
else
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
fi
echo $? > "${TEMP_LOG}.exit"
) &
@@ -55,6 +81,7 @@ DOWNLOAD_FILE_CURL() {
wait $CURL_PID
EXIR_CODE=$(cat "${TEMP_LOG}.exit" 2>/dev/null || echo "1")
HTTP_CODE=$(grep -i "^HTTP" "$HEADER_TMP" 2>/dev/null | tail -1 | cut -d' ' -f2)
if [ "$EXIR_CODE" -eq 0 ] && [ "$LAST_PROGRESS" -ne 100 ]; then
LOG_OUT "Downloading:【$(basename "$DOWNLOAD_PATH") - 100%】"
@@ -66,29 +93,82 @@ DOWNLOAD_FILE_CURL() {
rm -f "$TEMP_LOG" "${TEMP_LOG}.exit"
if [ "$EXIR_CODE" -ne 0 ]; then
if [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "304" ] && [ -e "$FILE_PATH" ]; then
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
return 2
fi
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" != "200" ]; then
LOG_OUT "【$DOWNLOAD_PATH】Download Failed:【$OUTPUT】"
rm -rf $DOWNLOAD_PATH
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
SLOG_CLEAN
return 1
fi
else
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 60 --speed-time 30 --speed-limit 1 --retry 2 -H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" -o "$DOWNLOAD_PATH" 2>&1)
EXIR_CODE=$?
HTTP_CODE=$(echo "$CURL_OUTPUT" | tail -n1)
DOWNLOAD_TRY=0
MAX_DOWNLOAD_RETRIES=3
while [ "$DOWNLOAD_TRY" -lt "$MAX_DOWNLOAD_RETRIES" ]; do
DOWNLOAD_TRY=$((DOWNLOAD_TRY + 1))
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
if [ -n "$SECRET_KEY" ] && [ -n "$ETAG_HEADER" ]; then
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" \
-H "X-Age-Public-Key: ${SECRET_KEY}" \
-H "$ETAG_HEADER" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
elif [ -n "$SECRET_KEY" ]; then
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" \
-H "X-Age-Public-Key: ${SECRET_KEY}" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
elif [ -n "$ETAG_HEADER" ]; then
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" \
-H "$ETAG_HEADER" \
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
else
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
-D "$HEADER_TMP" \
-H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
fi
EXIR_CODE=$?
HTTP_CODE=$(echo "$CURL_OUTPUT" | tail -n1)
if { [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "200" ]; } || { [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "304" ] && [ -e "$FILE_PATH" ]; }; then
break
fi
[ "$DOWNLOAD_TRY" -lt "$MAX_DOWNLOAD_RETRIES" ] && sleep 1
done
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" -ne 200 ]; then
if [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "304" ] && [ -e "$FILE_PATH" ]; then
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
return 2
fi
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" != "200" ]; then
OUTPUT=$(echo "$CURL_OUTPUT" | sed '$d' | grep -a 'curl:' | tail -n 1)
LOG_OUT "【$DOWNLOAD_PATH】Download Failed:【$OUTPUT】"
rm -rf $DOWNLOAD_PATH
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
SLOG_CLEAN
return 1
fi
fi
if ! mv -f "$DOWNLOAD_TMP" "$DOWNLOAD_PATH"; then
LOG_OUT "【$DOWNLOAD_PATH】Download Failed:【Unable to save download file】"
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
SLOG_CLEAN
return 1
fi
NEW_ETAG=$(grep -i "^etag:" "$HEADER_TMP" 2>/dev/null | tail -1 | cut -d' ' -f2- | tr -d '\r\n' | sed 's/^"//;s/"$//')
if [ -n "$NEW_ETAG" ] && [ "$HTTP_CODE" = "200" ]; then
SAVE_ETAG_TO_CACHE "$DOWNLOAD_URL" "$NEW_ETAG" "$FILE_PATH"
fi
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
return 0
}
}
@@ -16,9 +16,9 @@ del_lock() {
ipk_v()
{
if [ -x "/bin/opkg" ]; then
echo $(opkg status "$1" 2>/dev/null |grep 'Version' |awk -F ': ' '{print $2}' 2>/dev/null)
echo $(rm -f /var/lock/opkg.lock && opkg status "$1" 2>/dev/null |grep 'Version' |awk -F ': ' '{print $2}' 2>/dev/null)
elif [ -x "/usr/bin/apk" ]; then
echo $(apk list "$1" 2>/dev/null |grep 'installed' | grep -oE '\d+(\.\d+)*' | head -1)
echo $(rm -f /lib/apk/db/lock && apk list "$1" 2>/dev/null |grep 'installed' | grep -oE '\d+(\.\d+)*' | head -1)
fi
}
@@ -42,9 +42,9 @@ RAW_CONFIG_FILE=$(uci_get_config "config_path")
CONFIG_FILE="/etc/openclash/$(uci_get_config "config_path" |awk -F '/' '{print $5}' 2>/dev/null)"
core_model=$(uci_get_config "core_version")
if [ -x "/bin/opkg" ]; then
cpu_model=$(opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' 2>/dev/null)
cpu_model=$(rm -f /var/lock/opkg.lock && opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' 2>/dev/null)
elif [ -x "/usr/bin/apk" ]; then
cpu_model=$(apk list libc 2>/dev/null|awk '{print $2}')
cpu_model=$(rm -f /lib/apk/db/lock && apk list libc 2>/dev/null|awk '{print $2}')
fi
core_meta_version=$(/etc/openclash/core/clash_meta -v 2>/dev/null |awk -F ' ' '{print $3}' |head -1 2>/dev/null)
op_version=$(ipk_v "luci-app-openclash")
@@ -54,6 +54,19 @@ router_self_proxy=$(uci_get_config "router_self_proxy")
core_type=$(uci_get_config "core_type" || echo "Dev")
da_password=$(uci_get_config "dashboard_password")
cn_port=$(uci_get_config "cn_port")
stack_type=$(uci_get_config "stack_type")
delay_start=$(uci_get_config "delay_start")
log_size=$(uci_get_config "log_size")
bypass_gateway_compatible=$(uci_get_config "bypass_gateway_compatible")
disable_quic_go_gso=$(uci_get_config "disable_quic_go_gso")
small_flash_memory=$(uci_get_config "small_flash_memory")
enable_meta_sniffer=$(uci_get_config "enable_meta_sniffer")
enable_respect_rules=$(uci_get_config "enable_respect_rules")
skip_proxy_address=$(uci_get_config "skip_proxy_address")
disable_udp_quic=$(uci_get_config "disable_udp_quic")
lan_ac_mode=$(uci_get_config "lan_ac_mode")
ipv6_mode=$(uci_get_config "ipv6_mode")
china_ip6_route=$(uci_get_config "china_ip6_route")
lan_interface_name=$(uci_get_config "lan_interface_name" || echo "0")
if [ "$lan_interface_name" = "0" ]; then
lan_ip=$(uci -q get network.lan.ipaddr |awk -F '/' '{print $1}' 2>/dev/null || ip address show $(uci -q -p /tmp/state get network.lan.device || uci -q -p /tmp/state get network.lan.device) | grep -w "inet" 2>/dev/null |grep -Eo 'inet [0-9\.]+' | awk '{print $2}' |head -1 || ip addr show 2>/dev/null | grep -w 'inet' | grep 'global' | grep 'brd' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -n 1)
@@ -122,6 +135,12 @@ cat >> "$DEBUG_LOG" <<-EOF
LuCI版本: $(ipk_v "luci")
内核版本: $(uname -r 2>/dev/null)
处理器架构: $cpu_model
系统运行时间: $(uptime 2>/dev/null)
#磁盘与内存
$(df -h / /tmp /etc/openclash 2>/dev/null)
$(free -m 2>/dev/null)
$(cat /proc/meminfo 2>/dev/null | grep -E '^(MemTotal|MemAvailable|SwapTotal|SwapFree)')
#此项有值时,如不使用IPv6,建议到网络-接口-lan的设置中禁用IPV6的DHCP
IPV6-DHCP: $(uci -q get dhcp.lan.dhcpv6)
@@ -129,6 +148,8 @@ IPV6-DHCP: $(uci -q get dhcp.lan.dhcpv6)
DNS劫持: $(dns_re "$enable_redirect_dns")
#DNS劫持为Dnsmasq时,此项结果应仅有配置文件的DNS监听地址
Dnsmasq转发设置: $(uci -q get dhcp.@dnsmasq[0].server)
Dnsmasq完整配置:
$(uci show dhcp.@dnsmasq[0] 2>/dev/null)
EOF
cat >> "$DEBUG_LOG" <<-EOF
@@ -147,6 +168,8 @@ ruby: $(ts_re "$(ipk_v "ruby")")
ruby-yaml: $(ts_re "$(ipk_v "ruby-yaml")")
ruby-psych: $(ts_re "$(ipk_v "ruby-psych")")
ruby-pstore: $(ts_re "$(ipk_v "ruby-pstore")")
ruby版本: $(ruby --version 2>/dev/null || echo "未安装")
ruby功能测试: $(ruby -e "require 'yaml'; YAML.load('test: ok'); puts '正常'" 2>/dev/null || echo "异常")
kmod-tun(TUN模式): $(ts_re "$(ipk_v "kmod-tun")")
luci-compat(Luci >= 19.07): $(ts_re "$(ipk_v "luci-compat")")
kmod-inet-diag(PROCESS-NAME): $(ts_re "$(ipk_v "kmod-inet-diag")")
@@ -166,6 +189,12 @@ kmod-ipt-nat: $(ts_re "$(ipk_v "kmod-ipt-nat")")
EOF
fi
cat >> "$DEBUG_LOG" <<-EOF
#内核模块加载状态:
$(lsmod | grep -E 'tun|tproxy|inet_diag' 2>/dev/null || echo "无相关模块")
EOF
#core
cat >> "$DEBUG_LOG" <<-EOF
@@ -219,6 +248,22 @@ fi
cat >> "$DEBUG_LOG" <<-EOF
#===================== GEO 数据文件 =====================#
$(ls -lh /etc/openclash/Country.mmdb /etc/openclash/GeoIP.dat /etc/openclash/GeoSite.dat /etc/openclash/ASN.mmdb 2>/dev/null)
#===================== 模型、缓存文件状态 =====================#
Model.bin: $(ls -lh /etc/openclash/Model.bin 2>/dev/null || echo "不存在")
cache.db: $(ls -lh /etc/openclash/cache.db 2>/dev/null || echo "不存在")
#===================== 冲突插件检测 =====================#
$(ps | grep -E 'passwall|ssr-plus|bypass|helloworld' | grep -v grep 2>/dev/null || echo "未检测到冲突插件")
EOF
cat >> "$DEBUG_LOG" <<-EOF
#===================== 插件设置 =====================#
当前配置文件: $RAW_CONFIG_FILE
@@ -236,11 +281,31 @@ IPV6-DNS解析: $(ts_cf "$ipv6_dns")
仅允许常用端口流量: $(ts_cf "$common_ports")
绕过中国大陆IP: $(ts_cf "$china_ip_route")
路由本机代理: $(ts_cf "$router_self_proxy")
TUN堆栈类型: ${stack_type:-system}
启动延迟: ${delay_start:-0}秒
日志大小: ${log_size:-1024}KB
旁路由兼容: $(ts_cf "$bypass_gateway_compatible")
禁用quic-go GSO: $(ts_cf "$disable_quic_go_gso")
小闪存模式: $(ts_cf "$small_flash_memory")
域名嗅探: $(ts_cf "$enable_meta_sniffer")
DNS代理: $(ts_cf "$enable_respect_rules")
绕过服务器地址: $(ts_cf "$skip_proxy_address")
禁用QUIC: $(ts_cf "$disable_udp_quic")
访问控制模式: $([ "$lan_ac_mode" = "1" ] && echo "White" || echo "Black")
IPv6模式: ${ipv6_mode:-0}
绕过IPv6区域: $(ts_cf "$china_ip6_route")
EOF
cat >> "$DEBUG_LOG" <<-EOF
#===================== Cron 定时任务 =====================#
$(crontab -l 2>/dev/null | grep -i openclash || echo "无 OpenClash 相关 cron 任务")
EOF
cat >> "$DEBUG_LOG" <<-EOF
#===================== 覆写模块设置 =====================#
$(uci -q show openclash.@overwrite[0])
@@ -408,6 +473,14 @@ netstat -nlp |grep clash >> "$DEBUG_LOG" 2>/dev/null
cat >> "$DEBUG_LOG" <<-EOF
#===================== 网络接口状态 =====================#
EOF
ip link show >> "$DEBUG_LOG" 2>/dev/null
ip addr show | grep -E 'inet |utun' >> "$DEBUG_LOG" 2>/dev/null
cat >> "$DEBUG_LOG" <<-EOF
#===================== 测试本机DNS查询(www.baidu.com) =====================#
EOF
@@ -462,6 +535,15 @@ else
curl -SsIL -m 3 --retry 2 "$VERSION_URL" >> "$DEBUG_LOG" 2>/dev/null
fi
if pidof clash >/dev/null; then
cat >> "$DEBUG_LOG" <<-EOF
#===================== Mihomo API 健康检查 =====================#
EOF
curl -Ss -m 3 http://127.0.0.1:${cn_port}/version >> "$DEBUG_LOG" 2>/dev/null
fi
cat >> "$DEBUG_LOG" <<-EOF
#===================== 最近运行日志 (切换为Debug模式) =====================#
@@ -50,7 +50,7 @@ if [ -z "$CONFIG_FILE" ] || [ ! -f "$CONFIG_FILE" ]; then
fi
if [ -n "$(pidof clash)" ] && [ -f "$CONFIG_FILE" ]; then
if [ "$small_flash_memory" == "1" ] || [ -n "$(echo $core_version |grep mips)" ] || [ -n "$(echo $DISTRIB_ARCH |grep mips)" ] || [ -n "$(opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' |grep mips)" ] || [ -n "$(apk list libc 2>/dev/null |grep mips)" ]; then
if [ "$small_flash_memory" == "1" ] || [ -n "$(echo $core_version |grep mips)" ] || [ -n "$(echo $DISTRIB_ARCH |grep mips)" ] || [ -n "$(rm -f /var/lock/opkg.lock && opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' |grep mips)" ] || [ -n "$(rm -f /lib/apk/db/lock && apk list libc 2>/dev/null |grep mips)" ]; then
CACHE_PATH="/tmp/etc/openclash/cache.db"
if [ -f "$CACHE_PATH" ]; then
cmp -s "$CACHE_PATH" "$HISTORY_PATH"
@@ -0,0 +1,49 @@
#!/usr/bin/lua
require "nixio"
require "luci.util"
require "luci.sys"
local uci = require("luci.model.uci").cursor()
local fs = require "luci.openclash"
local json = require "luci.jsonc"
local function oix_checkin()
local info, path, checkin
local token = fs.uci_get_config("config", "oix_token")
local enable = fs.uci_get_config("config", "oix_checkin") or 0
local interval = fs.uci_get_config("config", "oix_checkin_interval") or 1
local multiple = fs.uci_get_config("config", "oix_checkin_multiple") or 1
path = "/tmp/oix_checkin"
if token and enable == "1" then
checkin = string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '{\"multiple\":\"%s\"}' -X POST https://oix-api.dler.io/api/v1/checkin -o %s", token, multiple, path)
if fs.readfile(path) == "" or not fs.readfile(path) then
luci.sys.exec(checkin)
else
if (os.time() - fs.mtime(path) > interval*3600+1) then
fs.unlink(path)
luci.sys.exec(checkin)
else
os.exit(0)
end
end
if fs.readfile(path) == "" or not fs.readfile(path) then
fs.writefile(path, " ")
end
info = fs.readfile(path)
if info then
info = json.parse(info)
end
if info and info.ret == 200 then
luci.sys.exec(string.format('echo "%s [Info] oixCloud Checkin Successful, Result:【%s】" >> /tmp/openclash.log', os.date("%Y-%m-%d %H:%M:%S"), info.data.checkin))
else
if info and info.msg then
luci.sys.exec(string.format('echo "%s [Info] oixCloud Checkin Failed, Result:【%s】" >> /tmp/openclash.log', os.date("%Y-%m-%d %H:%M:%S"), info.msg))
else
luci.sys.exec(string.format('echo "%s [Info] oixCloud Checkin Failed! Please Check And Try Again..." >> /tmp/openclash.log', os.date("%Y-%m-%d %H:%M:%S")))
end
end
end
os.exit(0)
end
oix_checkin()
@@ -709,7 +709,7 @@ function table_sort_by_cache(t)
end
end
if #tab > 0 then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." Group:".."【"..group_match_name.."】".."Cached Compliant Nodes Number:".."【"..#(tab).."】"..", Cached Non-compliant Nodes Number:".."【"..#(tab_b).."】"..", Prioritize Testing With Cached Compliant Nodes...")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] 【"..group_match_name.."】".."Cached Compliant Nodes Number:".."【"..#(tab).."】"..", Cached Non-compliant Nodes Number:".."【"..#(tab_b).."】"..", Prioritize Testing With Cached Compliant Nodes...")
end
for k,v in pairs(tab_b) do table.insert(tab, v) end
return tab
@@ -4,6 +4,7 @@
local nixio = require "nixio"
local jsonc = require "luci.jsonc"
local util = require "luci.util"
local fs = require "luci.openclash"
if not nixio.fs.access("/usr/bin/base64") and not nixio.fs.access("/bin/base64") then
os.exit(1)
@@ -14,31 +15,8 @@ if not file_path or not nixio.fs.access(file_path) then
os.exit(1)
end
local function raw_base64_decode(str)
if not str or str == "" then return nil end
str = str:gsub("-", "+"):gsub("_", "/")
local padding = #str % 4
if padding > 0 then
str = str .. string.rep("=", 4 - padding)
end
local cmd = "printf '%s' '" .. str .. "' | base64 -d 2>/dev/null"
local f = io.popen(cmd, "r")
if not f then return nil end
local result = f:read("*a")
f:close()
if result and result ~= "" then
return result
end
return nil
end
local function base64_decode_subscription(str)
local result = raw_base64_decode(str)
local result = fs.decode64(str)
if result and result:find("://") then
return result
end
@@ -126,7 +104,7 @@ local function get_server_from_url(line)
original_body = original_body:match("([^#]+)") or original_body
-- base64
local decoded = raw_base64_decode(original_body)
local decoded = fs.decode64(original_body)
if decoded then
-- V2RayN JSON
local ok, data = pcall(jsonc.parse, decoded)
@@ -160,7 +138,7 @@ local function get_server_from_url(line)
else
-- ss://base64
original_body = original_body:match("([^#]+)") or original_body
local decoded = raw_base64_decode(original_body)
local decoded = fs.decode64(original_body)
if decoded then
server = decoded:match("@([^:/]+)")
else
@@ -170,7 +148,7 @@ local function get_server_from_url(line)
elseif scheme == "ssr" then
original_body = original_body:match("([^#]+)") or original_body
local decoded = raw_base64_decode(original_body)
local decoded = fs.decode64(original_body)
if decoded then
-- ssr://host:port:protocol:method:obfs:urlsafebase64pass/?params
local before_query = decoded:match("^([^/?]+)")
@@ -214,7 +192,7 @@ local function get_server_from_url(line)
-- fallback
if not server then
local body_to_parse = raw_base64_decode(original_body) or original_body
local body_to_parse = fs.decode64(original_body) or original_body
server = body_to_parse:match("@([^:/]+)") or -- user@host
body_to_parse:match("([^:/]+)") or -- host
body_to_parse:match("//([^:/]+)") -- //host
@@ -51,12 +51,31 @@ version_compare() {
return 1
}
run_with_timeout() {
local timeout_sec="$1"
shift
"$@" &
local _pid=$!
(
sleep "$timeout_sec"
kill $_pid 2>/dev/null
sleep 0.5
kill -9 $_pid 2>/dev/null
) &
local _watchdog=$!
wait $_pid 2>/dev/null
local _ret=$?
kill $_watchdog 2>/dev/null
wait $_watchdog 2>/dev/null
return $_ret
}
LAST_OPVER="/tmp/openclash_last_version"
LAST_VER=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
if [ -x "/bin/opkg" ]; then
OP_CV=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
elif [ -x "/usr/bin/apk" ]; then
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
OP_CV=$(rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
fi
OP_LV=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
@@ -129,27 +148,48 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
LOG_TIP "【$retry_count/$max_retries】【OpenClash - v$LAST_VER】Download successful, start pre update test..."
pre_test_success=false
pkg_update_success=true
if [ -x "/bin/opkg" ]; then
opkg update >/dev/null 2>&1
if [ $? -ne 0 ]; then
sleep 2
pkg_update_success=false
continue
fi
update_retry=0
max_update_retry=2
while [ $update_retry -lt $max_update_retry ]; do
update_retry=$((update_retry + 1))
run_with_timeout 30 opkg update >/dev/null 2>&1
opkg_ret=$?
rm -f /var/lock/opkg.lock
if [ $opkg_ret -eq 0 ]; then
break
fi
if [ $update_retry -lt $max_update_retry ]; then
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】opkg update failed or timed out, retrying..."
sleep 2
else
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】opkg update failed, trying pre update test..."
fi
done
if [ -s "/tmp/openclash.ipk" ]; then
if [ -n "$(opkg install /tmp/openclash.ipk --noaction 2>/dev/null |grep 'Upgrading luci-app-openclash on root' 2>/dev/null)" ]; then
pre_test_success=true
fi
fi
elif [ -x "/usr/bin/apk" ]; then
apk update >/dev/null 2>&1
if [ $? -ne 0 ]; then
sleep 2
pkg_update_success=false
continue
fi
update_retry=0
max_update_retry=2
while [ $update_retry -lt $max_update_retry ]; do
update_retry=$((update_retry + 1))
run_with_timeout 30 apk update >/dev/null 2>&1
apk_ret=$?
rm -f /lib/apk/db/lock /tmp/apk.lock
if [ $apk_ret -eq 0 ]; then
break
fi
if [ $update_retry -lt $max_update_retry ]; then
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】apk update failed or timed out, retrying..."
sleep 2
else
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】apk update failed, trying pre update test..."
fi
done
if [ -s "/tmp/openclash.apk" ]; then
apk add -s -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk >/dev/null 2>&1
if [ $? -eq 0 ]; then
@@ -37,7 +37,7 @@ RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
if [ -x "/bin/opkg" ]; then
OP_CV=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
elif [ -x "/usr/bin/apk" ]; then
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
OP_CV=$(rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
fi
OP_LV=$(sed -n 1p "$DOWNLOAD_FILE" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
@@ -5,6 +5,7 @@
. /usr/share/openclash/uci.sh
LOG_FILE="/tmp/openclash.log"
CLASH="/etc/openclash/clash"
CFG_UPDATE_INT=0
SKIP_PROXY_ADDRESS=1
SKIP_PROXY_ADDRESS_INTERVAL=30
@@ -29,8 +30,6 @@ begin
exit;
end
require 'thread'
servers_to_process = Array.new
# Servers from proxies
@@ -46,20 +45,41 @@ begin
if provider.key?('path') and not provider['path'].empty?
path = provider['path'].start_with?('./') ? '/etc/openclash/' + provider['path'][2..-1] : provider['path']
if File.exist?(path)
file_is_age_encrypted = File.read(path, 512).include?('BEGIN AGE ENCRYPTED FILE') rescue false
begin
provider_config = YAML.load_file(path)
if provider.key?('age-secret-key') and not provider['age-secret-key'].to_s.empty?
begin
provider_config = YAML.load_file(path, secret: provider['age-secret-key']) rescue nil
rescue Exception => e
YAML.LOG_WARN('Set Proxies Address Skip Failed,【' + path + ': ' + e.message+'】')
continue
end
else
if file_is_age_encrypted
YAML.LOG_WARN('Set Proxies Address Skip Failed,【' + path + ': File is AGE encrypted but no secret key provided】')
next
end
provider_config = YAML.load_file(path)
end
if provider_config.is_a?(Hash) and provider_config.key?('proxies') and not provider_config['proxies'].nil?
provider_config['proxies'].each do |p|
servers_to_process.push(p['server']) if p.key?('server')
end
end
rescue Psych::SyntaxError, ArgumentError
begin
syscall = \"lua /usr/share/openclash/openclash_sub_parser.lua \\\"#{path}\\\"\"
sub_servers = IO.popen(syscall).read.split(/\n+/)
servers_to_process.concat(sub_servers) if sub_servers
rescue Exception => e
YAML.LOG_WARN('Failed to parse subscription file with Lua helper ' + path + ': ' + e.message)
if not provider.key?('age-secret-key') or provider['age-secret-key'].to_s.empty?
if file_is_age_encrypted
YAML.LOG_WARN('Failed to parse config file with Lua helper【' + path + ': File is AGE encrypted, cannot parse with Lua】')
next
end
begin
syscall = \"lua /usr/share/openclash/openclash_sub_parser.lua \\\"#{path}\\\"\"
sub_servers = IO.popen(syscall).read.split(/\n+/)
servers_to_process.concat(sub_servers) if sub_servers
rescue Exception => e
YAML.LOG_WARN('Failed to parse config file with Lua helper【' + path + ': ' + e.message+'】')
end
end
end
end
@@ -195,8 +215,8 @@ fi
## Log File Size Manage:
LOGSIZE=`ls -l /tmp/openclash.log |awk '{print int($5/1024)}'`
if [ "$LOGSIZE" -gt "$log_size" ]; then
: > /tmp/openclash.log
LOG_WATCHDOG "Log Size Limit, Clean Up All Log Records..."
: > /tmp/openclash.log
LOG_WATCHDOG "Log Size Limit, Clean Up All Log Records..."
fi
## 防火墙检查
@@ -361,6 +381,9 @@ fi
fi
fi
##Dler Cloud Checkin
/usr/share/openclash/openclash_oix_checkin.lua >/dev/null 2>&1
## 配置文件循环更新
if [ "$cfg_update" -eq 1 ] && [ "$cfg_update_mode" -eq 1 ]; then
if [ "$CFG_UPDATE_INT" -ne 0 ]; then
@@ -374,59 +397,59 @@ fi
if [ "$STREAM_AUTO_SELECT" -ne 0 ]; then
if [ "$(expr "$STREAM_AUTO_SELECT" % "$stream_auto_select_interval")" -eq 0 ] || [ "$STREAM_AUTO_SELECT" -eq 1 ]; then
if [ "$stream_auto_select_netflix" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Netflix】..."
LOG_TIP "【Netflix】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Netflix" >> $LOG_FILE
fi
if [ "$stream_auto_select_disney" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Disney Plus】..."
LOG_TIP "【Disney Plus】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Disney Plus" >> $LOG_FILE
fi
if [ "$stream_auto_select_google_not_cn" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Google Not CN】..."
LOG_TIP "【Google Not CN】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Google" >> $LOG_FILE
fi
if [ "$stream_auto_select_ytb" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【YouTube Premium】..."
LOG_TIP "【YouTube Premium】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "YouTube Premium" >> $LOG_FILE
fi
if [ "$stream_auto_select_prime_video" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Amazon Prime Video】..."
LOG_TIP "【Amazon Prime Video】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Amazon Prime Video" >> $LOG_FILE
fi
if [ "$stream_auto_select_hbo_max" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【HBO Max】..."
LOG_TIP "【HBO Max】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "HBO Max" >> $LOG_FILE
fi
if [ "$stream_auto_select_tvb_anywhere" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【TVB Anywhere+】..."
LOG_TIP "【TVB Anywhere+】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "TVB Anywhere+" >> $LOG_FILE
fi
if [ "$stream_auto_select_dazn" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【DAZN】..."
LOG_TIP "【DAZN】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "DAZN" >> $LOG_FILE
fi
if [ "$stream_auto_select_paramount_plus" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Paramount Plus】..."
LOG_TIP "【Paramount Plus】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Paramount Plus" >> $LOG_FILE
fi
if [ "$stream_auto_select_discovery_plus" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Discovery Plus】..."
LOG_TIP "【Discovery Plus】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Discovery Plus" >> $LOG_FILE
fi
if [ "$stream_auto_select_bilibili" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Bilibili】..."
LOG_TIP "【Bilibili】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Bilibili" >> $LOG_FILE
fi
if [ "$stream_auto_select_openai" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【OpenAI】..."
LOG_TIP "【OpenAI】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "OpenAI" >> $LOG_FILE
fi
if [ "$stream_auto_select_claude" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Claude】..."
LOG_TIP "【Claude】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Claude" >> $LOG_FILE
fi
if [ "$stream_auto_select_gemini" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Gemini】..."
LOG_TIP "【Gemini】Start Auto Select Unlock Proxy..."
/usr/share/openclash/openclash_streaming_unlock.lua "Gemini" >> $LOG_FILE
fi
fi
@@ -1,6 +1,73 @@
#!/bin/sh
. /lib/functions.sh
uci_get_config() {
local key="$1"
uci -q get openclash.@overwrite[0]."$key" || uci -q get openclash.config."$key"
}
uci_get_age_public_keys() {
local name="$1"
[ -n "$name" ] || return 0
_print_pub() {
local section="$1"
config_get cfg_name "$section" name
if [ "$cfg_name" = "$name" ]; then
config_get pub "$section" public
[ -n "$pub" ] && printf '%s\n' "$pub"
fi
}
config_load openclash
config_foreach _print_pub config_age_secret
}
uci_get_age_secret_keys() {
local name="$1"
[ -n "$name" ] || return 0
_print_sec() {
local section="$1"
config_get cfg_name "$section" name
if [ "$cfg_name" = "$name" ]; then
config_get sec "$section" secret
[ -n "$sec" ] && printf '%s\n' "$sec"
fi
}
config_load openclash
config_foreach _print_sec config_age_secret
}
uci_set_age_keys_by_name() {
local name="$1"
local secret="$2"
local public="$3"
local target_section=""
[ -n "$name" ] || return 1
_find_age_section() {
local section="$1"
local cfg_name
config_get cfg_name "$section" name
[ "$cfg_name" = "$name" ] && target_section="$section"
}
config_load openclash
config_foreach _find_age_section config_age_secret
if [ -z "$target_section" ]; then
target_section=$(uci -q add openclash config_age_secret)
fi
[ -n "$target_section" ] || return 1
uci -q set openclash."$target_section".name="$name"
[ -n "$secret" ] && uci -q set openclash."$target_section".secret="$secret"
[ -n "$public" ] && uci -q set openclash."$target_section".public="$public"
uci -q commit openclash
return 0
}
@@ -427,9 +427,9 @@ begin
Value['external-ui-url'] = 'https://codeload.github.com/MetaCubeX/Yacd-meta/zip/refs/heads/gh-pages'
end
when 'metacubexd'
Value['external-ui-url'] = 'https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages'
Value['external-ui-url'] = 'https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages'
when 'zashboard'
Value['external-ui-url'] = 'https://codeload.github.com/Zephyruso/zashboard/zip/refs/heads/gh-pages-cdn-fonts'
Value['external-ui-url'] = 'https://codeload.github.com/Zephyruso/zashboard/zip/refs/heads/gh-pages-cdn-fonts'
end
if !Value.key?('keep-alive-interval') && !Value.key?('keep-alive-idle')
Value['keep-alive-interval'] = 15
@@ -456,7 +456,7 @@ begin
end
if smart_collect
(Value['profile'] ||= {})['smart-collector-size'] = smart_collect_size.to_f
(Value['profile'] ||= {})['smart-collector-size'] = smart_collect_size.to_f
end
Value['geox-url'] ||= {}
@@ -22,7 +22,7 @@ yml_other_set()
begin
thread_pool = [];
# GEOIP replace
geoip_pattern = /GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
geoip_pattern = /^GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
match_pattern = /(^MATCH.*|^FINAL.*)/;
thread_pool << Thread.new{
#BT/P2P DIRECT Rules
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,60 @@
/**
* OpenClash Common JavaScript Utilities
* Shared functions used across multiple view templates.
*/
/**
* Detects if an element has a dark background by analyzing its computed CSS.
* Used by CodeMirror log editor to set dark mode attribute.
* @param {HTMLElement} element - The element to check
* @returns {boolean} True if the background is dark
*/
function isDarkBackground(element) {
var cachedTheme = localStorage.getItem('oc-theme');
if (cachedTheme === 'dark') {
return true;
} else if (cachedTheme === 'light') {
return false;
}
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return true;
}
var style = window.getComputedStyle(element);
var bgColor = style.backgroundColor;
var r, g, b;
if (/rgb\(/.test(bgColor)) {
var rgb = bgColor.match(/\d+/g);
r = parseInt(rgb[0]);
g = parseInt(rgb[1]);
b = parseInt(rgb[2]);
} else if (/#/.test(bgColor)) {
if (bgColor.length === 4) {
r = parseInt(bgColor[1] + bgColor[1], 16);
g = parseInt(bgColor[2] + bgColor[2], 16);
b = parseInt(bgColor[3] + bgColor[3], 16);
} else {
r = parseInt(bgColor.slice(1, 3), 16);
g = parseInt(bgColor.slice(3, 5), 16);
b = parseInt(bgColor.slice(5, 7), 16);
}
} else {
return false;
}
var luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
return luminance < 128;
}
/**
* Opens a URL in a new window. Used for external links (Wiki, GitHub, etc.).
* @param {string} url - The URL to open
* @returns {boolean} false to prevent default link behavior
*/
function winOpen(url) {
var win = window.open(url);
if (win == null || typeof(win) == 'undefined') {
window.location.href = url;
}
return false;
}