Help us shape the future of Go! Take the 2026 Go Developer Survey

attach

package
v0.32.1 Latest Latest
Warning

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

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

Documentation

Overview

Package attach is the local terminal's side of a session: an SSH client of the session's attach socket. upterm host, upterm attach and the functional tests all use it, so the local terminal is just another client in the code rather than in a design document.

Index

Constants

This section is empty.

Variables

View Source
var ErrHostKeyMismatch = errors.New("host key is not the daemon's")

ErrHostKeyMismatch is what Run's error wraps when the door presented a key that is not the daemon's. It is exported so a caller can tell this apart from every other reason an attachment did not happen: those are worth retrying, and this one is worth stopping for — whatever answered the socket is not the session, and the terminal must not be handed to it. x/crypto returns the callback's error as it is and wraps it with %w, so errors.Is finds it through the handshake's own message.

Functions

This section is empty.

Types

type Client

type Client struct {
	Socket  string
	Stdin   io.Reader
	Stdout  io.Writer
	Pty     *Pty
	Resizes <-chan termsize.Size
	Escape  byte
	Logger  *slog.Logger

	// HostKeys is the daemon's public keys, from the session record: a record
	// from a current daemon carries exactly one entry, the session host key
	// it generated for that run; a record written by an older daemon may
	// carry several, which is why this is a slice. Run refuses to attach
	// without at least one: a client that accepted any key would hand its
	// terminal to whatever bound the socket.
	HostKeys []ssh.PublicKey

	// Suspend stops the process this client runs in and returns the
	// terminal's size once it is running again — for a local terminal:
	// restore the terminal, SIGTSTP, re-enter raw mode, measure. Nil
	// disables the ~^Z sequence, and the bytes reach the session instead.
	//
	// It is the embedder's because what "suspend" means is the terminal's
	// business, not the protocol's: this package never touches termios and
	// never signals its own process.
	Suspend func() termsize.Size
	// contains filtered or unexported fields
}

Client attaches a pair of streams to a running session.

func (*Client) Run

func (c *Client) Run(ctx context.Context) (Result, error)

Run attaches and returns when the attachment ends. An error means the client never attached; once attached, every ending is a Result.

type EscapeAction

type EscapeAction int

EscapeAction is what an escape sequence asked for.

const (
	// EscapeNone: nothing completed in this read.
	EscapeNone EscapeAction = iota
	// EscapeDetach: the caller should leave. Feed returns at once, so bytes
	// after the sequence are dropped — they were typed at a terminal that is
	// no longer attached to anything.
	EscapeDetach
	// EscapeSuspend: the caller should stop this process and resume the
	// attachment afterwards. Feed keeps going, so the rest of the read is
	// still the session's.
	EscapeSuspend
)

type EscapeFilter

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

EscapeFilter recognises ssh's detach and suspend conventions in a stream of keystrokes: an escape byte at the start of a line followed by '.' detaches; followed by '^Z' (0x1a) suspends, when suspend is enabled; followed by itself sends one literal escape byte; followed by anything else sends both. "Start of a line" is the beginning of input or the byte after a CR or LF — CR because a raw-mode terminal sends Enter as CR.

Bytes after a detach are dropped: they were typed at a terminal that is no longer attached to anything. Bytes after a suspend are not: they are still the session's, queued for delivery once the process resumes.

func NewEscapeFilter

func NewEscapeFilter(esc byte, suspend bool) *EscapeFilter

NewEscapeFilter returns a filter for esc. Zero disables it: Feed passes everything through. suspend says whether esc followed by ^Z is offered; when it is not, both bytes reach the session like any other unrecognised pair.

func (*EscapeFilter) Feed

func (f *EscapeFilter) Feed(p []byte) (out []byte, action EscapeAction)

Feed filters p, returning what should reach the session and what escape sequence, if any, completed. out may alias an internal buffer; copy it before the next call if it must be kept.

func (*EscapeFilter) Flush

func (f *EscapeFilter) Flush() []byte

Flush returns a held escape byte, for the end of input.

type Pty

type Pty struct {
	Term string
	Size termsize.Size
}

Pty is a pty request: the terminal type and geometry the client's own terminal has. A client that requests one is eligible to be the session's primary client; one that does not is a viewer.

type Reason

type Reason int

Reason says how an attachment ended.

const (
	// Detached: the client ended it — the escape key, EOF on Stdin, or ctx.
	Detached Reason = iota
	// Exited: the session sent the command's exit status and closed.
	Exited
	// Disconnected: the session closed the connection without an exit status.
	// The daemon's log says why: a stalled or overflowed client, or a daemon
	// that went away.
	Disconnected
)

func (Reason) String

func (r Reason) String() string

type Result

type Result struct {
	Reason Reason
	Status int
}

Result is how an attachment ended. Status is meaningful only for Exited.

Jump to

Keyboard shortcuts

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