ollie/cmd/toolsrv/main.go

134 lines
3.3 KiB
Go

// toolsrv — 9P tool execution server.
//
// Executes tool calls in a sandboxed environment. Used both locally
// (spawned by olliesrv) and remotely (deployed via SSH bootstrap).
//
// Usage:
//
// toolsrv serve --cwd /path/to/project --listen /path/to/sock [--yolo]
//
// Listens on a Unix socket and serves a 9P filesystem for tool execution.
package main
import (
"context"
"flag"
"fmt"
"net"
"os"
"os/signal"
"path/filepath"
"syscall"
"ollie/env"
"ollie/fsedsl"
"ollie/toolsrv"
)
var (
cwd = flag.String("cwd", ".", "working directory for execution")
listenPath = flag.String("listen", "", "Unix socket path to listen on (required)")
yolo = flag.Bool("yolo", false, "skip sandbox enforcement")
)
func main() {
if len(os.Args) < 2 || os.Args[1] != "serve" {
fmt.Fprintln(os.Stderr, "usage: toolsrv serve --cwd <path> --listen <socket> [--yolo]")
os.Exit(1)
}
flag.CommandLine.Parse(os.Args[2:])
if *listenPath == "" {
fmt.Fprintln(os.Stderr, "error: --listen is required")
os.Exit(1)
}
env.EnsureDefaults()
// Prepend our bin dir to PATH so landrun is found (deployed alongside us).
home, _ := os.UserHomeDir()
binDir := filepath.Join(home, ".config", "ollie", "bin")
os.Setenv("PATH", binDir+":"+os.Getenv("PATH"))
// Get shared secret from environment
secret := os.Getenv("TOOLSRV_SECRET")
if secret == "" {
fmt.Fprintln(os.Stderr, "error: TOOLSRV_SECRET environment variable required")
os.Exit(1)
}
// Create tool registry
toolReg, _ := toolsrv.NewRegistry()
sessionID := os.Getenv("OLLIE_SESSION_ID")
// Create 9P server
srv := toolsrv.NewServer9P([]byte(secret))
srv.SetYolo(*yolo)
if toolReg != nil && sessionID != "" {
srv.SetRegistry(toolReg, sessionID)
}
// Build the filesystem tree
ctx := toolsrv.ToolsrvCtx{Server: srv}
tree := fsedsl.BuildTree(toolsrv.ToolsrvSpec(), ctx)
if tree == nil {
fmt.Fprintln(os.Stderr, "error: failed to build filesystem tree")
os.Exit(1)
}
// Set up signal handling
runCtx, cancel := context.WithCancel(context.Background())
defer cancel()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
cancel()
}()
// Remove stale socket if it exists
os.Remove(*listenPath)
// Listen on Unix socket
ln, err := net.Listen("unix", *listenPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error: listen: %v\n", err)
os.Exit(1)
}
defer ln.Close()
defer os.Remove(*listenPath)
fmt.Fprintf(os.Stderr, "toolsrv: listening on %s\n", *listenPath)
// Accept connections
go func() {
<-runCtx.Done()
ln.Close()
}()
for {
conn, err := ln.Accept()
if err != nil {
select {
case <-runCtx.Done():
return
default:
fmt.Fprintf(os.Stderr, "accept error: %v\n", err)
continue
}
}
go serve9P(runCtx, conn, tree)
}
}
// serve9P handles a single 9P connection.
// TODO: This needs to implement the 9P protocol using the fsedsl tree.
// For now this is a placeholder - the actual implementation should follow
// the pattern in cmd/olliesrv/server.go.
func serve9P(ctx context.Context, conn net.Conn, tree *fsedsl.Tree) {
defer conn.Close()
// TODO: Implement 9P protocol handling
// This would use plan9.ReadFcall/WriteFcall and dispatch to tree operations
fmt.Fprintf(os.Stderr, "toolsrv: new connection (9P serving not yet implemented)\n")
}