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

internal

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: 41 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultInitialClientTimeout = 10 * time.Second

DefaultInitialClientTimeout bounds how long a server told to await its initial client waits before giving up on the session.

View Source
const DefaultStopGrace = 5 * time.Second

DefaultStopGrace is how long each step of the teardown waits before the next, save the first: SIGHUP (bounded by hangupGrace instead), then close the pty master, then SIGTERM, then SIGKILL. Five seconds is long enough for a shell to hang up its jobs and a program to flush, and short enough that `session stop` against something that ignores everything is a worst case of about sixteen seconds -- hangupGrace plus three of these -- not a minute.

Variables

View Source
var ErrJoinTimeout = errors.New("no guest joined within the join timeout")

ErrJoinTimeout marks the winning first-guest deadline. Host passes it as the server context cancellation cause so attached clients also exit zero.

View Source
var ErrNoInitialClient = errors.New("no client attached before the command could start")

ErrNoInitialClient is returned by ServeWithContext when AwaitInitialClient was set and nobody attached within InitialClientTimeout. The command never started; the caller records startup_abandoned.

Functions

This section is empty.

Types

type AdminServer

type AdminServer struct {
	Session    *api.GetSessionResponse
	ClientRepo *ClientRepo

	// LaunchID is the launch this server speaks for, from the record the
	// session claimed. StopSession refuses a request that names any other
	// launch, so that a client whose session ended between reading the
	// record and dialling the socket does not stop the session that took
	// the name over. Empty means no name was claimed -- a caller that
	// supplied its own admin socket -- and then no stop can be bound to a
	// launch, so none is accepted.
	LaunchID string

	// OnListening, if set, is called once the socket is bound. It is the other
	// half of readiness: a caller told "ready" must be able to connect, which
	// registering the actor does not establish and binding does.
	OnListening func()

	// OnStop is called when a client asks the session to end. The daemon
	// wires it to an explicit stop cause; the RPC returns at
	// once and the teardown follows.
	OnStop func()

	// OnSetJoinTimeout sets the session's join timeout and reports what it
	// found. Nil answers Unimplemented, as a daemon without the call would.
	OnSetJoinTimeout func(time.Duration) *api.SetJoinTimeoutResponse

	// JoinState reports the join timeout as the daemon holds it, for
	// GetSession. Nil reports none.
	JoinState func() *api.JoinState

	sync.Mutex
	// contains filtered or unexported fields
}

func (*AdminServer) Listen added in v0.30.0

func (s *AdminServer) Listen(sock string) error

Listen binds the admin socket. It is separate from Serve so the caller can bind synchronously, before any concurrent actor exists.

Binding inside the serving goroutine made a bind failure race the command's start: whichever lost the race decided how the run was classified, so the same broken socket path was reported as startup_failed on one run and signaled on the next. Bound up front, the failure is the caller's to return and there is nothing to race.

func (*AdminServer) Serve

func (s *AdminServer) Serve(ctx context.Context) error

Serve serves the listener Listen bound. Listen must have succeeded first: this deliberately refuses to bind on the caller's behalf, because doing so is exactly the race Listen exists to remove.

func (*AdminServer) Shutdown

func (s *AdminServer) Shutdown(ctx context.Context) error

Shutdown drains RPCs until ctx expires, then initiates forced transport closure. It cannot terminate a callback that ignores cancellation: gRPC Serve and its shutdown goroutines may still wait for that callback to return.

type ClientRepo

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

func NewClientRepo

func NewClientRepo() *ClientRepo

func (*ClientRepo) Add

func (c *ClientRepo) Add(client *api.Client) error

func (*ClientRepo) Clients

func (c *ClientRepo) Clients() []*api.Client

func (*ClientRepo) Delete

func (c *ClientRepo) Delete(clientId string)

func (*ClientRepo) Get

func (c *ClientRepo) Get(clientId string) *api.Client

type CommandResult added in v0.30.0

type CommandResult struct {
	// Exited is true when the command terminated under its own control.
	Exited bool
	// Code is its status; meaningful only when Exited.
	Code int
	// Signal names the signal that killed it, when it was killed.
	Signal string
	// SignalNumber is the originating Unix terminating signal, when available.
	SignalNumber *int
}

CommandResult is a command's own outcome, as distinct from the session's shutdown cause.

type ExitError added in v0.25.0

type ExitError struct {
	// Code is the status GetExitCodeProcess reported, kept unsigned because
	// that is what Windows produces and what SSH puts on the wire. On a
	// 32-bit build, narrowing it to int would make every status with the high
	// bit set negative -- an unhandled exception such as 0xC0000005, or a
	// deliberate "exit -1" -- and exitCode would then read it as a process
	// that never exited under its own control.
	Code uint32
}

ExitError reports that a PTY's process exited with a non-zero status.

Unix gets this for free from exec.Cmd.Wait, which returns *exec.ExitError. Windows waits on a process handle rather than an exec.Cmd, so it has no such error to return and needs this to carry the code instead of burying it in a message string.

func (*ExitError) Error added in v0.25.0

func (e *ExitError) Error() string

type PTY added in v0.18.0

