Something went wrong. Try again.
small gleam coding and (not yet) persistent agent daemon with a detachable cli forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220package cli
import ( "errors" "fmt" "net" "net/http" "os" "path/filepath" "slices" "strconv" "strings" "time"
"albedo/cli/internal/daemon" "albedo/cli/internal/webui"
"github.com/spf13/cobra")
func newWeb(deps Dependencies) *cobra.Command { var port int var noOpen, save, local, foreground, serve, stop, newLink, tailnetHTTPS, tailnetAuth bool var allow []string var publicURL string command := &cobra.Command{Use: "web", Short: "open the canvas client in a browser", Long: `Serve the browser client and open it. The daemon is found or started as forthe terminal client. The page reaches the daemon through this command, whichkeeps the daemon's token; it signs the browser in with the link it prints, sokeep that link to yourself. It keeps serving after this command exits.Use --stop to stop it, --new-link to sign browsers out, or --foreground toserve in this terminal until Ctrl-C.
In a terminal it also draws a QR code of the link another device can reach (thetailnet name, else the first network address) so a phone can scan it.
On a machine signed in to Tailscale it also serves your tailnet (100.64.0.0/10)and answers to the machine's tailnet name, printing a link for it; otherdevices on the tailnet open that link. --local serves this machine only.
--allow opens it to devices on a network as well, instead of the tailnet: itlistens on this machine's addresses inside that network and prints a link foreach, and only devices on an allowed network may connect. A network is CIDR(10.0.0.0/8) or an address whose trailing zeros stand for any (10.0.0.0).Repeat it for several. --save remembers the networks given, inthe daemon's settings, for every later albedo web; --save alone forgets them.Anyone holding a link controls the daemon, so only open it to your own devices.
--public-url names the origin a proxy in front serves this through(https://albedo.example); the printed link and QR code carry it.--tailnet-https points tailscale serve at this listener instead, which givesthe page an https origin on the machine's MagicDNS name, so notifications andclipboard work. It needs HTTPS certificates turned on for the tailnet, refusesa serve config that publishes anything else or uses Funnel, and removes itshandler when the server stops.
--tailnet-auth treats the tailnet as the sign-in: a device on it, or the localtailscale serve proxy that terminates its TLS, gets the cookie without openinga link, and the cookie keeps it signed in across restarts. Enable it only on atailnet you trust with the daemon: every device on it then controls it.
The client is read from web/dist in the albedo checkout, or from ALBEDO_WEB_DIST.`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { dist := os.Getenv("ALBEDO_WEB_DIST") if dist == "" { dist = filepath.Join(deps.ProjectRoot, "web", "dist") } if port < 0 || port > 65535 { return fmt.Errorf("port must be between 0 and 65535") } options := webui.Options{Port: port, Allow: allow, Local: local, TailnetHTTPS: tailnetHTTPS, TailnetAuth: tailnetAuth} if publicURL != "" { public, err := webui.ParseOrigin(publicURL) if err != nil { return err } options.Public = public.String() } if !foreground && !serve { return backgroundWeb(cmd, deps, options, save, stop, newLink, noOpen) } conn, err := deps.Application.Connect(cmd.Context()) if err != nil { return err } server, err := webui.New(dist, conn) if err != nil { return err } settings, err := daemon.GetSettings(cmd.Context(), conn) if err != nil { return err } if save { if err := daemon.SaveWebAllow(cmd.Context(), conn, settings.UI.ETag, options.Allow); err != nil { return fmt.Errorf("cannot save the allowed networks: %w", err) } } else if len(options.Allow) == 0 && !options.Local { options.Allow = settings.UI.WebAllow } var networks []*net.IPNet for _, text := range options.Allow { network, err := webui.ParseNetwork(text) if err != nil { return err } networks = append(networks, network) } var tailnet webui.Tailnet onTailnet := false if !options.Local { tailnet, onTailnet = webui.CurrentTailnet(cmd.Context()) } implicit := len(networks) == 0 && onTailnet if implicit { networks = []*net.IPNet{webui.TailscaleNetwork} } bind := "127.0.0.1" var addresses, names []string if len(networks) > 0 { machine, err := webui.NetworkAddresses() if err != nil { return fmt.Errorf("cannot list this machine's network addresses: %w", err) } addresses = server.Share(networks, machine) if len(addresses) == 0 && !implicit { return fmt.Errorf("this machine has no address in %s", strings.Join(options.Allow, ", ")) } if len(addresses) > 0 { bind = "0.0.0.0" } if onTailnet && slices.ContainsFunc(addresses, func(a string) bool { return webui.TailscaleNetwork.Contains(net.ParseIP(a)) }) { server.Answer(tailnet.Names...) names = tailnet.Names } } // A wildcard listener binds beside one already on loopback (Go sets // SO_REUSEADDR), and the loopback one keeps answering 127.0.0.1, so // an older albedo web would silently shadow this one: claim loopback // first to find out. if bind != "127.0.0.1" && options.Port != 0 { probe, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(options.Port))) if err != nil { return fmt.Errorf("cannot listen on 127.0.0.1:%d, is another albedo web running? (use --port to pick another): %w", options.Port, err) } probe.Close() } listener, err := net.Listen("tcp", net.JoinHostPort(bind, strconv.Itoa(options.Port))) if err != nil { return fmt.Errorf("cannot listen on %s:%d (use --port to pick another): %w", bind, options.Port, err) } defer listener.Close() options.Port = listener.Addr().(*net.TCPAddr).Port if options.TailnetHTTPS { claim, err := tailnet.ServeHTTPS(options.Port) if err != nil { return err } origin, err := claim.Claim(cmd.Context()) if err != nil { return err } options.Public = origin.String() // Tailscale's handler outlives this process unless it is removed: // take it down on Ctrl-C, --stop and a failed start alike. defer releaseTailnet(cmd, options.Port) } if public := options.PublicOrigin(); public != nil { server.Publish(public) } if options.TailnetAuth { server.TrustTailnet() } manifest := webui.Manifest{Options: options, Names: names, Addresses: addresses, PID: os.Getpid(), URL: webui.Loopback(options.Port).String()} manifestPath := webui.ManifestPath() if existing, readErr := webui.ReadManifest(manifestPath); readErr != nil || !webui.ManifestAlive(existing) { if err := webui.WriteManifest(manifestPath, manifest); err != nil { return fmt.Errorf("cannot write web.json: %w", err) } defer func() { if current, readErr := webui.ReadManifest(manifestPath); readErr == nil && current.PID == os.Getpid() { _ = os.Remove(manifestPath) } }() } if !serve { if err := printWebLinks(cmd, deps, server.Key(), manifest, noOpen); err != nil { return err } } httpServer := &http.Server{Handler: server, ReadHeaderTimeout: 10 * time.Second} go func() { <-cmd.Context().Done() httpServer.Close() }() if err := httpServer.Serve(listener); !errors.Is(err, http.ErrServerClosed) { return err } return nil }} command.Flags().IntVar(&port, "port", 4317, "port to serve on; 0 picks a free one") command.Flags().BoolVar(&noOpen, "no-open", false, "print the link without opening a browser") command.Flags().StringArrayVar(&allow, "allow", nil, "serve devices on this network instead of the tailnet (CIDR, or 10.0.0.0 for 10.0.0.0/8); repeatable") command.Flags().BoolVar(&local, "local", false, "serve this machine only, even on a tailnet") command.MarkFlagsMutuallyExclusive("local", "allow") command.Flags().BoolVar(&save, "save", false, "remember the --allow networks for later runs (alone, forget them)") command.Flags().BoolVar(&foreground, "foreground", false, "serve in this terminal until interrupted") command.Flags().BoolVar(&serve, "serve", false, "background web server") _ = command.Flags().MarkHidden("serve") command.Flags().BoolVar(&stop, "stop", false, "stop the background web server") command.Flags().BoolVar(&newLink, "new-link", false, "replace the sign-in link and sign browsers out") command.Flags().StringVar(&publicURL, "public-url", "", "origin a proxy in front serves this through (https://host), for links and requests") command.Flags().BoolVar(&tailnetHTTPS, "tailnet-https", false, "serve https on your tailnet through tailscale serve, printing its https link") command.Flags().BoolVar(&tailnetAuth, "tailnet-auth", false, "sign in tailnet devices without a link; the tailnet is the trust boundary") command.MarkFlagsMutuallyExclusive("public-url", "tailnet-https") command.MarkFlagsMutuallyExclusive("local", "tailnet-https") command.MarkFlagsMutuallyExclusive("foreground", "serve", "stop") command.MarkFlagsMutuallyExclusive("stop", "new-link") command.MarkFlagsMutuallyExclusive("foreground", "new-link") return command}