Run byre and the app on their own embedded Tailscale nodes
This commit is contained in:
+37
-15
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -22,6 +21,8 @@ import (
|
||||
"g.fc.run/ai/byre/internal/terminal"
|
||||
"g.fc.run/ai/byre/internal/todo"
|
||||
"g.fc.run/ai/byre/internal/workflow"
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/tsnet"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
@@ -30,6 +31,8 @@ type Server struct {
|
||||
hub *hub
|
||||
clients *clients.Registry
|
||||
terminals *terminal.Terminals
|
||||
tailnet *local.Client
|
||||
hostname string
|
||||
startedAt time.Time
|
||||
|
||||
usersMu sync.Mutex
|
||||
@@ -38,7 +41,7 @@ type Server struct {
|
||||
|
||||
type ctxKey struct{}
|
||||
|
||||
func New(dataDir string, portFirst int) (*Server, error) {
|
||||
func New(dataDir string, portFirst int, ts *tsnet.Server) (*Server, error) {
|
||||
err := os.MkdirAll(dataDir, 0o755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -48,9 +51,15 @@ func New(dataDir string, portFirst int) (*Server, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lc, err := ts.LocalClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &Server{
|
||||
hub: newHub(),
|
||||
terminals: terminals,
|
||||
tailnet: lc,
|
||||
hostname: ts.Hostname,
|
||||
startedAt: time.Now().UTC(),
|
||||
users: map[string]string{},
|
||||
}
|
||||
@@ -73,7 +82,7 @@ func New(dataDir string, portFirst int) (*Server, error) {
|
||||
}
|
||||
}
|
||||
|
||||
s.agents = agents.New(s.store, filepath.Join(dataDir, "worktrees"), filepath.Join(dataDir, "leads"), filepath.Join(dataDir, "hands"), filepath.Join(dataDir, "codex"), filepath.Join(dataDir, "antigravity"), s.status, s.createProject, s.clients, s.terminals)
|
||||
s.agents = agents.New(s.store, filepath.Join(dataDir, "worktrees"), filepath.Join(dataDir, "leads"), filepath.Join(dataDir, "hands"), filepath.Join(dataDir, "codex"), filepath.Join(dataDir, "antigravity"), s.status, s.createProject, s.clients, s.terminals, ts)
|
||||
err = s.agents.Recover(context.Background())
|
||||
if err != nil {
|
||||
s.store.Close()
|
||||
@@ -115,26 +124,39 @@ func (s *Server) Handler() http.Handler {
|
||||
return mux
|
||||
}
|
||||
|
||||
func (s *Server) LoopbackHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /api/tailnet", s.GetTailnet)
|
||||
return mux
|
||||
}
|
||||
|
||||
func (s *Server) GetTailnet(w http.ResponseWriter, r *http.Request) {
|
||||
st, err := s.tailnet.StatusWithoutPeers(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
origin := ""
|
||||
if len(st.CertDomains) > 0 {
|
||||
origin = "https://" + st.CertDomains[0]
|
||||
}
|
||||
writeJSON(w, http.StatusOK, api.Tailnet{State: st.BackendState, AuthUrl: st.AuthURL, Hostname: s.hostname, Origin: origin})
|
||||
}
|
||||
|
||||
func (s *Server) identify(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
who, err := s.tailnet.WhoIs(r.Context(), r.RemoteAddr)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusForbidden, err)
|
||||
writeError(w, http.StatusUnauthorized, fmt.Errorf("identify %s on the tailnet: %w", r.RemoteAddr, err))
|
||||
return
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil || !ip.IsLoopback() {
|
||||
writeError(w, http.StatusForbidden, fmt.Errorf("request from %s did not arrive via tailscale serve", host))
|
||||
return
|
||||
}
|
||||
|
||||
login := r.Header.Get("Tailscale-User-Login")
|
||||
name := r.Header.Get("Tailscale-User-Name")
|
||||
if login == "" || name == "" {
|
||||
writeError(w, http.StatusUnauthorized, errors.New("missing Tailscale-User-Login or Tailscale-User-Name"))
|
||||
if who.Node.IsTagged() {
|
||||
writeError(w, http.StatusForbidden, fmt.Errorf("%s is a tagged node, not a user's", who.Node.Name))
|
||||
return
|
||||
}
|
||||
|
||||
login := who.UserProfile.LoginName
|
||||
name := who.UserProfile.DisplayName
|
||||
err = s.ensureUser(r.Context(), login, name)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
|
||||
Reference in New Issue
Block a user