Skip to main content

GunnarSsh

Struct GunnarSsh 

Source
pub struct GunnarSsh { /* private fields */ }
Expand description

gunnar’s SSH listener.

Implementations§

Source§

impl GunnarSsh

Source

pub fn new( config: SshConfig, control: Control, transport: Arc<dyn GitTransport>, ) -> Self

Build a server over the control plane and the wire seam.

Source

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.

Source

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.

Source

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”.

Source

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”.

Source

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.

Source

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.

Source

pub fn metrics(&self) -> &Arc<ServerMetrics> ⓘ

The counters this listener writes to.

Source

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.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more