Run byre and the app on their own embedded Tailscale nodes
This commit is contained in:
+90
-11
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -17,9 +19,13 @@ import (
|
||||
|
||||
"g.fc.run/ai/byre/internal/server"
|
||||
"g.fc.run/ai/byre/internal/store"
|
||||
"tailscale.com/tsnet"
|
||||
)
|
||||
|
||||
const defaultListen = "127.0.0.1:8270"
|
||||
const (
|
||||
defaultListen = "127.0.0.1:8270"
|
||||
loopbackURL = "http://" + defaultListen
|
||||
)
|
||||
|
||||
func serveCmd(args []string) {
|
||||
configDir, err := os.UserConfigDir()
|
||||
@@ -29,11 +35,34 @@ func serveCmd(args []string) {
|
||||
|
||||
flags := flag.NewFlagSet("serve", flag.ExitOnError)
|
||||
dataDir := flags.String("data", filepath.Join(configDir, "byre"), "directory holding byre's database, worktrees, leads and blobs")
|
||||
listenAddr := flags.String("listen", defaultListen, "address to listen on")
|
||||
listenAddr := flags.String("listen", defaultListen, "loopback address serving GET /api/tailnet")
|
||||
ports := flags.Int("ports", store.DefaultPortFirst, "first port of the tasks' port blocks")
|
||||
hostname := flags.String("ts-hostname", "byre-"+macSlug(), "hostname of byre's tailnet node")
|
||||
flags.Parse(args)
|
||||
|
||||
srv, err := server.New(*dataDir, *ports)
|
||||
host, _, err := net.SplitHostPort(*listenAddr)
|
||||
if err != nil {
|
||||
fatal("parse --listen", "err", err)
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil || !ip.IsLoopback() {
|
||||
fatal("--listen must be a loopback address", "addr", *listenAddr)
|
||||
}
|
||||
|
||||
ts := &tsnet.Server{
|
||||
Dir: filepath.Join(*dataDir, "tsnet"),
|
||||
Hostname: *hostname,
|
||||
UserLogf: func(format string, args ...any) {
|
||||
slog.Info("tsnet", "msg", fmt.Sprintf(format, args...))
|
||||
},
|
||||
}
|
||||
err = ts.Start()
|
||||
if err != nil {
|
||||
fatal("start tsnet", "err", err)
|
||||
}
|
||||
defer ts.Close()
|
||||
|
||||
srv, err := server.New(*dataDir, *ports, ts)
|
||||
if err != nil {
|
||||
fatal("start server", "err", err)
|
||||
}
|
||||
@@ -46,26 +75,76 @@ func serveCmd(args []string) {
|
||||
go watchExecutable(ctx, replaced)
|
||||
go updateDaily(ctx)
|
||||
|
||||
hs := &http.Server{
|
||||
Addr: *listenAddr,
|
||||
Handler: srv.Handler(),
|
||||
BaseContext: func(net.Listener) context.Context { return ctx },
|
||||
}
|
||||
base := func(net.Listener) context.Context { return ctx }
|
||||
loopback := &http.Server{Addr: *listenAddr, Handler: srv.LoopbackHandler(), BaseContext: base}
|
||||
tailnet := &http.Server{Handler: srv.Handler(), BaseContext: base}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
hs.Shutdown(shutdownCtx)
|
||||
loopback.Shutdown(shutdownCtx)
|
||||
tailnet.Shutdown(shutdownCtx)
|
||||
}()
|
||||
|
||||
slog.Info("listening", "addr", *listenAddr, "data", *dataDir, "ports", *ports)
|
||||
err = hs.ListenAndServe()
|
||||
go serveTailnet(ctx, ts, tailnet)
|
||||
|
||||
slog.Info("listening", "addr", *listenAddr, "hostname", *hostname, "data", *dataDir, "ports", *ports)
|
||||
err = loopback.ListenAndServe()
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
fatal("serve", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func serveTailnet(ctx context.Context, ts *tsnet.Server, hs *http.Server) {
|
||||
st, err := ts.Up(ctx)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
fatal("tsnet up", "err", err)
|
||||
}
|
||||
if len(st.CertDomains) == 0 {
|
||||
slog.Error("tailnet has no https certificates; turn on HTTPS on the DNS page of the tailscale admin console, then restart byre")
|
||||
return
|
||||
}
|
||||
lc, err := ts.LocalClient()
|
||||
if err != nil {
|
||||
fatal("tsnet local client", "err", err)
|
||||
}
|
||||
ln, err := ts.Listen("tcp", ":443")
|
||||
if err != nil {
|
||||
fatal("tsnet listen", "err", err)
|
||||
}
|
||||
tlsLn := tls.NewListener(ln, &tls.Config{GetCertificate: lc.GetCertificate, NextProtos: []string{"h2", "http/1.1"}})
|
||||
slog.Info("listening on tailnet", "origin", "https://"+st.CertDomains[0])
|
||||
err = hs.Serve(tlsLn)
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
fatal("serve tailnet", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func macSlug() string {
|
||||
out, err := exec.Command("scutil", "--get", "ComputerName").Output()
|
||||
if err != nil {
|
||||
fatal("computer name", "err", err)
|
||||
}
|
||||
slug := strings.Builder{}
|
||||
dash := false
|
||||
for _, r := range strings.ToLower(strings.TrimSpace(string(out))) {
|
||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
||||
if dash && slug.Len() > 0 {
|
||||
slug.WriteByte('-')
|
||||
}
|
||||
slug.WriteRune(r)
|
||||
dash = false
|
||||
continue
|
||||
}
|
||||
dash = true
|
||||
}
|
||||
return slug.String()
|
||||
}
|
||||
|
||||
func watchExecutable(ctx context.Context, replaced context.CancelFunc) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user