sshclient

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package sshclient implements the SSH transport: authentication, host key verification, proxy traversal and interactive sessions.

The connection method follows zsuroy/ctty's internal/sftpconfig: a trust-on-first-use known_hosts callback, already-trusted host key algorithms offered first, and an authentication chain of agent, identity file, default keys and finally password / keyboard-interactive.

Index

Constants

View Source
const DefaultDialTimeout = 15 * time.Second

DefaultDialTimeout bounds the TCP and handshake phases of a connection.

Variables

This section is empty.

Functions

func AcceptNewHostKeyCallback

func AcceptNewHostKeyCallback(knownHostsPath string) (ssh.HostKeyCallback, error)

AcceptNewHostKeyCallback returns a trust-on-first-use callback: a host already present in known_hosts must match exactly, an unknown host is recorded, and a changed key is rejected. This mirrors OpenSSH's StrictHostKeyChecking=accept-new.

func DefaultKnownHostsPath

func DefaultKnownHostsPath() (string, error)

DefaultKnownHostsPath returns ~/.ssh/known_hosts.

func ExitCode

func ExitCode(err error) int

ExitCode extracts the remote exit status from err, defaulting to 1 for any non-exit failure so callers can map it straight onto os.Exit.

func HostKeyAlgorithms

func HostKeyAlgorithms(knownHostsPath string, addrs ...string) []string

