// Command albedo wires the services and reports command failures once.
package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"albedo/cli/internal/app"
"albedo/cli/internal/cli"
"albedo/cli/internal/config"
"albedo/cli/internal/daemon"
"albedo/cli/internal/storage"
"albedo/cli/internal/terminal"
)
var buildRoot string // Embedded via -ldflags "-X main.buildRoot=...".
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(args []string) error {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
streams := cli.Streams{In: os.Stdin, Out: os.Stdout, Err: os.Stderr}
home := config.HomeDir()
term := &terminal.Service{In: streams.In, Out: streams.Out, Err: streams.Err, Capable: terminal.IsTTY(streams.In, streams.Out), Home: home}
if os.Getenv("ALBEDO_NO_BROWSER") == "" {
term.OpenBrowser = config.OpenBrowser
}
options := daemon.LocalOptions{HomeDir: home, ProjectRoot: findProjectRoot()}
rediscover := func(ctx context.Context) (daemon.ConnectionSnapshot, error) { return daemon.Rediscover(ctx, home) }
var chosen *daemon.Connection
application := &app.Service{
Connect: func(ctx context.Context) (*daemon.Connection, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if chosen != nil {
return chosen, nil
}
found, err := daemon.Discover(ctx, home)
if err != nil {
if _, compatibleFailure := errors.AsType[*daemon.CompatibilityError](err); !compatibleFailure || found.Kind != daemon.Running {
return nil, err
}
}
if found.Kind != daemon.Running {
chosen, err = daemon.Launch(ctx, options)
return chosen, err
}
// An unknown protocol cannot safely authorize the shutdown operation.
if found.Health.Version != daemon.ProtocolVersion {
return nil, daemon.CheckCompatible(found.Health)
}
selectedBuild := os.Getenv("ALBEDO_BUILD")
if selectedBuild == "" {
selectedBuild, _ = filepath.EvalSymlinks(os.Getenv("ALBEDO_DAEMON"))
}
restart, err := term.ConfirmRestart(ctx, found.Snapshot, selectedBuild)
if err != nil {
return nil, err
}
if restart {
chosen, err = daemon.Upgrade(ctx, options, found.Snapshot)
} else {
chosen, err = daemon.Attach(ctx, found.Snapshot, rediscover)
}
return chosen, err
},
Existing: func(ctx context.Context) (*daemon.Connection, error) {
found, err := daemon.Discover(ctx, home)
if err != nil {
if _, compatibleFailure := errors.AsType[*daemon.CompatibilityError](err); !compatibleFailure || found.Kind != daemon.Running {
return nil, err
}
}
if found.Kind != daemon.Running {
return nil, nil
}
if found.Health.Version != daemon.ProtocolVersion {
return nil, daemon.CheckCompatible(found.Health)
}
return daemon.NewConnection(found.Snapshot, nil), nil
},
}
store := &storage.Service{
Home: home, Now: time.Now,
Running: func() (bool, error) {
found, err := daemon.Discover(ctx, home)
if _, compatibilityFailure := errors.AsType[*daemon.CompatibilityError](err); compatibilityFailure && found.Kind == daemon.Running {
return true, nil
}
return found.Kind == daemon.Running, err
},
DeleteSessions: application.DeleteSessions,
OnlineReport: application.StorageReport,
}
workspace, err := os.Getwd()
if err != nil {
workspace = "."
}
return cli.Execute(ctx, args, cli.Dependencies{Application: application, Storage: store, Terminal: term, Workspace: workspace}, streams)
}