// Package render generates frpc worker configuration (JSON) from canvas data. package render import ( "encoding/json" "fmt" "webui4frpc/internal/store" ) // Proxy is a single proxy to render inside a worker config. It carries enough // info to build any supported frpc proxy type. type Proxy struct { // Name is the display name (the local service name). Name string // Type is the proxy type: tcp | udp | http | https. Type string // LocalIP and LocalPort are the backend address. LocalIP string LocalPort int // RemotePort is the exposed port on the frps server (tcp/udp only). RemotePort int // SubDomain is used by http/https proxies when set. SubDomain string // CustomDomains is used by http/https proxies when set. CustomDomains []string // Advanced transport knobs (M1): proxy-level encryption/compression/limit. UseEncryption bool UseCompression bool BandwidthLimit string PoolCount int Metadatas map[string]string Annotations map[string]string } // frpcAuth mirrors frpc's auth section. type frpcAuth struct { Method string `json:"method"` Token string `json:"token,omitempty"` } // frpcTransport is the transport section used by frpc. type frpcTransport struct { Protocol string `json:"protocol,omitempty"` TLS *frpcTLS `json:"tls,omitempty"` PoolCount int `json:"poolCount,omitempty"` } // frpcTLS mirrors frpc's transport.tls section. type frpcTLS struct { Enable bool `json:"enable,omitempty"` ServerName string `json:"serverName,omitempty"` } // frpcProxy is the serialized proxy object. Different types use different // fields; empty ones are omitted so the JSON is accepted by frpc. type frpcProxy struct { Name string `json:"name"` Type string `json:"type"` LocalIP string `json:"localIP,omitempty"` LocalPort int `json:"localPort"` RemotePort int `json:"remotePort,omitempty"` CustomDomains []string `json:"customDomains,omitempty"` SubDomain string `json:"subdomain,omitempty"` // M1 advanced transport fields. UseEncryption bool `json:"useEncryption,omitempty"` UseCompression bool `json:"useCompression,omitempty"` BandwidthLimit string `json:"bandwidthLimit,omitempty"` PoolCount int `json:"poolCount,omitempty"` Metadatas map[string]string `json:"metadatas,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` } // Config is a complete frpc configuration document. type Config struct { ServerAddr string `json:"serverAddr"` ServerPort int `json:"serverPort"` Auth frpcAuth `json:"auth"` Transport frpcTransport `json:"transport,omitempty"` LoginFailExit bool `json:"loginFailExit"` Proxies []frpcProxy `json:"proxies"` } // Render builds the worker config JSON for a remote server from its setting // plus the list of proxies to forward. func Render(remote store.Remote, proxies []Proxy) ([]byte, error) { cfg := Config{ ServerAddr: remote.IP, ServerPort: remote.Port, Auth: frpcAuth{Method: "token", Token: remote.Token}, LoginFailExit: false, Proxies: make([]frpcProxy, 0, len(proxies)), } if remote.TransportProtocol != "" || remote.TransportTLS || remote.TransportPool > 0 || remote.TransportTLSServerName != "" { cfg.Transport = frpcTransport{ Protocol: remote.TransportProtocol, PoolCount: remote.TransportPool, } if remote.TransportTLS || remote.TransportTLSServerName != "" { cfg.Transport.TLS = &frpcTLS{ Enable: remote.TransportTLS, ServerName: remote.TransportTLSServerName, } } } nameCounts := make(map[string]int) for _, p := range proxies { frpcP := frpcProxy{ Name: p.Name, Type: p.Type, LocalIP: p.LocalIP, LocalPort: p.LocalPort, SubDomain: p.SubDomain, UseEncryption: p.UseEncryption, UseCompression: p.UseCompression, BandwidthLimit: p.BandwidthLimit, PoolCount: p.PoolCount, Metadatas: p.Metadatas, Annotations: p.Annotations, } // http/https proxies use customDomains instead of remotePort. The // default domain is .local; frps matches it by Host header. if p.Type == "http" || p.Type == "https" { frpcP.RemotePort = 0 if frpcP.SubDomain == "" && len(frpcP.CustomDomains) == 0 { frpcP.CustomDomains = []string{p.Name + ".local"} } } else { frpcP.RemotePort = p.RemotePort } if len(p.CustomDomains) > 0 { frpcP.CustomDomains = p.CustomDomains } // Duplicate service name -> suffix with the remote port so frpc accepts // multiple proxies for the same local service. nameCounts[frpcP.Name]++ if nameCounts[frpcP.Name] > 1 { frpcP.Name = fmt.Sprintf("%s-%d", frpcP.Name, frpcP.RemotePort) } cfg.Proxies = append(cfg.Proxies, frpcP) } return json.MarshalIndent(cfg, "", " ") }