Documentation
¶
Index ¶
Constants ¶
const DefaultInitialClientTimeout = 10 * time.Second
DefaultInitialClientTimeout bounds how long a server told to await its initial client waits before giving up on the session.
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 ¶
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.
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()
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) Clients ¶
func (c *ClientRepo) Clients() []*api.Client
func (*ClientRepo) Delete ¶
func (c *ClientRepo) Delete(clientId string)
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.
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 (c *ReverseTunnel) Establish(ctx context.Context) (*server.CreateSessionResponse, error)
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.