pub struct GunnarSsh { /* private fields */ }Expand description
gunnar’s SSH listener.
Implementations§
Source§impl GunnarSsh
impl GunnarSsh
Sourcepub fn new(
config: SshConfig,
control: Control,
transport: Arc<dyn GitTransport>,
) -> Self
pub fn new( config: SshConfig, control: Control, transport: Arc<dyn GitTransport>, ) -> Self
Build a server over the control plane and the wire seam.
Sourcepub fn with_readiness(self, ready: Arc<AtomicBool>) -> Self
pub fn with_readiness(self, ready: Arc<AtomicBool>) -> Self
Watch a readiness flag, and answer git traffic with a retryable sentence until it is set.
§The boot window — MEASURED 2026-08-30
After a power cut the appliance answered ssh :2222 and grpc :50051
0.6–0.7 s before its stores had finished their post-crash open, and
a read in that window came back the repository is unavailable; try again. That sentence is gunnar_wire::Error::Store’s, it arrives with
a denial exit status, and it is what a client is told when a
repository is genuinely broken — so a harness could not tell “not yet”
from “gone”. The first power-cut door reported 64 acked pushes
missing; a boot one second later found every one of them, and the same
window wedged a whole bench run’s clone and fetch phases.
The fix is not to bind later — PID 1 must answer quickly or an operator
watching a console sees a dead box — but to answer honestly. While
the flag is clear every git exec is refused in git’s own protocol with
EXIT_BUSY (75, EX_TEMPFAIL — the same status the admission gate
uses, which is the status that means retry) and a sentence containing
the word starting, which is the word a script may key on. Nothing is
served, nothing is opened, and nothing looks like a missing repository.
A flag and not a trait: gunnar-ssh may not read the operating system,
and “have the stores finished opening” is gunnar-server’s fact to
publish, not this crate’s to discover.
Sourcepub fn with_refused_local(self, refused: RefusedLocal) -> Self
pub fn with_refused_local(self, refused: RefusedLocal) -> Self
Refuse every connection that arrives at a local address refused
names — closed at accept, nothing said, logged once per peer at
debug. gunnar serve --wg-only names the utility address: the public’s
git clients still reach SSH, a box over plain IP does not.
Sourcepub fn with_tunnel_address_door(self, door: TunnelAddressDoor) -> Self
pub fn with_tunnel_address_door(self, door: TunnelAddressDoor) -> Self
Answer TUNNEL_ADDRESS_VERB with door — gunnar-server hands in
its appliance::tunnel_address_answer, the function both gRPC doors
call. Without one the verb is answered “no WireGuard tunnel”.
Sourcepub fn with_repo_door(self, door: RepoDoor) -> Self
pub fn with_repo_door(self, door: RepoDoor) -> Self
Answer REPO_VERB with door — gunnar-server hands in the
control plane’s own Repos handlers. Without one the verb is answered
“this server answers repository settings only on its control plane”.
Sourcepub fn with_flow(self, flow: Arc<FlowLedger>) -> Self
pub fn with_flow(self, flow: Arc<FlowLedger>) -> Self
Give the admission gate the per-namespace flow ledger.
The gate is what all three front doors — this exec line, smart HTTP and
the gRPC transfer gate — ask before a transfer, so it is the one place a
refusal can be charged to a tenant once rather than three times. The
exec line’s own no-grant denial reaches the same ledger back through
Admission::flow.
A listener built without one refuses exactly as it always did and counts
the refusal only in its own AdmissionStats, which is what a test that
drives gunnar-ssh alone wants — and what every build before the ledger
existed did.
The gate is rebuilt rather than mutated for the reason
Self::with_readiness rebuilds Shared: bind has not run, but the
Arc is already there.
Sourcepub fn with_metrics(self, metrics: Arc<ServerMetrics>) -> Self
pub fn with_metrics(self, metrics: Arc<ServerMetrics>) -> Self
Share the server-wide counters with the rest of the process.
Not a second set of counters. gunnar serve mints one
ServerMetrics and hands the same Arc to this listener and to the
git transport, so “connections refused” and “pushes rejected by gate”
are numbers about one server rather than two crates’ private tallies.
A listener built without this one still counts, into a set nobody else
can read, which is what a test that only drives gunnar-ssh wants.
Sourcepub fn metrics(&self) -> &Arc<ServerMetrics> ⓘ
pub fn metrics(&self) -> &Arc<ServerMetrics> ⓘ
The counters this listener writes to.
Sourcepub async fn bind(self, host_key: PrivateKey) -> Result<BoundServer>
pub async fn bind(self, host_key: PrivateKey) -> Result<BoundServer>
Bind the listener and start accepting.
Returns once the socket is bound, so a caller (or a test) knows the
real address before the first client connects. That matters for
SshConfig::ephemeral_loopback, where the port is chosen by the
kernel.