From bd96c0475dc23c3e89b73a5dc71d9e7a07004e79 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sat, 2 May 2026 05:44:09 -0400 Subject: [PATCH] feat(config): ARCHIPELAGO_USE_QUADLET_BACKENDS env override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an env-var lever for Phase 3.2's use_quadlet_backends flag so the 20× harness can flip the path on per-node without a config.json edit (which would require an archipelago.service restart — and that triggers FM3 cgroup cascade until Phase 3.5 ships, so we can't ask anyone to reconfigure live nodes that way today). Truthy parsing centralised in `parse_truthy_env` (1, true, yes, on — case-insensitive, whitespace-trimmed). Anything else is false. The helper is unit-tested so future env-var flags can reuse the same shape. Also adds a default-off regression test for use_quadlet_backends so flipping the default ahead of the 20× verification fires immediately. TESTING.md documents the Environment= snippet for the systemd drop-in so the next operator can flip the flag on a debug node without re-deriving the recipe. Co-Authored-By: Claude Opus 4.7 (1M context) --- core/archipelago/src/config.rs | 43 ++++++++++++++++++++++++++++++++++ tests/lifecycle/TESTING.md | 15 +++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/core/archipelago/src/config.rs b/core/archipelago/src/config.rs index d09481bc..6ad480cb 100644 --- a/core/archipelago/src/config.rs +++ b/core/archipelago/src/config.rs @@ -182,6 +182,17 @@ impl Config { config.nostr_tor_proxy = if s.is_empty() { None } else { Some(s) }; } + // Phase 3.2 of v1.7.52. Truthy values (1, true, yes, on — case-insensitive) + // route backend installs through the Quadlet path without requiring a + // config.json edit + archipelago.service restart (which would trigger + // FM3 cgroup cascade until 3.5 ships). Anything else (or unset) leaves + // the config.json value untouched. + if let Ok(v) = std::env::var("ARCHIPELAGO_USE_QUADLET_BACKENDS") { + if parse_truthy_env(&v) { + config.use_quadlet_backends = true; + } + } + // Host IP for container env vars (detect if not set) if let Ok(ip) = std::env::var("ARCHIPELAGO_HOST_IP") { config.host_ip = ip; @@ -234,6 +245,18 @@ impl Default for Config { } } +/// Recognise the canonical "the user meant true" forms for boolean env +/// vars: 1, true, yes, on (case-insensitive, surrounding whitespace +/// trimmed). Anything else — including the typo'd "ture" or the empty +/// string — counts as false. Centralised so future env flags stay +/// consistent with each other. +fn parse_truthy_env(raw: &str) -> bool { + matches!( + raw.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) +} + #[cfg(test)] mod tests { use super::*; @@ -437,4 +460,24 @@ mod tests { assert!(!config.nostr_relays.is_empty()); assert!(config.nostr_relays.iter().all(|r| r.starts_with("wss://"))); } + + #[test] + fn parse_truthy_env_recognises_canonical_forms() { + for t in ["1", "true", "TRUE", "yes", "Yes", "on", "ON", " true "] { + assert!(parse_truthy_env(t), "{t:?} should parse truthy"); + } + for f in ["", "0", "false", "no", "off", "ture", "anything else", " "] { + assert!(!parse_truthy_env(f), "{f:?} should NOT parse truthy"); + } + } + + #[test] + fn test_config_use_quadlet_backends_defaults_off() { + // Phase 3.2 of v1.7.52 — the new path stays gated until the 20× + // harness goes green on .228 and .198. Flipping this default + // ahead of that would route every backend install through code + // we haven't fleet-validated yet. + let config = Config::default(); + assert!(!config.use_quadlet_backends); + } } diff --git a/tests/lifecycle/TESTING.md b/tests/lifecycle/TESTING.md index 86d273ee..e5fccc1b 100644 --- a/tests/lifecycle/TESTING.md +++ b/tests/lifecycle/TESTING.md @@ -54,7 +54,7 @@ v1.7.52 tags. | Layer | Tests | Suites | Status | |---|---:|---:|---| -| L0 unit | 628 | n/a | ● green | +| L0 unit | 630 | n/a | ● green | | L1 RPC | 70 | bitcoin-knots, lnd, electrumx, btcpay, mempool, fedimint, required-stack, package-update-smoke | ● for the 6 core apps | | L2 UI | 9 | ui-coverage | ● for dashboard + 7 proxy paths + bitcoin-ui:8334 | | L3 lifecycle survival | 14 | companion-survives-archipelago-restart, backend-survives-archipelago-restart, required-stack-destructive, use-quadlet-backends-install | ◐ companions ● ; backends ◐ regression-gate (will fail until Phase 3 Quadlet ships); quadlet post-condition gate ✅ skip-clean today, hard gate when flag flipped | @@ -82,6 +82,19 @@ ARCHY_PASSWORD=password123 ARCHY_ALLOW_DESTRUCTIVE=1 \ tests/lifecycle/run-20x.sh ``` +To exercise the Phase 3.2 Quadlet-backend path on a target node without +editing config.json (which would require an archipelago restart and +trigger FM3 until 3.5 ships), set the env var on `archipelago.service`: + +```bash +sudo systemctl edit archipelago # add: [Service]\nEnvironment=ARCHIPELAGO_USE_QUADLET_BACKENDS=1 +sudo systemctl restart archipelago # one cgroup-cascade hit; survivable on a debug node +``` + +After the restart, `package.install` for any orchestrator-managed backend +will route through `install_via_quadlet`, and the +`use-quadlet-backends-install.bats` suite turns from skip → hard gate. + ## LoC budget Goal: minimum-viable container subsystem.