//go:build tsnet package cmd import ( "context" "errors" "log/slog" "net" "net/http" "time" "tailscale.com/tsnet" "github.com/nextlevelbuilder/goclaw/internal/config" ) // initTailscale starts an additional Tailscale listener alongside the main gateway. // Only compiled with -tags tsnet. The listener shares the same http.Handler (mux). func initTailscale(ctx context.Context, cfg *config.Config, mux http.Handler) func() { tc := cfg.Tailscale if tc.Hostname == "" { slog.Debug("Tailscale available but not configured (set GOCLAW_TSNET_HOSTNAME to enable)") return nil } srv := &tsnet.Server{ Hostname: tc.Hostname, AuthKey: tc.AuthKey, Ephemeral: tc.Ephemeral, } if tc.StateDir != "" { srv.Dir = tc.StateDir } var ( ln net.Listener err error ) if tc.EnableTLS { ln, err = srv.ListenTLS("tcp", ":443") } else { ln, err = srv.Listen("tcp", ":80") } if err != nil { slog.Warn("Tailscale listener failed to start", "error", err) srv.Close() return nil } port := ":80" if tc.EnableTLS { port = ":443 (TLS)" } slog.Info("Tailscale listener started", "hostname", tc.Hostname, "port", port, ) httpSrv := &http.Server{Handler: mux} go func() { if err := httpSrv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) { slog.Warn("Tailscale HTTP server error", "error", err) } }() // Graceful shutdown on context cancellation go func() { <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() httpSrv.Shutdown(shutdownCtx) }() return func() { httpSrv.Close() ln.Close() srv.Close() slog.Info("Tailscale listener stopped") } }