HostKeyAlgorithms returns the host key types already trusted for the given dial targets. Offering these first stops a server from presenting a different key type than the one recorded, which would look like a mismatch (golang/go#29286).

func IsAuthFailure

func IsAuthFailure(err error) bool

IsAuthFailure reports whether err came from the server rejecting our credentials, as opposed to a network or host key problem. Callers use this to decide whether prompting for a password is worth a retry.

func RunCommand

func RunCommand(ctx context.Context, client *ssh.Client, command []string, stdout, stderr io.Writer) error

RunCommand executes command on the remote host without a PTY and streams its output. The command is a positional vector, not a shell string, and is joined with spaces for the remote shell exactly as OpenSSH does when given trailing arguments.

A non-zero remote exit status is returned as an *ExitError.

Types

type Client

type Client struct {
	*ssh.Client
	Host config.SSHHost
	// contains filtered or unexported fields
}

Client is an established SSH connection to a single host.

func Dial

func Dial(ctx context.Context, host config.SSHHost, opts DialOptions) (*Client, error)

Dial establishes a connection to host, traversing ProxyJump or ProxyCommand when the host config specifies one.

func (*Client) Close

func (c *Client) Close() error

Close releases the underlying connection and any proxy resources. It is safe to call while the keepalive loop is also closing the connection: the first one in does the teardown, and the second finds it done.

type DialOptions

type DialOptions struct {
	// Password enables password and keyboard-interactive auth. It also unlocks
	// passphrase-protected private keys.
	Password string
	// DisableAgent skips ~/.ssh/ssh-agent discovery.
	DisableAgent bool
	// KnownHosts overrides the default ~/.ssh/known_hosts path.
	KnownHosts string
	// InsecureIgnoreHostKey skips host key verification entirely. Intended for
	// throwaway test hosts; it makes the connection vulnerable to interception.
	InsecureIgnoreHostKey bool
	// Timeout bounds the TCP connect and SSH handshake. Zero means DefaultDialTimeout.
	Timeout time.Duration
	// ProxyJump overrides the host config's ProxyJump.
	ProxyJump string
	// ProxyCommand overrides the host config's ProxyCommand.
	ProxyCommand string
	// LookupHost resolves a ProxyJump alias against parsed SSH config. When nil,
	// the alias is treated as a literal [user@]host[:port].
	LookupHost func(alias string) (config.SSHHost, bool)
}

DialOptions tunes how a connection is established.

type Direction

type Direction int

Direction says which way a transfer moves bytes.

const (
	// Upload sends a local path to a host.
	Upload Direction = iota
	// Download brings a path from a host to the local machine.
	Download
)

func (Direction) Verb

func (d Direction) Verb() string

Verb names the direction the way the command line spells it, so a summary reads the same whichever side the bytes came from.

type ExitError

type ExitError struct {
	Code int
	Err  error
}

ExitError carries the exit status reported by the remote command.

func (*ExitError) Error

func (e *ExitError) Error() string

func (*ExitError) Unwrap

func (e *ExitError) Unwrap() error

type InteractiveSession

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

InteractiveSession runs a login shell or a single command on a remote host attached to the local terminal.

It implements bubbletea's ExecCommand interface, so a TUI can hand the terminal over to it with tea.Exec and reclaim it once the session ends.

func NewInteractiveSession

func NewInteractiveSession(client *ssh.Client, command []string) *InteractiveSession

NewInteractiveSession prepares a session. An empty command opens the login shell; otherwise the command runs under a PTY like `ssh -t`.

func (*InteractiveSession) Run

func (s *InteractiveSession) Run() error

Run opens the session and blocks until it ends. The remote exit status is returned as an *ExitError so the caller can propagate it.

func (*InteractiveSession) SetStderr

func (s *InteractiveSession) SetStderr(w io.Writer)

SetStderr satisfies bubbletea.ExecCommand.

func (*InteractiveSession) SetStdin

func (s *InteractiveSession) SetStdin(r io.Reader)

SetStdin satisfies bubbletea.ExecCommand.

func (*InteractiveSession) SetStdout

func (s *InteractiveSession) SetStdout(w io.Writer)

SetStdout satisfies bubbletea.ExecCommand.

type Plan

type Plan struct {
	Direction Direction
	Items     []TransferItem
	// Dirs are the directories the transfer has to create before it can write,
	// parents first. They are remote paths for an upload and local ones for a
	// download: the direction decides which end they belong to.
	Dirs []string
	// Bytes is the total the items add up to.
	Bytes int64
}

Plan is a transfer measured before any byte moves.

Measuring first is what gives the overall progress bar a denominator. It also means a path that cannot be read is reported while nothing has been written yet, rather than half way through.

func (*Plan) Files

func (p *Plan) Files() int

Files is how many files the plan moves.

type Progress

type Progress struct {
	File      string
	FileDone  int64
	FileTotal int64
	// Done marks the report that closes a file, so a renderer can be sure the
	// file's bar reaches the end and a throttled reporter knows to let it past.
	Done bool

	TotalDone  int64
	TotalBytes int64
	FilesDone  int
	FilesTotal int

	Rate float64       // bytes per second
	ETA  time.Duration // zero until the rate means something
}

Progress is one report of how a transfer is going.

It carries the file and the whole transfer at once. The rate and the ETA are worked out here rather than by each caller, so the command line and the browser show the same numbers from the same arithmetic.

type ProgressFunc

type ProgressFunc func(Progress)

ProgressFunc receives progress reports. The copy loop calls it between chunks, so an implementation must not block for long.

type SFTPClient

type SFTPClient struct {
	*sftp.Client
	// contains filtered or unexported fields
}

SFTPClient is an SFTP session riding on an established SSH connection.

It does not own that connection: Close shuts the SFTP channel and leaves the SSH client alone, because the same connection may outlive it — and because sshclient.Client.Close is what releases a ProxyJump's bastions.

func NewSFTPClient

func NewSFTPClient(client *Client) (*SFTPClient, error)

NewSFTPClient opens an SFTP session on client.

The subsystem is tried first, which is what a properly configured host answers to. Hosts whose sshd_config carries no "Subsystem sftp" refuse that request, so sftp-server is then started by name and its stdio handed to the SFTP client instead.

func (*SFTPClient) Close

func (c *SFTPClient) Close() error

Close shuts the SFTP session. The SSH connection underneath is left open.

func (*SFTPClient) PlanDownload

func (c *SFTPClient) PlanDownload(remotePath, localPath string) (*Plan, error)

PlanDownload measures a remote path against where it is going.

It is a method because the measurement happens over the connection: uploads walk the local filesystem, downloads have to walk the host.

func (*SFTPClient) PlanUpload

func (c *SFTPClient) PlanUpload(localPath, remotePath string) (*Plan, error)

PlanUpload measures a local path against where it is going.

What lands where follows cp, scp and rsync. A directory's contents copy into the remote path, while a file keeps its own name when the remote path is a directory — whether that is because the path ends in a slash or because the directory is already there.

It takes a connection because of that last case: whether a path names a directory is not something the string can say. Reading the trailing slash alone is what makes "put a.txt /root" fail with an SFTP error that says nothing about the destination being a directory.

func (*SFTPClient) Run

func (c *SFTPClient) Run(ctx context.Context, plan *Plan, progress ProgressFunc) error

Run carries out plan, reporting progress as it goes.

A cancelled context stops the transfer at the next packet rather than in the middle of a write: both directions copy through pkg/sftp, which moves a file in MaxPacket-sized requests, and the wrappers below check the context on every one of them.

type TransferItem

type TransferItem struct {
	Source string
	Dest   string
	Size   int64
}

TransferItem is one file in a transfer plan. Source is on the sending side and Dest on the receiving one, whichever those turn out to be.

Jump to

Keyboard shortcuts

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