pub struct AnvilSession { /* private fields */ }Expand description
An active SSH session connected to a GitHub (or GHE) host.
§Typical Usage
use anvil_ssh::{AnvilConfig, AnvilSession};
let config = AnvilConfig::github();
let mut session = AnvilSession::connect(&config).await?;
// authenticate, exec, close…Implementations§
Source§impl AnvilSession
impl AnvilSession
Sourcepub async fn connect(config: &AnvilConfig) -> Result<Self, AnvilError>
pub async fn connect(config: &AnvilConfig) -> Result<Self, AnvilError>
Establishes a TCP connection to the host in config and completes the
SSH handshake (including host-key verification).
Does not authenticate; call authenticate or
authenticate_best after this.
M18 / FR-80 / FR-81 / FR-82: the TCP connect is wrapped in
a crate::retry::run loop with per-attempt
tokio::time::timeout (when config.connect_timeout is
Some). Transient failures (ECONNREFUSED, ETIMEDOUT,
DNS NXDOMAIN, …) are retried with jittered exponential
backoff; auth / host-key / protocol errors are fatal and
surface immediately. See Self::retry_history for the
per-attempt history captured during the loop.
§Errors
Returns an error on terminal network failure (after exhausting the retry budget) or if the server’s host key does not match any pinned fingerprint (fatal — never retried).
Sourcepub fn retry_history(&self) -> &[RetryAttempt]
pub fn retry_history(&self) -> &[RetryAttempt]
Returns the crate::retry::RetryAttempt history captured
during the most-recent connect* call on this session.
Empty when the first attempt succeeded. Non-empty when the
retry loop fired at least once; the last entry’s attempt
matches the attempt number that ultimately succeeded.
Surfaced by gitway --test --json’s data.retry_attempts
envelope (FR-83).
Sourcepub async fn connect_via_jump_hosts(
config: &AnvilConfig,
jumps: &[JumpHost],
) -> Result<Self, AnvilError>
pub async fn connect_via_jump_hosts( config: &AnvilConfig, jumps: &[JumpHost], ) -> Result<Self, AnvilError>
Establishes the SSH session through a chain of ProxyJump
bastion hops (FR-56).
For each hop in jumps:
- Build a per-hop
AnvilConfigfrom the [JumpHost] fields, inheritingstrict_host_key_checking,custom_known_hosts, andverbosefrom the primaryconfig. Per-hop user andidentity_filescome from the [JumpHost] when set, else from the primary config. - Connect: the first hop uses
russh::client::connectover TCP; subsequent hops use the previous hop’sdirect-tcpipchannel as the underlying transport viarussh::client::connect_stream. - Run host-key verification — every hop runs the full
[
GitwayHandler::check_server_key] path independently (NFR-17: failure at hopn+1aborts the entire chain; no partial-success path). - Authenticate the hop with
AnvilSession::authenticate_bestso the chain can opendirect-tcpipto the next hop.
After the loop, the last bastion’s handle is used to open
direct-tcpip to the primary config.host / config.port,
and the resulting [ChannelStream] becomes the SSH transport
for the final session this method returns.
§Per-hop ssh_config
This method does NOT re-resolve ssh_config per hop — that
requires the caller’s [SshConfigPaths], which the session
module deliberately does not depend on. The CLI dispatcher
(M13.6) is responsible for populating
[JumpHost::identity_files] (and any other per-hop overrides)
from per-hop crate::ssh_config::resolve calls before
invoking this method.
§Errors
Returns the first error encountered. An empty jumps slice is
rejected with a clear message — callers should use
Self::connect when no chain is in play. Authentication
failures at any intermediate hop terminate the whole chain.
ChannelStream-based transport errors propagate via the
usual russh / AnvilError mapping.
§Panics
Does not panic. An internal expect fires only on a logic bug
(the empty-jumps check at the top of the function would have
already returned).
Sourcepub async fn connect_via_proxy_command(
config: &AnvilConfig,
proxy_command_template: &str,
alias: &str,
) -> Result<Self, AnvilError>
pub async fn connect_via_proxy_command( config: &AnvilConfig, proxy_command_template: &str, alias: &str, ) -> Result<Self, AnvilError>
Establishes the SSH session over a child process spawned from a
ProxyCommand template (FR-55).
proxy_command_template is the raw template (typically from
crate::ssh_config::ResolvedSshConfig::proxy_command or a CLI
override). %h, %p, %r, %n, and %% are expanded against
config.host, config.port, config.username, and alias
respectively before the platform shell (sh -c / cmd /C)
spawns the command. The child’s stdin/stdout become the SSH
transport via russh::client::connect_stream.
alias is the original argument the user typed before
HostName resolution — it powers the %n token. Pass
config.host if you do not track the alias separately.
The literal value "none" (case-insensitive) is recognized as
the FR-59 disable sentinel: this method returns an error
directing the caller to use Self::connect instead. In
practice the caller’s dispatcher should never invoke this
method in that case, but the guard keeps the spawn path safe
against accidental “none” input.
§Errors
Returns an error on shell-spawn failure, on a host-key mismatch, or on any russh handshake failure.
Sourcepub async fn authenticate(
&mut self,
username: &str,
key: PrivateKeyWithHashAlg,
) -> Result<(), AnvilError>
pub async fn authenticate( &mut self, username: &str, key: PrivateKeyWithHashAlg, ) -> Result<(), AnvilError>
Authenticates with an explicit key.
Use [authenticate_best] to let the library discover the key
automatically.
§Errors
Returns an error on SSH protocol failures. Returns
AnvilError::is_authentication_failed when the server accepts the
exchange but rejects the key.
Sourcepub async fn authenticate_with_cert(
&mut self,
username: &str,
key: PrivateKey,
cert: Certificate,
) -> Result<(), AnvilError>
pub async fn authenticate_with_cert( &mut self, username: &str, key: PrivateKey, cert: Certificate, ) -> Result<(), AnvilError>
Authenticates with a private key and an accompanying OpenSSH certificate (FR-12).
The certificate is presented to the server in place of the raw public
key. This is typically used with organisation-issued certificates that
grant access without requiring the public key to be listed in
authorized_keys.
§Errors
Returns an error on SSH protocol failures or if the server rejects the certificate.
Sourcepub async fn authenticate_best(
&mut self,
config: &AnvilConfig,
) -> Result<(), AnvilError>
pub async fn authenticate_best( &mut self, config: &AnvilConfig, ) -> Result<(), AnvilError>
Discovers the best available key and authenticates using it.
Priority order (FR-9):
- Explicit
--identitypath from config. - Default
.sshpaths (id_ed25519→id_ecdsa→id_rsa). - SSH agent via
$SSH_AUTH_SOCK(Unix only).
If a certificate path is configured in config.cert_file, certificate
authentication (FR-12) is used instead of raw public-key authentication
for file-based keys.
When the chosen key requires a passphrase this method returns an error
whose is_key_encrypted predicate is
true; the caller (CLI layer) should then prompt and call
authenticate_with_passphrase.
§Errors
Returns AnvilError::is_no_key_found when no key is available via
any discovery method.
Sourcepub async fn authenticate_with_passphrase(
&mut self,
config: &AnvilConfig,
path: &Path,
passphrase: &str,
) -> Result<(), AnvilError>
pub async fn authenticate_with_passphrase( &mut self, config: &AnvilConfig, path: &Path, passphrase: &str, ) -> Result<(), AnvilError>
Loads an encrypted key with passphrase and authenticates.
Call this after [authenticate_best] returns an encrypted-key error
and the CLI has collected the passphrase from the terminal.
If config.cert_file is set, certificate authentication is used
(FR-12).
§Errors
Returns an error if the passphrase is wrong or authentication fails.
Sourcepub async fn authenticate_with_agent(
&mut self,
username: &str,
conn: AgentConnection,
) -> Result<(), AnvilError>
pub async fn authenticate_with_agent( &mut self, username: &str, conn: AgentConnection, ) -> Result<(), AnvilError>
Tries each identity held in conn until one succeeds or all are
exhausted.
On Unix this is called automatically by [authenticate_best] when no
file-based key is found. For plain public-key identities the signing
challenge is forwarded to the agent; for certificate identities the
full certificate is presented alongside the agent-signed challenge.
§Errors
Returns AnvilError::is_authentication_failed if all identities are
rejected, or AnvilError::is_no_key_found if the agent was empty.
Sourcepub async fn exec(&mut self, command: &str) -> Result<u32, AnvilError>
pub async fn exec(&mut self, command: &str) -> Result<u32, AnvilError>
Opens a session channel, executes command, and relays stdio
bidirectionally until the remote process exits.
Returns the remote exit code (FR-16). Exit-via-signal returns
128 + signal_number (FR-17).
§Errors
Returns an error on channel open failure or SSH protocol errors.
Sourcepub async fn close(self) -> Result<(), AnvilError>
pub async fn close(self) -> Result<(), AnvilError>
Sends a graceful SSH_MSG_DISCONNECT and closes the connection.
§Errors
Returns an error if the disconnect message cannot be sent.
Returns the authentication banner last received from the server (if any).
For GitHub.com this contains the “Hi
§Panics
Panics if the internal mutex is poisoned, which can only occur if another thread panicked while holding the lock — a programming error.