2026-10-15 13:15:49 -07:00
|
|
|
package tailscale
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-10-16 07:04:24 -07:00
|
|
|
"encoding/json"
|
2026-10-15 13:15:49 -07:00
|
|
|
"fmt"
|
2026-10-16 07:04:24 -07:00
|
|
|
"os"
|
2026-10-15 13:15:49 -07:00
|
|
|
"os/exec"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const cli = "/Applications/Tailscale.app/Contents/MacOS/Tailscale"
|
|
|
|
|
|
|
|
|
|
func Serve(ctx context.Context, port int) error {
|
2026-10-18 15:19:57 -07:00
|
|
|
_, err := serve(ctx, port, "http://127.0.0.1:"+strconv.Itoa(port))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ServeRoot(ctx context.Context, target string) (string, error) {
|
|
|
|
|
host, err := serve(ctx, 443, target)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return "https://" + strings.TrimSuffix(host, ":443"), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func serve(ctx context.Context, port int, target string) (string, error) {
|
|
|
|
|
_, err := run(ctx, "serve", "--bg", "--https="+strconv.Itoa(port), target)
|
2026-10-16 07:04:24 -07:00
|
|
|
if err != nil {
|
2026-10-18 15:19:57 -07:00
|
|
|
return "", err
|
2026-10-16 07:04:24 -07:00
|
|
|
}
|
|
|
|
|
out, err := run(ctx, "serve", "status", "--json")
|
|
|
|
|
if err != nil {
|
2026-10-18 15:19:57 -07:00
|
|
|
return "", err
|
2026-10-16 07:04:24 -07:00
|
|
|
}
|
|
|
|
|
status := struct {
|
|
|
|
|
Web map[string]json.RawMessage
|
|
|
|
|
}{}
|
|
|
|
|
err = json.Unmarshal(out, &status)
|
|
|
|
|
if err != nil {
|
2026-10-18 15:19:57 -07:00
|
|
|
return "", fmt.Errorf("parse tailscale serve status: %w", err)
|
2026-10-16 07:04:24 -07:00
|
|
|
}
|
|
|
|
|
suffix := ":" + strconv.Itoa(port)
|
|
|
|
|
for host := range status.Web {
|
|
|
|
|
if strings.HasSuffix(host, suffix) {
|
2026-10-18 15:19:57 -07:00
|
|
|
return host, nil
|
2026-10-16 07:04:24 -07:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-18 15:19:57 -07:00
|
|
|
return "", fmt.Errorf("tailscale serve --https=%d exited 0 but port %d is not in serve status", port, port)
|
2026-10-15 13:15:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Off(ctx context.Context, port int) error {
|
2026-10-16 07:04:24 -07:00
|
|
|
_, err := run(ctx, "serve", "--https="+strconv.Itoa(port), "off")
|
|
|
|
|
return err
|
2026-10-15 13:15:49 -07:00
|
|
|
}
|
|
|
|
|
|
2026-10-16 07:04:24 -07:00
|
|
|
func run(ctx context.Context, args ...string) ([]byte, error) {
|
|
|
|
|
cmd := exec.CommandContext(ctx, cli, args...)
|
|
|
|
|
cmd.Env = append(os.Environ(), "TAILSCALE_BE_CLI=1")
|
|
|
|
|
stderr := strings.Builder{}
|
|
|
|
|
cmd.Stderr = &stderr
|
|
|
|
|
out, err := cmd.Output()
|
2026-10-15 13:15:49 -07:00
|
|
|
if err != nil {
|
2026-10-16 07:04:24 -07:00
|
|
|
return nil, fmt.Errorf("tailscale %s: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(stderr.String()+string(out)))
|
2026-10-15 13:15:49 -07:00
|
|
|
}
|
2026-10-16 07:04:24 -07:00
|
|
|
return out, nil
|
2026-10-15 13:15:49 -07:00
|
|
|
}
|