Documentation
¶
Overview ¶
Package devtunnel holds the transport-level pieces of `civitai app dev-tunnel`: the EPHEMERAL SSH keypair the CLI mints per session, the reverse-tunnel dialer (`ssh -R` to the sish endpoint) behind an interface, and a small clock/timer seam — so the command's lifecycle (mint → tunnel → teardown on signal / idle) is unit-testable without a live server or a real network.
DARK/INERT: this drives the P1 server contract + the P3 public sish endpoint, neither of which is live yet. The dialer is real code, but there is no way to exercise it end-to-end until P3 exposes the SSH listener; the interface is what the tests cover.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DialLocalDevServer ¶ added in v0.1.44
DialLocalDevServer connects to the developer's local dev server on `host:port`.
- host "" or "localhost" (the SAFE default) → the loopback behavior: try each family in turn (127.0.0.1 then ::1) with a per-attempt timeout and return the first successful connection. A `--host localhost` dev server binds only ONE family (`::1` on a dual-stack box), so both must be tried.
- any other host (e.g. a container/pod-netns IP like 10.42.0.100, a VM, or a specific bound interface) → dial EXACTLY that host:port (single target; the both-families logic is loopback-specific).
Shared by the pre-flight probe (which closes the returned conn) and the live tunnel proxy (which uses it) so the two always agree on whether the dev server is reachable.
Types ¶
type DialOptions ¶
type DialOptions struct {
// Endpoint is the public sish SSH listener `host:port` (P3-provisioned).
Endpoint string
// RemoteHost is the server-assigned `dev-<16hex>.<APPS_DOMAIN>` the reverse
// tunnel binds to (from StartDevTunnel — never client-chosen).
RemoteHost string
// LocalPort is the developer's running dev server port on 127.0.0.1.
LocalPort int
// LocalHost is the host the developer's dev server is bound to. Empty or
// "localhost" means loopback (the SAFE default: try 127.0.0.1 then ::1); any
// other value (e.g. a container/VPN IP like 10.42.0.100, or a specific bound
// interface) is dialed EXACTLY as given. This lets the tunnel reach a dev
// server that is NOT on the CLI's loopback (a container/pod netns, a VM, a
// specific interface) — the silent-502 case where sish has the tunnel but the
// proxy can't reach the local server.
LocalHost string
// Signer is the ephemeral private key authenticating the `ssh -R` bind.
Signer ssh.Signer
// SSHHostPublicKey is the sish endpoint's OpenSSH host public-key line
// (`ssh-ed25519 AAAA...`, from StartDevTunnel's sshHostPublicKey) that the
// dialer PINS as its HostKeyCallback. The dialer FAILS CLOSED when this is
// empty (refuses to connect) — it NEVER falls back to InsecureIgnoreHostKey.
SSHHostPublicKey string
}
DialOptions parametrizes a reverse-tunnel dial.
type Dialer ¶
type Dialer interface {
Dial(ctx context.Context, opts DialOptions) (Tunnel, error)
}
Dialer opens a reverse tunnel. Behind an interface so the command's lifecycle is testable with a mock (no SSH, no network).
func NewSSHDialer ¶
NewSSHDialer builds the production reverse-tunnel dialer, logging connection status to log.
type EphemeralKey ¶
type EphemeralKey struct {
// Signer authenticates the `ssh -R` bind (the private key stays here, in
// memory only).
Signer ssh.Signer
// AuthorizedKey is the normalized OpenSSH public-key line
// (`ssh-ed25519 AAAA...`, no comment) the CLI POSTs as `sshPublicKey`. It
// matches the server's normalizeSshPublicKey form exactly.
AuthorizedKey string
}
EphemeralKey is a per-session SSH keypair generated IN MEMORY and never written to disk (not to the user's ~/.ssh, nowhere). The private half lives only for the tunnel's lifetime; the public half is sent to blocks.startDevTunnel, which keys the tunnel credential by its fingerprint.
func GenerateEphemeralKey ¶
func GenerateEphemeralKey() (*EphemeralKey, error)
GenerateEphemeralKey mints a fresh ed25519 SSH keypair in memory. ed25519 is small, fast, and universally supported by modern OpenSSH/sish. The returned AuthorizedKey is the trimmed single-line authorized-keys form (no trailing comment/newline), which is exactly what the server fingerprints.
type Timer ¶
Timer is the minimal clock seam the idle-timeout loop needs, so tests drive the timeout deterministically instead of sleeping.
func NewRealTimer ¶
NewRealTimer is the production Timer factory (a wall-clock time.Timer).
type Tunnel ¶
type Tunnel interface {
// Done is closed (or receives) when the tunnel terminates on its own (the
// SSH connection dropped, the remote closed the forward, etc.).
Done() <-chan struct{}
// Activity fires (best-effort, coalesced) when a browser connection is
// proxied through the tunnel, so the caller can treat the session as
// non-idle. May be nil (a nil channel simply never fires).
Activity() <-chan struct{}
// Close tears the tunnel down (idempotent).
Close() error
}
Tunnel is a live reverse tunnel. Consumers select on Done (terminated), Activity (a connection arrived — resets the idle timer), and Close it on teardown.