Files
Havline/internal/frp-1.0.2/config.go
T
mrlee 278f8267b7 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
2026-09-23 09:40:59 +08:00

209 lines
5.3 KiB
Go

package frp
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/fonu/fonu/internal/secret"
"github.com/fonu/fonu/internal/settings"
)
const (
maskedToken = "********"
)
type Config struct {
Enabled bool `json:"enabled"`
ServerAddr string `json:"server_addr"`
ServerPort int `json:"server_port"`
AuthToken string `json:"auth_token"`
HasAuthToken bool `json:"has_auth_token"`
TLSEnabled bool `json:"tls_enabled"`
CustomDomains []string `json:"custom_domains"`
}
type SaveInput struct {
Enabled bool
ServerAddr string
ServerPort int
AuthToken string
TLSEnabled bool
CustomDomains []string
}
type Store struct {
settings *settings.Store
secretBox *secret.Box
}
func NewStore(settingsStore *settings.Store, secretBox *secret.Box) *Store {
return &Store{settings: settingsStore, secretBox: secretBox}
}
func (s *Store) Load(ctx context.Context) (Config, error) {
enabled, _ := s.settings.GetBool(ctx, settings.KeyFRPEnabled)
serverAddr, _ := s.settings.Get(ctx, settings.KeyFRPServerAddr)
serverPort, _ := s.settings.GetInt(ctx, settings.KeyFRPServerPort)
if serverPort <= 0 {
serverPort = 7000
}
tlsEnabled, _ := s.settings.GetBool(ctx, settings.KeyFRPTLSEnabled)
domains, err := s.loadDomains(ctx)
if err != nil {
return Config{}, err
}
encToken, _ := s.settings.Get(ctx, settings.KeyFRPAuthToken)
hasToken := strings.TrimSpace(encToken) != ""
token := ""
if hasToken {
token = maskedToken
}
return Config{
Enabled: enabled,
ServerAddr: serverAddr,
ServerPort: serverPort,
AuthToken: token,
HasAuthToken: hasToken,
TLSEnabled: tlsEnabled,
CustomDomains: domains,
}, nil
}
func (s *Store) LoadRuntime(ctx context.Context) (Config, string, error) {
cfg, err := s.Load(ctx)
if err != nil {
return Config{}, "", err
}
if !cfg.HasAuthToken {
return cfg, "", nil
}
enc, _ := s.settings.Get(ctx, settings.KeyFRPAuthToken)
plain, err := s.secretBox.Decrypt(enc)
if err != nil {
return cfg, "", secret.DecryptHint(err)
}
return cfg, plain, nil
}
func (s *Store) Save(ctx context.Context, in SaveInput, keepToken bool) error {
if err := validateSaveInput(in, keepToken); err != nil {
return err
}
if err := s.settings.SetBool(ctx, settings.KeyFRPEnabled, in.Enabled); err != nil {
return err
}
if err := s.settings.Set(ctx, settings.KeyFRPServerAddr, normalizeServerAddr(in.ServerAddr)); err != nil {
return err
}
port := in.ServerPort
if port <= 0 {
port = 7000
}
if err := s.settings.SetInt(ctx, settings.KeyFRPServerPort, port); err != nil {
return err
}
if err := s.settings.SetBool(ctx, settings.KeyFRPTLSEnabled, in.TLSEnabled); err != nil {
return err
}
domains := normalizeDomains(in.CustomDomains)
raw, err := json.Marshal(domains)
if err != nil {
return err
}
if err := s.settings.Set(ctx, settings.KeyFRPCustomDomains, string(raw)); err != nil {
return err
}
token := strings.TrimSpace(in.AuthToken)
if token != "" && token != maskedToken {
enc, err := s.secretBox.Encrypt(token)
if err != nil {
return err
}
if err := s.settings.Set(ctx, settings.KeyFRPAuthToken, enc); err != nil {
return err
}
} else if in.Enabled && !keepToken {
return fmt.Errorf("请填写 FRP 认证 Token")
}
return nil
}
func (s *Store) SetLastError(ctx context.Context, msg string) error {
return s.settings.Set(ctx, settings.KeyFRPLastError, strings.TrimSpace(msg))
}
func (s *Store) LastError(ctx context.Context) string {
v, _ := s.settings.Get(ctx, settings.KeyFRPLastError)
return strings.TrimSpace(v)
}
func (s *Store) StartedAt(ctx context.Context) string {
v, _ := s.settings.Get(ctx, settings.KeyFRPStartedAt)
return strings.TrimSpace(v)
}
func (s *Store) SetStartedAt(ctx context.Context, value string) error {
return s.settings.Set(ctx, settings.KeyFRPStartedAt, strings.TrimSpace(value))
}
func (s *Store) loadDomains(ctx context.Context) ([]string, error) {
raw, _ := s.settings.Get(ctx, settings.KeyFRPCustomDomains)
raw = strings.TrimSpace(raw)
if raw == "" {
return nil, nil
}
var domains []string
if err := json.Unmarshal([]byte(raw), &domains); err != nil {
return nil, fmt.Errorf("FRP 域名配置无效")
}
return normalizeDomains(domains), nil
}
func normalizeServerAddr(addr string) string {
addr = strings.TrimSpace(addr)
addr = strings.TrimPrefix(addr, "https://")
addr = strings.TrimPrefix(addr, "http://")
if idx := strings.IndexAny(addr, "/?#"); idx >= 0 {
addr = addr[:idx]
}
return strings.TrimSpace(addr)
}
func normalizeDomains(domains []string) []string {
seen := make(map[string]bool)
var out []string
for _, d := range domains {
d = strings.TrimSpace(d)
if d == "" || seen[strings.ToLower(d)] {
continue
}
seen[strings.ToLower(d)] = true
out = append(out, d)
}
return out
}
func validateSaveInput(in SaveInput, keepToken bool) error {
if !in.Enabled {
return nil
}
if strings.TrimSpace(in.ServerAddr) == "" {
return fmt.Errorf("请填写 FRP 服务器地址")
}
if in.ServerPort < 0 || in.ServerPort > 65535 {
return fmt.Errorf("FRP 服务器端口无效")
}
if len(normalizeDomains(in.CustomDomains)) == 0 {
return fmt.Errorf("请至少填写一个穿透域名")
}
token := strings.TrimSpace(in.AuthToken)
if token == "" || token == maskedToken {
if !keepToken {
return fmt.Errorf("请填写 FRP 认证 Token")
}
}
return nil
}