427 lines
13 KiB
Bash
427 lines
13 KiB
Bash
PODKOP_LIB="/usr/lib/podkop"
|
|
. "$PODKOP_LIB/helpers.sh"
|
|
. "$PODKOP_LIB/sing_box_config_manager.sh"
|
|
|
|
sing_box_cf_add_dns_server() {
|
|
local config="$1"
|
|
local type="$2"
|
|
local tag="$3"
|
|
local server="$4"
|
|
local domain_resolver="$5"
|
|
local detour="$6"
|
|
|
|
local server_address server_port
|
|
server_address=$(url_get_host "$server")
|
|
server_port=$(url_get_port "$server")
|
|
|
|
case "$type" in
|
|
udp)
|
|
[ -z "$server_port" ] && server_port=53
|
|
config=$(sing_box_cm_add_udp_dns_server "$config" "$tag" "$server_address" "$server_port" "$domain_resolver" \
|
|
"$detour")
|
|
;;
|
|
dot)
|
|
[ -z "$server_port" ] && server_port=853
|
|
config=$(sing_box_cm_add_tls_dns_server "$config" "$tag" "$server_address" "$server_port" "$domain_resolver" \
|
|
"$detour")
|
|
;;
|
|
doh)
|
|
[ -z "$server_port" ] && server_port=443
|
|
local path headers
|
|
path=$(url_get_path "$server")
|
|
headers="" # TODO(ampetelin): implement it if necessary
|
|
config=$(sing_box_cm_add_https_dns_server "$config" "$tag" "$server_address" "$server_port" "$path" "$headers" \
|
|
"$domain_resolver" "$detour")
|
|
;;
|
|
*)
|
|
log "Unsupported DNS server type: $type. Aborted." "fatal"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
sing_box_cf_add_mixed_inbound_and_route_rule() {
|
|
local config="$1"
|
|
local tag="$2"
|
|
local listen_address="$3"
|
|
local listen_port="$4"
|
|
local outbound="$5"
|
|
|
|
config=$(sing_box_cm_add_mixed_inbound "$config" "$tag" "$listen_address" "$listen_port")
|
|
config=$(sing_box_cm_add_route_rule "$config" "" "$tag" "$outbound")
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
sing_box_cf_add_proxy_outbound() {
|
|
local config="$1"
|
|
local section="$2"
|
|
local url="$3"
|
|
local udp_over_tcp="$4"
|
|
|
|
url=$(url_decode "$url")
|
|
url=$(url_strip_fragment "$url")
|
|
|
|
local scheme
|
|
scheme="$(url_get_scheme "$url")"
|
|
case "$scheme" in
|
|
socks4 | socks4a | socks5)
|
|
local tag host port version userinfo username password udp_over_tcp
|
|
|
|
tag=$(get_outbound_tag_by_section "$section")
|
|
host=$(url_get_host "$url")
|
|
port=$(url_get_port "$url")
|
|
version="${scheme#socks}"
|
|
if [ "$scheme" = "socks5" ]; then
|
|
userinfo=$(url_get_userinfo "$url")
|
|
if [ -n "$userinfo" ]; then
|
|
username="${userinfo%%:*}"
|
|
password="${userinfo#*:}"
|
|
fi
|
|
fi
|
|
config="$(sing_box_cm_add_socks_outbound \
|
|
"$config" \
|
|
"$tag" \
|
|
"$host" \
|
|
"$port" \
|
|
"$version" \
|
|
"$username" \
|
|
"$password" \
|
|
"" \
|
|
"$([ "$udp_over_tcp" == "1" ] && echo 2)" # if udp_over_tcp is enabled, enable version 2
|
|
)"
|
|
;;
|
|
vless)
|
|
local tag host port uuid flow packet_encoding
|
|
tag=$(get_outbound_tag_by_section "$section")
|
|
host=$(url_get_host "$url")
|
|
port=$(url_get_port "$url")
|
|
uuid=$(url_get_userinfo "$url")
|
|
flow=$(url_get_query_param "$url" "flow")
|
|
packet_encoding=$(url_get_query_param "$url" "packetEncoding")
|
|
|
|
config=$(sing_box_cm_add_vless_outbound "$config" "$tag" "$host" "$port" "$uuid" "$flow" "" "$packet_encoding")
|
|
config=$(_add_outbound_security "$config" "$tag" "$url")
|
|
config=$(_add_outbound_transport "$config" "$tag" "$url")
|
|
;;
|
|
ss)
|
|
local userinfo tag host port method password udp_over_tcp
|
|
|
|
userinfo=$(url_get_userinfo "$url")
|
|
if ! is_shadowsocks_userinfo_format "$userinfo"; then
|
|
userinfo=$(base64_decode "$userinfo")
|
|
if [ $? -ne 0 ]; then
|
|
log "Cannot decode shadowsocks userinfo or it does not match the expected format. Aborted." "fatal"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
tag=$(get_outbound_tag_by_section "$section")
|
|
host=$(url_get_host "$url")
|
|
port=$(url_get_port "$url")
|
|
method="${userinfo%%:*}"
|
|
password="${userinfo#*:}"
|
|
|
|
config=$(
|
|
sing_box_cm_add_shadowsocks_outbound \
|
|
"$config" \
|
|
"$tag" \
|
|
"$host" \
|
|
"$port" \
|
|
"$method" \
|
|
"$password" \
|
|
"" \
|
|
"$([ "$udp_over_tcp" == "1" ] && echo 2)" # if udp_over_tcp is enabled, enable version 2
|
|
)
|
|
;;
|
|
trojan)
|
|
local tag host port password
|
|
tag=$(get_outbound_tag_by_section "$section")
|
|
host=$(url_get_host "$url")
|
|
port=$(url_get_port "$url")
|
|
password=$(url_get_userinfo "$url")
|
|
|
|
config=$(sing_box_cm_add_trojan_outbound "$config" "$tag" "$host" "$port" "$password")
|
|
config=$(_add_outbound_security "$config" "$tag" "$url")
|
|
config=$(_add_outbound_transport "$config" "$tag" "$url")
|
|
;;
|
|
hysteria2 | hy2)
|
|
local tag host port password obfuscator_type obfuscator_password upload_mbps download_mbps
|
|
tag=$(get_outbound_tag_by_section "$section")
|
|
host=$(url_get_host "$url")
|
|
port="$(url_get_port "$url")"
|
|
password=$(url_get_userinfo "$url")
|
|
obfuscator_type=$(url_get_query_param "$url" "obfs")
|
|
obfuscator_password=$(url_get_query_param "$url" "obfs-password")
|
|
upload_mbps=$(url_get_query_param "$url" "upmbps")
|
|
download_mbps=$(url_get_query_param "$url" "downmbps")
|
|
|
|
config=$(sing_box_cm_add_hysteria2_outbound "$config" "$tag" "$host" "$port" "$password" "$obfuscator_type" \
|
|
"$obfuscator_password" "$upload_mbps" "$download_mbps")
|
|
config=$(_add_outbound_security "$config" "$tag" "$url")
|
|
;;
|
|
*)
|
|
log "Unsupported proxy $scheme type. Aborted." "fatal"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
_add_outbound_security() {
|
|
local config="$1"
|
|
local outbound_tag="$2"
|
|
local url="$3"
|
|
|
|
local security scheme
|
|
security=$(url_get_query_param "$url" "security")
|
|
if [ -z "$security" ]; then
|
|
scheme="$(url_get_scheme "$url")"
|
|
if [ "$scheme" = "hysteria2" ] || [ "$scheme" = "hy2" ]; then
|
|
security="tls"
|
|
fi
|
|
fi
|
|
|
|
case "$security" in
|
|
tls | reality)
|
|
local sni insecure alpn fingerprint public_key short_id
|
|
sni=$(url_get_query_param "$url" "sni")
|
|
insecure=$(_get_insecure_query_param_from_url "$url")
|
|
alpn=$(comma_string_to_json_array "$(url_get_query_param "$url" "alpn")")
|
|
fingerprint=$(url_get_query_param "$url" "fp")
|
|
public_key=$(url_get_query_param "$url" "pbk")
|
|
short_id=$(url_get_query_param "$url" "sid")
|
|
|
|
config=$(
|
|
sing_box_cm_set_tls_for_outbound \
|
|
"$config" \
|
|
"$outbound_tag" \
|
|
"$sni" \
|
|
"$([ "$insecure" == "1" ] && echo true)" \
|
|
"$([ "$alpn" == "[]" ] && echo null || echo "$alpn")" \
|
|
"$fingerprint" \
|
|
"$public_key" \
|
|
"$short_id"
|
|
)
|
|
;;
|
|
none) ;;
|
|
*)
|
|
log "Unknown security '$security' detected." "error"
|
|
;;
|
|
esac
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
_get_insecure_query_param_from_url() {
|
|
local url="$1"
|
|
|
|
local insecure
|
|
insecure=$(url_get_query_param "$url" "allowInsecure")
|
|
if [ -z "$insecure" ]; then
|
|
insecure=$(url_get_query_param "$url" "insecure")
|
|
fi
|
|
|
|
echo "$insecure"
|
|
}
|
|
|
|
_add_outbound_transport() {
|
|
local config="$1"
|
|
local outbound_tag="$2"
|
|
local url="$3"
|
|
|
|
local transport
|
|
transport=$(url_get_query_param "$url" "type")
|
|
case "$transport" in
|
|
tcp | raw) ;;
|
|
ws)
|
|
local ws_path ws_host ws_early_data
|
|
ws_path=$(url_get_query_param "$url" "path")
|
|
ws_host=$(url_get_query_param "$url" "host")
|
|
ws_early_data=$(url_get_query_param "$url" "ed")
|
|
|
|
config=$(
|
|
sing_box_cm_set_ws_transport_for_outbound "$config" "$outbound_tag" "$ws_path" "$ws_host" "$ws_early_data"
|
|
)
|
|
;;
|
|
grpc)
|
|
# TODO(ampetelin): Add handling of optional gRPC parameters; example links are needed.
|
|
local grpc_service_name
|
|
grpc_service_name=$(url_get_query_param "$url" "serviceName")
|
|
|
|
config=$(
|
|
sing_box_cm_set_grpc_transport_for_outbound "$config" "$outbound_tag" "$grpc_service_name"
|
|
)
|
|
;;
|
|
*)
|
|
log "Unknown transport '$transport' detected." "error"
|
|
;;
|
|
esac
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
sing_box_cf_add_json_outbound() {
|
|
local config="$1"
|
|
local section="$2"
|
|
local json_outbound="$3"
|
|
|
|
local tag
|
|
tag=$(get_outbound_tag_by_section "$section")
|
|
|
|
config=$(sing_box_cm_add_raw_outbound "$config" "$tag" "$json_outbound")
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
sing_box_cf_add_interface_outbound() {
|
|
local config="$1"
|
|
local section="$2"
|
|
local interface_name="$3"
|
|
|
|
local tag
|
|
tag=$(get_outbound_tag_by_section "$section")
|
|
|
|
config=$(sing_box_cm_add_interface_outbound "$config" "$tag" "$interface_name")
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
sing_box_cf_proxy_domain() {
|
|
local config="$1"
|
|
local inbound="$2"
|
|
local domain="$3"
|
|
local outbound="$4"
|
|
|
|
tag="$(gen_id)"
|
|
config=$(sing_box_cm_add_route_rule "$config" "$tag" "$inbound" "$outbound")
|
|
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "domain" "$domain")
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
sing_box_cf_override_domain_port() {
|
|
local config="$1"
|
|
local domain="$2"
|
|
local port="$3"
|
|
|
|
tag="$(gen_id)"
|
|
config=$(sing_box_cm_add_options_route_rule "$config" "$tag")
|
|
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "domain" "$domain")
|
|
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "override_port" "$port")
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
sing_box_cf_add_single_key_reject_rule() {
|
|
local config="$1"
|
|
local inbound="$2"
|
|
local key="$3"
|
|
local value="$4"
|
|
|
|
tag="$(gen_id)"
|
|
config=$(sing_box_cm_add_reject_route_rule "$config" "$tag" "$inbound")
|
|
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "$key" "$value")
|
|
|
|
echo "$config"
|
|
}
|
|
|
|
#######################################
|
|
# Parse a sing-box subscription JSON and add all proxy outbounds to the configuration.
|
|
# Filters out non-proxy types (selector, urltest, direct, dns, block).
|
|
# Uses 'tag' field (or 'remark' if present) as display name for each outbound.
|
|
# Arguments:
|
|
# config: string (JSON), sing-box configuration to modify
|
|
# section: string, the UCI section name
|
|
# subscription_json_path: string, path to the downloaded subscription JSON file
|
|
# Outputs:
|
|
# Writes updated JSON configuration to stdout
|
|
# Sets global variable SUBSCRIPTION_OUTBOUND_TAGS (comma-separated list of tags)
|
|
# Sets global variable SUBSCRIPTION_OUTBOUND_NAMES (newline-separated list of display names)
|
|
#######################################
|
|
sing_box_cf_add_subscription_outbounds() {
|
|
local config="$1"
|
|
local section="$2"
|
|
local subscription_json_path="$3"
|
|
|
|
SUBSCRIPTION_OUTBOUND_TAGS=""
|
|
SUBSCRIPTION_OUTBOUND_NAMES=""
|
|
|
|
if [ ! -f "$subscription_json_path" ]; then
|
|
log "Subscription JSON file not found: $subscription_json_path" "error"
|
|
echo "$config"
|
|
return 1
|
|
fi
|
|
|
|
# Extract proxy outbounds from subscription JSON
|
|
# Filter out non-proxy types: selector, urltest, direct, dns, block
|
|
local outbounds_count
|
|
outbounds_count=$(jq -r '[.outbounds[] | select(
|
|
.type != "selector" and
|
|
.type != "urltest" and
|
|
.type != "direct" and
|
|
.type != "dns" and
|
|
.type != "block"
|
|
)] | length' "$subscription_json_path" 2>/dev/null)
|
|
|
|
if [ -z "$outbounds_count" ] || [ "$outbounds_count" -eq 0 ]; then
|
|
log "No proxy outbounds found in subscription JSON" "error"
|
|
echo "$config"
|
|
return 1
|
|
fi
|
|
|
|
log "Found $outbounds_count proxy outbounds in subscription" "info"
|
|
|
|
local i=1
|
|
local outbound_json display_name outbound_tag
|
|
|
|
while [ "$i" -le "$outbounds_count" ]; do
|
|
# Extract the i-th proxy outbound as raw JSON
|
|
outbound_json=$(jq -c "[.outbounds[] | select(
|
|
.type != \"selector\" and
|
|
.type != \"urltest\" and
|
|
.type != \"direct\" and
|
|
.type != \"dns\" and
|
|
.type != \"block\"
|
|
)][$i - 1]" "$subscription_json_path" 2>/dev/null)
|
|
|
|
if [ -z "$outbound_json" ] || [ "$outbound_json" = "null" ]; then
|
|
i=$((i + 1))
|
|
continue
|
|
fi
|
|
|
|
# Get display name: prefer remark, then tag, then fallback
|
|
display_name=$(echo "$outbound_json" | jq -r '.remark // .tag // "server-'"$i"'"' 2>/dev/null)
|
|
|
|
# Create the tag in podkop format
|
|
outbound_tag="$(get_outbound_tag_by_section "$section-$i")"
|
|
|
|
# Remove tag from raw outbound (it will be set by sing_box_cm_add_raw_outbound)
|
|
local clean_outbound
|
|
clean_outbound=$(echo "$outbound_json" | jq -c 'del(.tag) | del(.remark)' 2>/dev/null)
|
|
|
|
config=$(sing_box_cm_add_raw_outbound "$config" "$outbound_tag" "$clean_outbound")
|
|
|
|
if [ -z "$SUBSCRIPTION_OUTBOUND_TAGS" ]; then
|
|
SUBSCRIPTION_OUTBOUND_TAGS="$outbound_tag"
|
|
else
|
|
SUBSCRIPTION_OUTBOUND_TAGS="$SUBSCRIPTION_OUTBOUND_TAGS,$outbound_tag"
|
|
fi
|
|
|
|
if [ -z "$SUBSCRIPTION_OUTBOUND_NAMES" ]; then
|
|
SUBSCRIPTION_OUTBOUND_NAMES="$display_name"
|
|
else
|
|
SUBSCRIPTION_OUTBOUND_NAMES="$(printf '%s\n%s' "$SUBSCRIPTION_OUTBOUND_NAMES" "$display_name")"
|
|
fi
|
|
|
|
i=$((i + 1))
|
|
done
|
|
|
|
log "Added $((i - 1)) subscription outbounds for section '$section'" "info"
|
|
|
|
echo "$config"
|
|
}
|