devtunnel

package
v0.1.50 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

func DialLocalDevServer(host string, port int, timeout time.Duration) (net.Conn, error)

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

func NewSSHDialer(log io.Writer) Dialer

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

type Timer interface {
	C() <-chan time.Time
	Reset(d time.Duration) bool
	Stop() bool
}

Timer is the minimal clock seam the idle-timeout loop needs, so tests drive the timeout deterministically instead of sleeping.

func NewRealTimer

func NewRealTimer(d time.Duration) Timer

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.

Jump to

Keyboard shortcuts

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