type PTY interface {
	io.ReadWriteCloser

	// Setsize changes the terminal dimensions.
	// On Unix, this sends a SIGWINCH to the slave process.
	// On Windows, this resizes the ConPTY buffer.
	Setsize(h, w int) error

	// Redraw asks the process on the pty to repaint without changing its
	// geometry: a replay repaints what the ring holds, and a full-screen
	// program redraws the rest on SIGWINCH. On Windows there is no SIGWINCH,
	// so the nudge is a resize, and unavailable under a pinned size.
	Redraw() error

	// Wait waits for the process associated with this PTY to exit.
	// On Unix, this delegates to exec.Cmd.Wait().
	// On Windows, this waits on the process handle.
	Wait() error

	// Kill terminates the process associated with this PTY.
	// On Unix, this delegates to exec.Cmd.Process.Kill().
	// On Windows, this calls TerminateProcess on the handle.
	Kill() error

	// Signal sends sig to the command's process group, so that a shell's
	// hangup reaches its jobs. Unsupported on Windows, which returns
	// errors.ErrUnsupported and lets the caller go straight to Kill.
	Signal(sig syscall.Signal) error
}

PTY represents a pseudo-terminal abstraction that works across platforms. On Unix, it wraps a traditional PTY created via creack/pty. On Windows, it wraps a ConPTY (Console Pseudo Terminal).

The interface provides a common abstraction for:

  • Reading/writing terminal I/O (via io.ReadWriteCloser)
  • Resizing the terminal window
  • Managing process lifecycle (Wait/Kill)

Platform-specific implementations:

  • Unix: see pty_unix.go
  • Windows: see pty_windows.go

type PermissionDeniedError

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

PermissionDeniedError is a relay that would not authenticate this host.

It says which of the two ways that happened, because they are different problems: a host that offered nothing has no identity for the relay to have rejected, while a host whose keys were all refused has keys this relay does not accept. x/crypto's own error stays reachable through Unwrap for anything that wants the handshake's words instead.

func (*PermissionDeniedError) Error

func (e *PermissionDeniedError) Error() string

func (*PermissionDeniedError) Unwrap

func (e *PermissionDeniedError) Unwrap() error

type ReverseTunnel

type ReverseTunnel struct {
	*ssh.Client

	Host    *url.URL
	Signers []ssh.Signer
	// HostKey is the key the embedded sshd presents. Its public half is what
	// the relay is told to expect on every guest's upstream hop; Signers
	// authenticate this tunnel and are used for nothing else.
	HostKey           ssh.Signer
	AuthorizedKeys    []ssh.PublicKey
	KeepAliveDuration time.Duration
	// ProxyURL, when non-nil, is the HTTP proxy to connect to Host through.
	ProxyURL        *url.URL
	HostKeyCallback ssh.HostKeyCallback
	Logger          *slog.Logger
	// contains filtered or unexported fields
}

func (*ReverseTunnel) Close

func (c *ReverseTunnel) Close()

Close releases whatever Establish took, and works on a tunnel that never established anything. Establish gives up at three different points — the dial, the session request, the listen — and each leaves a different subset of these set; a Close that assumed the successful one would turn a failed dial into a nil dereference in the caller's teardown.

It is bounded: closing ln goes through the wrapper, which gives the relay listenerCloseGrace and then closes the client itself.

func (*ReverseTunnel) Establish

func (*ReverseTunnel) Listener

func (c *ReverseTunnel) Listener() net.Listener

Listener returns the forwarded listener the guest server serves on, wrapped so that whoever closes it — Shutdown, from an interrupt — cannot be left waiting on a relay that has stopped replying.

type SFTPSession added in v0.22.0

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

SFTPSession tracks permission state for a single SFTP session

type Server

type Server struct {
	Command      []string
	CommandEnv   []string
	ForceCommand []string
	// HostKey is the key both doors present. A session's key, not the
	// operator's identity: a key exchange signs with whatever is here, on
	// every join, attach and rekey, and an identity held by a confirming
	// agent would be asked each time.
	HostKey                 ssh.Signer
	AuthorizedKeys          []ssh.PublicKey
	EventEmitter            *emitter.Emitter
	KeepAliveDuration       time.Duration
	Logger                  *slog.Logger
	ReadOnly                bool
	AllowLocalTCPForwarding bool
	PtySize                 termsize.Size
	PinPtySize              bool
	Term                    string

	// AwaitInitialClient defers starting the command until the first host
	// client's output subscription is installed, and defers serving guests
	// until the command has started. Foreground use sets it: a command that
	// exits at once — `upterm host -- false` — would otherwise race the local
	// terminal's attach, and lose. Headless use leaves it off.
	AwaitInitialClient bool
	// InitialClientTimeout bounds that wait; zero means
	// DefaultInitialClientTimeout.
	InitialClientTimeout time.Duration

	// StopGrace bounds each step of the command's teardown; zero means
	// DefaultStopGrace.
	StopGrace time.Duration

	// OnCommandStarted, if set, is called once the hosted command is running.
	// Readiness is a claim about facts, and this is one of the two facts it
	// rests on: until this fires, "ready" would mean a command that may still
	// fail to start.
	OnCommandStarted func()

	// OnGuestServerStopped is called when the guest listener stops serving,
	// which in practice means the reverse tunnel is gone. The session does not
	// end: the command keeps running and keeps its pty. Reporting it is the
	// caller's job, because internal must not know about on-disk state.
	OnGuestServerStopped func(error)

	// SFTP configuration
	SFTPDisabled          bool                   // Disable SFTP subsystem entirely
	SFTPPermissionChecker sftp.PermissionChecker // Optional: prompts user for SFTP permissions (nil = auto-allow)
	// contains filtered or unexported fields
}

func (*Server) CommandResult added in v0.30.0

func (s *Server) CommandResult() CommandResult

CommandResult returns the hosted command's outcome. Valid after ServeWithContext returns.

func (*Server) ServeWithContext

func (s *Server) ServeWithContext(ctx context.Context, guest, host net.Listener) error

ServeWithContext runs the session: the hosted command, the guest door on guest, and — when host is not nil — the host door on host, for clients that are already on this machine.

Jump to

Keyboard shortcuts

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