mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-24 17:41:29 +00:00
Add filemanager api
This commit is contained in:
parent
b6068cea6b
commit
98c2c439aa
3
box.go
3
box.go
|
@ -62,6 +62,7 @@ func New(options Options) (*Box, error) {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
|
Context: ctx,
|
||||||
Options: common.PtrValueOrDefault(options.Log),
|
Options: common.PtrValueOrDefault(options.Log),
|
||||||
Observable: needClashAPI,
|
Observable: needClashAPI,
|
||||||
DefaultWriter: defaultLogWriter,
|
DefaultWriter: defaultLogWriter,
|
||||||
|
@ -142,7 +143,7 @@ func New(options Options) (*Box, error) {
|
||||||
preServices := make(map[string]adapter.Service)
|
preServices := make(map[string]adapter.Service)
|
||||||
postServices := make(map[string]adapter.Service)
|
postServices := make(map[string]adapter.Service)
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,40 +3,13 @@ package constant
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dirName = "sing-box"
|
const dirName = "sing-box"
|
||||||
|
|
||||||
var (
|
var resourcePaths []string
|
||||||
basePath string
|
|
||||||
tempPath string
|
|
||||||
resourcePaths []string
|
|
||||||
)
|
|
||||||
|
|
||||||
func BasePath(name string) string {
|
|
||||||
if basePath == "" || strings.HasPrefix(name, "/") {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return filepath.Join(basePath, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTemp(pattern string) (*os.File, error) {
|
|
||||||
if tempPath == "" {
|
|
||||||
tempPath = os.TempDir()
|
|
||||||
}
|
|
||||||
return os.CreateTemp(tempPath, pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
|
||||||
basePath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetTempPath(path string) {
|
|
||||||
tempPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindPath(name string) (string, bool) {
|
func FindPath(name string) (string, bool) {
|
||||||
name = os.ExpandEnv(name)
|
name = os.ExpandEnv(name)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package experimental
|
package experimental
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
@ -8,7 +9,7 @@ import (
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServerConstructor = func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
|
type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
|
||||||
|
|
||||||
var clashServerConstructor ClashServerConstructor
|
var clashServerConstructor ClashServerConstructor
|
||||||
|
|
||||||
|
@ -16,9 +17,9 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) {
|
||||||
clashServerConstructor = constructor
|
clashServerConstructor = constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
if clashServerConstructor == nil {
|
if clashServerConstructor == nil {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
return clashServerConstructor(router, logFactory, options)
|
return clashServerConstructor(ctx, router, logFactory, options)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
"github.com/sagernet/websocket"
|
"github.com/sagernet/websocket"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -37,6 +38,7 @@ func init() {
|
||||||
var _ adapter.ClashServer = (*Server)(nil)
|
var _ adapter.ClashServer = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
ctx context.Context
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
|
@ -53,10 +55,11 @@ type Server struct {
|
||||||
externalUIDownloadDetour string
|
externalUIDownloadDetour string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
trafficManager := trafficontrol.NewManager()
|
trafficManager := trafficontrol.NewManager()
|
||||||
chiRouter := chi.NewRouter()
|
chiRouter := chi.NewRouter()
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
ctx: ctx,
|
||||||
router: router,
|
router: router,
|
||||||
logger: logFactory.NewLogger("clash-api"),
|
logger: logFactory.NewLogger("clash-api"),
|
||||||
httpServer: &http.Server{
|
httpServer: &http.Server{
|
||||||
|
@ -82,7 +85,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||||
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
||||||
cachePath = foundPath
|
cachePath = foundPath
|
||||||
} else {
|
} else {
|
||||||
cachePath = C.BasePath(cachePath)
|
cachePath = filemanager.BasePath(ctx, cachePath)
|
||||||
}
|
}
|
||||||
server.cacheFilePath = cachePath
|
server.cacheFilePath = cachePath
|
||||||
}
|
}
|
||||||
|
@ -113,7 +116,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||||
server.setupMetaAPI(r)
|
server.setupMetaAPI(r)
|
||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
server.externalUI = C.BasePath(os.ExpandEnv(options.ExternalUI))
|
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
|
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||||
|
|
|
@ -12,11 +12,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) checkAndDownloadExternalUI() {
|
func (s *Server) checkAndDownloadExternalUI() {
|
||||||
|
@ -79,7 +79,7 @@ func (s *Server) downloadExternalUI() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
||||||
tempFile, err := C.CreateTemp(name)
|
tempFile, err := filemanager.CreateTemp(s.ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
savePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1])
|
savePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1])
|
||||||
err = downloadZIPEntry(file, savePath)
|
err = downloadZIPEntry(s.ctx, file, savePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -120,8 +120,8 @@ func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadZIPEntry(zipFile *zip.File, savePath string) error {
|
func downloadZIPEntry(ctx context.Context, zipFile *zip.File, savePath string) error {
|
||||||
saveFile, err := os.Create(savePath)
|
saveFile, err := filemanager.Create(ctx, savePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,3 +27,27 @@ func RedirectStderr(path string) error {
|
||||||
stderrFile = outputFile
|
stderrFile = outputFile
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RedirectStderrAsUser(path string, uid, gid int) error {
|
||||||
|
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
|
||||||
|
_ = os.Rename(path, path+".old")
|
||||||
|
}
|
||||||
|
outputFile, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = outputFile.Chown(uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
os.Remove(outputFile.Name())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
os.Remove(outputFile.Name())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stderrFile = outputFile
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BoxService struct {
|
type BoxService struct {
|
||||||
|
@ -30,6 +31,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
ctx = filemanager.WithDefault(ctx, sBasePath, sTempPath, sUserID, sGroupID)
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
var (
|
||||||
C.SetBasePath(path)
|
sBasePath string
|
||||||
}
|
sTempPath string
|
||||||
|
sUserID int
|
||||||
|
sGroupID int
|
||||||
|
)
|
||||||
|
|
||||||
func SetTempPath(path string) {
|
func Setup(basePath string, tempPath string, userID int, groupID int) {
|
||||||
C.SetTempPath(path)
|
sBasePath = basePath
|
||||||
|
sTempPath = tempPath
|
||||||
|
sUserID = userID
|
||||||
|
sGroupID = groupID
|
||||||
|
if sUserID == -1 {
|
||||||
|
sUserID = os.Getuid()
|
||||||
|
}
|
||||||
|
if sGroupID == -1 {
|
||||||
|
sGroupID = os.Getgid()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Version() string {
|
func Version() string {
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
package include
|
package include
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
@ -11,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
experimental.RegisterClashServerConstructor(func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`)
|
return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type factoryWithFile struct {
|
type factoryWithFile struct {
|
||||||
|
@ -36,6 +37,7 @@ func (f *observableFactoryWithFile) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
Context context.Context
|
||||||
Options option.LogOptions
|
Options option.LogOptions
|
||||||
Observable bool
|
Observable bool
|
||||||
DefaultWriter io.Writer
|
DefaultWriter io.Writer
|
||||||
|
@ -65,7 +67,7 @@ func New(options Options) (Factory, error) {
|
||||||
logWriter = os.Stdout
|
logWriter = os.Stdout
|
||||||
default:
|
default:
|
||||||
var err error
|
var err error
|
||||||
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
logFile, err = filemanager.OpenFile(options.Context, logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Router) GeoIPReader() *geoip.Reader {
|
func (r *Router) GeoIPReader() *geoip.Reader {
|
||||||
|
@ -51,7 +52,7 @@ func (r *Router) prepareGeoIPDatabase() error {
|
||||||
geoPath = foundPath
|
geoPath = foundPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
geoPath = C.BasePath(geoPath)
|
geoPath = filemanager.BasePath(r.ctx, geoPath)
|
||||||
if rw.FileExists(geoPath) {
|
if rw.FileExists(geoPath) {
|
||||||
geoReader, codes, err := geoip.Open(geoPath)
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -95,7 +96,7 @@ func (r *Router) prepareGeositeDatabase() error {
|
||||||
geoPath = foundPath
|
geoPath = foundPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
geoPath = C.BasePath(geoPath)
|
geoPath = filemanager.BasePath(r.ctx, geoPath)
|
||||||
if !rw.FileExists(geoPath) {
|
if !rw.FileExists(geoPath) {
|
||||||
r.logger.Warn("geosite database not exists: ", geoPath)
|
r.logger.Warn("geosite database not exists: ", geoPath)
|
||||||
var err error
|
var err error
|
||||||
|
@ -142,10 +143,10 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
os.MkdirAll(parentDir, 0o755)
|
filemanager.MkdirAll(r.ctx, parentDir, 0o755)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
saveFile, err := filemanager.Create(r.ctx, savePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
}
|
}
|
||||||
|
@ -190,10 +191,10 @@ func (r *Router) downloadGeositeDatabase(savePath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
os.MkdirAll(parentDir, 0o755)
|
filemanager.MkdirAll(r.ctx, parentDir, 0o755)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
saveFile, err := filemanager.Create(r.ctx, savePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue