ollie/cmd/olliesrv/main.go

162 lines
3.5 KiB
Go

// olliesrv - 9P server for ollie sessions
package main
import (
"context"
"flag"
"fmt"
"net"
"os"
"os/signal"
"path/filepath"
"syscall"
"ollie/cmd/olliesrv/internal/agent"
"ollie/cmd/olliesrv/internal/fs"
olog "ollie/log"
"ollie/util"
"9fans.net/go/plan9/client"
"github.com/godbus/dbus/v5"
)
const serviceName = "ollie"
var tcpAddr = flag.String("tcp", "", "also listen on TCP address (e.g. :564)")
var yolo = flag.Bool("yolo", false, "skip native Landlock sandbox for shell")
func main() {
flag.CommandLine.Parse(os.Args[1:]) //nolint:errcheck
ns := client.Namespace()
if ns == "" {
fmt.Fprintln(os.Stderr, "no namespace")
os.Exit(1)
}
if err := os.MkdirAll(ns, 0700); err != nil {
fmt.Fprintf(os.Stderr, "create namespace: %v\n", err)
os.Exit(1)
}
sockPath := filepath.Join(ns, serviceName)
runServer(sockPath)
}
func runServer(sockPath string) {
util.EnsureEnv()
// Remove stale sockets from this and prior runs
if _, err := os.Stat(sockPath); err == nil {
os.Remove(sockPath) //nolint:errcheck
}
sockDir := filepath.Dir(sockPath)
if entries, err := filepath.Glob(filepath.Join(sockDir, "toolsrv-*.sock")); err == nil {
for _, s := range entries {
os.Remove(s) //nolint:errcheck
}
}
sink := olog.NewSink(os.Stdout, os.Stderr, olog.ParseLevel(os.Getenv("OLLIE_LOG"), olog.LevelWarn))
agentsDirs := agent.AgentsDirs()
sessionsDir := util.DataDir() + "/sessions"
daemonCtx, daemonCancel := context.WithCancel(context.Background())
defer daemonCancel()
modelCache := fs.NewModelCache()
// Desktop notifications for bypass prompts
if conn, err := dbus.SessionBus(); err == nil {
initBypassNotifier(conn)
}
rootTree := fs.NewRoot(fs.Config{
Ctx: daemonCtx,
AgentsDir: agentsDirs[0],
SessionsDir: sessionsDir,
Log: sink.NewLogger("9p"),
Sink: sink,
Yolo: *yolo,
ModelCache: modelCache,
BypassNotify: notifyBypass,
})
// Create 9P server
srv := New(Config{
Sink: sink,
RootTree: rootTree,
})
// Prefetch models asynchronously at startup
go modelCache.Get()
var listener net.Listener
var err error
listener, err = net.Listen("unix", sockPath)
if err != nil {
fmt.Fprintf(os.Stderr, "listen: %v\n", err)
os.Exit(1)
}
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
go srv.Start(conn)
}
}()
fmt.Printf("olliesrv listening on %s\n", sockPath)
// Restore sessions now that the 9P listener is ready.
fs.RestoreSessions()
var tcpListener net.Listener
if *tcpAddr != "" {
tcpListener, err = net.Listen("tcp", *tcpAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "tcp listen: %v\n", err)
os.Exit(1)
}
go func() {
for {
conn, err := tcpListener.Accept()
if err != nil {
return
}
go srv.Start(conn)
}
}()
fmt.Printf("olliesrv listening on tcp %s\n", *tcpAddr)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
fmt.Println("shutting down")
// Stop accepting new connections first.
if listener != nil {
listener.Close() //nolint:errcheck
}
if tcpListener != nil {
tcpListener.Close() //nolint:errcheck
}
daemonCancel() // signal all sessions via context propagation
if srv != nil {
srv.Kill()
}
os.Remove(sockPath)
// Remove only an empty namespace directory. Other Plan 9 services may own
// entries here; os.Remove never removes non-empty directories, so those
// entries are left untouched.
_ = os.Remove(filepath.Dir(sockPath))
sink.Flush()
}