diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index bab3bc75a..2a1adefe4 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -59,6 +59,7 @@ import ( "tailscale.com/tsd" "tailscale.com/tsweb/varz" "tailscale.com/types/flagtype" + "tailscale.com/types/lazy" "tailscale.com/types/logger" "tailscale.com/types/logid" "tailscale.com/util/clientmetric" @@ -153,6 +154,14 @@ var subCommands = map[string]*func([]string) error{ var beCLI func() // non-nil if CLI is linked in +// tailscaledInit facilitates the lazy initialization of tailscaled. +// It defers potentially expensive and platform-specific +// initialization steps until the main function is called and +// determines that the current process is an actual tailscaled +// process, rather than a CLI symlink, `tailscaled --version`, or +// another subcommand that doesn't require full initialization. +var tailscaledInit lazy.DeferredInit + func main() { envknob.PanicIfAnyEnvCheckedInInit() envknob.ApplyDiskConfig() @@ -227,6 +236,11 @@ func main() { log.Fatalf("--bird-socket is not supported on %s", runtime.GOOS) } + if err := tailscaledInit.Do(); err != nil { + log.SetFlags(0) + log.Fatalf("tailscaled init failed: %v", err) + } + // Only apply a default statepath when neither have been provided, so that a // user may specify only --statedir if they wish. if args.statepath == "" && args.statedir == "" { diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 7208e03da..d291415c8 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -60,31 +60,34 @@ import ( "tailscale.com/wf" ) -func init() { - // Initialize COM process-wide. - comProcessType := com.Service - if !isWindowsService() { - comProcessType = com.ConsoleApp - } - if err := com.StartRuntime(comProcessType); err != nil { - log.Printf("wingoes.com.StartRuntime(%d) failed: %v", comProcessType, err) - } -} - // permitPolicyLocks is a function to be called to lift the restriction on acquiring // [gp.PolicyLock]s once the service is running. // It is safe to be called multiple times. var permitPolicyLocks = func() {} func init() { - if isWindowsService() { - // We prevent [gp.PolicyLock]s from being acquired until the service enters the running state. - // Otherwise, if tailscaled starts due to a GPSI policy installing Tailscale, it may deadlock - // while waiting for the write counterpart of the GP lock to be released by Group Policy, - // which is itself waiting for the installation to complete and tailscaled to start. - // See tailscale/tailscale#14416 for more information. - permitPolicyLocks = gp.RestrictPolicyLocks() - } + tailscaledInit.Defer(func() error { + // Initialize COM process-wide. + comProcessType := com.Service + isService := isWindowsService() + if !isService { + comProcessType = com.ConsoleApp + } + if err := com.StartRuntime(comProcessType); err != nil { + return errors.New(fmt.Sprintf("wingoes.com.StartRuntime(%d) failed: %v", comProcessType, err)) + } + + if isService { + // We prevent [gp.PolicyLock]s from being acquired until the service enters the running state. + // Otherwise, if tailscaled starts due to a GPSI policy installing Tailscale, it may deadlock + // while waiting for the write counterpart of the GP lock to be released by Group Policy, + // which is itself waiting for the installation to complete and tailscaled to start. + // See tailscale/tailscale#14416 for more information. + permitPolicyLocks = gp.RestrictPolicyLocks() + } + + return nil + }) } const serviceName = "Tailscale"