Migrate to independent cache file
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/bbolt"
|
||||
bboltErrors "github.com/sagernet/bbolt/errors"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
@@ -31,11 +32,15 @@ var (
|
||||
cacheIDDefault = []byte("default")
|
||||
)
|
||||
|
||||
var _ adapter.ClashCacheFile = (*CacheFile)(nil)
|
||||
var _ adapter.CacheFile = (*CacheFile)(nil)
|
||||
|
||||
type CacheFile struct {
|
||||
ctx context.Context
|
||||
path string
|
||||
cacheID []byte
|
||||
storeFakeIP bool
|
||||
|
||||
DB *bbolt.DB
|
||||
cacheID []byte
|
||||
saveAccess sync.RWMutex
|
||||
saveDomain map[netip.Addr]string
|
||||
saveAddress4 map[string]netip.Addr
|
||||
@@ -43,7 +48,29 @@ type CacheFile struct {
|
||||
saveMetadataTimer *time.Timer
|
||||
}
|
||||
|
||||
func Open(ctx context.Context, path string, cacheID string) (*CacheFile, error) {
|
||||
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
var path string
|
||||
if options.Path != "" {
|
||||
path = options.Path
|
||||
} else {
|
||||
path = "cache.db"
|
||||
}
|
||||
var cacheIDBytes []byte
|
||||
if options.CacheID != "" {
|
||||
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
|
||||
}
|
||||
return &CacheFile{
|
||||
ctx: ctx,
|
||||
path: filemanager.BasePath(ctx, path),
|
||||
cacheID: cacheIDBytes,
|
||||
storeFakeIP: options.StoreFakeIP,
|
||||
saveDomain: make(map[netip.Addr]string),
|
||||
saveAddress4: make(map[string]netip.Addr),
|
||||
saveAddress6: make(map[string]netip.Addr),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheFile) start() error {
|
||||
const fileMode = 0o666
|
||||
options := bbolt.Options{Timeout: time.Second}
|
||||
var (
|
||||
@@ -51,7 +78,7 @@ func Open(ctx context.Context, path string, cacheID string) (*CacheFile, error)
|
||||
err error
|
||||
)
|
||||
for i := 0; i < 10; i++ {
|
||||
db, err = bbolt.Open(path, fileMode, &options)
|
||||
db, err = bbolt.Open(c.path, fileMode, &options)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
@@ -59,23 +86,20 @@ func Open(ctx context.Context, path string, cacheID string) (*CacheFile, error)
|
||||
continue
|
||||
}
|
||||
if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {
|
||||
rmErr := os.Remove(path)
|
||||
rmErr := os.Remove(c.path)
|
||||
if rmErr != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
err = filemanager.Chown(ctx, path)
|
||||
err = filemanager.Chown(c.ctx, c.path)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "platform chown")
|
||||
}
|
||||
var cacheIDBytes []byte
|
||||
if cacheID != "" {
|
||||
cacheIDBytes = append([]byte{0}, []byte(cacheID)...)
|
||||
db.Close()
|
||||
return E.Cause(err, "platform chown")
|
||||
}
|
||||
err = db.Batch(func(tx *bbolt.Tx) error {
|
||||
return tx.ForEach(func(name []byte, b *bbolt.Bucket) error {
|
||||
@@ -97,15 +121,30 @@ func Open(ctx context.Context, path string, cacheID string) (*CacheFile, error)
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
db.Close()
|
||||
return err
|
||||
}
|
||||
return &CacheFile{
|
||||
DB: db,
|
||||
cacheID: cacheIDBytes,
|
||||
saveDomain: make(map[netip.Addr]string),
|
||||
saveAddress4: make(map[string]netip.Addr),
|
||||
saveAddress6: make(map[string]netip.Addr),
|
||||
}, nil
|
||||
c.DB = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheFile) PreStart() error {
|
||||
return c.start()
|
||||
}
|
||||
|
||||
func (c *CacheFile) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheFile) Close() error {
|
||||
if c.DB == nil {
|
||||
return nil
|
||||
}
|
||||
return c.DB.Close()
|
||||
}
|
||||
|
||||
func (c *CacheFile) StoreFakeIP() bool {
|
||||
return c.storeFakeIP
|
||||
}
|
||||
|
||||
func (c *CacheFile) LoadMode() string {
|
||||
@@ -218,7 +257,3 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) Close() error {
|
||||
return c.DB.Close()
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
package clashapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func cacheRouter(router adapter.Router) http.Handler {
|
||||
func cacheRouter(ctx context.Context) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/fakeip/flush", flushFakeip(router))
|
||||
r.Post("/fakeip/flush", flushFakeip(ctx))
|
||||
return r
|
||||
}
|
||||
|
||||
func flushFakeip(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
||||
func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if cacheFile := router.ClashServer().CacheFile(); cacheFile != nil {
|
||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||
if cacheFile != nil {
|
||||
err := cacheFile.FakeIPReset()
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/cachefile"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@@ -49,12 +48,6 @@ type Server struct {
|
||||
mode string
|
||||
modeList []string
|
||||
modeUpdateHook chan<- struct{}
|
||||
storeMode bool
|
||||
storeSelected bool
|
||||
storeFakeIP bool
|
||||
cacheFilePath string
|
||||
cacheID string
|
||||
cacheFile adapter.ClashCacheFile
|
||||
|
||||
externalController bool
|
||||
externalUI string
|
||||
@@ -76,9 +69,6 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||
trafficManager: trafficManager,
|
||||
modeList: options.ModeList,
|
||||
externalController: options.ExternalController != "",
|
||||
storeMode: options.StoreMode,
|
||||
storeSelected: options.StoreSelected,
|
||||
storeFakeIP: options.StoreFakeIP,
|
||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
||||
}
|
||||
@@ -94,18 +84,10 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||
server.modeList = append([]string{defaultMode}, server.modeList...)
|
||||
}
|
||||
server.mode = defaultMode
|
||||
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" {
|
||||
cachePath := os.ExpandEnv(options.CacheFile)
|
||||
if cachePath == "" {
|
||||
cachePath = "cache.db"
|
||||
}
|
||||
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
||||
cachePath = foundPath
|
||||
} else {
|
||||
cachePath = filemanager.BasePath(ctx, cachePath)
|
||||
}
|
||||
server.cacheFilePath = cachePath
|
||||
server.cacheID = options.CacheID
|
||||
//goland:noinspection GoDeprecation
|
||||
//nolint:staticcheck
|
||||
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" {
|
||||
return nil, E.New("cache_file and related fields in Clash API is deprecated in sing-box 1.8.0, use experimental.cache_file instead.")
|
||||
}
|
||||
cors := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
@@ -128,7 +110,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||
r.Mount("/providers/rules", ruleProviderRouter())
|
||||
r.Mount("/script", scriptRouter())
|
||||
r.Mount("/profile", profileRouter())
|
||||
r.Mount("/cache", cacheRouter(router))
|
||||
r.Mount("/cache", cacheRouter(ctx))
|
||||
r.Mount("/dns", dnsRouter(router))
|
||||
|
||||
server.setupMetaAPI(r)
|
||||
@@ -147,19 +129,13 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||
}
|
||||
|
||||
func (s *Server) PreStart() error {
|
||||
if s.cacheFilePath != "" {
|
||||
cacheFile, err := cachefile.Open(s.ctx, s.cacheFilePath, s.cacheID)
|
||||
if err != nil {
|
||||
return E.Cause(err, "open cache file")
|
||||
}
|
||||
s.cacheFile = cacheFile
|
||||
if s.storeMode {
|
||||
mode := s.cacheFile.LoadMode()
|
||||
if common.Any(s.modeList, func(it string) bool {
|
||||
return strings.EqualFold(it, mode)
|
||||
}) {
|
||||
s.mode = mode
|
||||
}
|
||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||
if cacheFile != nil {
|
||||
mode := cacheFile.LoadMode()
|
||||
if common.Any(s.modeList, func(it string) bool {
|
||||
return strings.EqualFold(it, mode)
|
||||
}) {
|
||||
s.mode = mode
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -187,7 +163,6 @@ func (s *Server) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.httpServer),
|
||||
s.trafficManager,
|
||||
s.cacheFile,
|
||||
s.urlTestHistory,
|
||||
)
|
||||
}
|
||||
@@ -224,8 +199,9 @@ func (s *Server) SetMode(newMode string) {
|
||||
}
|
||||
}
|
||||
s.router.ClearDNSCache()
|
||||
if s.storeMode {
|
||||
err := s.cacheFile.StoreMode(newMode)
|
||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||
if cacheFile != nil {
|
||||
err := cacheFile.StoreMode(newMode)
|
||||
if err != nil {
|
||||
s.logger.Error(E.Cause(err, "save mode"))
|
||||
}
|
||||
@@ -233,18 +209,6 @@ func (s *Server) SetMode(newMode string) {
|
||||
s.logger.Info("updated mode: ", newMode)
|
||||
}
|
||||
|
||||
func (s *Server) StoreSelected() bool {
|
||||
return s.storeSelected
|
||||
}
|
||||
|
||||
func (s *Server) StoreFakeIP() bool {
|
||||
return s.storeFakeIP
|
||||
}
|
||||
|
||||
func (s *Server) CacheFile() adapter.ClashCacheFile {
|
||||
return s.cacheFile
|
||||
}
|
||||
|
||||
func (s *Server) HistoryStorage() *urltest.HistoryStorage {
|
||||
return s.urlTestHistory
|
||||
}
|
||||
|
||||
@@ -159,11 +159,7 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
||||
|
||||
func writeGroups(writer io.Writer, boxService *BoxService) error {
|
||||
historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
|
||||
var cacheFile adapter.ClashCacheFile
|
||||
if clashServer := boxService.instance.Router().ClashServer(); clashServer != nil {
|
||||
cacheFile = clashServer.CacheFile()
|
||||
}
|
||||
|
||||
cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx)
|
||||
outbounds := boxService.instance.Router().Outbounds()
|
||||
var iGroups []adapter.OutboundGroup
|
||||
for _, it := range outbounds {
|
||||
@@ -288,16 +284,15 @@ func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := s.service
|
||||
if service == nil {
|
||||
serviceNow := s.service
|
||||
if serviceNow == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
|
||||
if cacheFile := clashServer.CacheFile(); cacheFile != nil {
|
||||
err = cacheFile.StoreGroupExpand(groupTag, isExpand)
|
||||
if err != nil {
|
||||
return writeError(conn, err)
|
||||
}
|
||||
cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx)
|
||||
if cacheFile != nil {
|
||||
err = cacheFile.StoreGroupExpand(groupTag, isExpand)
|
||||
if err != nil {
|
||||
return writeError(conn, err)
|
||||
}
|
||||
}
|
||||
return writeError(conn, nil)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func (c *CommandClient) URLTest(groupTag string) error {
|
||||
@@ -37,11 +38,11 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := s.service
|
||||
if service == nil {
|
||||
serviceNow := s.service
|
||||
if serviceNow == nil {
|
||||
return nil
|
||||
}
|
||||
abstractOutboundGroup, isLoaded := service.instance.Router().Outbound(groupTag)
|
||||
abstractOutboundGroup, isLoaded := serviceNow.instance.Router().Outbound(groupTag)
|
||||
if !isLoaded {
|
||||
return writeError(conn, E.New("outbound group not found: ", groupTag))
|
||||
}
|
||||
@@ -53,15 +54,9 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||
if isURLTest {
|
||||
go urlTest.CheckOutbounds()
|
||||
} else {
|
||||
var historyStorage *urltest.HistoryStorage
|
||||
if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
|
||||
historyStorage = clashServer.HistoryStorage()
|
||||
} else {
|
||||
return writeError(conn, E.New("Clash API is required for URLTest on non-URLTest group"))
|
||||
}
|
||||
|
||||
historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx)
|
||||
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||
itOutbound, _ := service.instance.Router().Outbound(it)
|
||||
itOutbound, _ := serviceNow.instance.Router().Outbound(it)
|
||||
return itOutbound
|
||||
}), func(it adapter.Outbound) bool {
|
||||
if it == nil {
|
||||
@@ -73,12 +68,12 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||
}
|
||||
return true
|
||||
})
|
||||
b, _ := batch.New(service.ctx, batch.WithConcurrencyNum[any](10))
|
||||
b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10))
|
||||
for _, detour := range outbounds {
|
||||
outboundToTest := detour
|
||||
outboundTag := outboundToTest.Tag()
|
||||
b.Go(outboundTag, func() (any, error) {
|
||||
t, err := urltest.URLTest(service.ctx, "", outboundToTest)
|
||||
t, err := urltest.URLTest(serviceNow.ctx, "", outboundToTest)
|
||||
if err != nil {
|
||||
historyStorage.DeleteURLTestHistory(outboundTag)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user