Skip to content

Commit 1d3b961

Browse files
committed
feat(server): add tailscale service listen mode
Add a tssvc:// listen endpoint that configures Tailscale Services via LocalAPI, updates CLI/docs, and covers behavior with endpoint/server tests. Also wire tsnet internal logs through the existing structured logger.
1 parent 2f3584e commit 1d3b961

File tree

7 files changed

+542
-10
lines changed

7 files changed

+542
-10
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ Then connect with:
5656
cleanroom exec --host tsnet://cleanroom:7777 -- "npm test"
5757
```
5858

59+
To expose the API as a Tailscale Service using the local `tailscaled` daemon:
60+
61+
```bash
62+
cleanroom serve --listen tssvc://cleanroom
63+
```
64+
65+
This configures `svc:cleanroom` with HTTPS on port 443 and advertises the
66+
service from this host. Connect from another tailnet device with:
67+
68+
```bash
69+
cleanroom exec --host https://cleanroom.<your-tailnet>.ts.net -- "npm test"
70+
```
71+
5972
### 3) Launch -> Run -> Terminate via API
6073

6174
```bash

internal/cli/cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ type ConsoleCommand struct {
8282
}
8383

8484
type ServeCommand struct {
85-
Listen string `help:"Listen endpoint for control API (defaults to runtime endpoint; supports tsnet://hostname[:port])"`
85+
Listen string `help:"Listen endpoint for control API (defaults to runtime endpoint; supports tsnet://hostname[:port] and tssvc://service[:local-port])"`
8686
LogLevel string `help:"Server log level (debug|info|warn|error)"`
8787
}
8888

internal/controlserver/server.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,25 @@ type tsnetServer interface {
4040
Close() error
4141
}
4242

43-
var newTSNetServer = func(ep endpoint.Endpoint, stateDir string) tsnetServer {
43+
var newTSNetServer = func(ep endpoint.Endpoint, stateDir string, tsLogf func(format string, args ...any)) tsnetServer {
4444
return &tsnet.Server{
4545
Dir: stateDir,
4646
Hostname: ep.TSNetHostname,
47+
Logf: tsLogf,
48+
}
49+
}
50+
51+
func tsnetLogf(logger *log.Logger) func(format string, args ...any) {
52+
if logger == nil {
53+
return nil
54+
}
55+
tsLogger := logger.With("subsystem", "tsnet")
56+
return func(format string, args ...any) {
57+
msg := strings.TrimSpace(fmt.Sprintf(format, args...))
58+
if msg == "" {
59+
return
60+
}
61+
tsLogger.Debug(msg)
4762
}
4863
}
4964

@@ -606,7 +621,7 @@ func (s *Server) handleTerminateCleanroom(w http.ResponseWriter, r *http.Request
606621
}
607622

608623
func Serve(ctx context.Context, ep endpoint.Endpoint, handler http.Handler, logger *log.Logger) error {
609-
listener, cleanup, err := listen(ep)
624+
listener, cleanup, err := listen(ep, logger)
610625
if err != nil {
611626
return err
612627
}
@@ -653,7 +668,7 @@ func Serve(ctx context.Context, ep endpoint.Endpoint, handler http.Handler, logg
653668
}
654669
}
655670

656-
func listen(ep endpoint.Endpoint) (net.Listener, func() error, error) {
671+
func listen(ep endpoint.Endpoint, logger *log.Logger) (net.Listener, func() error, error) {
657672
if ep.Scheme == "unix" {
658673
if err := os.MkdirAll(filepath.Dir(ep.Address), 0o755); err != nil {
659674
return nil, nil, err
@@ -680,7 +695,7 @@ func listen(ep endpoint.Endpoint) (net.Listener, func() error, error) {
680695
if err := os.MkdirAll(stateDir, 0o700); err != nil {
681696
return nil, nil, fmt.Errorf("create tsnet state directory: %w", err)
682697
}
683-
server := newTSNetServer(ep, stateDir)
698+
server := newTSNetServer(ep, stateDir, tsnetLogf(logger))
684699
listener, err := server.Listen("tcp", ep.Address)
685700
if err != nil {
686701
_ = server.Close()
@@ -689,6 +704,20 @@ func listen(ep endpoint.Endpoint) (net.Listener, func() error, error) {
689704
return listener, server.Close, nil
690705
}
691706

707+
if ep.Scheme == "tssvc" {
708+
listener, err := net.Listen("tcp", ep.Address)
709+
if err != nil {
710+
return nil, nil, fmt.Errorf("start tailscale service listener for %q: %w", ep.Address, err)
711+
}
712+
setupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
713+
defer cancel()
714+
if err := configureTailscaleService(setupCtx, ep, listener.Addr().String()); err != nil {
715+
_ = listener.Close()
716+
return nil, nil, err
717+
}
718+
return listener, nil, nil
719+
}
720+
692721
if ep.Scheme == "https" {
693722
return nil, nil, errors.New("https listen endpoints are not supported yet: TLS configuration is not implemented")
694723
}

0 commit comments

Comments
 (0)