mirror of
https://github.com/fatedier/frp.git
synced 2026-03-08 02:49:10 +08:00
368 lines
8.2 KiB
Go
368 lines
8.2 KiB
Go
// Copyright 2026 The frp Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package source
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
|
"github.com/fatedier/frp/pkg/util/jsonx"
|
|
)
|
|
|
|
type StoreSourceConfig struct {
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
type storeData struct {
|
|
Proxies []v1.TypedProxyConfig `json:"proxies,omitempty"`
|
|
Visitors []v1.TypedVisitorConfig `json:"visitors,omitempty"`
|
|
}
|
|
|
|
type StoreSource struct {
|
|
baseSource
|
|
config StoreSourceConfig
|
|
}
|
|
|
|
var (
|
|
ErrAlreadyExists = errors.New("already exists")
|
|
ErrNotFound = errors.New("not found")
|
|
)
|
|
|
|
func NewStoreSource(cfg StoreSourceConfig) (*StoreSource, error) {
|
|
if cfg.Path == "" {
|
|
return nil, fmt.Errorf("path is required")
|
|
}
|
|
|
|
s := &StoreSource{
|
|
baseSource: newBaseSource(),
|
|
config: cfg,
|
|
}
|
|
|
|
if err := s.loadFromFile(); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("failed to load existing data: %w", err)
|
|
}
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (s *StoreSource) loadFromFile() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.loadFromFileUnlocked()
|
|
}
|
|
|
|
func (s *StoreSource) loadFromFileUnlocked() error {
|
|
data, err := os.ReadFile(s.config.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type rawStoreData struct {
|
|
Proxies []jsonx.RawMessage `json:"proxies,omitempty"`
|
|
Visitors []jsonx.RawMessage `json:"visitors,omitempty"`
|
|
}
|
|
stored := rawStoreData{}
|
|
if err := jsonx.Unmarshal(data, &stored); err != nil {
|
|
return fmt.Errorf("failed to parse JSON: %w", err)
|
|
}
|
|
|
|
s.proxies = make(map[string]v1.ProxyConfigurer)
|
|
s.visitors = make(map[string]v1.VisitorConfigurer)
|
|
|
|
for i, proxyData := range stored.Proxies {
|
|
proxyCfg, err := v1.DecodeProxyConfigurerJSON(proxyData, v1.DecodeOptions{
|
|
DisallowUnknownFields: false,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode proxy at index %d: %w", i, err)
|
|
}
|
|
name := proxyCfg.GetBaseConfig().Name
|
|
if name == "" {
|
|
return fmt.Errorf("proxy name cannot be empty")
|
|
}
|
|
s.proxies[name] = proxyCfg
|
|
}
|
|
|
|
for i, visitorData := range stored.Visitors {
|
|
visitorCfg, err := v1.DecodeVisitorConfigurerJSON(visitorData, v1.DecodeOptions{
|
|
DisallowUnknownFields: false,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode visitor at index %d: %w", i, err)
|
|
}
|
|
name := visitorCfg.GetBaseConfig().Name
|
|
if name == "" {
|
|
return fmt.Errorf("visitor name cannot be empty")
|
|
}
|
|
s.visitors[name] = visitorCfg
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) saveToFileUnlocked() error {
|
|
stored := storeData{
|
|
Proxies: make([]v1.TypedProxyConfig, 0, len(s.proxies)),
|
|
Visitors: make([]v1.TypedVisitorConfig, 0, len(s.visitors)),
|
|
}
|
|
|
|
for _, p := range s.proxies {
|
|
stored.Proxies = append(stored.Proxies, v1.TypedProxyConfig{ProxyConfigurer: p})
|
|
}
|
|
for _, v := range s.visitors {
|
|
stored.Visitors = append(stored.Visitors, v1.TypedVisitorConfig{VisitorConfigurer: v})
|
|
}
|
|
|
|
data, err := jsonx.MarshalIndent(stored, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal JSON: %w", err)
|
|
}
|
|
|
|
dir := filepath.Dir(s.config.Path)
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return fmt.Errorf("failed to create directory: %w", err)
|
|
}
|
|
|
|
tmpPath := s.config.Path + ".tmp"
|
|
|
|
f, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temp file: %w", err)
|
|
}
|
|
|
|
if _, err := f.Write(data); err != nil {
|
|
f.Close()
|
|
os.Remove(tmpPath)
|
|
return fmt.Errorf("failed to write temp file: %w", err)
|
|
}
|
|
|
|
if err := f.Sync(); err != nil {
|
|
f.Close()
|
|
os.Remove(tmpPath)
|
|
return fmt.Errorf("failed to sync temp file: %w", err)
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
os.Remove(tmpPath)
|
|
return fmt.Errorf("failed to close temp file: %w", err)
|
|
}
|
|
|
|
if err := os.Rename(tmpPath, s.config.Path); err != nil {
|
|
os.Remove(tmpPath)
|
|
return fmt.Errorf("failed to rename temp file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) AddProxy(proxy v1.ProxyConfigurer) error {
|
|
if proxy == nil {
|
|
return fmt.Errorf("proxy cannot be nil")
|
|
}
|
|
|
|
name := proxy.GetBaseConfig().Name
|
|
if name == "" {
|
|
return fmt.Errorf("proxy name cannot be empty")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if _, exists := s.proxies[name]; exists {
|
|
return fmt.Errorf("%w: proxy %q", ErrAlreadyExists, name)
|
|
}
|
|
|
|
s.proxies[name] = proxy
|
|
|
|
if err := s.saveToFileUnlocked(); err != nil {
|
|
delete(s.proxies, name)
|
|
return fmt.Errorf("failed to persist: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) UpdateProxy(proxy v1.ProxyConfigurer) error {
|
|
if proxy == nil {
|
|
return fmt.Errorf("proxy cannot be nil")
|
|
}
|
|
|
|
name := proxy.GetBaseConfig().Name
|
|
if name == "" {
|
|
return fmt.Errorf("proxy name cannot be empty")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
oldProxy, exists := s.proxies[name]
|
|
if !exists {
|
|
return fmt.Errorf("%w: proxy %q", ErrNotFound, name)
|
|
}
|
|
|
|
s.proxies[name] = proxy
|
|
|
|
if err := s.saveToFileUnlocked(); err != nil {
|
|
s.proxies[name] = oldProxy
|
|
return fmt.Errorf("failed to persist: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) RemoveProxy(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("proxy name cannot be empty")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
oldProxy, exists := s.proxies[name]
|
|
if !exists {
|
|
return fmt.Errorf("%w: proxy %q", ErrNotFound, name)
|
|
}
|
|
|
|
delete(s.proxies, name)
|
|
|
|
if err := s.saveToFileUnlocked(); err != nil {
|
|
s.proxies[name] = oldProxy
|
|
return fmt.Errorf("failed to persist: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) GetProxy(name string) v1.ProxyConfigurer {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
p, exists := s.proxies[name]
|
|
if !exists {
|
|
return nil
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (s *StoreSource) AddVisitor(visitor v1.VisitorConfigurer) error {
|
|
if visitor == nil {
|
|
return fmt.Errorf("visitor cannot be nil")
|
|
}
|
|
|
|
name := visitor.GetBaseConfig().Name
|
|
if name == "" {
|
|
return fmt.Errorf("visitor name cannot be empty")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if _, exists := s.visitors[name]; exists {
|
|
return fmt.Errorf("%w: visitor %q", ErrAlreadyExists, name)
|
|
}
|
|
|
|
s.visitors[name] = visitor
|
|
|
|
if err := s.saveToFileUnlocked(); err != nil {
|
|
delete(s.visitors, name)
|
|
return fmt.Errorf("failed to persist: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) UpdateVisitor(visitor v1.VisitorConfigurer) error {
|
|
if visitor == nil {
|
|
return fmt.Errorf("visitor cannot be nil")
|
|
}
|
|
|
|
name := visitor.GetBaseConfig().Name
|
|
if name == "" {
|
|
return fmt.Errorf("visitor name cannot be empty")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
oldVisitor, exists := s.visitors[name]
|
|
if !exists {
|
|
return fmt.Errorf("%w: visitor %q", ErrNotFound, name)
|
|
}
|
|
|
|
s.visitors[name] = visitor
|
|
|
|
if err := s.saveToFileUnlocked(); err != nil {
|
|
s.visitors[name] = oldVisitor
|
|
return fmt.Errorf("failed to persist: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) RemoveVisitor(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("visitor name cannot be empty")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
oldVisitor, exists := s.visitors[name]
|
|
if !exists {
|
|
return fmt.Errorf("%w: visitor %q", ErrNotFound, name)
|
|
}
|
|
|
|
delete(s.visitors, name)
|
|
|
|
if err := s.saveToFileUnlocked(); err != nil {
|
|
s.visitors[name] = oldVisitor
|
|
return fmt.Errorf("failed to persist: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *StoreSource) GetVisitor(name string) v1.VisitorConfigurer {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
v, exists := s.visitors[name]
|
|
if !exists {
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
|
|
func (s *StoreSource) GetAllProxies() ([]v1.ProxyConfigurer, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
result := make([]v1.ProxyConfigurer, 0, len(s.proxies))
|
|
for _, p := range s.proxies {
|
|
result = append(result, p)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *StoreSource) GetAllVisitors() ([]v1.VisitorConfigurer, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
result := make([]v1.VisitorConfigurer, 0, len(s.visitors))
|
|
for _, v := range s.visitors {
|
|
result = append(result, v)
|
|
}
|
|
return result, nil
|
|
}
|