pub struct MachineConfig {Show 20 fields
pub name: String,
pub provider: String,
pub vendor: Option<String>,
pub nickname: Option<String>,
pub location: Option<String>,
pub server_type: Option<String>,
pub hosts_mirrors: Vec<String>,
pub mesh_tags: Vec<String>,
pub region: Option<String>,
pub zone: Option<String>,
pub arch: Option<String>,
pub bucket: Option<BucketSpec>,
pub legacy_hostkey_fingerprint: Option<String>,
pub ssh_keys: Vec<u64>,
pub cloudflared: Option<String>,
pub hosts_operator_bridge: bool,
pub connect: Option<ConnectSpec>,
pub allocatable: Option<NodeAllocatable>,
pub taints: Vec<String>,
pub registration: MachineRegistration,
}Expand description
Per-machine TOML from .yah/infra/machines/<name>.toml.
Two halves, split by provenance (R707-T1): everything here is declaration
— operator intent under review and blame — except [registration], which
carries what the fleet observed. See MachineRegistration for why the
boundary is drawn there and what depends on it.
Fields§
§name: String§provider: String§vendor: Option<String>Who the hardware actually comes from ("ovh", "vultr", "on-prem").
Deliberately not provider, which selects the
auto-provision driver: a box we rented by hand and brought up over SSH
is provider = "static" for its whole life, and writing the vendor
there instead would flip it driver-backed and make
validate demand location + server_type it has no
answer for. The two axes genuinely differ — vendor is who bills you,
provider is who yah can call an API against.
Worth recording because vendor-scoped policy is invisible in every other field and decides real work: outbound port 25, rDNS/PTR control, IP reputation, egress billing. It survived only in TOML prose until now, which made it ungreppable at exactly the moment you need it.
nickname: Option<String>Human label for the box ("gamer", "the GEEKOM"). Free-form and never
matched on — name stays the identity everywhere. This is
only so operators and agents can say which box they mean out loud.
location: Option<String>Provider DC code (e.g. Hetzner "hil"). Provisioning-only: required
iff the provider has an auto-provision driver (provider_has_machine_driver);
a BYO static node we brought up over SSH has no such code. Optional at
load time so static machine.tomls omit it; MachineConfig::validate
enforces presence at the right moment for driver-backed providers.
server_type: Option<String>Provider SKU/size (e.g. Hetzner "ccx13"). Provisioning-only, same
optionality contract as location.
hosts_mirrors: Vec<String>Deprecated (R330-F16). A machine should describe itself (region,
zone, provider, mesh_tags); which mirrors run on it is derived by the
reconciler from each mirror’s required placement spec, not declared
here. Now optional + omitted-when-empty so new machine.tomls leave it
out. The legacy resolve_mirror_machine topology fallback still reads
it until yubaba’s reverse-index supersedes the topology.toml path; once
that lands, this field and its readers are removed wholesale.
region: Option<String>Canonical geo region label (latency axis), e.g. "us-west". F16’s three
topology axes are orthogonal: region = geo (latency), zone = failure
domain within a region (HA), provider = network/cost. region is
distinct from location (the provider’s DC code, e.g. Hetzner "hil"):
location is provider-scoped, region is our provider-neutral label.
Optional for backward-compat; a machine without it never satisfies a
required.regions constraint.
zone: Option<String>Failure-domain label within a region (HA axis), e.g. "hil". For
single-DC Hetzner this typically mirrors location. F16 placement
matches required.zones against this. Optional for backward-compat.
arch: Option<String>Declared CPU architecture ("x86_64" / "aarch64"). A machine has
exactly one — it’s a first-class property of the box, not a reach
detail and not a mesh tag. Drives the yubaba release triple. Optional
only because there’s no provider API to probe it (static nodes declare
it; a driver-backed provider may leave it unset until known).
bucket: Option<BucketSpec>§legacy_hostkey_fingerprint: Option<String>Legacy location, superseded by [registration].hostkey_fingerprint
(R707-T1). Still deserialized so machine TOMLs written before the split
keep parsing; never read directly — go through
MachineConfig::hostkey_fingerprint, which prefers the registration
block. MachineConfig::normalize folds this into registration, and
MachineConfig::save normalizes before writing, so a load→save cycle
migrates the file rather than dropping the value.
ssh_keys: Vec<u64>Provider-side SSH-key IDs (Hetzner: from GET /v1/ssh_keys)
authorized for root at create time. Defaults to empty for
backwards-compat with existing machine declarations; an empty
list yields a Hetzner-emailed random root password (which the
driver currently discards). Populate this when you want pre-mesh
SSH access for bootstrap deploys or recovery.
cloudflared: Option<String>Cloudflare Tunnel ID this machine joins (e.g. abc123.cfargotunnel.com).
None → no tunnel (mesh-only node, no public ingress).
When set, yah cloud machine provision reads cloudflare-tunnel-token
from the keys vault and injects the cloudflared install block into
cloud-init so the new machine connects to CF edge on first boot.
hosts_operator_bridge: boolWhen true, this machine hosts operator-bridge workloads (Tailscale
operator access to mesh-internal services). yah cloud machine provision
will install tailscaled and run tailscale up during cloud-init via the
{{OPERATOR_BRIDGE_BLOCK}} placeholder. Defaults to false for
backward-compat with existing machine declarations.
connect: Option<ConnectSpec>BYO static-node reach descriptor. Static nodes have no provider API to
probe, so how the camp reaches them (SSH user@host + the yubaba URL,
which is loopback until the WireGuard mesh lands) is declared here.
None for driver-backed providers (Hetzner/Vultr), whose address is
resolved from the provider API / mesh at provision time.
allocatable: Option<NodeAllocatable>Static node capacity (R572-F3). Declares the node’s total hardware budget; F5’s scheduler subtracts committed workload requests from this to check whether a new workload fits. Absent means unconstrained.
taints: Vec<String>Repel-unless-tolerate taint keys (R572-F3). A workload must tolerate
every taint on a candidate node for the scheduler to place it there.
Examples: "no-appliance" prevents Appliance workloads; "no-voter"
prevents a node from joining quorum; "public-ip" is a positive
requirement marker the ingress appliance (W267) demands.
registration: MachineRegistration[registration] — the observed half (R707-T1). Empty until the box has
been attached / mesh-joined. See MachineRegistration.
Implementations§
Source§impl MachineConfig
impl MachineConfig
Sourcepub fn location(&self) -> &str
pub fn location(&self) -> &str
Provider DC code, or "" when omitted (static nodes). Most readers want
a &str; the driver-backed provision/status paths still go through
validate which guarantees presence for those.
Sourcepub fn server_type(&self) -> &str
pub fn server_type(&self) -> &str
Provider SKU, or "" when omitted (static nodes).
Sourcepub fn validate(&self) -> Result<()>
pub fn validate(&self) -> Result<()>
Enforce the provisioning-only-field contract: a machine whose provider
has an auto-provision driver MUST declare location + server_type
(the driver can’t create a server without them). Static nodes may omit
both. Call this before any provision/diff that assumes a driver.
Sourcepub fn hostkey_fingerprint(&self) -> Option<&str>
pub fn hostkey_fingerprint(&self) -> Option<&str>
Yubaba’s TOFU’d hostkey fingerprint, from [registration] and falling
back to the pre-R707-T1 top-level field. The only read path — a
caller that reaches for legacy_hostkey_fingerprint directly sees
None on every migrated machine.
Sourcepub fn set_hostkey_fingerprint(&mut self, fingerprint: Option<String>)
pub fn set_hostkey_fingerprint(&mut self, fingerprint: Option<String>)
Record (or clear) the observed hostkey fingerprint. Writes
[registration] and drops any pre-R707-T1 top-level value, so the two
locations can never disagree after a writeback.
Sourcepub fn mesh_ipv4(&self) -> Option<&str>
pub fn mesh_ipv4(&self) -> Option<&str>
Mesh (tailnet) IPv4 for this node, or None pre-mesh.
Prefers [registration].mesh_ipv4; falls back to the host of a legacy
[connect].yubaba URL when that host is in the 100.64.0.0/10 CGNAT
range the mesh uses. A loopback placeholder (http://127.0.0.1:7443,
meaning “pre-mesh, reachable only through an SSH tunnel”) is not a
mesh address and yields None.
Sourcepub fn yubaba_url(&self) -> Option<String>
pub fn yubaba_url(&self) -> Option<String>
Base URL for this node’s yubaba, or None when it declares no reach.
A declared [connect].yubaba wins whenever present — full stop, not
only for the pre-mesh loopback placeholder. [registration].mesh_ipv4
[connect].yubaba_portis the derivation used only when nothing is declared (R707-T6 / W295).
This was narrower once: a declared literal won only when there was no
registered mesh address, on the reasoning that the loopback placeholder
(http://127.0.0.1:7443, “reach me through the SSH tunnel”) is a
genuine declaration and not a stale observation. That reasoning still
holds — it just never considered a non-loopback literal coexisting
with a mesh address, which is exactly R608-F18’s forcing case:
us-west-014 is mesh-joined (mesh_ipv4) but its raft peers are
LAN-only, so rollout::yubaba::membership_to_nodes needs the LAN
literal, not the mesh-derived URL, to match the raft membership
address. A declared literal is always the more specific statement —
whether it says “SSH tunnel only” or “reach me on the LAN” — and
mesh_ipv4 is only ever a convenience for the common case where
nothing more specific was declared. There is no third state to add: the
fields already say everything needed, only their precedence was wrong
for a declared-and-mesh-joined node.
Sourcepub fn normalize(&mut self)
pub fn normalize(&mut self)
Fold the pre-R707-T1 top-level hostkey_fingerprint into
[registration], and lift a mesh IP out of a legacy [connect].yubaba
URL. Idempotent; a machine already on the split shape is untouched.
save calls this, so writing a machine TOML migrates it
rather than round-tripping the old shape back out.
Sourcepub fn save(&self, cloud_dir: &Path) -> Result<()>
pub fn save(&self, cloud_dir: &Path) -> Result<()>
Persist to <cloud_dir>/machines/<name>.toml, creating the dir if needed.
⚠ Serializes the struct, so operator comments in the target file are
lost. Pre-existing behaviour, not introduced here, but it is why
registration writeback (yah cloud machine attach) goes through
crate::state::MachineState and the comment-preserving path in the
CLI rather than calling this on a hand-authored inventory file.
Trait Implementations§
Source§impl Clone for MachineConfig
impl Clone for MachineConfig
Source§fn clone(&self) -> MachineConfig
fn clone(&self) -> MachineConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for MachineConfig
impl Debug for MachineConfig
Source§impl<'de> Deserialize<'de> for MachineConfig
impl<'de> Deserialize<'de> for MachineConfig
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for MachineConfig
impl RefUnwindSafe for MachineConfig
impl Send for MachineConfig
impl Sync for MachineConfig
impl Unpin for MachineConfig
impl UnsafeUnpin for MachineConfig
impl UnwindSafe for MachineConfig
Blanket Implementations§
impl<T> Allocation for T
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,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<T> Fruit for T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
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> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more