main.go for olliesrv
This commit is contained in:
parent
35cbee0e35
commit
96dc6e442b
|
|
@ -0,0 +1,167 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"ollie/elevate"
|
||||
)
|
||||
|
||||
// elevateNotifier handles desktop notifications for elevation requests
|
||||
// and maps notification action callbacks back to the broker.
|
||||
type elevateNotifier struct {
|
||||
conn *dbus.Conn
|
||||
broker *elevate.Broker
|
||||
|
||||
mu sync.Mutex
|
||||
notifID map[uint32]string // notification ID -> request ID
|
||||
}
|
||||
|
||||
var notifier *elevateNotifier
|
||||
|
||||
// initElevateNotifier sets up the notification action listener.
|
||||
func initElevateNotifier(conn *dbus.Conn, broker *elevate.Broker) {
|
||||
if conn == nil || broker == nil {
|
||||
return
|
||||
}
|
||||
notifier = &elevateNotifier{
|
||||
conn: conn,
|
||||
broker: broker,
|
||||
notifID: make(map[uint32]string),
|
||||
}
|
||||
|
||||
// Listen for ActionInvoked signals from the notification daemon
|
||||
conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
|
||||
"type='signal',interface='org.freedesktop.Notifications',member='ActionInvoked'") //nolint:errcheck
|
||||
conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
|
||||
"type='signal',interface='org.freedesktop.Notifications',member='NotificationClosed'") //nolint:errcheck
|
||||
|
||||
ch := make(chan *dbus.Signal, 32)
|
||||
conn.Signal(ch)
|
||||
go notifier.listenSignals(ch)
|
||||
}
|
||||
|
||||
// notifyElevation sends a desktop notification for an elevation request.
|
||||
func notifyElevation(req *elevate.Request) {
|
||||
if notifier == nil {
|
||||
return
|
||||
}
|
||||
notifier.sendNotification(req)
|
||||
}
|
||||
|
||||
func (n *elevateNotifier) sendNotification(req *elevate.Request) {
|
||||
obj := n.conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
|
||||
|
||||
summary := "Elevation Request"
|
||||
body := fmt.Sprintf("<b>%s</b>\ncwd: %s", escapeMarkup(req.Cmd), escapeMarkup(req.Cwd))
|
||||
if req.SessionID != "" {
|
||||
body = fmt.Sprintf("session: %s\n%s", escapeMarkup(req.SessionID), body)
|
||||
}
|
||||
|
||||
actions := []string{
|
||||
"approve", "Approve",
|
||||
"deny", "Deny",
|
||||
"persist", "Persist",
|
||||
}
|
||||
|
||||
hints := map[string]dbus.Variant{
|
||||
"urgency": dbus.MakeVariant(byte(2)), // critical
|
||||
}
|
||||
|
||||
call := obj.Call("org.freedesktop.Notifications.Notify", 0,
|
||||
"ollie", // app_name
|
||||
uint32(0), // replaces_id
|
||||
"dialog-warning", // icon
|
||||
summary,
|
||||
body,
|
||||
actions,
|
||||
hints,
|
||||
int32(300000), // timeout ms (matches TTL)
|
||||
)
|
||||
if call.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var notifID uint32
|
||||
call.Store(¬ifID)
|
||||
|
||||
n.mu.Lock()
|
||||
n.notifID[notifID] = req.ID
|
||||
n.mu.Unlock()
|
||||
}
|
||||
|
||||
func (n *elevateNotifier) listenSignals(ch chan *dbus.Signal) {
|
||||
for sig := range ch {
|
||||
switch sig.Name {
|
||||
case "org.freedesktop.Notifications.ActionInvoked":
|
||||
if len(sig.Body) < 2 {
|
||||
continue
|
||||
}
|
||||
nid, _ := sig.Body[0].(uint32)
|
||||
action, _ := sig.Body[1].(string)
|
||||
|
||||
n.mu.Lock()
|
||||
reqID, ok := n.notifID[nid]
|
||||
delete(n.notifID, nid)
|
||||
n.mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var res elevate.Resolution
|
||||
switch action {
|
||||
case "approve":
|
||||
res = elevate.ResolveApprove
|
||||
case "deny":
|
||||
res = elevate.ResolveDeny
|
||||
case "persist":
|
||||
res = elevate.ResolvePersist
|
||||
default:
|
||||
res = elevate.ResolveDeny
|
||||
}
|
||||
n.broker.Resolve(reqID, res)
|
||||
|
||||
case "org.freedesktop.Notifications.NotificationClosed":
|
||||
if len(sig.Body) < 2 {
|
||||
continue
|
||||
}
|
||||
nid, _ := sig.Body[0].(uint32)
|
||||
reason, _ := sig.Body[1].(uint32)
|
||||
|
||||
n.mu.Lock()
|
||||
reqID, ok := n.notifID[nid]
|
||||
delete(n.notifID, nid)
|
||||
n.mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// reason 2 = dismissed by user, 1 = expired, 3 = closed by call, 4 = undefined
|
||||
// Treat dismiss as deny
|
||||
if reason == 2 {
|
||||
n.broker.Resolve(reqID, elevate.ResolveDeny)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func escapeMarkup(s string) string {
|
||||
// Basic XML/HTML entity escaping for notification body
|
||||
var out []byte
|
||||
for _, c := range []byte(s) {
|
||||
switch c {
|
||||
case '&':
|
||||
out = append(out, []byte("&")...)
|
||||
case '<':
|
||||
out = append(out, []byte("<")...)
|
||||
case '>':
|
||||
out = append(out, []byte(">")...)
|
||||
default:
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,460 @@
|
|||
// olliesrv - 9P server for ollie sessions
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"9fans.net/go/plan9/client"
|
||||
"ollie/agent"
|
||||
"ollie/backend"
|
||||
oldbus "ollie/dbus"
|
||||
"ollie/elevate"
|
||||
"ollie/env"
|
||||
"ollie/fs/session"
|
||||
olog "ollie/log"
|
||||
"ollie/mount"
|
||||
"ollie/paths"
|
||||
"ollie/skills"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
const serviceName = "ollie"
|
||||
|
||||
var tcpAddr = flag.String("tcp", "", "also listen on TCP address (e.g. :564)")
|
||||
var strict = flag.Bool("strict", false, "only allow tool steps; reject inline code")
|
||||
var yolo = flag.Bool("yolo", false, "skip landrun sandbox for shell")
|
||||
var nodbus = flag.Bool("nodbus", false, "disable D-Bus adapter (for headless/container use)")
|
||||
var no9p = flag.Bool("no9p", false, "disable 9P listener (D-Bus only mode)")
|
||||
|
||||
// ModelCache caches the list of models from all backends.
|
||||
type ModelCache struct {
|
||||
mu sync.RWMutex
|
||||
data []byte
|
||||
fetched bool
|
||||
}
|
||||
|
||||
var modelCache = &ModelCache{}
|
||||
|
||||
// Get returns the cached models, fetching if not yet cached.
|
||||
func (c *ModelCache) Get() []byte {
|
||||
c.mu.RLock()
|
||||
if c.fetched {
|
||||
data := c.data
|
||||
c.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
return c.Refresh()
|
||||
}
|
||||
|
||||
// Refresh fetches models from all backends and updates the cache.
|
||||
func (c *ModelCache) Refresh() []byte {
|
||||
var sb strings.Builder
|
||||
for _, name := range backend.Backends() {
|
||||
b, err := backend.NewWithName(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, m := range b.Models(context.Background()) {
|
||||
sb.WriteString(name)
|
||||
sb.WriteByte('\t')
|
||||
sb.WriteString(m)
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
data := []byte(sb.String())
|
||||
c.mu.Lock()
|
||||
c.data = data
|
||||
c.fetched = true
|
||||
c.mu.Unlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// Invalidate clears the cache, forcing a refresh on next Get.
|
||||
func (c *ModelCache) Invalidate() {
|
||||
c.mu.Lock()
|
||||
c.fetched = false
|
||||
c.data = nil
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.CommandLine.Parse(os.Args[1:]) //nolint:errcheck
|
||||
|
||||
// Handle mount subcommand (kept as a convenience)
|
||||
if flag.NArg() > 0 && flag.Arg(0) == "mount" {
|
||||
cmdMount()
|
||||
return
|
||||
}
|
||||
|
||||
ns := client.Namespace()
|
||||
if ns == "" {
|
||||
fmt.Fprintln(os.Stderr, "no namespace")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sockPath := filepath.Join(ns, serviceName)
|
||||
runServer(sockPath)
|
||||
}
|
||||
|
||||
func cmdMount() {
|
||||
if flag.NArg() < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: olliesrv mount <address> [mountpoint]")
|
||||
os.Exit(1)
|
||||
}
|
||||
addr := flag.Arg(0)
|
||||
mnt := flag.Arg(1)
|
||||
if mnt == "" {
|
||||
home, _ := os.UserHomeDir()
|
||||
mnt = filepath.Join(home, "mnt", addr)
|
||||
}
|
||||
if err := os.MkdirAll(mnt, fs.PermMkdir); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot create mount dir: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
network := "unix"
|
||||
if strings.Contains(addr, ":") {
|
||||
network = "tcp"
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func runServer(sockPath string) {
|
||||
env.EnsureDefaults()
|
||||
|
||||
// -no9p requires D-Bus (otherwise there's no interface at all)
|
||||
if *no9p && *nodbus {
|
||||
fmt.Fprintln(os.Stderr, "error: -no9p and -nodbus are mutually exclusive")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Remove stale socket
|
||||
if !*no9p {
|
||||
if _, err := os.Stat(sockPath); err == nil {
|
||||
os.Remove(sockPath) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
sink := olog.NewSink(os.Stdout, os.Stderr, olog.ParseLevel(os.Getenv("OLLIE_LOG"), olog.LevelWarn))
|
||||
|
||||
agentsDirs := agent.AgentsDirs()
|
||||
sessionsDir := paths.DataDir() + "/sessions"
|
||||
|
||||
// Create the tool registry
|
||||
toolRegistry, regErr := toolsrv.NewRegistry()
|
||||
if regErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: tool registry: %v\n", regErr)
|
||||
}
|
||||
|
||||
// Create the skills registry
|
||||
skillsRegistry := skills.NewRegistry()
|
||||
|
||||
// D-Bus adapter (initialized after manager so callbacks can reference it).
|
||||
var dbusAdapter *oldbus.DBusAdapter
|
||||
|
||||
// Elevate broker (initialized after manager; closures capture the pointer).
|
||||
var elevateBroker *elevate.Broker
|
||||
|
||||
daemonCtx, daemonCancel := context.WithCancel(context.Background())
|
||||
defer daemonCancel()
|
||||
|
||||
sessionTree := fs.NewRoot(fs.Config{
|
||||
Ctx: daemonCtx,
|
||||
ToolRegistry: toolRegistry,
|
||||
SkillsRegistry: skillsRegistry,
|
||||
AgentsDir: agentsDirs[0],
|
||||
SessionsDir: sessionsDir,
|
||||
Log: sink.Logger("9p", olog.LevelDebug),
|
||||
Sink: sink,
|
||||
Strict: *strict,
|
||||
Yolo: *yolo,
|
||||
NoMount: *no9p || *tcpAddr != "",
|
||||
Enable9P: !*no9p,
|
||||
EnableDBus: !*nodbus && (*no9p || *tcpAddr == ""),
|
||||
InvalidateModels: modelCache.Invalidate,
|
||||
OnSessionCreated: func(id string, sess *fs.Session) {
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.OnSessionCreated(id, sess)
|
||||
}
|
||||
},
|
||||
OnSessionKilled: func(id string) {
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.OnSessionKilled(id)
|
||||
}
|
||||
},
|
||||
OnSessionRenamed: func(oldID, newID string) {
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.OnSessionRenamed(oldID, newID)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
var srv *Server
|
||||
// Server creation deferred until after elevate broker is ready (see below)
|
||||
|
||||
// Start D-Bus adapter.
|
||||
// -tcp implies -nodbus (remote/headless deployments have no session bus).
|
||||
// -no9p forces D-Bus on (it's the only interface).
|
||||
if !*nodbus && (*no9p || *tcpAddr == "") {
|
||||
dbusAdapter = oldbus.StartDBus(sessionTree)
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.WatchExisting()
|
||||
}
|
||||
if *no9p && dbusAdapter == nil {
|
||||
fmt.Fprintln(os.Stderr, "error: -no9p mode requires D-Bus but session bus is unavailable")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Start elevation broker
|
||||
elevateSocket := os.Getenv("OLLIE_ELEVATE_SOCKET")
|
||||
if elevateSocket == "" {
|
||||
xdg := os.Getenv("XDG_RUNTIME_DIR")
|
||||
if xdg == "" {
|
||||
xdg = fmt.Sprintf("/run/user/%d", os.Getuid())
|
||||
}
|
||||
elevateSocket = filepath.Join(xdg, "ollie", "elevate.sock")
|
||||
}
|
||||
policyPath := filepath.Join(paths.DataDir(), "elevate-policy.yaml")
|
||||
|
||||
{
|
||||
notifyFn := func(req *elevate.Request) {
|
||||
notifyElevation(req)
|
||||
}
|
||||
var err error
|
||||
elevateBroker, err = elevate.NewBroker(elevate.BrokerConfig{
|
||||
SocketPath: elevateSocket,
|
||||
PolicyPath: policyPath,
|
||||
Notify: notifyFn,
|
||||
Logf: func(f string, a ...any) { fmt.Printf(f+"\n", a...) },
|
||||
SessionValid: func(id string) bool { return fs.Lookup(sessionTree, id) != nil },
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: %v\n", err)
|
||||
} else {
|
||||
defer elevateBroker.Close()
|
||||
if dbusAdapter != nil {
|
||||
initElevateNotifier(dbusAdapter.Conn(), elevateBroker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create 9P server (after broker is ready so it can be passed in)
|
||||
if !*no9p {
|
||||
srv = New(Config{
|
||||
Sink: sink,
|
||||
SessionTree: sessionTree,
|
||||
RootStore: NewRootStore(toolRegistry),
|
||||
ElevateBroker: elevateBroker,
|
||||
InvalidateModels: modelCache.Invalidate,
|
||||
ToolPrompt: toolsrv.ToolPrompt,
|
||||
})
|
||||
}
|
||||
|
||||
// Prefetch models asynchronously at startup
|
||||
go modelCache.Refresh()
|
||||
|
||||
var listener net.Listener
|
||||
if !*no9p {
|
||||
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.Serve(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf("olliesrv listening on %s\n", sockPath)
|
||||
}
|
||||
|
||||
var tcpListener net.Listener
|
||||
if !*no9p && *tcpAddr != "" {
|
||||
var err error
|
||||
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.Serve(conn)
|
||||
}
|
||||
}()
|
||||
fmt.Printf("olliesrv listening on tcp %s\n", *tcpAddr)
|
||||
}
|
||||
|
||||
if *no9p {
|
||||
fmt.Println("olliesrv running in D-Bus only mode")
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
fmt.Println("shutting down")
|
||||
daemonCancel() // signal all sessions via context propagation
|
||||
if dbusAdapter != nil {
|
||||
dbusAdapter.Close()
|
||||
}
|
||||
if srv != nil {
|
||||
srv.Shutdown()
|
||||
}
|
||||
if listener != nil {
|
||||
listener.Close() //nolint:errcheck
|
||||
}
|
||||
if tcpListener != nil {
|
||||
tcpListener.Close() //nolint:errcheck
|
||||
}
|
||||
if !*no9p {
|
||||
os.Remove(sockPath)
|
||||
}
|
||||
sink.Flush()
|
||||
}
|
||||
|
||||
// NewRootStore returns a read-only FileTree for synthetic root-level files.
|
||||
func NewRootStore(reg *toolsrv.Registry) *fs.Tree {
|
||||
helpPath := paths.CfgDir() + "/help.md"
|
||||
readEntries := map[string]func() ([]byte, error){
|
||||
"backends": func() ([]byte, error) {
|
||||
return []byte(strings.Join(backend.Backends(), "\n") + "\n"), nil
|
||||
},
|
||||
"models": func() ([]byte, error) {
|
||||
return modelCache.Get(), nil
|
||||
},
|
||||
"help": func() ([]byte, error) {
|
||||
return os.ReadFile(helpPath)
|
||||
},
|
||||
"agents": func() ([]byte, error) {
|
||||
var sb strings.Builder
|
||||
for _, dir := range agent.AgentsDirs() {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() && strings.HasSuffix(e.Name(), ".json") {
|
||||
sb.WriteString(strings.TrimSuffix(e.Name(), ".json"))
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
return []byte(sb.String()), nil
|
||||
},
|
||||
}
|
||||
notBlocking := func(context.Context, string) ([]byte, string, error) {
|
||||
return nil, "", fmt.Errorf("blocking read not supported")
|
||||
}
|
||||
readOnly := func([]byte) error { return fmt.Errorf("read-only") }
|
||||
|
||||
// ctl commands
|
||||
ctlHandler := func(data []byte) error {
|
||||
cmd := strings.TrimSpace(string(data))
|
||||
switch cmd {
|
||||
case "invalidate":
|
||||
modelCache.Invalidate()
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown ctl command: %s", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
return fs.NewTree(nil, fs.Perms[fs.PathRoot].FileMode,
|
||||
fs.WithResolver(func(_ []string, name string) (string, error) {
|
||||
if _, ok := readEntries[name]; ok {
|
||||
return name, nil
|
||||
}
|
||||
if name == "ctl" || name == "complete" || name == "generate" || name == "route" {
|
||||
return name, nil
|
||||
}
|
||||
return "", fmt.Errorf("%s: not found", name)
|
||||
}),
|
||||
fs.WithLister(func(_ []string) ([]os.DirEntry, error) {
|
||||
return []os.DirEntry{
|
||||
fs.FileEntry("backends", fs.Perms[fs.PathRoot].FileMode),
|
||||
fs.FileEntry("ctl", 0666),
|
||||
fs.DirEntry("elevate", 0755),
|
||||
fs.FileEntry("generate", 0666),
|
||||
fs.FileEntry("models", fs.Perms[fs.PathRoot].FileMode),
|
||||
fs.FileEntry("help", fs.Perms[fs.PathRoot].FileMode),
|
||||
fs.FileEntry("route", 0666),
|
||||
}, nil
|
||||
}),
|
||||
fs.WithOpener(func(_ []string, name string) (fs.File, error) {
|
||||
if name == "complete" {
|
||||
return &fs.FileConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &fs.SyntheticFileInfo{Name_: "complete", Mode_: 0666}, nil },
|
||||
ReadFn: func() ([]byte, error) { return nil, nil },
|
||||
WriteFn: func([]byte) error { return nil },
|
||||
BlockingReadFn: notBlocking,
|
||||
}, nil
|
||||
}
|
||||
if name == "generate" {
|
||||
return &fs.FileConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &fs.SyntheticFileInfo{Name_: "generate", Mode_: 0666}, nil },
|
||||
ReadFn: func() ([]byte, error) { return nil, nil },
|
||||
WriteFn: func([]byte) error { return nil },
|
||||
BlockingReadFn: notBlocking,
|
||||
}, nil
|
||||
}
|
||||
if name == "route" {
|
||||
return &fs.FileConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &fs.SyntheticFileInfo{Name_: "route", Mode_: 0666}, nil },
|
||||
ReadFn: func() ([]byte, error) { return nil, nil },
|
||||
WriteFn: func([]byte) error { return nil },
|
||||
BlockingReadFn: notBlocking,
|
||||
}, nil
|
||||
}
|
||||
if name == "ctl" {
|
||||
return &fs.FileConfig{
|
||||
StatFn: func() (os.FileInfo, error) { return &fs.SyntheticFileInfo{Name_: "ctl", Mode_: 0666}, nil },
|
||||
ReadFn: func() ([]byte, error) { return nil, nil },
|
||||
WriteFn: ctlHandler,
|
||||
BlockingReadFn: notBlocking,
|
||||
}, nil
|
||||
}
|
||||
readFn, ok := readEntries[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: not found", name)
|
||||
}
|
||||
return &fs.FileConfig{
|
||||
StatFn: func() (os.FileInfo, error) {
|
||||
data, _ := readFn()
|
||||
return &fs.SyntheticFileInfo{Name_: name, Mode_: fs.Perms[fs.PathRoot].FileMode, Size_: int64(len(data))}, nil
|
||||
},
|
||||
ReadFn: readFn,
|
||||
WriteFn: readOnly,
|
||||
BlockingReadFn: notBlocking,
|
||||
}, nil
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// 9P filesystem server for ollie sessions.
|
||||
// See doc comment below for filesystem layout.
|
||||
package server
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
|
|||
12
dbus/dbus.go
12
dbus/dbus.go
|
|
@ -33,9 +33,9 @@ type DBusAdapter struct {
|
|||
watchers map[string]context.CancelFunc // sessionID -> cancel for state watcher
|
||||
}
|
||||
|
||||
// startDBus connects to the session bus, claims the well-known name, and
|
||||
// StartDBus connects to the session bus, claims the well-known name, and
|
||||
// exports the adapter. Returns nil (no-op) if the bus is unavailable.
|
||||
func startDBus(tree *fs.Tree) *DBusAdapter {
|
||||
func StartDBus(tree *fs.Tree) *DBusAdapter {
|
||||
conn, err := dbus.ConnectSessionBus()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "dbus: session bus unavailable: %v\n", err)
|
||||
|
|
@ -89,6 +89,14 @@ func (a *DBusAdapter) Close() {
|
|||
a.conn.Close()
|
||||
}
|
||||
|
||||
// Conn returns the underlying D-Bus connection, or nil if the adapter is nil.
|
||||
func (a *DBusAdapter) Conn() *dbus.Conn {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
return a.conn
|
||||
}
|
||||
|
||||
// --- Lifecycle callbacks ---
|
||||
|
||||
func (a *DBusAdapter) OnSessionCreated(id string, sess *fs.Session) {
|
||||
|
|
|
|||
4
justfile
4
justfile
|
|
@ -30,8 +30,8 @@ core:
|
|||
|
||||
ninep:
|
||||
@echo === Building ollie-9p ===
|
||||
go build -buildvcs=false -o {{bin}}/olliesrv ./cmd/olliesrv/
|
||||
go build -buildvcs=false -o {{bin}}/ollie-9p ./cmd/ollie-9p/
|
||||
go build -o {{bin}}/olliesrv ./cmd/olliesrv/
|
||||
go build -o {{bin}}/ollie-9p ./cmd/ollie-9p/
|
||||
|
||||
acme:
|
||||
@echo === Building ollie-acme ===
|
||||
|
|
|
|||
Loading…
Reference in New Issue