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:
parent
8ef22ab90d
commit
300d07b2aa
|
|
@ -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
16
main.go
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -23,6 +22,7 @@ import (
|
|||
"ollie/pkg/paths"
|
||||
"ollie/pkg/tools/execute"
|
||||
fs "olliesrv/fs"
|
||||
"olliesrv/mount"
|
||||
"olliesrv/server"
|
||||
"olliesrv/session"
|
||||
)
|
||||
|
|
@ -125,15 +125,15 @@ func cmdMount() {
|
|||
if strings.Contains(addr, ":") {
|
||||
network = "tcp"
|
||||
}
|
||||
exe, _ := os.Executable()
|
||||
cmd := exec.Command(filepath.Join(filepath.Dir(exe), "ollie-9p-mount"),
|
||||
"-u", os.Getenv("USER"), "-net", network, "-addr", addr, mnt)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ollie-9p-mount: %v\n", err)
|
||||
fmt.Printf("mounting %s at %s\n", addr, mnt)
|
||||
if err := mount.Run(mount.Config{
|
||||
Network: network,
|
||||
Address: addr,
|
||||
Mountpoint: mnt,
|
||||
}); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mount: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("mounted %s at %s (pid %d)\n", addr, mnt, cmd.Process.Pid)
|
||||
}
|
||||
|
||||
func runServer(sockPath string) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package mount
|
||||
|
||||
import (
|
||||
"context"
|
||||
122
session/mount.go
122
session/mount.go
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ type Session struct {
|
|||
prevPrompt []byte // last submitted prompt; overwritten on each new submission
|
||||
peers map[string]bool // peer session IDs (bidirectional links)
|
||||
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).
|
||||
modelsMu sync.Mutex
|
||||
|
|
@ -853,7 +853,6 @@ func (s *Manager) restoreSession(ps *agent.PersistedSession) error {
|
|||
} else {
|
||||
var execOpts []execute.Option
|
||||
if !s.cfg.NoMount {
|
||||
execOpts = append(execOpts, WithMount9P())
|
||||
}
|
||||
if s.cfg.Strict {
|
||||
execOpts = append(execOpts, execute.WithStrict())
|
||||
|
|
@ -972,9 +971,7 @@ func (s *Manager) Shutdown() {
|
|||
if sess != nil {
|
||||
sess.Cancel()
|
||||
sess.Core.Close()
|
||||
if sess.mount != nil {
|
||||
teardownMount(sess.mount)
|
||||
}
|
||||
|
||||
s.cfg.Log.Info("shutdown session %s", id)
|
||||
}
|
||||
}
|
||||
|
|
@ -1009,9 +1006,6 @@ func (s *Manager) KillSession(id string) {
|
|||
if sess != nil {
|
||||
sess.Cancel()
|
||||
sess.Core.Close()
|
||||
if sess.mount != nil {
|
||||
teardownMount(sess.mount)
|
||||
}
|
||||
s.removePersistedSession(id)
|
||||
s.cfg.Log.Info("killed session %s", id)
|
||||
if s.cfg.OnSessionKilled != nil {
|
||||
|
|
@ -1141,7 +1135,6 @@ func (s *Manager) CreateSession(args []string) (string, error) {
|
|||
|
||||
var execOpts []execute.Option
|
||||
if !s.cfg.NoMount {
|
||||
execOpts = append(execOpts, WithMount9P())
|
||||
}
|
||||
if s.cfg.Strict {
|
||||
execOpts = append(execOpts, execute.WithStrict())
|
||||
|
|
|
|||
Reference in New Issue