pub struct ServeConfig {Show 32 fields
pub ssh_bind: SocketAddr,
pub grpc_bind: BindSpec,
pub grpc_bind_device: Option<String>,
pub admin_key: Option<String>,
pub restore_pubkeys: Vec<String>,
pub no_grpc: bool,
pub wg_only: bool,
pub twin_role: bool,
pub allow_anonymous: bool,
pub anonymous: AnonymousPolicy,
pub runner: Option<WatchConfig>,
pub grpc_tls: Option<GrpcTls>,
pub web_relying_parties: Vec<String>,
pub web_require_user_verification: bool,
pub identity_root_site: bool,
pub grpc_replication_bind: Option<SocketAddr>,
pub max_concurrent_transfers: Option<usize>,
pub memory_budget_bytes: Option<u64>,
pub admission_wait: Option<Duration>,
pub max_transfers_per_principal: Option<usize>,
pub max_anonymous_transfers: Option<usize>,
pub per_principal_limit_is_hard: bool,
pub control_plane_workers: Option<usize>,
pub max_object_bytes: Option<u64>,
pub metrics_interval: Option<Duration>,
pub metrics_bind: Option<SocketAddr>,
pub replication_view: ReplicationView,
pub tenants_view: TenantView,
pub flow_view: FlowView,
pub wire: WireConfig,
pub disk_floor: DiskFloor,
pub space_probe: Option<Arc<dyn SpaceProbe>>,
}Expand description
What gunnar serve was asked for.
Fields§
§ssh_bind: SocketAddrWhere the SSH listener binds.
grpc_bind: BindSpecWhere the gRPC control plane binds. Loopback by default.
A BindSpec rather than a SocketAddr because an appliance’s private
address is DHCP-learned: the declaration names eth1:50051 and
run resolves it, ONCE, after GUNNAR_NET has brought the link up
and before anything binds. Downstream — the readiness line, the
dashboard row, Admin.Health — then reports the address the listener
actually took, not the spelling it was asked for.
grpc_bind_device: Option<String>The DEVICE the control plane listens on, when grpc_bind was spelled as
an interface (eth1:50051) — set by run/[start] from that spelling
and honoured as SO_BINDTODEVICE on the listener socket, so the
interface name means the interface and not merely the address it held
at boot. MEASURED 2026-09-05 (the KVM bench rig, two slirp NICs on the
same 10.0.2.0/24): an address-only bind of eth1’s 10.0.2.15 answered
through eth0, because both NICs held that address and the route decided.
On a cloud whose utility NIC is eth1 on its own subnet the two agree;
the device bind makes them agree everywhere. None for an address spec.
admin_key: Option<String>An OpenSSH public key line to install as the bootstrap admin when the identity table is empty (the secret-store seam).
restore_pubkeys: Vec<String>The keys Bootstrap.Restore is taken from (--restore-pubkey, h-v1-6a): OpenSSH
public key lines or @<path>, resolved and checked at start. An installer-fed box adds
the fed admin keys itself; a box with neither refuses every restore by name.
no_grpc: boolStart the SSH listener only.
wg_only: bool--wg-only: every box-to-box connection runs over the estate’s
WireGuard overlay (owner, 2026-10-04). The address the control plane
is bound on (eth1:50051, the utility network) serves NOTHING: the
control plane and the replication listener answer only on the tunnel’s
address (crate::tunnel_addr), and SSH — bound for the public’s git
clients — drops a connection that arrives at the utility address before
a byte is spoken. A plain-IP connection between two boxes is refused.
twin_role: bool--role twin: no forge listener from the first boot (loopback only),
whether or not Bootstrap.BecomeTwin has run yet.
allow_anonymous: boolWhether a caller with no key may clone a repository marked
Visibility::Public. true by
default; nothing is anonymously readable until a repository is
deliberately made public, because Private is the default for a new
one.
Reaches SshConfig::allow_anonymous
and HttpConfig::allow_anonymous, from this one field. Two fields
is how one of them ends up stubbed: an operator who passed
--no-anonymous and got it honoured on one transport has a server that
serves strangers and reports that it does not. The gRPC control plane
has no anonymous mode to switch, at any setting.
anonymous: AnonymousPolicy--anonymous allow|refuse (the browse replica): whether an
anonymous read is SERVED here or sent to the browse replica the
refusal names. Orthogonal to allow_anonymous, which is whether a
stranger authenticates at all; this is what the dispatcher tells one
who did. Both front doors read it through the one Control.
runner: Option<WatchConfig>Build every push, in this process, hermetically (roadmap phase 3).
None — the default — is a server that stores and serves and does not
build. Some starts the runner on the same event bus every push
publishes on, which is what makes the golden path’s fourth step
nothing at all.
It is in the serving process on purpose: a second process cannot subscribe to an in-process bus, and a runner that polled the attestation log instead would build a push some time after it landed rather than because it landed.
grpc_tls: Option<GrpcTls>Require a client certificate on the gRPC control plane, minted by this server’s own issuer (the mTLS plan).
None — the default — is the loopback shape the unauthenticated v1 assumes: the listener
binds 127.0.0.1 and a credential in metadata is the identity.
Some(GrpcTls::SelfMinted) is the shape a control plane reachable off
the box needs, and it changes two things at once: the transport refuses
a peer whose certificate this issuer did not sign, and the gate maps the
certificate that got through to an ACL principal.
Deliberately not on by default. Turning it on rewrites how every
existing client must connect, and a security default that silently
breaks the working configuration gets switched back off in a hurry —
which leaves it off everywhere. It is a flag an operator turns on when
the listener leaves loopback, and --grpc-bind off loopback without it
is what the startup warning names.
web_relying_parties: Vec<String>--web-relying-party, unparsed: the WebAuthn relying parties the gRPC
plane answers for (gunnar_grpc::webauthn::RelyingParty).
web_require_user_verification: bool--web-require-user-verification (A34-12).
identity_root_site: bool--identity-root-site (IDENTITY-HOST): sign-up is open here.
grpc_replication_bind: Option<SocketAddr>The mutual replication listener (--grpc-replication-bind, lane T9):
the door a twin follows this box through. Served only with a client
certificate this box’s issuer signed, TLS 1.3 only, its leaf minted from
the issuer at every start under the fixed name
gunnar_grpc::client_signer::REPLICATION_SERVER_NAME.
None DERIVES it, and the derivation is what lets the ONE medium carry
no flag: a control plane bound off loopback (the appliance,
0.0.0.0:50051) gets this listener on the same address, port + 1
(50052); a loopback one (a laptop, a test) gets none. Its main
listener stays exactly what it was, because turning --grpc-tls-self on
for the whole plane would lock out every client that has no certificate
— and the bootstrap door with them.
max_concurrent_transfers: Option<usize>How many git transfers may be in flight across the whole server.
None takes
DEFAULT_MAX_CONCURRENT_TRANSFERS.
The fix note on the admission gate says every one of these numbers needs to be
settable, because the right one is deployment-specific; until now
neither was reachable from gunnar serve and the two defaults were the
only values any deployment could have. That is also why the pressure
harness could not reach contention without 320 client processes.
memory_budget_bytes: Option<u64>The memory budget the admission ledger may give out to git
operations in flight, in bytes (gate 2). None takes
crate::budget::budgets’s derived figure — MemAvailable x
crate::budget::TRANSFER_MEMORY_PERCENT — rather than
gunnar-ssh’s compiled-in 8 GiB, which was a number about the cgroup
one bakeoff ran in and not about this box.
admission_wait: Option<Duration>How long a transfer may wait for a slot before it is refused.
None takes gunnar_ssh::governor::DEFAULT_ADMISSION_WAIT;
Some(Duration::ZERO) restores the instant refusal.
max_transfers_per_principal: Option<usize>A principal’s guaranteed floor and its share of a contended server.
None takes
DEFAULT_MAX_TRANSFERS_PER_PRINCIPAL.
max_anonymous_transfers: Option<usize>How many git reads may be served without a key at once.
None takes
DEFAULT_MAX_ANONYMOUS_TRANSFERS.
Note the asymmetry with the field above, which is deliberate: that one
is a floor a principal is guaranteed and may exceed on a quiet box, this
one is a wall that binds however empty the server is. See
gunnar_ssh::admission for why work-conserving is the right instinct
for a tenant and the wrong one for a stranger.
per_principal_limit_is_hard: boolMake the per-principal number an unconditional wall again rather than a
max-min share. See
SshConfig::per_principal_limit_is_hard.
control_plane_workers: Option<usize>How many worker threads the control plane’s own runtime gets.
None derives it from control_plane_workers.
A knob rather than a constant, because the right number is a deployment question: a box whose operators live in the UI wants more, an appliance serving one CI fleet wants fewer. A test also has to be able to make the pool small enough to saturate it deliberately.
max_object_bytes: Option<u64>The largest object Refs.ReadObject will inflate. None
takes
CoreControlPlane’s default of 64
MiB; Some(0) means no ceiling.
A knob for the same reason control_plane_workers is one: the number is
memory budget / concurrent inflates, and the budget is a deployment
question. It is also the only one of these that turns a request which
works today into a refusal, which is exactly why it must be reachable
rather than compiled in.
metrics_interval: Option<Duration>How often the counter heartbeat is written. None turns it
off.
The heartbeat answers “what is this server doing right now” from a log the operator already has open, with nothing to scrape. It stays quiet while nothing changes, so an idle appliance does not fill its journal.
metrics_bind: Option<SocketAddr>Where the scrape endpoint binds. None does not start it,
which is the default.
The heartbeat above and this are the same nineteen numbers reached two
ways, and both are wanted: the heartbeat needs nothing set up and is
already in the journal when an incident is reconstructed afterwards; the
endpoint can be asked, at whatever rate the person asking chooses,
which is the only way transfers_in_flight distinguishes a slow
transfer from a stuck one while it is still happening.
Off unless asked for, for the same reason [ServeConfig::http_bind] is:
a second listening port is an operator’s decision. See
crate::scrape for why it carries no authentication and what
that does and does not expose.
replication_view: ReplicationViewWhere replication’s counters are published to, for the console band, the
staleness alarm and Admin.Stats.
It is on the config rather than returned from Server::start because
the console band starts BEFORE the server does — run hands the same
view to both, and start drops the replicator into it once there is
one. Left at its default it reports “nothing configured”, which is what
a server with no standby is.
tenants_view: TenantViewWhere the band’s tenants row is read from, the same shape and
for the same reason as replication_view: a metered Server::start
fills it from its entitlement slot; a community build never does, and
the band draws no row.
flow_view: FlowViewWhere the band’s flow row is read from. Filled on every build —
counting what moved is not the metered appliance’s capability. See
crate::dashboard::FlowView.
wire: WireConfigWhat the two git state machines serve under (gunnar-wire’s own
Config types, held once — see WireConfig).
Defaults to gunnar-wire’s defaults, which is what every deployment
silently ran on while with_config had no callers. The knobs it carries
were fully implemented in both protocol versions and reachable from
nowhere; --no-filter in particular is the difference between an
appliance that declines partial clone and one that only says it might.
disk_floor: DiskFloorThe disk-headroom floor (--disk-floor): the community plan gate,
first link of every front door’s chain and the guard on Repos.Create.
See gunnar_ssh::headroom. Always present — the default is 5 % or
2 GiB, whichever is larger — because a box with no floor is a box that
finds out it is full from a torn pack.
space_probe: Option<Arc<dyn SpaceProbe>>What measures the data set for the floor and the space ledger.
None — every deployment — is one statvfs on the data root. A test
that must fill a data set of a stated size without a filesystem of that
size hands its own probe here (tests/push_space_admission.rs’s
virtual disk); nothing else about the server changes.
Implementations§
Source§impl ServeConfig
impl ServeConfig
Trait Implementations§
Source§impl Clone for ServeConfig
impl Clone for ServeConfig
Source§impl Debug for ServeConfig
impl Debug for ServeConfig
Auto Trait Implementations§
impl !RefUnwindSafe for ServeConfig
impl !UnwindSafe for ServeConfig
impl Freeze for ServeConfig
impl Send for ServeConfig
impl Sync for ServeConfig
impl Unpin for ServeConfig
impl UnsafeUnpin for ServeConfig
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request