mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-11 02:44:57 +08:00
1004 lines
24 KiB
Bash
1004 lines
24 KiB
Bash
#!/bin/sh /etc/rc.common
|
|
|
|
START=99
|
|
USE_PROCD=1
|
|
|
|
NAME=frpc
|
|
PROG=/usr/bin/$NAME
|
|
CONF_FILE=/var/etc/$NAME.toml
|
|
|
|
_err() {
|
|
echo "$*" >&2
|
|
logger -p daemon.err -t "$NAME" "$*"
|
|
}
|
|
|
|
_trim() {
|
|
printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
|
|
}
|
|
|
|
_toml_escape() {
|
|
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
|
}
|
|
|
|
_toml_quote() {
|
|
printf '"%s"' "$(_toml_escape "$1")"
|
|
}
|
|
|
|
_toml_key_quote() {
|
|
_toml_quote "$1"
|
|
}
|
|
|
|
_toml_bool() {
|
|
local v
|
|
v="$(printf '%s' "$1" | tr 'A-Z' 'a-z')"
|
|
|
|
case "$v" in
|
|
1|true|yes|on|enabled)
|
|
printf 'true'
|
|
;;
|
|
0|false|no|off|disabled)
|
|
printf 'false'
|
|
;;
|
|
*)
|
|
printf 'false'
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_TOML_ERR=0
|
|
_ALLOW_UNSAFE_TOKEN_SOURCE_EXEC=0
|
|
|
|
_is_uinteger() {
|
|
case "$1" in
|
|
''|*[!0-9]*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
return 0
|
|
}
|
|
|
|
_is_integer() {
|
|
local value="$1"
|
|
|
|
case "$value" in
|
|
+*|-*)
|
|
value="${value#?}"
|
|
;;
|
|
esac
|
|
|
|
_is_uinteger "$value"
|
|
}
|
|
|
|
_is_port_value() {
|
|
_is_uinteger "$1" || return 1
|
|
[ "$1" -ge 1 ] 2>/dev/null && [ "$1" -le 65535 ] 2>/dev/null
|
|
}
|
|
|
|
_is_port_or_zero_value() {
|
|
_is_uinteger "$1" || return 1
|
|
[ "$1" -ge 0 ] 2>/dev/null && [ "$1" -le 65535 ] 2>/dev/null
|
|
}
|
|
|
|
_is_negative_integer() {
|
|
local value
|
|
|
|
case "$1" in
|
|
-*)
|
|
value="${1#-}"
|
|
_is_uinteger "$value" && [ "$value" -gt 0 ] 2>/dev/null
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_is_visitor_bind_port_value() {
|
|
_is_negative_integer "$1" && return 0
|
|
_is_port_value "$1"
|
|
}
|
|
|
|
_toml_line() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local type="$3"
|
|
|
|
[ -z "$value" ] && return 0
|
|
|
|
case "$type" in
|
|
bool)
|
|
printf '%s = %s\n' "$key" "$(_toml_bool "$value")"
|
|
;;
|
|
int|integer|number)
|
|
if ! _is_integer "$value"; then
|
|
_err "invalid integer for $key: $value"
|
|
_TOML_ERR=1
|
|
return 1
|
|
fi
|
|
|
|
printf '%s = %s\n' "$key" "$value"
|
|
;;
|
|
port)
|
|
if ! _is_port_value "$value"; then
|
|
_err "invalid port for $key: $value"
|
|
_TOML_ERR=1
|
|
return 1
|
|
fi
|
|
|
|
printf '%s = %s\n' "$key" "$value"
|
|
;;
|
|
port0)
|
|
if ! _is_port_or_zero_value "$value"; then
|
|
_err "invalid port for $key: $value"
|
|
_TOML_ERR=1
|
|
return 1
|
|
fi
|
|
|
|
printf '%s = %s\n' "$key" "$value"
|
|
;;
|
|
visitor_bind_port)
|
|
if ! _is_visitor_bind_port_value "$value"; then
|
|
_err "invalid visitor bind port for $key: $value"
|
|
_TOML_ERR=1
|
|
return 1
|
|
fi
|
|
|
|
printf '%s = %s\n' "$key" "$value"
|
|
;;
|
|
*)
|
|
printf '%s = %s\n' "$key" "$(_toml_quote "$value")"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_emit_opt() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local toml_key="$3"
|
|
local type="$4"
|
|
local value
|
|
|
|
config_get value "$section" "$option"
|
|
_toml_line "$toml_key" "$value" "$type"
|
|
}
|
|
|
|
_TOML_ARRAY=
|
|
_TOML_HAS_LIST=0
|
|
|
|
_toml_array_add() {
|
|
local item="$1"
|
|
|
|
item="$(_trim "$item")"
|
|
[ -z "$item" ] && return 0
|
|
|
|
if [ -n "$_TOML_ARRAY" ]; then
|
|
_TOML_ARRAY="${_TOML_ARRAY}, "
|
|
fi
|
|
|
|
_TOML_ARRAY="${_TOML_ARRAY}$(_toml_quote "$item")"
|
|
}
|
|
|
|
_collect_array_item() {
|
|
_TOML_HAS_LIST=1
|
|
_toml_array_add "$1"
|
|
}
|
|
|
|
_collect_array_option() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local scalar item
|
|
|
|
_TOML_ARRAY=
|
|
_TOML_HAS_LIST=0
|
|
|
|
config_list_foreach "$section" "$option" _collect_array_item
|
|
|
|
if [ "$_TOML_HAS_LIST" = "0" ]; then
|
|
config_get scalar "$section" "$option"
|
|
|
|
while [ -n "$scalar" ]; do
|
|
case "$scalar" in
|
|
*,*)
|
|
item="${scalar%%,*}"
|
|
scalar="${scalar#*,}"
|
|
;;
|
|
*)
|
|
item="$scalar"
|
|
scalar=
|
|
;;
|
|
esac
|
|
|
|
_toml_array_add "$item"
|
|
done
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
_emit_array_opt() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local toml_key="$3"
|
|
|
|
_collect_array_option "$section" "$option"
|
|
|
|
[ -n "$_TOML_ARRAY" ] || return 0
|
|
printf '%s = [%s]\n' "$toml_key" "$_TOML_ARRAY"
|
|
}
|
|
|
|
_emit_kv_pair() {
|
|
local prefix="$1"
|
|
local line="$2"
|
|
local value_type="$3"
|
|
local key value toml_key
|
|
|
|
case "$line" in
|
|
*=*)
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
;;
|
|
*)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
key="$(_trim "$key")"
|
|
value="$(_trim "$value")"
|
|
|
|
[ -z "$key" ] && return 0
|
|
|
|
toml_key="${prefix}.$(_toml_key_quote "$key")"
|
|
|
|
case "$value_type" in
|
|
bool)
|
|
printf '%s = %s\n' "$toml_key" "$(_toml_bool "$value")"
|
|
;;
|
|
int|integer|number)
|
|
printf '%s = %s\n' "$toml_key" "$value"
|
|
;;
|
|
*)
|
|
printf '%s = %s\n' "$toml_key" "$(_toml_quote "$value")"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_KV_PREFIX=
|
|
_KV_TYPE=
|
|
_KV_HAS_LIST=0
|
|
|
|
_emit_kv_list_item() {
|
|
_KV_HAS_LIST=1
|
|
_emit_kv_pair "$_KV_PREFIX" "$1" "$_KV_TYPE"
|
|
}
|
|
|
|
_emit_kv_opt() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local prefix="$3"
|
|
local value_type="$4"
|
|
local scalar
|
|
|
|
_KV_PREFIX="$prefix"
|
|
_KV_TYPE="${value_type:-string}"
|
|
_KV_HAS_LIST=0
|
|
|
|
config_list_foreach "$section" "$option" _emit_kv_list_item
|
|
|
|
if [ "$_KV_HAS_LIST" = "0" ]; then
|
|
config_get scalar "$section" "$option"
|
|
[ -n "$scalar" ] && _emit_kv_pair "$prefix" "$scalar" "$value_type"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
_HEADERS_ARRAY=
|
|
_HEADERS_HAS_LIST=0
|
|
|
|
_headers_array_add() {
|
|
local line="$1"
|
|
local key value item
|
|
|
|
case "$line" in
|
|
*=*)
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
;;
|
|
*)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
key="$(_trim "$key")"
|
|
value="$(_trim "$value")"
|
|
|
|
[ -z "$key" ] && return 0
|
|
|
|
item="{ name = $(_toml_quote "$key"), value = $(_toml_quote "$value") }"
|
|
|
|
if [ -n "$_HEADERS_ARRAY" ]; then
|
|
_HEADERS_ARRAY="${_HEADERS_ARRAY}, "
|
|
fi
|
|
|
|
_HEADERS_ARRAY="${_HEADERS_ARRAY}${item}"
|
|
}
|
|
|
|
_collect_header_item() {
|
|
_HEADERS_HAS_LIST=1
|
|
_headers_array_add "$1"
|
|
}
|
|
|
|
_emit_headers_opt() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local toml_key="$3"
|
|
local scalar
|
|
|
|
_HEADERS_ARRAY=
|
|
_HEADERS_HAS_LIST=0
|
|
|
|
config_list_foreach "$section" "$option" _collect_header_item
|
|
|
|
if [ "$_HEADERS_HAS_LIST" = "0" ]; then
|
|
config_get scalar "$section" "$option"
|
|
[ -n "$scalar" ] && _headers_array_add "$scalar"
|
|
fi
|
|
|
|
[ -n "$_HEADERS_ARRAY" ] || return 0
|
|
printf '%s = [%s]\n' "$toml_key" "$_HEADERS_ARRAY"
|
|
}
|
|
|
|
_emit_name_value_array_opt() {
|
|
_emit_headers_opt "$@"
|
|
}
|
|
|
|
_RAW_HAS_LIST=0
|
|
|
|
_emit_raw_item() {
|
|
_RAW_HAS_LIST=1
|
|
[ -n "$1" ] || return 0
|
|
printf '%s\n' "$1"
|
|
}
|
|
|
|
_emit_raw_opt() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local scalar
|
|
|
|
_RAW_HAS_LIST=0
|
|
config_list_foreach "$section" "$option" _emit_raw_item
|
|
|
|
if [ "$_RAW_HAS_LIST" = "0" ]; then
|
|
config_get scalar "$section" "$option"
|
|
[ -n "$scalar" ] || return 0
|
|
printf '%s\n' "$scalar"
|
|
return $?
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
_emit_auth_scopes() {
|
|
local section="$1"
|
|
local hb nwc v
|
|
|
|
_collect_array_option "$section" auth_additional_scopes
|
|
|
|
if [ -z "$_TOML_ARRAY" ]; then
|
|
config_get hb "$section" authenticate_heartbeats
|
|
config_get nwc "$section" authenticate_new_work_conns
|
|
|
|
v="$(_toml_bool "$hb")"
|
|
[ "$v" = "true" ] && _toml_array_add "HeartBeats"
|
|
|
|
v="$(_toml_bool "$nwc")"
|
|
[ "$v" = "true" ] && _toml_array_add "NewWorkConns"
|
|
fi
|
|
|
|
[ -n "$_TOML_ARRAY" ] || return 0
|
|
printf 'auth.additionalScopes = [%s]\n' "$_TOML_ARRAY"
|
|
}
|
|
|
|
_emit_admin_web_tls() {
|
|
local section="$1"
|
|
local enabled cert key
|
|
|
|
config_get enabled "$section" admin_tls_enable
|
|
|
|
[ "$(_toml_bool "$enabled")" = "true" ] || return 0
|
|
|
|
config_get cert "$section" admin_tls_cert_file
|
|
config_get key "$section" admin_tls_key_file
|
|
|
|
if [ -z "$cert" ] || [ -z "$key" ]; then
|
|
_err "admin_tls_cert_file and admin_tls_key_file are required when admin_tls_enable is enabled"
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -r "$cert" ]; then
|
|
_err "admin TLS certificate file is not readable: $cert"
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -r "$key" ]; then
|
|
_err "admin TLS private key file is not readable: $key"
|
|
return 1
|
|
fi
|
|
|
|
_toml_line webServer.tls.certFile "$cert" string
|
|
_toml_line webServer.tls.keyFile "$key" string
|
|
|
|
return 0
|
|
}
|
|
|
|
_emit_admin_web() {
|
|
local section="$1"
|
|
local port addr
|
|
|
|
config_get port "$section" admin_port
|
|
|
|
# Empty or 0 means web server is disabled.
|
|
[ -n "$port" ] && [ "$port" != "0" ] || return 0
|
|
|
|
config_get addr "$section" admin_addr
|
|
_toml_line webServer.addr "${addr:-127.0.0.1}" string
|
|
_toml_line webServer.port "$port" port
|
|
_emit_opt "$section" admin_user webServer.user string
|
|
_emit_opt "$section" admin_pwd webServer.password string
|
|
_emit_admin_web_tls "$section" || return 1
|
|
_emit_opt "$section" assets_dir webServer.assetsDir string
|
|
_emit_opt "$section" pprof_enable webServer.pprofEnable bool
|
|
|
|
return 0
|
|
}
|
|
|
|
_emit_common() {
|
|
local section="common"
|
|
local method token token_source_type token_source_file_path
|
|
|
|
# Root options
|
|
_emit_opt "$section" client_id clientID string
|
|
_emit_opt "$section" user user string
|
|
_emit_opt "$section" server_addr serverAddr string
|
|
_emit_opt "$section" server_port serverPort port
|
|
_emit_opt "$section" nat_hole_stun_server natHoleStunServer string
|
|
_emit_opt "$section" login_fail_exit loginFailExit bool
|
|
_emit_opt "$section" dns_server dnsServer string
|
|
_emit_array_opt "$section" start start
|
|
_emit_opt "$section" udp_packet_size udpPacketSize int
|
|
_emit_array_opt "$section" includes includes
|
|
|
|
# Auth
|
|
config_get method "$section" authentication_method
|
|
config_get token "$section" token
|
|
config_get token_source_type "$section" token_source_type
|
|
config_get token_source_file_path "$section" token_source_file_path
|
|
|
|
[ -z "$method" ] && { [ -n "$token" ] || [ -n "$token_source_type" ]; } && method="token"
|
|
|
|
_toml_line auth.method "$method" string
|
|
|
|
if [ "$method" = "token" ] || [ -z "$method" ]; then
|
|
if [ -n "$token_source_type" ]; then
|
|
if [ -n "$token" ]; then
|
|
_err "token and token_source_type are mutually exclusive"
|
|
return 1
|
|
fi
|
|
|
|
case "$token_source_type" in
|
|
file)
|
|
if [ -z "$token_source_file_path" ]; then
|
|
_err "token_source_file_path is required when token_source_type=file"
|
|
return 1
|
|
fi
|
|
|
|
_toml_line auth.tokenSource.type "$token_source_type" string
|
|
_toml_line auth.tokenSource.file.path "$token_source_file_path" string
|
|
;;
|
|
|
|
exec)
|
|
local token_source_exec_command
|
|
|
|
config_get token_source_exec_command "$section" token_source_exec_command
|
|
if [ -z "$token_source_exec_command" ]; then
|
|
_err "token_source_exec_command is required when token_source_type=exec"
|
|
return 1
|
|
fi
|
|
|
|
_ALLOW_UNSAFE_TOKEN_SOURCE_EXEC=1
|
|
_toml_line auth.tokenSource.type "$token_source_type" string
|
|
_toml_line auth.tokenSource.exec.command "$token_source_exec_command" string
|
|
_emit_array_opt "$section" token_source_exec_args auth.tokenSource.exec.args
|
|
_emit_name_value_array_opt "$section" token_source_exec_env auth.tokenSource.exec.env
|
|
;;
|
|
|
|
*)
|
|
_err "unsupported token_source_type: $token_source_type"
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
_toml_line auth.token "$token" string
|
|
fi
|
|
fi
|
|
|
|
_emit_auth_scopes "$section"
|
|
|
|
if [ "$method" = "oidc" ]; then
|
|
_emit_opt "$section" oidc_client_id auth.oidc.clientID string
|
|
_emit_opt "$section" oidc_client_secret auth.oidc.clientSecret string
|
|
_emit_opt "$section" oidc_audience auth.oidc.audience string
|
|
_emit_opt "$section" oidc_scope auth.oidc.scope string
|
|
_emit_opt "$section" oidc_token_endpoint_url auth.oidc.tokenEndpointURL string
|
|
_emit_kv_opt "$section" oidc_additional_endpoint_params auth.oidc.additionalEndpointParams string
|
|
_emit_opt "$section" oidc_trusted_ca_file auth.oidc.trustedCaFile string
|
|
_emit_opt "$section" oidc_insecure_skip_verify auth.oidc.insecureSkipVerify bool
|
|
_emit_opt "$section" oidc_proxy_url auth.oidc.proxyURL string
|
|
fi
|
|
|
|
# Transport
|
|
_emit_opt "$section" dial_server_timeout transport.dialServerTimeout int
|
|
_emit_opt "$section" dial_server_keepalive transport.dialServerKeepalive int
|
|
_emit_opt "$section" http_proxy transport.proxyURL string
|
|
_emit_opt "$section" pool_count transport.poolCount int
|
|
_emit_opt "$section" tcp_mux transport.tcpMux bool
|
|
_emit_opt "$section" tcp_mux_keepalive_interval transport.tcpMuxKeepaliveInterval int
|
|
_emit_opt "$section" protocol transport.protocol string
|
|
_emit_opt "$section" wire_protocol transport.wireProtocol string
|
|
_emit_opt "$section" connect_server_local_ip transport.connectServerLocalIP string
|
|
_emit_opt "$section" heartbeat_interval transport.heartbeatInterval int
|
|
_emit_opt "$section" heartbeat_timeout transport.heartbeatTimeout int
|
|
|
|
# QUIC
|
|
_emit_opt "$section" quic_keepalive_period transport.quic.keepalivePeriod int
|
|
_emit_opt "$section" quic_max_idle_timeout transport.quic.maxIdleTimeout int
|
|
_emit_opt "$section" quic_max_incoming_streams transport.quic.maxIncomingStreams int
|
|
|
|
# TLS
|
|
_emit_opt "$section" tls_enable transport.tls.enable bool
|
|
_emit_opt "$section" tls_cert_file transport.tls.certFile string
|
|
_emit_opt "$section" tls_key_file transport.tls.keyFile string
|
|
_emit_opt "$section" tls_trusted_ca_file transport.tls.trustedCaFile string
|
|
_emit_opt "$section" tls_server_name transport.tls.serverName string
|
|
_emit_opt "$section" disable_custom_tls_first_byte transport.tls.disableCustomTLSFirstByte bool
|
|
|
|
# Web admin server
|
|
_emit_admin_web "$section" || return 1
|
|
|
|
# Feature gates / virtual net
|
|
_emit_kv_opt "$section" feature_gates featureGates bool
|
|
_emit_opt "$section" virtual_net_address virtualNet.address string
|
|
_emit_opt "$section" store_path store.path string
|
|
|
|
# Client metadata
|
|
_emit_kv_opt "$section" metadatas metadatas string
|
|
|
|
# Log
|
|
_emit_opt "$section" log_file log.to string
|
|
_emit_opt "$section" log_level log.level string
|
|
_emit_opt "$section" log_max_days log.maxDays int
|
|
_emit_opt "$section" disable_log_color log.disablePrintColor bool
|
|
|
|
# Raw extra TOML lines kept for manual UCI usage; LuCI intentionally hides this.
|
|
_emit_raw_opt "$section" _
|
|
}
|
|
|
|
_emit_plugin() {
|
|
local section="$1"
|
|
local plugin role
|
|
|
|
config_get plugin "$section" plugin
|
|
config_get role "$section" role
|
|
|
|
[ -z "$plugin" ] && return 0
|
|
|
|
_toml_line plugin.type "$plugin" string
|
|
|
|
case "$plugin" in
|
|
http_proxy)
|
|
_emit_opt "$section" plugin_http_user plugin.httpUser string
|
|
_emit_opt "$section" plugin_http_passwd plugin.httpPassword string
|
|
;;
|
|
|
|
socks5)
|
|
_emit_opt "$section" plugin_user plugin.username string
|
|
_emit_opt "$section" plugin_passwd plugin.password string
|
|
;;
|
|
|
|
unix_domain_socket)
|
|
_emit_opt "$section" plugin_unix_path plugin.unixPath string
|
|
;;
|
|
|
|
static_file)
|
|
_emit_opt "$section" plugin_local_path plugin.localPath string
|
|
_emit_opt "$section" plugin_strip_prefix plugin.stripPrefix string
|
|
_emit_opt "$section" plugin_http_user plugin.httpUser string
|
|
_emit_opt "$section" plugin_http_passwd plugin.httpPassword string
|
|
;;
|
|
|
|
https2http|https2https)
|
|
_emit_opt "$section" plugin_local_addr plugin.localAddr string
|
|
_emit_opt "$section" plugin_crt_path plugin.crtPath string
|
|
_emit_opt "$section" plugin_key_path plugin.keyPath string
|
|
_emit_opt "$section" plugin_host_header_rewrite plugin.hostHeaderRewrite string
|
|
_emit_opt "$section" plugin_enable_http2 plugin.enableHTTP2 bool
|
|
_emit_kv_opt "$section" plugin_request_headers plugin.requestHeaders.set string
|
|
;;
|
|
|
|
http2https|http2http)
|
|
_emit_opt "$section" plugin_local_addr plugin.localAddr string
|
|
_emit_opt "$section" plugin_host_header_rewrite plugin.hostHeaderRewrite string
|
|
_emit_kv_opt "$section" plugin_request_headers plugin.requestHeaders.set string
|
|
;;
|
|
|
|
tls2raw)
|
|
_emit_opt "$section" plugin_local_addr plugin.localAddr string
|
|
_emit_opt "$section" plugin_crt_path plugin.crtPath string
|
|
_emit_opt "$section" plugin_key_path plugin.keyPath string
|
|
;;
|
|
|
|
virtual_net)
|
|
[ "$role" = "visitor" ] && \
|
|
_emit_opt "$section" plugin_destination_ip plugin.destinationIP string
|
|
;;
|
|
esac
|
|
|
|
return 0
|
|
}
|
|
|
|
_emit_proxy() {
|
|
local section="$1"
|
|
local pname ptype plugin hct
|
|
|
|
[ "$section" = "common" ] && return 0
|
|
|
|
config_get pname "$section" name "$section"
|
|
config_get ptype "$section" type
|
|
config_get plugin "$section" plugin
|
|
|
|
[ -z "$ptype" ] && ptype="tcp"
|
|
|
|
printf '\n[[proxies]]\n'
|
|
_toml_line name "$pname" string
|
|
_toml_line type "$ptype" string
|
|
|
|
_emit_opt "$section" enabled enabled bool
|
|
|
|
# localIP/localPort are useful for normal local-service proxies.
|
|
# If plugin is enabled, plugin usually handles the local service itself.
|
|
if [ -z "$plugin" ]; then
|
|
case "$ptype" in
|
|
tcp|udp|http|https|stcp|xtcp|sudp|tcpmux)
|
|
_emit_opt "$section" local_ip localIP string
|
|
_emit_opt "$section" local_port localPort port
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# remotePort is mainly used by tcp/udp-style listeners.
|
|
# Plugin proxies with type=tcp also need remotePort.
|
|
case "$ptype" in
|
|
tcp|udp)
|
|
_emit_opt "$section" remote_port remotePort port0
|
|
;;
|
|
esac
|
|
|
|
# Common proxy transport options.
|
|
_emit_opt "$section" bandwidth_limit transport.bandwidthLimit string
|
|
_emit_opt "$section" bandwidth_limit_mode transport.bandwidthLimitMode string
|
|
_emit_opt "$section" use_encryption transport.useEncryption bool
|
|
_emit_opt "$section" use_compression transport.useCompression bool
|
|
_emit_opt "$section" proxy_protocol_version transport.proxyProtocolVersion string
|
|
|
|
# HTTP / HTTPS domain options.
|
|
case "$ptype" in
|
|
http|https)
|
|
_emit_array_opt "$section" custom_domains customDomains
|
|
_emit_opt "$section" subdomain subdomain string
|
|
;;
|
|
esac
|
|
|
|
# HTTP-only options. Skip these when plugin is enabled to avoid stale normal HTTP proxy fields.
|
|
case "$ptype" in
|
|
http)
|
|
_emit_opt "$section" route_by_http_user routeByHTTPUser string
|
|
if [ -z "$plugin" ]; then
|
|
_emit_array_opt "$section" locations locations
|
|
_emit_opt "$section" http_user httpUser string
|
|
_emit_opt "$section" http_pwd httpPassword string
|
|
_emit_opt "$section" host_header_rewrite hostHeaderRewrite string
|
|
_emit_kv_opt "$section" request_headers requestHeaders.set string
|
|
_emit_kv_opt "$section" response_headers responseHeaders.set string
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# TCPMUX options.
|
|
case "$ptype" in
|
|
tcpmux)
|
|
_emit_array_opt "$section" custom_domains customDomains
|
|
_emit_opt "$section" http_user httpUser string
|
|
_emit_opt "$section" http_pwd httpPassword string
|
|
_emit_opt "$section" multiplexer multiplexer string
|
|
_emit_opt "$section" route_by_http_user routeByHTTPUser string
|
|
;;
|
|
esac
|
|
|
|
# Load balancer options.
|
|
_emit_opt "$section" group loadBalancer.group string
|
|
_emit_opt "$section" group_key loadBalancer.groupKey string
|
|
|
|
# Health check options.
|
|
config_get hct "$section" health_check_type
|
|
if [ -n "$hct" ] && [ -z "$plugin" ]; then
|
|
case "$ptype" in
|
|
tcp|http|https|tcpmux)
|
|
_emit_opt "$section" health_check_type healthCheck.type string
|
|
_emit_opt "$section" health_check_timeout_s healthCheck.timeoutSeconds int
|
|
_emit_opt "$section" health_check_max_failed healthCheck.maxFailed int
|
|
_emit_opt "$section" health_check_interval_s healthCheck.intervalSeconds int
|
|
|
|
if [ "$hct" = "http" ]; then
|
|
_emit_opt "$section" health_check_url healthCheck.path string
|
|
_emit_headers_opt "$section" health_check_headers healthCheck.httpHeaders
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# STCP / XTCP / SUDP server options.
|
|
case "$ptype" in
|
|
stcp|xtcp|sudp)
|
|
_emit_opt "$section" sk secretKey string
|
|
_emit_array_opt "$section" allow_users allowUsers
|
|
;;
|
|
esac
|
|
|
|
# NAT traversal options are mainly useful for XTCP.
|
|
case "$ptype" in
|
|
xtcp)
|
|
_emit_opt "$section" nat_disable_assisted_addrs natTraversal.disableAssistedAddrs bool
|
|
;;
|
|
esac
|
|
|
|
# Metadata / annotations.
|
|
_emit_kv_opt "$section" metadatas metadatas string
|
|
_emit_kv_opt "$section" annotations annotations string
|
|
|
|
# Plugin options are filtered by plugin type in _emit_plugin().
|
|
_emit_plugin "$section"
|
|
|
|
# Raw extra TOML lines.
|
|
_emit_raw_opt "$section" _
|
|
}
|
|
|
|
_emit_visitor() {
|
|
local section="$1"
|
|
local vname vtype
|
|
|
|
config_get vname "$section" name "$section"
|
|
config_get vtype "$section" type
|
|
|
|
[ -z "$vtype" ] && vtype="stcp"
|
|
|
|
printf '\n[[visitors]]\n'
|
|
_toml_line name "$vname" string
|
|
_toml_line type "$vtype" string
|
|
|
|
_emit_opt "$section" server_user serverUser string
|
|
_emit_opt "$section" server_name serverName string
|
|
_emit_opt "$section" sk secretKey string
|
|
_emit_opt "$section" bind_addr bindAddr string
|
|
_emit_opt "$section" bind_port bindPort visitor_bind_port
|
|
_emit_opt "$section" enabled enabled bool
|
|
_emit_opt "$section" use_encryption transport.useEncryption bool
|
|
_emit_opt "$section" use_compression transport.useCompression bool
|
|
|
|
# XTCP visitor-specific options.
|
|
case "$vtype" in
|
|
xtcp)
|
|
_emit_opt "$section" visitor_protocol protocol string
|
|
_emit_opt "$section" keep_tunnel_open keepTunnelOpen bool
|
|
_emit_opt "$section" max_retries_an_hour maxRetriesAnHour int
|
|
_emit_opt "$section" min_retry_interval minRetryInterval int
|
|
_emit_opt "$section" fallback_to fallbackTo string
|
|
_emit_opt "$section" fallback_timeout_ms fallbackTimeoutMs int
|
|
_emit_opt "$section" nat_disable_assisted_addrs natTraversal.disableAssistedAddrs bool
|
|
;;
|
|
esac
|
|
|
|
# Visitor plugin, for example virtual_net.
|
|
_emit_plugin "$section"
|
|
|
|
# Raw extra TOML lines.
|
|
_emit_raw_opt "$section" _
|
|
}
|
|
|
|
_emit_conf_section() {
|
|
local section="$1"
|
|
local role ptype
|
|
|
|
[ "$section" = "common" ] && return 0
|
|
|
|
config_get role "$section" role
|
|
config_get ptype "$section" type
|
|
|
|
if [ "$role" = "visitor" ]; then
|
|
case "$ptype" in
|
|
''|stcp|xtcp|sudp)
|
|
_emit_visitor "$section"
|
|
return $?
|
|
;;
|
|
*)
|
|
_err "visitor section $section has unsupported type: $ptype"
|
|
_TOML_ERR=1
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
_emit_proxy "$section"
|
|
}
|
|
|
|
_find_init_section() {
|
|
[ -z "$init_cfg" ] && init_cfg="$1"
|
|
return 0
|
|
}
|
|
|
|
_append_conf_file() {
|
|
local file="$1"
|
|
local dir="/etc/frp/$NAME.d/"
|
|
local real_file
|
|
|
|
case "$file" in
|
|
"$dir"*.toml)
|
|
case "$file" in
|
|
*../*)
|
|
_err "additional config file outside $dir is not allowed: $file"
|
|
_TOML_ERR=1
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
_err "additional config file must be under $dir and end with .toml: $file"
|
|
_TOML_ERR=1
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
[ -r "$file" ] || {
|
|
_err "additional config file not readable: $file"
|
|
_TOML_ERR=1
|
|
return 1
|
|
}
|
|
|
|
real_file="$(readlink -f "$file")" || {
|
|
_err "additional config file path cannot be resolved: $file"
|
|
_TOML_ERR=1
|
|
return 1
|
|
}
|
|
|
|
case "$real_file" in
|
|
"$dir"*.toml)
|
|
;;
|
|
*)
|
|
_err "additional config file resolves outside $dir or is not .toml: $file"
|
|
_TOML_ERR=1
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
printf '\n' >> "$CONF_FILE"
|
|
cat "$real_file" >> "$CONF_FILE"
|
|
printf '\n' >> "$CONF_FILE"
|
|
}
|
|
|
|
_watch_conf_file() {
|
|
local file="$1"
|
|
|
|
[ -r "$file" ] && procd_set_param file "$file"
|
|
return 0
|
|
}
|
|
|
|
_append_env() {
|
|
procd_append_param env "$1"
|
|
}
|
|
|
|
service_triggers() {
|
|
procd_add_reload_trigger "$NAME"
|
|
}
|
|
|
|
start_service() {
|
|
local init_cfg=
|
|
local stdout=1
|
|
local stderr=1
|
|
local respawn=1
|
|
local run_user=
|
|
local run_group=
|
|
local old_umask
|
|
|
|
mkdir -p /var/etc
|
|
_TOML_ERR=0
|
|
_ALLOW_UNSAFE_TOKEN_SOURCE_EXEC=0
|
|
|
|
config_load "$NAME"
|
|
|
|
config_foreach _find_init_section init
|
|
|
|
old_umask="$(umask)"
|
|
umask 077
|
|
: > "$CONF_FILE" || {
|
|
umask "$old_umask"
|
|
_err "failed to create $CONF_FILE"
|
|
return 1
|
|
}
|
|
umask "$old_umask"
|
|
chmod 600 "$CONF_FILE" || {
|
|
_err "failed to chmod $CONF_FILE"
|
|
return 1
|
|
}
|
|
|
|
{
|
|
printf '# This file is automatically generated from /etc/config/%s.\n' "$NAME"
|
|
printf '# Do not edit this file directly.\n\n'
|
|
|
|
_emit_common
|
|
} >> "$CONF_FILE" || return 1
|
|
[ "$_TOML_ERR" = "0" ] || return 1
|
|
|
|
if [ -n "$init_cfg" ]; then
|
|
config_list_foreach "$init_cfg" conf_inc _append_conf_file
|
|
|
|
config_get_bool stdout "$init_cfg" stdout 1
|
|
config_get_bool stderr "$init_cfg" stderr 1
|
|
config_get_bool respawn "$init_cfg" respawn 1
|
|
config_get run_user "$init_cfg" user
|
|
config_get run_group "$init_cfg" group
|
|
fi
|
|
|
|
{
|
|
config_foreach _emit_conf_section conf
|
|
} >> "$CONF_FILE" || return 1
|
|
[ "$_TOML_ERR" = "0" ] || return 1
|
|
|
|
if [ -n "$run_user" ]; then
|
|
chown "$run_user${run_group:+:$run_group}" "$CONF_FILE" 2>/dev/null || {
|
|
_err "failed to chown $CONF_FILE to $run_user${run_group:+:$run_group}"
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
chmod 600 "$CONF_FILE" || {
|
|
_err "failed to chmod $CONF_FILE"
|
|
return 1
|
|
}
|
|
|
|
procd_open_instance
|
|
if [ "$_ALLOW_UNSAFE_TOKEN_SOURCE_EXEC" = "1" ]; then
|
|
procd_set_param command "$PROG" -c "$CONF_FILE" --allow-unsafe=TokenSourceExec
|
|
else
|
|
procd_set_param command "$PROG" -c "$CONF_FILE"
|
|
fi
|
|
procd_set_param file "$CONF_FILE"
|
|
procd_set_param file "/etc/config/$NAME"
|
|
|
|
if [ -n "$init_cfg" ]; then
|
|
config_list_foreach "$init_cfg" conf_inc _watch_conf_file
|
|
fi
|
|
|
|
procd_set_param stdout "$stdout"
|
|
procd_set_param stderr "$stderr"
|
|
|
|
[ -n "$run_user" ] && procd_set_param user "$run_user"
|
|
[ -n "$run_group" ] && procd_set_param group "$run_group"
|
|
[ "$respawn" -eq 1 ] && procd_set_param respawn
|
|
|
|
if [ -n "$init_cfg" ]; then
|
|
config_list_foreach "$init_cfg" env _append_env
|
|
fi
|
|
|
|
procd_close_instance
|
|
}
|