Files
byre/internal/tailscale/tailscale.go
T

95 lines
2.5 KiB
Go
Raw Normal View History

package tailscale
import (
"context"
2026-10-16 07:04:24 -07:00
"encoding/json"
"fmt"
2026-10-16 07:04:24 -07:00
"os"
"os/exec"
"strconv"
"strings"
)
const cli = "/Applications/Tailscale.app/Contents/MacOS/Tailscale"
func Serve(ctx context.Context, port int) error {
_, 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 {
return "", err
2026-10-16 07:04:24 -07:00
}
out, err := run(ctx, "serve", "status", "--json")
if err != nil {
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 {
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) {
return host, nil
2026-10-16 07:04:24 -07:00
}
}
return "", fmt.Errorf("tailscale serve --https=%d exited 0 but port %d is not in serve status", port, port)
}
2026-10-18 22:17:54 -07:00
func Problem(ctx context.Context) string {
_, err := os.Stat(cli)
if err != nil {
return "Install the Tailscale app from https://tailscale.com/download/mac and sign in."
}
out, err := run(ctx, "status", "--json", "--peers=false")
if err != nil {
return "Open Tailscale and sign in (" + err.Error() + ")."
}
status := struct {
BackendState string
CertDomains []string
}{}
err = json.Unmarshal(out, &status)
if err != nil {
return "Parse tailscale status: " + err.Error()
}
if status.BackendState != "Running" {
return "Open Tailscale and sign in (it is " + status.BackendState + ")."
}
if len(status.CertDomains) == 0 {
return "Turn on HTTPS Certificates on the DNS page of Tailscale's admin console, https://login.tailscale.com/admin/dns."
}
return ""
}
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-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()
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-16 07:04:24 -07:00
return out, nil
}