session

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 13, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package session manages persistent PTY shell sessions that survive SSH disconnects.

Two diagrams — one per direction — because they are almost independent paths.

Input: user keystrokes → shell stdin

┌──────┐    SSH     ┌────────┐   WriteInput()    ┌────────┐     ┌────────┐
│ user │  channel   │ Client │ ──(writer only)─► │  PTY   │ ──► │ shell  │
│ term │ ─────────► │        │  → s.ptmx.Write   │ master │     │ stdin  │
└──────┘            └────────┘                   └────────┘     └────────┘

Only the Client currently marked as writer gets to push bytes into the PTY. Non-writer clients are read-only while they remain non-writers. If the writer detaches, the most recently attached remaining client is promoted.

Output: shell stdout → user terminal (+ VTE side channel)

┌────────┐    ┌────────┐     ┌──────────────┐     deliver()     ┌─────────┐  SSH   ┌──────┐
│ shell  │ ─► │  PTY   │ ──► │   pump()     │ ─── fan-out  ───► │ Client₁ │ ─────► │ user │
│ stdout │    │ master │     │ ptmx.Read()  │     (N clients)   │ Client₂ │ chan   │ term │
└────────┘    └────────┘     └──────┬───────┘                   │  ...    │        └──────┘
                                    │                           └─────────┘
                                    │ under s.mu
                                    ▼
                             ┌─────────────┐   response pipe   ┌──────────┐
                             │ vte.Write() │ ────────────────► │ drainVTE │  (discarded,
                             │ updates:    │  (DSR/DA/CPR etc) └──────────┘   safety valve)
                             │  • screen   │
                             │  • cursor   │
                             │  • scrollbk │
                             │  • alt-scr  │
                             └─────▲───────┘
                                   │
                                   │ snapshot on Attach() / re-attach
                                   │   • renderVTEScrollback()
                                   │   • vte.Render()        (visible screen)
                                   │   • vte.CursorPosition()
                                   ▼
                             ┌─────────────┐
                             │ replay blob │  → prepended to the new client's
                             │ ESC c +     │    output stream in normal-screen
                             │ scrollback +│    mode, so its terminal is
                             │ screen +    │    restored across reconnects
                             │ cursor pos  │
                             └─────────────┘

Three things worth calling out:

  1. The VTE is on a side branch of the output pump, not in series. Live clients get the raw PTY bytes unmodified. The VTE is fed on every pump read and is continuously drained by drainVTE, but its rendered screen, cursor position, and scrollback are only consulted on Attach() to synthesize replay state. If nobody ever re-attaches, the VTE is mostly just absorbing output to preserve future replay state.

  2. The normal-screen replay path uses the blob shown above: terminal reset, rendered scrollback, visible screen, and cursor position. Alternate-screen sessions take a different path. When vte.IsAltScreen() is true, Attach() sends enter-alt-screen + clear to the new client, writes Ctrl-L directly to the PTY, and relies on the running application to redraw.

  3. Terminal queries get answered by the writer client's real terminal, not by the VTE. When an app writes ESC[6n, those bytes flow to the VTE and to every attached client's SSH channel. A real terminal emulator can respond on its input stream, but only the writer client's response can pass WriteInput() and reach the PTY; non-writer responses are rejected as read-only. The VTE's own response goes into drainVTE and is thrown away. With no client attached, TUI queries go unanswered. The drain exists to keep vte.Write from blocking on a full response pipe.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MergeEnv

func MergeEnv(sessionEnv []string) []string

MergeEnv returns the container's environment with session-provided vars overlaid. Filters out vibed-internal config variables (VIBEPIT_SSH_PUBKEY).

Types

type Client

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

Client represents a connection to a session. It implements io.ReadWriteCloser. Reads return PTY output; writes send PTY input (only if this client is the session's writer).

func (*Client) Close

func (c *Client) Close() error

Close detaches the client from the session.

func (*Client) Read

func (c *Client) Read(p []byte) (int, error)

Read returns the next chunk of PTY output. It blocks until data is available or the client is closed. Supports partial reads — if the caller's buffer is smaller than the available data, the remainder is preserved for the next Read call.

Read is not safe for concurrent use. It assumes a single reader goroutine (typically io.Copy in the SSH handler).

func (*Client) Write

func (c *Client) Write(p []byte) (int, error)

Write sends input to the session's PTY. Returns an error if this client is not the writer.

type Manager

type Manager struct {

	// Command is the shell command and arguments used for new sessions.
	// Defaults to ["/bin/bash", "--login"] when empty.
	Command []string
	// contains filtered or unexported fields
}

Manager owns all sessions and enforces the concurrency limit.

func NewManager

func NewManager(limit int) *Manager

NewManager creates a session manager with the given maximum number of concurrent active sessions.

func (*Manager) Create

func (m *Manager) Create(cols, rows uint16, env []string) (*Session, error)

Create starts a new shell session with the given terminal dimensions. The env parameter provides additional environment variables (e.g., from the SSH session) that are merged with the container's environment.

func (*Manager) Get

func (m *Manager) Get(id string) *Session

Get returns the session with the given ID, or nil if not found.

func (*Manager) List

func (m *Manager) List() []SessionInfo

List returns info snapshots for all current sessions.

func (*Manager) SetStateFilePath

func (m *Manager) SetStateFilePath(path string)

SetStateFilePath sets the path where session state is written on changes.

type Session

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

Session represents a persistent PTY shell session that survives client disconnects. It manages the shell process, PTY, attached clients, and output fan-out.

func (*Session) Attach

func (s *Session) Attach(cols, rows uint16) *Client

Attach creates a new client and attaches it to this session. The first client to attach becomes the writer. If cols/rows are provided and this client becomes the writer, the PTY is resized.

On attach, the client receives a replay of scrollback history plus the current VTE screen state so the terminal appears restored.

func (*Session) Detach

func (s *Session) Detach(c *Client)

Detach removes a client from the session. If the detached client was the writer, the most recently attached remaining client is promoted.

func (*Session) Exited

func (s *Session) Exited() bool

Exited returns true if the shell process has exited.

func (*Session) ID

func (s *Session) ID() string

ID returns the session identifier.

func (*Session) Info

func (s *Session) Info() SessionInfo

Info returns a snapshot of the session's current state.

func (*Session) Resize

func (s *Session) Resize(c *Client, cols, rows uint16)

Resize changes the PTY dimensions. Only the writer client may resize. Resizes that don't change the dimensions are dropped to avoid redundant ioctls and VTE allocations from a chatty client.

func (*Session) TakeOver

func (s *Session) TakeOver(c *Client, cols, rows uint16)

TakeOver promotes the given client to writer, replacing the current writer. If cols/rows are non-zero, the PTY and VTE are resized to match the new writer's terminal dimensions.

func (*Session) WriteInput

func (s *Session) WriteInput(c *Client, p []byte) (int, error)

WriteInput sends input to the PTY. Only the writer client may write.

type SessionInfo

type SessionInfo struct {
	ID          string
	Command     string
	ClientCount int
	Status      Status
	ExitCode    int
	CreatedAt   time.Time
	ExitedAt    time.Time
	DetachedAt  time.Time
}

SessionInfo holds a snapshot of session state for display purposes.

type Status

type Status string
const (
	Attached Status = "attached"
	Detached Status = "detached"
	Exited   Status = "exited"
)

Jump to

Keyboard shortcuts

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