remove ollie-9p-mount binary, inline FUSE into olliesrv

The mount logic is now a library package (mount/) called directly
by `olliesrv mount`. No external binary needed.

Per-session auto-mounts are gone entirely. The mount subcommand
remains for network-transparent human access to remote servers.
This commit is contained in:
Levi Neely 2026-07-27 11:51:56 +02:00
parent 8ef22ab90d
commit 300d07b2aa
6 changed files with 76 additions and 238 deletions

View File

@ -1,98 +0,0 @@
// ollie-9p-mount — per-session FUSE proxy that attaches to olliesrv with a custom uname.
//
// Each agent session gets its own mount. Tools running inside the session see
// the full ollie namespace through $OLLIE, but all 9P operations carry the
// session's identity (Uname). The server can then enforce access control
// (e.g. rejecting self-prompts).
//
// Usage:
//
// ollie-9p-mount [-u uname] [-s service] [-addr address] <mountpoint>
//
// If -u is not given, $OLLIE_UNAME is used.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"9fans.net/go/plan9/client"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
perms "olliesrv/fs"
)
var (
userFlag = flag.String("u", "", "uname for 9P attach (default: $OLLIE_SESSION_ID)")
serviceFlag = flag.String("s", "ollie", "9P service name")
netFlag = flag.String("net", "", "network type (unix, tcp); empty = namespace")
addrFlag = flag.String("addr", "", "9P address; empty = namespace service")
)
func main() {
flag.Parse()
if flag.NArg() != 1 {
fmt.Fprintf(os.Stderr, "usage: ollie-9p-mount [-u uname] [-s service] <mountpoint>\n")
os.Exit(1)
}
mnt := flag.Arg(0)
uname := *userFlag
if uname == "" {
uname = os.Getenv("OLLIE_UNAME")
}
if uname == "" {
fmt.Fprintf(os.Stderr, "ollie-9p-mount: no uname (-u or $OLLIE_UNAME)\n")
os.Exit(1)
}
fsys, err := dial(uname)
if err != nil {
log.Fatalf("dial: %v", err)
}
os.MkdirAll(mnt, perms.PermMkdir)
root := &p9Dir{fsys: fsys, path: ""}
server, err := fs.Mount(mnt, root, &fs.Options{
MountOptions: fuse.MountOptions{
FsName: "ollie-9p",
Name: "ollie",
DisableXAttrs: true,
},
})
if err != nil {
log.Fatalf("mount: %v", err)
}
// Unmount on signal.
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sig
server.Unmount()
}()
server.Wait()
}
func dial(uname string) (*client.Fsys, error) {
var conn *client.Conn
var err error
if *addrFlag != "" {
n := *netFlag
if n == "" {
n = "tcp"
}
conn, err = client.Dial(n, *addrFlag)
} else {
conn, err = client.DialService(*serviceFlag)
}
if err != nil {
return nil, err
}
return conn.Attach(nil, uname, "")
}

