chore: 初始提交 Fonu 1.0.2
- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听) - 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP) - 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点) - 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
package frp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type renderedConfig struct {
|
||||
Server Server
|
||||
Token string
|
||||
OIDCClientSecret string
|
||||
LogPath string
|
||||
TLSCertPath string
|
||||
TLSKeyPath string
|
||||
TLSTrustedCAPath string
|
||||
Proxies []Proxy
|
||||
}
|
||||
|
||||
func renderTOML(cfg renderedConfig) string {
|
||||
var out strings.Builder
|
||||
writeKeyValue(&out, "serverAddr", cfg.Server.ServerAddr)
|
||||
writeKeyValue(&out, "serverPort", cfg.Server.ServerPort)
|
||||
options := normalizeServerOptions(cfg.Server.Options)
|
||||
if options.User != "" {
|
||||
writeKeyValue(&out, "user", options.User)
|
||||
}
|
||||
if options.AuthMethod != "none" {
|
||||
writeKeyValue(&out, "auth.method", options.AuthMethod)
|
||||
}
|
||||
if options.AuthMethod == "token" {
|
||||
writeKeyValue(&out, "auth.token", cfg.Token)
|
||||
}
|
||||
if options.AuthMethod == "oidc" {
|
||||
writeKeyValue(&out, "auth.oidc.clientID", options.OIDCClientID)
|
||||
writeKeyValue(&out, "auth.oidc.clientSecret", cfg.OIDCClientSecret)
|
||||
writeKeyValue(&out, "auth.oidc.tokenEndpointURL", options.OIDCTokenEndpointURL)
|
||||
if options.OIDCAudience != "" {
|
||||
writeKeyValue(&out, "auth.oidc.audience", options.OIDCAudience)
|
||||
}
|
||||
if options.OIDCScope != "" {
|
||||
writeKeyValue(&out, "auth.oidc.scope", options.OIDCScope)
|
||||
}
|
||||
}
|
||||
if cfg.LogPath != "" {
|
||||
writeKeyValue(&out, "log.to", cfg.LogPath)
|
||||
}
|
||||
writeKeyValue(&out, "log.level", options.LogLevel)
|
||||
writeKeyValue(&out, "log.maxDays", options.LogMaxDays)
|
||||
writeKeyValue(&out, "transport.protocol", options.Protocol)
|
||||
writeKeyValue(&out, "transport.tls.enable", cfg.Server.TLS)
|
||||
writeKeyValue(&out, "transport.tls.disableCustomTLSFirstByte", options.TLSDisableCustomFirstByte)
|
||||
if cfg.Server.TLSServerName != "" {
|
||||
writeKeyValue(&out, "transport.tls.serverName", cfg.Server.TLSServerName)
|
||||
}
|
||||
if cfg.TLSCertPath != "" {
|
||||
writeKeyValue(&out, "transport.tls.certFile", cfg.TLSCertPath)
|
||||
}
|
||||
if cfg.TLSKeyPath != "" {
|
||||
writeKeyValue(&out, "transport.tls.keyFile", cfg.TLSKeyPath)
|
||||
}
|
||||
if cfg.TLSTrustedCAPath != "" {
|
||||
writeKeyValue(&out, "transport.tls.trustedCaFile", cfg.TLSTrustedCAPath)
|
||||
}
|
||||
if options.ProxyURL != "" {
|
||||
writeKeyValue(&out, "transport.proxyURL", options.ProxyURL)
|
||||
}
|
||||
writeKeyValue(&out, "transport.dialServerTimeout", options.DialServerTimeout)
|
||||
writeKeyValue(&out, "transport.dialServerKeepalive", options.DialServerKeepalive)
|
||||
if options.ConnectServerLocalIP != "" {
|
||||
writeKeyValue(&out, "transport.connectServerLocalIP", options.ConnectServerLocalIP)
|
||||
}
|
||||
if options.DNSServer != "" {
|
||||
writeKeyValue(&out, "transport.dnsServer", options.DNSServer)
|
||||
}
|
||||
writeKeyValue(&out, "transport.poolCount", options.PoolCount)
|
||||
writeKeyValue(&out, "transport.tcpMux", options.TCPMux)
|
||||
writeKeyValue(&out, "transport.tcpMuxKeepaliveInterval", options.TCPMuxKeepaliveInterval)
|
||||
writeKeyValue(&out, "transport.heartbeatInterval", options.HeartbeatInterval)
|
||||
writeKeyValue(&out, "transport.heartbeatTimeout", options.HeartbeatTimeout)
|
||||
writeKeyValue(&out, "loginFailExit", options.LoginFailExit)
|
||||
|
||||
for _, proxy := range cfg.Proxies {
|
||||
out.WriteString("\n[[proxies]]\n")
|
||||
writeKeyValue(&out, "name", proxy.Name)
|
||||
writeKeyValue(&out, "type", proxy.Type)
|
||||
writeKeyValue(&out, "localIP", proxy.LocalIP)
|
||||
writeKeyValue(&out, "localPort", proxy.LocalPort)
|
||||
if proxy.Type == "tcp" && proxy.RemotePort != nil {
|
||||
writeKeyValue(&out, "remotePort", *proxy.RemotePort)
|
||||
}
|
||||
if proxy.Type == "udp" && proxy.RemotePort != nil {
|
||||
writeKeyValue(&out, "remotePort", *proxy.RemotePort)
|
||||
}
|
||||
if proxy.Type == "http" || proxy.Type == "https" || proxy.Type == "tcpmux" {
|
||||
writeStringSlice(&out, "customDomains", proxy.CustomDomains)
|
||||
}
|
||||
if proxy.HostHeaderRewrite != "" {
|
||||
writeKeyValue(&out, "hostHeaderRewrite", proxy.HostHeaderRewrite)
|
||||
}
|
||||
writeProxyOptions(&out, proxy)
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func writeProxyOptions(out *strings.Builder, proxy Proxy) {
|
||||
opts := proxy.Options
|
||||
if opts.Subdomain != "" {
|
||||
writeKeyValue(out, "subdomain", opts.Subdomain)
|
||||
}
|
||||
if len(opts.Locations) > 0 {
|
||||
writeStringSlice(out, "locations", opts.Locations)
|
||||
}
|
||||
if opts.RouteByHTTPUser != "" {
|
||||
writeKeyValue(out, "routeByHTTPUser", opts.RouteByHTTPUser)
|
||||
}
|
||||
if opts.HTTPUser != "" {
|
||||
writeKeyValue(out, "httpUser", opts.HTTPUser)
|
||||
}
|
||||
if opts.HTTPPassword != "" {
|
||||
writeKeyValue(out, "httpPassword", opts.HTTPPassword)
|
||||
}
|
||||
if opts.Multiplexer != "" {
|
||||
writeKeyValue(out, "multiplexer", opts.Multiplexer)
|
||||
}
|
||||
if opts.SecretKey != "" {
|
||||
writeKeyValue(out, "secretKey", opts.SecretKey)
|
||||
}
|
||||
if len(opts.AllowUsers) > 0 {
|
||||
writeStringSlice(out, "allowUsers", opts.AllowUsers)
|
||||
}
|
||||
writeProxyTransport(out, opts.Transport)
|
||||
writeProxyHealthCheck(out, opts.HealthCheck)
|
||||
if opts.LoadBalancer.Group != "" {
|
||||
writeKeyValue(out, "loadBalancer.group", opts.LoadBalancer.Group)
|
||||
}
|
||||
if opts.LoadBalancer.GroupKey != "" {
|
||||
writeKeyValue(out, "loadBalancer.groupKey", opts.LoadBalancer.GroupKey)
|
||||
}
|
||||
writeStringMap(out, "metadatas", opts.Metadatas)
|
||||
writeStringMap(out, "requestHeaders.set", opts.RequestHeaders)
|
||||
writeStringMap(out, "responseHeaders.set", opts.ResponseHeaders)
|
||||
// natTraversal 是较新版本 frpc 才支持的字段,旧版 frpc 会把未知字段判为校验错误;
|
||||
// disableAssistedAddrs=false 与 frp 默认行为等价,因此仅在显式禁用时写入。
|
||||
if opts.NATTraversal != nil && opts.NATTraversal.DisableAssistedAddrs {
|
||||
writeKeyValue(out, "natTraversal.disableAssistedAddrs", true)
|
||||
}
|
||||
if opts.Plugin != nil {
|
||||
writeProxyPlugin(out, *opts.Plugin)
|
||||
}
|
||||
}
|
||||
|
||||
func writeProxyTransport(out *strings.Builder, opts ProxyTransportOptions) {
|
||||
if opts.BandwidthLimit != "" {
|
||||
writeKeyValue(out, "transport.bandwidthLimit", opts.BandwidthLimit)
|
||||
}
|
||||
if opts.BandwidthLimitMode != "" {
|
||||
writeKeyValue(out, "transport.bandwidthLimitMode", opts.BandwidthLimitMode)
|
||||
}
|
||||
if opts.UseEncryption {
|
||||
writeKeyValue(out, "transport.useEncryption", true)
|
||||
}
|
||||
if opts.UseCompression {
|
||||
writeKeyValue(out, "transport.useCompression", true)
|
||||
}
|
||||
if opts.ProxyProtocolVersion != "" {
|
||||
writeKeyValue(out, "transport.proxyProtocolVersion", opts.ProxyProtocolVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func writeProxyHealthCheck(out *strings.Builder, opts ProxyHealthCheckOptions) {
|
||||
if opts.Type == "" {
|
||||
return
|
||||
}
|
||||
writeKeyValue(out, "healthCheck.type", opts.Type)
|
||||
if opts.Path != "" {
|
||||
writeKeyValue(out, "healthCheck.path", opts.Path)
|
||||
}
|
||||
if opts.IntervalSeconds > 0 {
|
||||
writeKeyValue(out, "healthCheck.intervalSeconds", opts.IntervalSeconds)
|
||||
}
|
||||
if opts.MaxFailed > 0 {
|
||||
writeKeyValue(out, "healthCheck.maxFailed", opts.MaxFailed)
|
||||
}
|
||||
if opts.TimeoutSeconds > 0 {
|
||||
writeKeyValue(out, "healthCheck.timeoutSeconds", opts.TimeoutSeconds)
|
||||
}
|
||||
if len(opts.HTTPHeaders) > 0 {
|
||||
values := make([]string, 0, len(opts.HTTPHeaders))
|
||||
for _, header := range opts.HTTPHeaders {
|
||||
values = append(values, fmt.Sprintf("{ name = %s, value = %s }", strconv.Quote(header.Name), strconv.Quote(header.Value)))
|
||||
}
|
||||
fmt.Fprintf(out, "healthCheck.httpHeaders = [%s]\n", strings.Join(values, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func writeProxyPlugin(out *strings.Builder, plugin ProxyPluginOptions) {
|
||||
out.WriteString("[proxies.plugin]\n")
|
||||
writeKeyValue(out, "type", plugin.Type)
|
||||
for _, item := range []struct{ key, value string }{
|
||||
{"unixPath", plugin.UnixPath}, {"localPath", plugin.LocalPath}, {"stripPrefix", plugin.StripPrefix},
|
||||
{"localAddr", plugin.LocalAddr}, {"httpUser", plugin.HTTPUser}, {"httpPassword", plugin.HTTPPassword},
|
||||
{"username", plugin.Username}, {"password", plugin.Password}, {"crtPath", plugin.CRTPath}, {"keyPath", plugin.KeyPath},
|
||||
{"hostHeaderRewrite", plugin.HostHeaderRewrite}, {"destinationIP", plugin.DestinationIP},
|
||||
} {
|
||||
if item.value != "" {
|
||||
writeKeyValue(out, item.key, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeStringMap(out *strings.Builder, prefix string, values map[string]string) {
|
||||
keys := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
if key != "" {
|
||||
writeKeyValue(out, prefix+"."+key, values[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderMaskedTOML(server Server, proxies []Proxy) string {
|
||||
return renderTOML(renderedConfig{Server: server, Token: "********", OIDCClientSecret: "********", Proxies: proxies})
|
||||
}
|
||||
|
||||
func writeKeyValue(out *strings.Builder, key string, value any) {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
fmt.Fprintf(out, "%s = %s\n", key, strconv.Quote(v))
|
||||
case int:
|
||||
fmt.Fprintf(out, "%s = %d\n", key, v)
|
||||
case bool:
|
||||
fmt.Fprintf(out, "%s = %t\n", key, v)
|
||||
}
|
||||
}
|
||||
|
||||
func writeStringSlice(out *strings.Builder, key string, values []string) {
|
||||
quoted := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
quoted = append(quoted, strconv.Quote(value))
|
||||
}
|
||||
fmt.Fprintf(out, "%s = [%s]\n", key, strings.Join(quoted, ", "))
|
||||
}
|
||||
Reference in New Issue
Block a user