local_driver/lib.rs
1//! Local-tier infrastructure primitives shared by `cloud` (sim/pond reconciler)
2//! and `yubaba` (pond MinIO slot lifecycle).
3//!
4//! Two concerns live here:
5//!
6//! 1. **`local_runtime`** — detect an orbstack/docker-desktop/colima/podman/
7//! docker socket and drive appliance containers via the docker CLI. The
8//! docker-CLI driver was previously `cloud::local_runtime`; yubaba grew a
9//! dep on it in R374-F3 when MinIO lifecycle moved into the pond
10//! workload-status surface.
11//!
12//! 2. **`s3_sign`** — AWS Signature Version 4 helpers for S3-compatible object
13//! storage. Used by cloud's Hetzner driver, the R2 publisher, and yubaba's
14//! MinIO bucket-public bring-up.
15//!
16//! The crate is intentionally backend-agnostic: no cloud-config types, no
17//! yubaba-config types. Callers wire it in via small adapters in their own
18//! crates (see `cloud::local_container_spec_from_provider`).
19//!
20//! @yah:ticket(R374-F3, "Extracted from cloud crate so yubaba owns MinIO lifecycle without a yubaba→cloud dep")
21//! @yah:at(2026-06-28T20:35:37Z)
22//! @yah:status(review)
23//! @yah:parent(R374)
24//! @arch:see(.yah/docs/working/W142-pond.md)
25//! @yah:gotcha("yubaba's MinioReconciler probe runs every 5s; after 3 consecutive restart failures it marks the workload Failed and exits the loop. PondPhase Failed is terminal — operator restarts the camp daemon.")
26//! @yah:gotcha("Camp passes Arc<LocalRuntime> to yubaba once at startup. If orbstack/colima/docker isn't running when camp starts, pond mirrors are skipped with a clear warning; restart yah-camp once the runtime is up.")
27//! @yah:gotcha("local-driver moved into the untracked oss/yah-base workspace (crate extraction reorg, not yet committed). cargo test -p local-driver must run from oss/yah-base, not oss/yubaba.")
28//! @yah:verify("cargo test -p yubaba --lib # 106 pass (grew past handoff's 85 as later work landed); F3's PondRegistry + MinioReconciler tests intact")
29//! @yah:verify("cargo test -p cloud --lib # 467 pass (grew past handoff's 212); F3 adopt-path + shared pond_minio primitives green")
30//! @yah:verify("cargo test -p local-driver --lib # 64/64 (run from oss/yah-base)")
31//! @yah:verify("YAH_LOCAL_SIM_E2E=1 cargo test -p yubaba --test pond_reconciler_smoke # LIVE re-verified 2026-06-28: docker kill MinIO → recover 11.2s; docker kill miniflare → recover 11.2s. Both reconcilers restart their slot; half-alive structurally impossible.")
32//! @yah:verify("Cold-start within S1 budget (cold ~1.2s / warm ~500ms) via YAH_LOCAL_SIM_E2E=1 cargo test -p cloud --release --test pond_smoke. Bundled in .yah/qed/pond-smoke.toml.")
33
34pub mod cloudflared_ingress;
35pub mod local_runtime;
36pub mod passway_ingress;
37pub mod pond_miniflare;
38pub mod pond_minio;
39pub mod pond_ssr_runtime;
40pub mod pond_warden;
41pub mod s3_sign;
42
43pub use local_runtime::{
44 canonical_label, canonical_name, pond_network_name, ContainerLauncher, ContainerRunSpec,
45 ContainerState,
46 CustomDockerHostProvider, DetectedRuntime, LocalContainerSpec, LocalDockerRuntime,
47 LocalRuntime, OwnedContainer, RuntimePref, RuntimeProvider, SocketRuntimeProvider, LABEL_KEY,
48 NAME_PREFIX,
49};
50
51/// Env var overriding the host used to probe pond containers' published
52/// host ports. Unset (the host-side default) means `127.0.0.1`.
53///
54/// The containerized pond yubaba (R454-F1) sets this to
55/// `host.docker.internal`: inside that container, loopback is the yubaba
56/// container itself, so liveness probes and S3 admin calls against
57/// host-published MinIO/miniflare ports must route through the host
58/// gateway instead. `pond_warden::build_warden_run_spec` injects the env
59/// var + the `--add-host host.docker.internal:host-gateway` mapping.
60pub const POND_PROBE_HOST_ENV: &str = "YAH_POND_PROBE_HOST";
61
62/// Host used for probing pond containers' published host ports — see
63/// [`POND_PROBE_HOST_ENV`]. `127.0.0.1` unless the env var overrides it.
64pub fn pond_probe_host() -> String {
65 std::env::var(POND_PROBE_HOST_ENV)
66 .ok()
67 .filter(|v| !v.trim().is_empty())
68 .unwrap_or_else(|| "127.0.0.1".to_string())
69}