Quickstart
Add Traversio to a Swift package and open your first SSH connection.
Before You Start
The package declares its public platform floor directly:
macOS 10.15iOS 13tvOS 13watchOS 6visionOS 1
At runtime, Traversio still prefers the newer Apple transport APIs on platform release 26 and later. Older supported releases automatically use the compatibility transport and listener backends behind the same public API surface.
Other practical notes:
- Traversio targets Apple platforms first.
- The package uses
swift-tools-version: 6.2. SSHClientLogHandler.osLog(...)requires the newer OSLog availability level on each platform. The core SSH API does not.
Traversio 1.0.x documents the current supported workflows. Validate those workflows against your own servers, proxy routes, and recovery policy before making it the default SSH engine for critical workloads.
Read Mental Model first for object graph context before code examples.
Add the Package
Add Traversio through the current 1.0.3 release line:
// Package.swift
dependencies: [
.package(
url: "https://github.com/GitSwiftHQ/Traversio.git",
from: "1.0.3"
)
]Then add the library to your target:
// Package.swift
.target(
name: "ExampleApp",
dependencies: [
.product(name: "Traversio", package: "Traversio")
]
)First Traversio Connection
The public API has two connection shapes:
SSHClient.connect(configuration:)for explicit long-lived ownershipSSHClient.withConnection(configuration:_:)as a convenience wrapper which closes the connection automatically
For the first integration pass, keep the shape small: describe the server and host-trust policy in one Swift value, open a closure-scoped connection, run a command, and decode the output.
import Traversio
func runRemoteUname(secret: String) async throws -> String {
let configuration = SSHClientConfiguration(
host: "demo.traversio.example",
username: "traversio",
authentication: .password(secret),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts")
)
let result = try await SSHClient.withConnection(configuration: configuration) { connection in
try await connection.execute("echo traversio")
}
return String(decoding: result.standardOutput, as: UTF8.self)
}This example shows the Traversio connection model:
SSHClientConfigurationowns endpoint, authentication, and host trust in one value.withConnection(...)keeps the SSH connection scoped to the operation and closes it on success or failure.execute(...)returns structured stdout, stderr, and exit-status data instead of asking the app to manage raw channel state.
Use SSHClient.connect(configuration:) when explicit long-lived ownership fits your flow. Close that connection yourself when the app is done with all child sessions, SFTP clients, or forwarding scopes.
Connection Flow
Within one connection call, Traversio performs:
- TCP connection setup through the selected transport backend. Apple 26+ systems prefer the newer backend, and older supported releases use the compatibility backend automatically.
- SSH identification exchange.
- Curve25519 or NIST ECDH key exchange and encrypted transport activation.
- Host-key trust evaluation using the configured policy.
- User authentication.
- Return of a live
SSHConnection, or execution of yourwithConnection(...)body with that same wrapper.
If you use withConnection(...), the connection is closed automatically when the closure returns or throws.
Transport Options
The default client profile keeps compression off, uses the current automatic rekey defaults, leaves keepalive disabled, applies a 30-second connection setup timeout, and allows 120 seconds for host-key trust confirmation. Reply timeouts stay unbounded until configured.
Configure RFC 4253 zlib, delayed OpenSSH compression, tighter client-initiated rekey thresholds, post-auth idle keepalive, or bounded waits for reply paths explicitly on SSHClientConfiguration:
import Traversio
func runCompressedSession() async throws {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
compressionPreference: .delayedZlib,
automaticRekeyPolicy: .init(
outboundPacketThreshold: 250_000,
inboundPacketThreshold: 250_000,
idleTimeInterval: 600
),
keepalivePolicy: .init(interval: 30),
timeoutPolicy: .init(
connectionSetupTimeInterval: 15,
hostKeyTrustTimeInterval: 120,
responseTimeInterval: 5
)
)
try await SSHClient.withConnection(configuration: configuration) { connection in
_ = try await connection.execute("hostname")
}
}That example enables:
- delayed OpenSSH compression through
.delayedZlib; use.zlibinstead when the target explicitly advertises RFC 4253zlib - tighter packet-threshold plus idle-time automatic rekey
- a 30-second keepalive for idle authenticated connections
- an explicit 15-second connection setup timeout, 120-second host-key trust timeout, and 5-second prompt reply timeout
For the full field-by-field guide, including defaults, tradeoffs, proxy settings, and how keepalivePolicy differs from timeoutPolicy, see Connection Configuration.
Structured Logging
If the app should record connection setup, authentication, and wrapped operation failures, use the logHandler: overloads:
import Traversio
func runLoggedCommand(configuration: SSHClientConfiguration) async throws {
let recorder = SSHClientLogRecorder(maximumEventCount: 80)
let logHandler = recorder.logHandler(
minimumLevel: .info
)
try await SSHClient.withConnection(
configuration: configuration,
logHandler: logHandler
) { connection in
_ = try await connection.execute("uptime")
}
}For a bounded recent-event buffer that can be exported with support data, attach SSHClientLogRecorder through diagnostics.logHandler(minimumLevel:) or SSHClientLogHandler.recorder(...).
If your deployment target also supports the newer OSLog helper overloads, SSHClientLogHandler.osLog(...) remains available as a convenience adapter.
If you already have your own logging pipeline, use SSHClientLogHandler.sink(...) instead and forward each SSHClientLogEvent yourself.
SSHClientLogEvent also exposes formattedLine, and SSHConnectionFailure / SSHOperationFailure each expose diagnosticReport for copyable support text.
For a reusable app-side pattern, see Diagnostics. That page shows the built-in bounded recorder, a one-tap export flow, and the data you can expect to include when a user reports that a host cannot be reached.
Lifetime Rules
These public wrappers stay valid while their owning SSHConnection remains open:
SSHConnectionSSHSessionSFTPClient
If you call connection.close(), or if a withConnection(...) scope ends, later use fails with SSHClientError.connectionScopeEnded.
That rule applies to child wrappers:
SSHSessionSFTPClient- raw forwarding channel wrappers
import Traversio
func invalidExample() async throws {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .acceptAnyVerifiedHostKey
)
var escapedConnection: SSHConnection?
let connection = try await SSHClient.connect(configuration: configuration)
escapedConnection = connection
await connection.close()
// Throws SSHClientError.connectionScopeEnded
_ = try await escapedConnection?.execute("true")
}Using Public-Key Authentication
Traversio loads OpenSSH Ed25519, ECDSA, and RSA private keys directly. For
user-imported or migrated keys, privateKeyPEM(...) also accepts OpenSSL-style
PKCS#8 Ed25519/RSA/ECDSA, traditional RSA, and traditional EC PEM containers.
Traditional RSA PEM may be unencrypted or passphrase-encrypted with supported
OpenSSL legacy headers:
import Traversio
func connectWithECDSAKey() async throws {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: try .privateKeyPEM(
contentsOfFile: "/Users/me/.ssh/id_ecdsa",
passphrase: "correct horse battery staple"
),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts")
)
try await SSHClient.withConnection(configuration: configuration) { connection in
let result = try await connection.execute("whoami")
print(String(decoding: result.standardOutput, as: UTF8.self))
}
}If you already manage the key material in memory yourself, the raw enum cases remain available:
.ed25519PrivateKey(rawRepresentation:).rsaPrivateKey(pkcs1DERRepresentation:).ecdsaP256PrivateKey(rawRepresentation:).ecdsaP384PrivateKey(rawRepresentation:).ecdsaP521PrivateKey(rawRepresentation:)
Supported limits:
- The OpenSSH helper path supports Ed25519, RSA, and ECDSA keys encrypted with the OpenSSH bcrypt KDF plus AES ciphers such as
aes256-ctrandaes256-cbc. - SSH agent-backed authentication is available through
SSHAgentClient. - Keychain-backed credential loading stays in the application layer.
Traversio also generates a new OpenSSH private key file and matching authorized-key line:
import Traversio
func makeKeyMaterial() throws -> SSHOpenSSHKeyPair {
try SSHOpenSSHKeyPair.generate(
algorithm: .ed25519,
comment: "[email protected]",
encryption: SSHOpenSSHPrivateKeyEncryption(
passphrase: "correct horse battery staple"
)
)
}For the full workflow, including file permissions, encrypted export options, and using the returned authenticationMethod directly, see Key Generation.
Inspect Connection Metadata
SSHConnection.metadata gives you useful handshake details after a successful connection:
- endpoint host and port
- authenticated username
- Traversio client identification string
- remote identification string
- pre-identification banner lines
- host-key algorithm
- SHA-256 fingerprint of the verified host key
- the trust method that accepted the host key
Example:
let metadata = try await SSHClient.withConnection(configuration: configuration) { connection in
connection.metadata
}
print(metadata.remoteIdentification)
print(metadata.hostKeyFingerprintSHA256)Read Next
- Host Key Trust for trust setup
- Password Authentication, Keyboard-Interactive Authentication, or Public-Key Authentication for auth details
- Running Commands for one-shot commands
- Forwarding for port forwarding
- SFTP for file transfer