16
main.go
View File

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strings" "strings"
@ -23,6 +22,7 @@ import (
"ollie/pkg/paths" "ollie/pkg/paths"
"ollie/pkg/tools/execute" "ollie/pkg/tools/execute"
fs "olliesrv/fs" fs "olliesrv/fs"
"olliesrv/mount"
"olliesrv/server" "olliesrv/server"
"olliesrv/session" "olliesrv/session"
) )
@ -125,15 +125,15 @@ func cmdMount() {
if strings.Contains(addr, ":") { if strings.Contains(addr, ":") {
network = "tcp" network = "tcp"
} }
exe, _ := os.Executable() fmt.Printf("mounting %s at %s\n", addr, mnt)
cmd := exec.Command(filepath.Join(filepath.Dir(exe), "ollie-9p-mount"), if err := mount.Run(mount.Config{
"-u", os.Getenv("USER"), "-net", network, "-addr", addr, mnt) Network: network,
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} Address: addr,
if err := cmd.Start(); err != nil { Mountpoint: mnt,
fmt.Fprintf(os.Stderr, "ollie-9p-mount: %v\n", err) }); err != nil {
fmt.Fprintf(os.Stderr, "mount: %v\n", err)
os.Exit(1) os.Exit(1)
} }
fmt.Printf("mounted %s at %s (pid %d)\n", addr, mnt, cmd.Process.Pid)
} }
func runServer(sockPath string) { func runServer(sockPath string) {

65
mount/mount.go Normal file
View File

@ -0,0 +1,65 @@
// Package mount provides FUSE-based 9P mount functionality.
// Used by `olliesrv mount` for network-transparent access to remote servers.
package mount
import (
"fmt"
"os"
"os/signal"
"syscall"
"9fans.net/go/plan9/client"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// Config configures a FUSE mount.
type Config struct {
Network string // "unix" or "tcp"
Address string // server address
Mountpoint string // local mountpoint
Uname string // uname for Tattach (default: $USER)
}
// Run mounts the remote 9P server and blocks until unmounted or signaled.
func Run(cfg Config) error {
if cfg.Uname == "" {
cfg.Uname = os.Getenv("USER")
}
if cfg.Uname == "" {
cfg.Uname = "none"
}
conn, err := client.Dial(cfg.Network, cfg.Address)
if err != nil {
return fmt.Errorf("dial %s!%s: %w", cfg.Network, cfg.Address, err)
}
fsys, err := conn.Attach(nil, cfg.Uname, "")
if err != nil {
conn.Close()
return fmt.Errorf("attach: %w", err)
}
root := &p9Dir{fsys: fsys, path: "/"}
server, err := fs.Mount(cfg.Mountpoint, root, &fs.Options{
MountOptions: fuse.MountOptions{
FsName: "ollie",
Name: "ollie9p",
DisableXAttrs: true,
MaxBackground: 32,
},
})
if err != nil {
return fmt.Errorf("mount %s: %w", cfg.Mountpoint, err)
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
server.Unmount()
}()
server.Wait()
return nil
}

View File

@ -1,4 +1,4 @@
package main package mount
import ( import (
"context" "context"

View File

@ -1,122 +0,0 @@
package session
import (
"context"
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"time"
"ollie/pkg/tools/execute"
)
// mountState holds the per-session mount process state.
type mountState struct {
mu sync.RWMutex
proc *os.Process
path string
envExtra map[string]string // reference to Server's envExtra for OLLIE_UNAME lookup
}
// MountPathFor returns the per-session FUSE mount path for a given uname.
func MountPathFor(uname string) string {
return filepath.Join(os.TempDir(), "ollie-mount-"+uname)
}
// WithMount9P returns an execute.Option that enables per-session 9P FUSE mounts.
// It wires up hooks to start, check, and tear down the mount lifecycle.
func WithMount9P() execute.Option {
state := &mountState{envExtra: make(map[string]string)}
return func(s *execute.Server) {
// Hook: OnEnvSet - start mount when OLLIE_UNAME is set
s.OnEnvSet = func(key, value string) {
state.mu.Lock()
state.envExtra[key] = value
if key == "OLLIE_UNAME" && value != "" {
startMount(state, value)
}
state.mu.Unlock()
}
// Hook: OnPreDispatch - check mount health before each dispatch
s.OnPreDispatch = func() {
remountIfStale(state)
}
// Hook: OnClose - tear down mount on session end
s.OnClose = func() {
teardownMount(state)
}
}
}
// startMount launches ollie-9p-mount for this session.
func startMount(state *mountState, uname string) {
bin, err := exec.LookPath("ollie-9p-mount")
if err != nil {
return
}
mnt := MountPathFor(uname)
os.MkdirAll(mnt, 0755) //nolint:errcheck
cmd := exec.Command(bin, "-u", uname, mnt)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if err := cmd.Start(); err != nil {
return
}
// Reap in background so Wait works later.
go cmd.Wait() //nolint:errcheck
// Wait for mount to be ready.
for i := 0; i < 20; i++ {
time.Sleep(50 * time.Millisecond)
if _, err := os.Stat(filepath.Join(mnt, "s")); err == nil {
break
}
}
state.proc = cmd.Process
state.path = mnt
state.envExtra["OLLIE"] = mnt
}
// remountIfStale checks the per-session FUSE mount health and re-establishes it if broken.
func remountIfStale(state *mountState) {
state.mu.RLock()
mnt := state.path
proc := state.proc
state.mu.RUnlock()
if mnt == "" || proc == nil {
return
}
if _, err := os.Stat(filepath.Join(mnt, "s")); err == nil {
return
}
// Mount is stale — tear down and restart.
state.mu.Lock()
defer state.mu.Unlock()
exec.Command("fusermount", "-u", state.path).Run() //nolint:errcheck
syscall.Kill(state.proc.Pid, syscall.SIGKILL) //nolint:errcheck
state.proc = nil
state.path = ""
if uname := state.envExtra["OLLIE_UNAME"]; uname != "" {
startMount(state, uname)
}
}
// teardownMount tears down the per-session FUSE mount.
func teardownMount(state *mountState) {
state.mu.Lock()
proc := state.proc
mnt := state.path
state.proc = nil
state.path = ""
state.mu.Unlock()
if proc != nil {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
exec.CommandContext(ctx, "fusermount", "-u", mnt).Run() //nolint:errcheck
cancel()
syscall.Kill(proc.Pid, syscall.SIGKILL) //nolint:errcheck
os.Remove(mnt) //nolint:errcheck
}
}

View File

@ -40,7 +40,7 @@ type Session struct {
prevPrompt []byte // last submitted prompt; overwritten on each new submission prevPrompt []byte // last submitted prompt; overwritten on each new submission
peers map[string]bool // peer session IDs (bidirectional links) peers map[string]bool // peer session IDs (bidirectional links)
remote string // SSH target for remote execution (empty = local) remote string // SSH target for remote execution (empty = local)
mount *mountState // non-nil for remote sessions that need a local mount
// Cached model list (expensive API call; refreshed every 24h). // Cached model list (expensive API call; refreshed every 24h).
modelsMu sync.Mutex modelsMu sync.Mutex
@ -853,7 +853,6 @@ func (s *Manager) restoreSession(ps *agent.PersistedSession) error {
} else { } else {
var execOpts []execute.Option var execOpts []execute.Option
if !s.cfg.NoMount { if !s.cfg.NoMount {
execOpts = append(execOpts, WithMount9P())
} }
if s.cfg.Strict { if s.cfg.Strict {
execOpts = append(execOpts, execute.WithStrict()) execOpts = append(execOpts, execute.WithStrict())
@ -972,9 +971,7 @@ func (s *Manager) Shutdown() {
if sess != nil { if sess != nil {
sess.Cancel() sess.Cancel()
sess.Core.Close() sess.Core.Close()
if sess.mount != nil {
teardownMount(sess.mount)
}
s.cfg.Log.Info("shutdown session %s", id) s.cfg.Log.Info("shutdown session %s", id)
} }
} }
@ -1009,9 +1006,6 @@ func (s *Manager) KillSession(id string) {
if sess != nil { if sess != nil {
sess.Cancel() sess.Cancel()
sess.Core.Close() sess.Core.Close()
if sess.mount != nil {
teardownMount(sess.mount)
}
s.removePersistedSession(id) s.removePersistedSession(id)
s.cfg.Log.Info("killed session %s", id) s.cfg.Log.Info("killed session %s", id)
if s.cfg.OnSessionKilled != nil { if s.cfg.OnSessionKilled != nil {
@ -1141,7 +1135,6 @@ func (s *Manager) CreateSession(args []string) (string, error) {
var execOpts []execute.Option var execOpts []execute.Option
if !s.cfg.NoMount { if !s.cfg.NoMount {
execOpts = append(execOpts, WithMount9P())
} }
if s.cfg.Strict { if s.cfg.Strict {
execOpts = append(execOpts, execute.WithStrict()) execOpts = append(execOpts, execute.WithStrict())