execsession

package
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2026 License: BSD-3-Clause Imports: 11 Imported by: 0

Documentation

Overview

Package execsession is the VM side of the user shell — and of every `crun exec` invocation. loom-server opens a session by publishing on `weft.exec.<vmID>.<sid>.open` (a JSON ExecRequest), then frames flow over two ephemeral subjects :

weft.exec.<vmID>.<sid>.in   — frames TO the pty
                               'i' + payload     stdin
                               'r' + cols(u16) + rows(u16) resize
                               'x'               eof / close

weft.exec.<vmID>.<sid>.out  — frames FROM the pty
                               'o' + payload     stdout
                               'e' + code(u32)  exited (process exit)

The same 1-byte-prefix wire shape as loom-server's existing /api/projects/{p}/shell — so the SPA bridge can forward bytes verbatim, the loom-server is a dumb proxy, and replacing local pty with NATS exec is a router change rather than a protocol change.

Two target modes :

Target.Kind = "shell"   — spawn /bin/bash inside the VM root.
                           The default shell-tab session.
Target.Kind = "exec"    — `crun exec <container>` with given
                           Command/Args. Used for compile jobs
                           + tool wrappers (`pdflatex` etc.)

Eviction : a session is closed when the input stream sends 'x', the underlying process exits, or the open-publisher disconnects (via JetStream subscription drop). Sessions never persist across agent restarts — the SPA reconnects + opens a fresh one.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(ctx context.Context, cfg PumpConfig) error

Run spawns the child + pumps until ctx is done OR the child exits. Always emits a final 'e' exit frame so the caller can flip its "running" state authoritatively.

func SubjectIn

func SubjectIn(vmID, sid string) string

SubjectIn / SubjectOut are the per-session frame subjects.

func SubjectOpen

func SubjectOpen(vmID string) string

SubjectOpen is the request subject for opening a new session.

func SubjectOut

func SubjectOut(vmID, sid string) string

Types

type ExecRequest

type ExecRequest struct {
	ID     string     `json:"id"`
	Target ExecTarget `json:"target"`
	// InitialSize lets the agent allocate the pty at the right
	// dimensions before any 'r' frame arrives. Optional.
	InitialCols uint16 `json:"cols,omitempty"`
	InitialRows uint16 `json:"rows,omitempty"`
}

ExecRequest is what loom-server publishes on SubjectOpen. ID is chosen by the publisher (typically a ULID), so the SPA can preconfigure its in/out subscriptions before the agent acks open.

func DecodeRequest

func DecodeRequest(data []byte) (ExecRequest, error)

DecodeRequest unmarshals an ExecRequest + validates it. Same testable shape as the other agent reconcilers.

func (*ExecRequest) Validate

func (r *ExecRequest) Validate() error

Validate is the schema check the agent runs before opening a session. Returns the descriptive error a loom-doctor surface can surface back to the publisher.

type ExecTarget

type ExecTarget struct {
	Kind string `json:"kind"` // "shell" | "exec"
	// For Kind="shell" : Command/Args/Env optionally override
	// /bin/bash. Empty = sensible defaults.
	// For Kind="exec"  : Container must be set ; Command + Args are
	// passed to `crun exec`.
	Container string            `json:"container,omitempty"`
	Command   []string          `json:"command,omitempty"`
	Args      []string          `json:"args,omitempty"`
	Env       map[string]string `json:"env,omitempty"`
	WorkDir   string            `json:"work_dir,omitempty"`
}

ExecTarget tells the agent which process to spawn. Shell vs exec is the only structural split.

type Logger

type Logger interface {
	Printf(format string, args ...any)
}

Logger is the slice of *log.Logger this package wants — mirrors pkg/mounts.Logger so we avoid a hard log/slog dep in the agent.

type PumpConfig

type PumpConfig struct {
	NC      *nats.Conn
	VMID    string
	Request ExecRequest
}

PumpConfig bundles everything the pump needs to wire one session. Agent main owns the NATS connection + VMID ; this struct is what it passes to Run for each incoming open request.

type Sessions

type Sessions struct {
	// contains filtered or unexported fields
}

Sessions tracks every live execution so the agent can clean up (kill pty, unsubscribe from in subject) when an open-publisher drops or the process exits. Concurrent-safe.

func NewSessions

func NewSessions() *Sessions

NewSessions builds an empty session registry. Agent main wires the NATS subscriber that calls Open / Close from message handlers.

func (*Sessions) Close

func (s *Sessions) Close(id string)

Close stops the session with id (no-op if unknown).

func (*Sessions) CloseAll

func (s *Sessions) CloseAll()

CloseAll terminates every live session — called on agent shutdown.

func (*Sessions) Open

func (s *Sessions) Open(id string, cancel context.CancelFunc)

Open registers a new session under id ; cancel will be invoked when Close(id) is called or the registry is shutdown.

type Subscriber

type Subscriber struct {
	// contains filtered or unexported fields
}

Subscriber owns the open subscription + the live sessions registry. Pattern mirrors pkg/mounts.Subscriber so the agent's main can wire it uniformly with the other reconcilers.

func NewSubscriber

func NewSubscriber(nc *nats.Conn, vmID string, logger Logger) *Subscriber

NewSubscriber builds a Subscriber over nc bound to vmID. Logger may be nil ; we silently drop log lines in that case.

func (*Subscriber) Start

func (s *Subscriber) Start(ctx context.Context) (stop func(), err error)

Start subscribes to the agent's open subject + dispatches each validated ExecRequest to Run on its own goroutine. The returned stop fn unsubscribes + cancels every live session.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL