Fix start lifecycle

This commit is contained in:
世界
2026-08-30 17:41:43 +08:00
parent 0a4e4c8061
commit b9572e5812
17 changed files with 254 additions and 140 deletions
+17 -1
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"runtime/debug" "runtime/debug"
"time" "time"
@@ -43,6 +44,8 @@ var _ adapter.SimpleLifecycle = (*Box)(nil)
type Box struct { type Box struct {
createdAt time.Time createdAt time.Time
debugOptions option.DebugOptions
debugHTTPServer *http.Server
logFactory log.Factory logFactory log.Factory
logger log.ContextLogger logger log.ContextLogger
network *route.NetworkManager network *route.NetworkManager
@@ -142,7 +145,8 @@ func New(options Options) (*Box, error) {
ctx = pause.WithDefaultManager(ctx) ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental) experimentalOptions := common.PtrValueOrDefault(options.Experimental)
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) debugOptions := common.PtrValueOrDefault(experimentalOptions.Debug)
err := checkDebugOptions(debugOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -470,6 +474,7 @@ func New(options Options) (*Box, error) {
router: router, router: router,
httpClientService: httpClientService, httpClientService: httpClientService,
createdAt: createdAt, createdAt: createdAt,
debugOptions: debugOptions,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.Logger(),
internalService: internalServices, internalService: internalServices,
@@ -523,6 +528,11 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return E.Cause(err, "start logger") return E.Cause(err, "start logger")
} }
applyDebugOptions(s.debugOptions)
s.debugHTTPServer, err = startDebugHTTPServer(s.debugOptions)
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
if err != nil { if err != nil {
return err return err
@@ -594,6 +604,12 @@ func (s *Box) Close() error {
close(s.done) close(s.done)
} }
var err error var err error
if s.debugHTTPServer != nil {
err = E.Append(err, s.debugHTTPServer.Close(), func(err error) error {
return E.Cause(err, "close debug HTTP server")
})
s.debugHTTPServer = nil
}
for _, closeItem := range []struct { for _, closeItem := range []struct {
name string name string
service adapter.Lifecycle service adapter.Lifecycle
@@ -96,6 +96,15 @@ type appleTransport struct {
closed bool closed bool
} }
func validateAppleTransport(ctx context.Context, options option.HTTPClientOptions) error {
sessionConfig, err := newAppleSessionConfig(ctx, options)
if err != nil {
return err
}
sessionConfig.close()
return nil
}
func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) { func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) {
sessionConfig, err := newAppleSessionConfig(ctx, options) sessionConfig, err := newAppleSessionConfig(ctx, options)
if err != nil { if err != nil {
@@ -111,6 +120,10 @@ func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDial
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = bridge.Start()
if err != nil {
return nil, err
}
shared := &appleTransportShared{ shared := &appleTransportShared{
logger: logger, logger: logger,
bridge: bridge, bridge: bridge,
@@ -11,6 +11,10 @@ import (
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
func validateAppleTransport(ctx context.Context, options option.HTTPClientOptions) error {
return E.New("Apple HTTP engine is not available on non-Apple platforms")
}
func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) { func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) {
return nil, E.New("Apple HTTP engine is not available on non-Apple platforms") return nil, E.New("Apple HTTP engine is not available on non-Apple platforms")
} }
+5 -7
View File
@@ -35,11 +35,11 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string,
var cheapRebuild bool var cheapRebuild bool
switch options.Engine { switch options.Engine {
case C.TLSEngineApple: case C.TLSEngineApple:
inner, transportErr := newAppleTransport(ctx, logger, rawDialer, options) err = validateAppleTransport(ctx, options)
if transportErr != nil { if err != nil {
return nil, transportErr return nil, err
} }
managedTransport := &ManagedTransport{ return &ManagedTransport{
dialer: rawDialer, dialer: rawDialer,
headers: headers, headers: headers,
host: host, host: host,
@@ -47,9 +47,7 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string,
factory: func() (innerTransport, error) { factory: func() (innerTransport, error) {
return newAppleTransport(ctx, logger, rawDialer, options) return newAppleTransport(ctx, logger, rawDialer, options)
}, },
} }, nil
managedTransport.epoch.Store(&transportEpoch{transport: inner})
return managedTransport, nil
case "", C.TLSEngineGo: case "", C.TLSEngineGo:
cheapRebuild = true cheapRebuild = true
default: default:
+12 -9
View File
@@ -34,23 +34,26 @@ type Bridge struct {
func New(ctx context.Context, logger logger.ContextLogger, tag string, dialer N.Dialer) (*Bridge, error) { func New(ctx context.Context, logger logger.ContextLogger, tag string, dialer N.Dialer) (*Bridge, error) {
username := randomHex(16) username := randomHex(16)
password := randomHex(16) password := randomHex(16)
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) return &Bridge{
if err != nil {
return nil, err
}
bridge := &Bridge{
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
tag: tag, tag: tag,
dialer: dialer, dialer: dialer,
connection: service.FromContext[adapter.ConnectionManager](ctx), connection: service.FromContext[adapter.ConnectionManager](ctx),
tcpListener: tcpListener,
username: username, username: username,
password: password, password: password,
authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}), authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}),
}, nil
}
func (b *Bridge) Start() error {
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
return err
} }
go bridge.acceptLoop() b.tcpListener = tcpListener
return bridge, nil go b.acceptLoop()
return nil
} }
func randomHex(size int) string { func randomHex(size int) string {
@@ -72,7 +75,7 @@ func (b *Bridge) Password() string {
} }
func (b *Bridge) Close() error { func (b *Bridge) Close() error {
return common.Close(b.tcpListener) return common.Close(common.PtrOrNil(b.tcpListener))
} }
func (b *Bridge) acceptLoop() { func (b *Bridge) acceptLoop() {
+39 -16
View File
@@ -29,18 +29,42 @@ type acmeWrapper struct {
ctx context.Context ctx context.Context
cfg *certmagic.Config cfg *certmagic.Config
cache *certmagic.Cache cache *certmagic.Cache
zapLogger *zap.Logger
dataDirectory string
domain []string domain []string
} }
func (w *acmeWrapper) Start() error { func (w *acmeWrapper) Start() error {
if w.dataDirectory != "" {
err := filemanager.MkdirAll(w.ctx, w.dataDirectory, 0o700)
if err != nil {
return E.Cause(err, "create ACME data directory")
}
}
config := w.cfg
cache := certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil
},
Logger: w.zapLogger,
})
config = certmagic.New(cache, *config)
w.cfg = config
w.cache = cache
return w.cfg.ManageSync(w.ctx, w.domain) return w.cfg.ManageSync(w.ctx, w.domain)
} }
func (w *acmeWrapper) Close() error { func (w *acmeWrapper) Close() error {
if w.cache != nil {
w.cache.Stop() w.cache.Stop()
}
return nil return nil
} }
func (w *acmeWrapper) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
return w.cfg.GetCertificate(hello)
}
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
var acmeServer string var acmeServer string
switch options.Provider { switch options.Provider {
@@ -54,13 +78,12 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
} }
acmeServer = options.Provider acmeServer = options.Provider
} }
var storage certmagic.Storage var (
storage certmagic.Storage
dataDirectory string
)
if options.DataDirectory != "" { if options.DataDirectory != "" {
dataDirectory := filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory)) dataDirectory = filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory))
err := filemanager.MkdirAll(ctx, dataDirectory, 0o700)
if err != nil {
return nil, nil, E.Cause(err, "create ACME data directory")
}
storage = &certmagic.FileStorage{ storage = &certmagic.FileStorage{
Path: dataDirectory, Path: dataDirectory,
} }
@@ -126,23 +149,23 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount) acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
} }
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)} config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
cache := certmagic.NewCache(certmagic.CacheOptions{ wrapper := &acmeWrapper{
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) { ctx: ctx,
return config, nil cfg: config,
}, zapLogger: zapLogger,
Logger: zapLogger, dataDirectory: dataDirectory,
}) domain: options.Domain,
config = certmagic.New(cache, *config) }
var tlsConfig *tls.Config var tlsConfig *tls.Config
if acmeConfig.DisableTLSALPNChallenge || acmeConfig.DNS01Solver != nil { if acmeConfig.DisableTLSALPNChallenge || acmeConfig.DNS01Solver != nil {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
GetCertificate: config.GetCertificate, GetCertificate: wrapper.GetCertificate,
} }
} else { } else {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
GetCertificate: config.GetCertificate, GetCertificate: wrapper.GetCertificate,
NextProtos: []string{C.ACMETLS1Protocol}, NextProtos: []string{C.ACMETLS1Protocol},
} }
} }
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil return tlsConfig, wrapper, nil
} }
+10 -4
View File
@@ -50,13 +50,10 @@ type Manager struct {
} }
func NewManager(outbound adapter.OutboundManager) *Manager { func NewManager(outbound adapter.OutboundManager) *Manager {
manager := &Manager{ return &Manager{
outbound: outbound, outbound: outbound,
eventSubscriber: observable.NewSubscriber[ConnectionEvent](256), eventSubscriber: observable.NewSubscriber[ConnectionEvent](256),
} }
manager.eventObserver = observable.NewObserver(manager.eventSubscriber, 64)
manager.cleaner = cleanup.Add(manager.Clear)
return manager
} }
func (m *Manager) Name() string { func (m *Manager) Name() string {
@@ -64,12 +61,21 @@ func (m *Manager) Name() string {
} }
func (m *Manager) Start(stage adapter.StartStage) error { func (m *Manager) Start(stage adapter.StartStage) error {
if stage == adapter.StartStateInitialize {
m.eventObserver = observable.NewObserver(m.eventSubscriber, 64)
m.cleaner = cleanup.Add(m.Clear)
}
return nil return nil
} }
func (m *Manager) Close() error { func (m *Manager) Close() error {
if m.cleaner != nil {
m.cleaner.Close() m.cleaner.Close()
}
if m.eventObserver != nil {
return m.eventObserver.Close() return m.eventObserver.Close()
}
return nil
} }
func (m *Manager) SubscribeEvents() (observable.Subscription[ConnectionEvent], <-chan struct{}, error) { func (m *Manager) SubscribeEvents() (observable.Subscription[ConnectionEvent], <-chan struct{}, error) {
+8 -6
View File
@@ -7,8 +7,14 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
func applyDebugOptions(options option.DebugOptions) error { func checkDebugOptions(options option.DebugOptions) error {
applyDebugListenOption(options) if options.OOMKiller != nil {
return E.New("legacy oom_killer in debug options is removed, use oom-killer service instead")
}
return nil
}
func applyDebugOptions(options option.DebugOptions) {
if options.GCPercent != nil { if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent) debug.SetGCPercent(*options.GCPercent)
} }
@@ -27,8 +33,4 @@ func applyDebugOptions(options option.DebugOptions) error {
if options.MemoryLimit.Value() != 0 { if options.MemoryLimit.Value() != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5)) debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
} }
if options.OOMKiller != nil {
return E.New("legacy oom_killer in debug options is removed, use oom-killer service instead")
}
return nil
} }
+10 -10
View File
@@ -1,6 +1,7 @@
package box package box
import ( import (
"net"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
"runtime" "runtime"
@@ -17,15 +18,9 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
var debugHTTPServer *http.Server func startDebugHTTPServer(options option.DebugOptions) (*http.Server, error) {
func applyDebugListenOption(options option.DebugOptions) {
if debugHTTPServer != nil {
debugHTTPServer.Close()
debugHTTPServer = nil
}
if options.Listen == "" { if options.Listen == "" {
return return nil, nil
} }
r := chi.NewMux() r := chi.NewMux()
r.Route("/debug", func(r chi.Router) { r.Route("/debug", func(r chi.Router) {
@@ -63,14 +58,19 @@ func applyDebugListenOption(options option.DebugOptions) {
r.HandleFunc("/trace", pprof.Trace) r.HandleFunc("/trace", pprof.Trace)
}) })
}) })
debugHTTPServer = &http.Server{ server := &http.Server{
Addr: options.Listen, Addr: options.Listen,
Handler: r, Handler: r,
} }
listener, err := net.Listen("tcp", options.Listen)
if err != nil {
return nil, E.Cause(err, "listen debug HTTP server")
}
go func() { go func() {
err := debugHTTPServer.ListenAndServe() err := server.Serve(listener)
if err != nil && !E.IsClosed(err) { if err != nil && !E.IsClosed(err) {
log.Error(E.Cause(err, "serve debug HTTP server")) log.Error(E.Cause(err, "serve debug HTTP server"))
} }
}() }()
return server, nil
} }
+2 -5
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"slices"
"github.com/sagernet/sing-box/experimental/locale" "github.com/sagernet/sing-box/experimental/locale"
@@ -14,11 +15,7 @@ func setLocaleFromContext(ctx context.Context) {
if !loaded { if !loaded {
return return
} }
for _, localeID := range requestMetadata.Get("accept-language") { slices.ContainsFunc(requestMetadata.Get("accept-language"), locale.Set)
if locale.Set(localeID) {
return
}
}
} }
func unaryLocaleInterceptor(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { func unaryLocaleInterceptor(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
+2 -2
View File
@@ -5,7 +5,7 @@ func init() {
Locale: "fa", Locale: "fa",
DeprecatedMessage: "%s از sing-box %s منسوخ شده است و در sing-box %s حذف خواهد شد؛ لطفاً راهنمای مهاجرت را ببینید.", DeprecatedMessage: "%s از sing-box %s منسوخ شده است و در sing-box %s حذف خواهد شد؛ لطفاً راهنمای مهاجرت را ببینید.",
DeprecatedMessageNoLink: "%s از sing-box %s منسوخ شده است و در sing-box %s حذف خواهد شد.", DeprecatedMessageNoLink: "%s از sing-box %s منسوخ شده است و در sing-box %s حذف خواهد شد.",
InsecureFeatureMessage: "%s در کلاینت گرافیکی sing-box برای Windows ناامن تلقی میشود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.", InsecureFeatureMessage: "%s در کلاینت گرافیکی sing-box برای Windows ناامن تلقی می\u200cشود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.",
ExternalPathFeature: "دسترسی به %s (خارج از پوشهٔ کاری) در کلاینت گرافیکی sing-box برای Windows ناامن تلقی میشود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.", ExternalPathFeature: "دسترسی به %s (خارج از پوشهٔ کاری) در کلاینت گرافیکی sing-box برای Windows ناامن تلقی می\u200cشود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.",
} }
} }
+3 -3
View File
@@ -68,9 +68,6 @@ func NewDefaultFactory(
/*if platformWriter != nil { /*if platformWriter != nil {
factory.platformFormatter.DisableColors = platformWriter.DisableColors() factory.platformFormatter.DisableColors = platformWriter.DisableColors()
}*/ }*/
if needObservable {
factory.observer = observable.NewObserver[Entry](factory.subscriber, 64)
}
return factory return factory
} }
@@ -85,6 +82,9 @@ func (f *defaultFactory) Start() error {
f.file = logFile f.file = logFile
} }
} }
if f.needObservable {
f.observer = observable.NewObserver[Entry](f.subscriber, 64)
}
f.startAccess.Lock() f.startAccess.Lock()
pendingEntries := f.pendingEntries pendingEntries := f.pendingEntries
f.pendingEntries = nil f.pendingEntries = nil
+20 -10
View File
@@ -30,9 +30,11 @@ var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct { type Outbound struct {
outbound.Adapter outbound.Adapter
ctx context.Context
dialer tls.Dialer dialer tls.Dialer
server M.Socksaddr server M.Socksaddr
tlsConfig tls.Config tlsConfig tls.Config
clientOptions anytls.ClientConfig
clientMetadata string clientMetadata string
client *anytls.Client client *anytls.Client
sessionClient *session.Client sessionClient *session.Client
@@ -43,6 +45,7 @@ type Outbound struct {
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) {
outbound := &Outbound{ outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeAnyTLS, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeAnyTLS, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
ctx: ctx,
server: options.ServerOptions.Build(), server: options.ServerOptions.Build(),
logger: logger, logger: logger,
} }
@@ -74,26 +77,33 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig) outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig)
client, err := anytls.NewClient(ctx, anytls.ClientConfig{ outbound.clientOptions = anytls.ClientConfig{
Password: options.Password, Password: options.Password,
IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(), IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(),
IdleSessionTimeout: options.IdleSessionTimeout.Build(), IdleSessionTimeout: options.IdleSessionTimeout.Build(),
MinIdleSession: options.MinIdleSession, MinIdleSession: options.MinIdleSession,
DialOut: outbound.dialOut, DialOut: outbound.dialOut,
Logger: logger, Logger: logger,
})
if err != nil {
return nil, err
} }
outbound.client = client
outbound.clientMetadata = options.ClientMetadata outbound.clientMetadata = options.ClientMetadata
outbound.sessionClient = sessionClientOf(client) return outbound, nil
}
outbound.uotClient = &uot.Client{ func (h *Outbound) Start(stage adapter.StartStage) error {
Dialer: (anytlsDialer)(outbound.createProxy), if stage != adapter.StartStateInitialize {
return nil
}
client, err := anytls.NewClient(h.ctx, h.clientOptions)
if err != nil {
return err
}
h.client = client
h.sessionClient = sessionClientOf(client)
h.uotClient = &uot.Client{
Dialer: (anytlsDialer)(h.createProxy),
Version: uot.Version, Version: uot.Version,
} }
return outbound, nil return nil
} }
func (h *Outbound) createProxy(ctx context.Context, destination M.Socksaddr) (net.Conn, error) { func (h *Outbound) createProxy(ctx context.Context, destination M.Socksaddr) (net.Conn, error) {
@@ -152,5 +162,5 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
} }
func (h *Outbound) Close() error { func (h *Outbound) Close() error {
return common.Close(h.client) return common.Close(common.PtrOrNil(h.client))
} }
+4 -4
View File
@@ -153,10 +153,6 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
} }
stateDirectory = filemanager.BasePath(ctx, os.ExpandEnv(stateDirectory)) stateDirectory = filemanager.BasePath(ctx, os.ExpandEnv(stateDirectory))
stateDirectory, _ = filepath.Abs(stateDirectory) stateDirectory, _ = filepath.Abs(stateDirectory)
mkdirErr := filemanager.MkdirAll(ctx, stateDirectory, 0o700)
if mkdirErr != nil {
return nil, E.Cause(mkdirErr, "create state directory")
}
if options.SSHServer != nil && options.SSHServer.Enabled { if options.SSHServer != nil && options.SSHServer.Enabled {
err := adapter.CheckSecurityFeature(ctx, "Tailscale `ssh_server`") err := adapter.CheckSecurityFeature(ctx, "Tailscale `ssh_server`")
if err != nil { if err != nil {
@@ -250,6 +246,10 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
func (t *Endpoint) Start(stage adapter.StartStage) error { func (t *Endpoint) Start(stage adapter.StartStage) error {
switch stage { switch stage {
case adapter.StartStateInitialize: case adapter.StartStateInitialize:
mkdirErr := filemanager.MkdirAll(t.ctx, t.server.Dir, 0o700)
if mkdirErr != nil {
return E.Cause(mkdirErr, "create state directory")
}
t.server.PeerDNSQueryHandler = (*peerDNSQueryHandler)(t) t.server.PeerDNSQueryHandler = (*peerDNSQueryHandler)(t)
case adapter.StartStateStart: case adapter.StartStateStart:
return t.start() return t.start()
+39 -19
View File
@@ -35,6 +35,7 @@ type Outbound struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
dialer N.Dialer
proxy *proxybridge.Bridge proxy *proxybridge.Bridge
startConf *tor.StartConf startConf *tor.StartConf
options map[string]string options map[string]string
@@ -51,10 +52,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
} }
startConf.TempDataDirBase = filemanager.TempPath(ctx) startConf.TempDataDirBase = filemanager.TempPath(ctx)
if startConf.DataDir != "" { if startConf.DataDir != "" {
err := filemanager.MkdirAll(ctx, startConf.DataDir, 0o755)
if err != nil {
return nil, err
}
dataDirAbs, _ := filepath.Abs(startConf.DataDir) dataDirAbs, _ := filepath.Abs(startConf.DataDir)
geoIPPath := filepath.Join(dataDirAbs, "geoip") geoIPPath := filepath.Join(dataDirAbs, "geoip")
geoIPInfo, err := filemanager.Stat(ctx, geoIPPath) geoIPInfo, err := filemanager.Stat(ctx, geoIPPath)
@@ -68,14 +65,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
} }
torrcFile := filepath.Join(startConf.DataDir, "torrc") torrcFile := filepath.Join(startConf.DataDir, "torrc")
torrcInfo, err := filemanager.Stat(ctx, torrcFile) torrcInfo, err := filemanager.Stat(ctx, torrcFile)
if os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
err = filemanager.WriteFile(ctx, torrcFile, []byte(""), 0o600)
if err != nil {
return nil, err return nil, err
} } else if err == nil && torrcInfo.IsDir() {
} else if err != nil {
return nil, err
} else if torrcInfo.IsDir() {
return nil, E.New("Tor configuration path is a directory: ", torrcFile) return nil, E.New("Tor configuration path is a directory: ", torrcFile)
} }
startConf.TorrcFile = torrcFile startConf.TorrcFile = torrcFile
@@ -94,26 +86,54 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
proxy, err := proxybridge.New(ctx, logger, "proxy", outboundDialer)
if err != nil {
return nil, err
}
return &Outbound{ return &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, tag, []string{N.NetworkTCP}, options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, tag, []string{N.NetworkTCP}, options.DialerOptions),
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
proxy: proxy, dialer: outboundDialer,
startConf: &startConf, startConf: &startConf,
options: options.Options, options: options.Options,
}, nil }, nil
} }
func (t *Outbound) Start() error { func (t *Outbound) Start(stage adapter.StartStage) error {
err := t.start() switch stage {
case adapter.StartStateInitialize:
if t.startConf.DataDir == "" {
return nil
}
err := filemanager.MkdirAll(t.ctx, t.startConf.DataDir, 0o755)
if err != nil {
return err
}
torrcInfo, err := filemanager.Stat(t.ctx, t.startConf.TorrcFile)
if os.IsNotExist(err) {
err = filemanager.WriteFile(t.ctx, t.startConf.TorrcFile, []byte(""), 0o600)
if err != nil {
return err
}
} else if err != nil {
return err
} else if torrcInfo.IsDir() {
return E.New("Tor configuration path is a directory: ", t.startConf.TorrcFile)
}
case adapter.StartStateStart:
proxy, err := proxybridge.New(t.ctx, t.logger, "proxy", t.dialer)
if err != nil {
return err
}
t.proxy = proxy
err = proxy.Start()
if err != nil {
return err
}
err = t.start()
if err != nil { if err != nil {
t.Close() t.Close()
}
return err return err
}
}
return nil
} }
var torLogEvents = []control.EventCode{ var torLogEvents = []control.EventCode{
+29 -16
View File
@@ -51,6 +51,8 @@ type Service struct {
ctx context.Context ctx context.Context
config *certmagic.Config config *certmagic.Config
cache *certmagic.Cache cache *certmagic.Cache
zapLogger *zap.Logger
dataDirectory string
domain []string domain []string
nextProtos []string nextProtos []string
} }
@@ -78,13 +80,12 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s
return nil, E.New("email is required to use the ZeroSSL ACME endpoint without external_account or account_key") return nil, E.New("email is required to use the ZeroSSL ACME endpoint without external_account or account_key")
} }
var storage certmagic.Storage var (
storage certmagic.Storage
dataDirectory string
)
if options.DataDirectory != "" { if options.DataDirectory != "" {
dataDirectory := filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory)) dataDirectory = filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory))
err := filemanager.MkdirAll(ctx, dataDirectory, 0o700)
if err != nil {
return nil, E.Cause(err, "create ACME data directory")
}
storage = &certmagic.FileStorage{Path: dataDirectory} storage = &certmagic.FileStorage{Path: dataDirectory}
} else { } else {
storage = certmagic.Default.Storage storage = certmagic.Default.Storage
@@ -169,13 +170,6 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s
} }
reflect.NewAt(httpClientField.Type(), unsafe.Pointer(httpClientField.UnsafeAddr())).Elem().Set(reflect.ValueOf(acmeHTTPClient)) reflect.NewAt(httpClientField.Type(), unsafe.Pointer(httpClientField.UnsafeAddr())).Elem().Set(reflect.ValueOf(acmeHTTPClient))
config.Issuers = []certmagic.Issuer{certmagicIssuer} config.Issuers = []certmagic.Issuer{certmagicIssuer}
cache := certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil
},
Logger: zapLogger,
})
config = certmagic.New(cache, *config)
var nextProtos []string var nextProtos []string
if !acmeIssuer.DisableTLSALPNChallenge && acmeIssuer.DNS01Solver == nil { if !acmeIssuer.DisableTLSALPNChallenge && acmeIssuer.DNS01Solver == nil {
@@ -185,17 +179,36 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s
Adapter: certificate.NewAdapter(C.TypeACME, tag), Adapter: certificate.NewAdapter(C.TypeACME, tag),
ctx: ctx, ctx: ctx,
config: config, config: config,
cache: cache, zapLogger: zapLogger,
dataDirectory: dataDirectory,
domain: options.Domain, domain: options.Domain,
nextProtos: nextProtos, nextProtos: nextProtos,
}, nil }, nil
} }
func (s *Service) Start(stage adapter.StartStage) error { func (s *Service) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart { switch stage {
return nil case adapter.StartStateInitialize:
if s.dataDirectory != "" {
err := filemanager.MkdirAll(s.ctx, s.dataDirectory, 0o700)
if err != nil {
return E.Cause(err, "create ACME data directory")
} }
}
config := s.config
cache := certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil
},
Logger: s.zapLogger,
})
config = certmagic.New(cache, *config)
s.config = config
s.cache = cache
case adapter.StartStateStart:
return s.config.ManageAsync(s.ctx, s.domain) return s.config.ManageAsync(s.ctx, s.domain)
}
return nil
} }
func (s *Service) Close() error { func (s *Service) Close() error {
+17 -8
View File
@@ -65,6 +65,7 @@ type Service struct {
timeFunc func() time.Time timeFunc func() time.Time
httpClient *http.Client httpClient *http.Client
storage certmagic.Storage storage certmagic.Storage
dataDirectory string
storageIssuerKey string storageIssuerKey string
storageNamesKey string storageNamesKey string
storageLockKey string storageLockKey string
@@ -109,14 +110,12 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s
cancel() cancel()
return nil, err return nil, err
} }
var storage certmagic.Storage var (
storage certmagic.Storage
dataDirectory string
)
if options.DataDirectory != "" { if options.DataDirectory != "" {
dataDirectory := filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory)) dataDirectory = filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory))
mkdirErr := filemanager.MkdirAll(ctx, dataDirectory, 0o700)
if mkdirErr != nil {
cancel()
return nil, E.Cause(mkdirErr, "create data directory")
}
storage = &certmagic.FileStorage{Path: dataDirectory} storage = &certmagic.FileStorage{Path: dataDirectory}
} else { } else {
storage = certmagic.Default.Storage storage = certmagic.Default.Storage
@@ -140,6 +139,7 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s
timeFunc: timeFunc, timeFunc: timeFunc,
httpClient: httpClient, httpClient: httpClient,
storage: storage, storage: storage,
dataDirectory: dataDirectory,
storageIssuerKey: storageIssuerKey, storageIssuerKey: storageIssuerKey,
storageNamesKey: storageNamesKey, storageNamesKey: storageNamesKey,
storageLockKey: storageLockKey, storageLockKey: storageLockKey,
@@ -162,7 +162,16 @@ func originCAHTTPClient(ctx context.Context, logger log.ContextLogger, options o
} }
func (s *Service) Start(stage adapter.StartStage) error { func (s *Service) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart { if stage == adapter.StartStateInitialize {
if s.dataDirectory == "" {
return nil
}
err := filemanager.MkdirAll(s.ctx, s.dataDirectory, 0o700)
if err != nil {
return E.Cause(err, "create data directory")
}
return nil
} else if stage != adapter.StartStateStart {
return nil return nil
} }
cachedCertificate, cachedLeaf, err := s.loadCachedCertificate() cachedCertificate, cachedLeaf, err := s.loadCachedCertificate()