From 2392b176e200dbf26dec1a04d1f26982cdbf90b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 15 Jul 2026 20:50:36 +0800 Subject: [PATCH] Fix start lifecycle --- box.go | 18 +++++- common/httpclient/apple_transport_darwin.go | 13 ++++ common/httpclient/apple_transport_stub.go | 4 ++ common/httpclient/client.go | 12 ++-- common/proxybridge/bridge.go | 21 ++++--- common/tls/acme.go | 65 +++++++++++++------- common/trafficcontrol/manager.go | 18 ++++-- debug.go | 14 +++-- debug_http.go | 20 +++--- experimental/boxdd/locale.go | 7 +-- experimental/locale/locale_fa.go | 4 +- log/observable.go | 6 +- protocol/anytls/outbound.go | 38 +++++++----- protocol/tailscale/endpoint.go | 8 +-- protocol/tor/outbound.go | 62 ++++++++++++------- route/rule/rule_set_local.go | 1 + service/acme/service.go | 67 ++++++++++++--------- service/origin_ca/service.go | 25 +++++--- 18 files changed, 259 insertions(+), 144 deletions(-) diff --git a/box.go b/box.go index 608b436df..eded0b8b4 100644 --- a/box.go +++ b/box.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "net/http" "os" "runtime/debug" "time" @@ -43,6 +44,8 @@ var _ adapter.SimpleLifecycle = (*Box)(nil) type Box struct { createdAt time.Time + debugOptions option.DebugOptions + debugHTTPServer *http.Server logFactory log.Factory logger log.ContextLogger network *route.NetworkManager @@ -142,7 +145,8 @@ func New(options Options) (*Box, error) { ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) - err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) + debugOptions := common.PtrValueOrDefault(experimentalOptions.Debug) + err := checkDebugOptions(debugOptions) if err != nil { return nil, err } @@ -470,6 +474,7 @@ func New(options Options) (*Box, error) { router: router, httpClientService: httpClientService, createdAt: createdAt, + debugOptions: debugOptions, logFactory: logFactory, logger: logFactory.Logger(), internalService: internalServices, @@ -523,6 +528,11 @@ func (s *Box) preStart() error { if err != nil { 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 if err != nil { return err @@ -594,6 +604,12 @@ func (s *Box) Close() error { close(s.done) } 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 { name string service adapter.Lifecycle diff --git a/common/httpclient/apple_transport_darwin.go b/common/httpclient/apple_transport_darwin.go index 4619dc58d..36584f9cf 100644 --- a/common/httpclient/apple_transport_darwin.go +++ b/common/httpclient/apple_transport_darwin.go @@ -96,6 +96,15 @@ type appleTransport struct { 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) { sessionConfig, err := newAppleSessionConfig(ctx, options) if err != nil { @@ -111,6 +120,10 @@ func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDial if err != nil { return nil, err } + err = bridge.Start() + if err != nil { + return nil, err + } shared := &appleTransportShared{ logger: logger, bridge: bridge, diff --git a/common/httpclient/apple_transport_stub.go b/common/httpclient/apple_transport_stub.go index 9735998f4..5e6caa6c3 100644 --- a/common/httpclient/apple_transport_stub.go +++ b/common/httpclient/apple_transport_stub.go @@ -11,6 +11,10 @@ import ( 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) { return nil, E.New("Apple HTTP engine is not available on non-Apple platforms") } diff --git a/common/httpclient/client.go b/common/httpclient/client.go index 9dbb8cc1d..b310152a3 100644 --- a/common/httpclient/client.go +++ b/common/httpclient/client.go @@ -35,11 +35,11 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string, var cheapRebuild bool switch options.Engine { case C.TLSEngineApple: - inner, transportErr := newAppleTransport(ctx, logger, rawDialer, options) - if transportErr != nil { - return nil, transportErr + err = validateAppleTransport(ctx, options) + if err != nil { + return nil, err } - managedTransport := &ManagedTransport{ + return &ManagedTransport{ dialer: rawDialer, headers: headers, host: host, @@ -47,9 +47,7 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string, factory: func() (innerTransport, error) { return newAppleTransport(ctx, logger, rawDialer, options) }, - } - managedTransport.epoch.Store(&transportEpoch{transport: inner}) - return managedTransport, nil + }, nil case "", C.TLSEngineGo: cheapRebuild = true default: diff --git a/common/proxybridge/bridge.go b/common/proxybridge/bridge.go index 3380cae44..0db61bb90 100644 --- a/common/proxybridge/bridge.go +++ b/common/proxybridge/bridge.go @@ -34,23 +34,26 @@ type Bridge struct { func New(ctx context.Context, logger logger.ContextLogger, tag string, dialer N.Dialer) (*Bridge, error) { username := randomHex(16) password := randomHex(16) - tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) - if err != nil { - return nil, err - } - bridge := &Bridge{ + return &Bridge{ ctx: ctx, logger: logger, tag: tag, dialer: dialer, connection: service.FromContext[adapter.ConnectionManager](ctx), - tcpListener: tcpListener, 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() - return bridge, nil + b.tcpListener = tcpListener + go b.acceptLoop() + return nil } func randomHex(size int) string { @@ -72,7 +75,7 @@ func (b *Bridge) Password() string { } func (b *Bridge) Close() error { - return common.Close(b.tcpListener) + return common.Close(common.PtrOrNil(b.tcpListener)) } func (b *Bridge) acceptLoop() { diff --git a/common/tls/acme.go b/common/tls/acme.go index efccdeced..c736d08f5 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -26,21 +26,45 @@ import ( ) type acmeWrapper struct { - ctx context.Context - cfg *certmagic.Config - cache *certmagic.Cache - domain []string + ctx context.Context + cfg *certmagic.Config + cache *certmagic.Cache + zapLogger *zap.Logger + dataDirectory string + domain []string } 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) } func (w *acmeWrapper) Close() error { - w.cache.Stop() + if w.cache != nil { + w.cache.Stop() + } 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) { var acmeServer string switch options.Provider { @@ -54,13 +78,12 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound } acmeServer = options.Provider } - var storage certmagic.Storage + var ( + storage certmagic.Storage + dataDirectory string + ) if 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") - } + dataDirectory = filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory)) storage = &certmagic.FileStorage{ Path: dataDirectory, } @@ -126,23 +149,23 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount) } config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)} - cache := certmagic.NewCache(certmagic.CacheOptions{ - GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) { - return config, nil - }, - Logger: zapLogger, - }) - config = certmagic.New(cache, *config) + wrapper := &acmeWrapper{ + ctx: ctx, + cfg: config, + zapLogger: zapLogger, + dataDirectory: dataDirectory, + domain: options.Domain, + } var tlsConfig *tls.Config if acmeConfig.DisableTLSALPNChallenge || acmeConfig.DNS01Solver != nil { tlsConfig = &tls.Config{ - GetCertificate: config.GetCertificate, + GetCertificate: wrapper.GetCertificate, } } else { tlsConfig = &tls.Config{ - GetCertificate: config.GetCertificate, + GetCertificate: wrapper.GetCertificate, NextProtos: []string{C.ACMETLS1Protocol}, } } - return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil + return tlsConfig, wrapper, nil } diff --git a/common/trafficcontrol/manager.go b/common/trafficcontrol/manager.go index 954263a60..c18a59f58 100644 --- a/common/trafficcontrol/manager.go +++ b/common/trafficcontrol/manager.go @@ -50,13 +50,10 @@ type Manager struct { } func NewManager(outbound adapter.OutboundManager) *Manager { - manager := &Manager{ + return &Manager{ outbound: outbound, 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 { @@ -64,12 +61,21 @@ func (m *Manager) Name() string { } 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 } func (m *Manager) Close() error { - m.cleaner.Close() - return m.eventObserver.Close() + if m.cleaner != nil { + m.cleaner.Close() + } + if m.eventObserver != nil { + return m.eventObserver.Close() + } + return nil } func (m *Manager) SubscribeEvents() (observable.Subscription[ConnectionEvent], <-chan struct{}, error) { diff --git a/debug.go b/debug.go index f620172be..96e74c13b 100644 --- a/debug.go +++ b/debug.go @@ -7,8 +7,14 @@ import ( E "github.com/sagernet/sing/common/exceptions" ) -func applyDebugOptions(options option.DebugOptions) error { - applyDebugListenOption(options) +func checkDebugOptions(options option.DebugOptions) error { + 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 { debug.SetGCPercent(*options.GCPercent) } @@ -27,8 +33,4 @@ func applyDebugOptions(options option.DebugOptions) error { if options.MemoryLimit.Value() != 0 { 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 } diff --git a/debug_http.go b/debug_http.go index e51a07314..ddafe8f2c 100644 --- a/debug_http.go +++ b/debug_http.go @@ -1,6 +1,7 @@ package box import ( + "net" "net/http" "net/http/pprof" "runtime" @@ -17,15 +18,9 @@ import ( "github.com/go-chi/chi/v5" ) -var debugHTTPServer *http.Server - -func applyDebugListenOption(options option.DebugOptions) { - if debugHTTPServer != nil { - debugHTTPServer.Close() - debugHTTPServer = nil - } +func startDebugHTTPServer(options option.DebugOptions) (*http.Server, error) { if options.Listen == "" { - return + return nil, nil } r := chi.NewMux() r.Route("/debug", func(r chi.Router) { @@ -63,14 +58,19 @@ func applyDebugListenOption(options option.DebugOptions) { r.HandleFunc("/trace", pprof.Trace) }) }) - debugHTTPServer = &http.Server{ + server := &http.Server{ Addr: options.Listen, Handler: r, } + listener, err := net.Listen("tcp", options.Listen) + if err != nil { + return nil, E.Cause(err, "listen debug HTTP server") + } go func() { - err := debugHTTPServer.ListenAndServe() + err := server.Serve(listener) if err != nil && !E.IsClosed(err) { log.Error(E.Cause(err, "serve debug HTTP server")) } }() + return server, nil } diff --git a/experimental/boxdd/locale.go b/experimental/boxdd/locale.go index d666984d8..7c6c2518e 100644 --- a/experimental/boxdd/locale.go +++ b/experimental/boxdd/locale.go @@ -2,6 +2,7 @@ package main import ( "context" + "slices" "github.com/sagernet/sing-box/experimental/locale" @@ -14,11 +15,7 @@ func setLocaleFromContext(ctx context.Context) { if !loaded { return } - for _, localeID := range requestMetadata.Get("accept-language") { - if locale.Set(localeID) { - return - } - } + slices.ContainsFunc(requestMetadata.Get("accept-language"), locale.Set) } func unaryLocaleInterceptor(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { diff --git a/experimental/locale/locale_fa.go b/experimental/locale/locale_fa.go index 0e9211d64..edd952faa 100644 --- a/experimental/locale/locale_fa.go +++ b/experimental/locale/locale_fa.go @@ -5,7 +5,7 @@ func init() { Locale: "fa", DeprecatedMessage: "%s از sing-box %s منسوخ شده است و در sing-box %s حذف خواهد شد؛ لطفاً راهنمای مهاجرت را ببینید.", DeprecatedMessageNoLink: "%s از sing-box %s منسوخ شده است و در sing-box %s حذف خواهد شد.", - InsecureFeatureMessage: "%s در کلاینت گرافیکی sing-box برای Windows ناامن تلقی می‌شود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.", - ExternalPathFeature: "دسترسی به %s (خارج از پوشهٔ کاری) در کلاینت گرافیکی sing-box برای Windows ناامن تلقی می‌شود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.", + InsecureFeatureMessage: "%s در کلاینت گرافیکی sing-box برای Windows ناامن تلقی می\u200cشود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.", + ExternalPathFeature: "دسترسی به %s (خارج از پوشهٔ کاری) در کلاینت گرافیکی sing-box برای Windows ناامن تلقی می\u200cشود. برای استفاده، `حالت ناامن` را در `تنظیمات - هسته - حالت ناامن` فعال کنید.", } } diff --git a/log/observable.go b/log/observable.go index d19001b6b..b6bc34f3b 100644 --- a/log/observable.go +++ b/log/observable.go @@ -56,9 +56,6 @@ func NewDefaultFactory( /*if platformWriter != nil { factory.platformFormatter.DisableColors = platformWriter.DisableColors() }*/ - if needObservable { - factory.observer = observable.NewObserver[Entry](factory.subscriber, 64) - } return factory } @@ -71,6 +68,9 @@ func (f *defaultFactory) Start() error { f.writer = logFile f.file = logFile } + if f.needObservable { + f.observer = observable.NewObserver[Entry](f.subscriber, 64) + } return nil } diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go index 2f24c2ef8..92fbbecd7 100644 --- a/protocol/anytls/outbound.go +++ b/protocol/anytls/outbound.go @@ -27,17 +27,20 @@ func RegisterOutbound(registry *outbound.Registry) { type Outbound struct { outbound.Adapter - dialer tls.Dialer - server M.Socksaddr - tlsConfig tls.Config - client *anytls.Client - uotClient *uot.Client - logger log.ContextLogger + ctx context.Context + dialer tls.Dialer + server M.Socksaddr + tlsConfig tls.Config + clientOptions anytls.ClientConfig + client *anytls.Client + uotClient *uot.Client + logger log.ContextLogger } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) { outbound := &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeAnyTLS, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + ctx: ctx, server: options.ServerOptions.Build(), logger: logger, } @@ -69,24 +72,31 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig) - client, err := anytls.NewClient(ctx, anytls.ClientConfig{ + outbound.clientOptions = anytls.ClientConfig{ Password: options.Password, IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(), IdleSessionTimeout: options.IdleSessionTimeout.Build(), MinIdleSession: options.MinIdleSession, DialOut: outbound.dialOut, Logger: logger, - }) - if err != nil { - return nil, err } - outbound.client = client + return outbound, nil +} - outbound.uotClient = &uot.Client{ +func (h *Outbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateInitialize { + return nil + } + client, err := anytls.NewClient(h.ctx, h.clientOptions) + if err != nil { + return err + } + h.client = client + h.uotClient = &uot.Client{ Dialer: (anytlsDialer)(client.CreateProxy), Version: uot.Version, } - return outbound, nil + return nil } type anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error) @@ -127,5 +137,5 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n } func (h *Outbound) Close() error { - return common.Close(h.client) + return common.Close(common.PtrOrNil(h.client)) } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 4d7111886..911b62d0a 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -151,10 +151,6 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL } stateDirectory = filemanager.BasePath(ctx, os.ExpandEnv(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 { err := adapter.CheckSecurityFeature(ctx, "Tailscale `ssh_server`") if err != nil { @@ -259,6 +255,10 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL func (t *Endpoint) Start(stage adapter.StartStage) error { switch stage { 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) case adapter.StartStateStart: return t.start() diff --git a/protocol/tor/outbound.go b/protocol/tor/outbound.go index 0a10f2ad0..ef46c417c 100644 --- a/protocol/tor/outbound.go +++ b/protocol/tor/outbound.go @@ -35,6 +35,7 @@ type Outbound struct { outbound.Adapter ctx context.Context logger logger.ContextLogger + dialer N.Dialer proxy *proxybridge.Bridge startConf *tor.StartConf options map[string]string @@ -51,10 +52,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } startConf.TempDataDirBase = filemanager.TempPath(ctx) if startConf.DataDir != "" { - err := filemanager.MkdirAll(ctx, startConf.DataDir, 0o755) - if err != nil { - return nil, err - } dataDirAbs, _ := filepath.Abs(startConf.DataDir) geoIPPath := filepath.Join(dataDirAbs, "geoip") 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") torrcInfo, err := filemanager.Stat(ctx, torrcFile) - if os.IsNotExist(err) { - err = filemanager.WriteFile(ctx, torrcFile, []byte(""), 0o600) - if err != nil { - return nil, err - } - } else if err != nil { + if err != nil && !os.IsNotExist(err) { return nil, err - } else if torrcInfo.IsDir() { + } else if err == nil && torrcInfo.IsDir() { return nil, E.New("Tor configuration path is a directory: ", torrcFile) } startConf.TorrcFile = torrcFile @@ -94,26 +86,54 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - proxy, err := proxybridge.New(ctx, logger, "proxy", outboundDialer) - if err != nil { - return nil, err - } return &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, tag, []string{N.NetworkTCP}, options.DialerOptions), ctx: ctx, logger: logger, - proxy: proxy, + dialer: outboundDialer, startConf: &startConf, options: options.Options, }, nil } -func (t *Outbound) Start() error { - err := t.start() - if err != nil { - t.Close() +func (t *Outbound) Start(stage adapter.StartStage) error { + 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 { + t.Close() + return err + } } - return err + return nil } var torLogEvents = []control.EventCode{ diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index e1bb83ddd..513b8bf90 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -113,6 +113,7 @@ func (s *LocalRuleSet) reloadFile(path string) error { if err != nil { return err } + defer setFile.Close() ruleSet, err = srs.Read(setFile, false) if err != nil { return err diff --git a/service/acme/service.go b/service/acme/service.go index fe2d516ea..2553100f2 100644 --- a/service/acme/service.go +++ b/service/acme/service.go @@ -48,11 +48,13 @@ var ( type Service struct { certificate.Adapter - ctx context.Context - config *certmagic.Config - cache *certmagic.Cache - domain []string - nextProtos []string + ctx context.Context + config *certmagic.Config + cache *certmagic.Cache + zapLogger *zap.Logger + dataDirectory string + domain []string + nextProtos []string } func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag string, options option.ACMECertificateProviderOptions) (adapter.CertificateProviderService, error) { @@ -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") } - var storage certmagic.Storage + var ( + storage certmagic.Storage + dataDirectory string + ) if 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") - } + dataDirectory = filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory)) storage = &certmagic.FileStorage{Path: dataDirectory} } else { storage = certmagic.Default.Storage @@ -169,33 +170,45 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s } reflect.NewAt(httpClientField.Type(), unsafe.Pointer(httpClientField.UnsafeAddr())).Elem().Set(reflect.ValueOf(acmeHTTPClient)) 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 if !acmeIssuer.DisableTLSALPNChallenge && acmeIssuer.DNS01Solver == nil { nextProtos = []string{C.ACMETLS1Protocol} } return &Service{ - Adapter: certificate.NewAdapter(C.TypeACME, tag), - ctx: ctx, - config: config, - cache: cache, - domain: options.Domain, - nextProtos: nextProtos, + Adapter: certificate.NewAdapter(C.TypeACME, tag), + ctx: ctx, + config: config, + zapLogger: zapLogger, + dataDirectory: dataDirectory, + domain: options.Domain, + nextProtos: nextProtos, }, nil } func (s *Service) Start(stage adapter.StartStage) error { - if stage != adapter.StartStateStart { - return nil + switch stage { + 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 { diff --git a/service/origin_ca/service.go b/service/origin_ca/service.go index d7bd235a1..9f303e402 100644 --- a/service/origin_ca/service.go +++ b/service/origin_ca/service.go @@ -65,6 +65,7 @@ type Service struct { timeFunc func() time.Time httpClient *http.Client storage certmagic.Storage + dataDirectory string storageIssuerKey string storageNamesKey string storageLockKey string @@ -109,14 +110,12 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s cancel() return nil, err } - var storage certmagic.Storage + var ( + storage certmagic.Storage + dataDirectory string + ) if 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") - } + dataDirectory = filemanager.BasePath(ctx, os.ExpandEnv(options.DataDirectory)) storage = &certmagic.FileStorage{Path: dataDirectory} } else { storage = certmagic.Default.Storage @@ -140,6 +139,7 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s timeFunc: timeFunc, httpClient: httpClient, storage: storage, + dataDirectory: dataDirectory, storageIssuerKey: storageIssuerKey, storageNamesKey: storageNamesKey, storageLockKey: storageLockKey, @@ -162,7 +162,16 @@ func originCAHTTPClient(ctx context.Context, logger log.ContextLogger, options o } 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 } cachedCertificate, cachedLeaf, err := s.loadCachedCertificate()