avarok_core/env_compat.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Backwards compatibility shim for the `ATLAS_*` to `AVAROK_*` env rename.
4//!
5//! The repo-wide rebrand moved every variable the server reads from `ATLAS_*`
6//! to `AVAROK_*`, but the CLI that launches the server lives in a different
7//! repository and still exports the old names (recipes set things like
8//! `ATLAS_MTP_DCUT_RATIO`, `ATLAS_FP8_ROWWISE`, `ATLAS_GDN_FLASHINFER`,
9//! `ATLAS_GDN_LIB` and `ATLAS_TARGET_MODEL`; the agent sets `ATLAS_HOME`).
10//! Without this shim a server built from the renamed tree ignores all of them
11//! silently, which looks exactly like a recipe that does nothing.
12//!
13//! [`mirror_legacy_env`] copies each legacy name onto its new name at process
14//! startup, so the ~440 `std::env::var("AVAROK_...")` reads scattered across
15//! the crates need no per-site fallback.
16//!
17//! REMOVAL POINT: delete this module, its call sites in `main`, and the
18//! `ATLAS_SKIP_BUILD` fallback in `avarok-kernels/build.rs` once the CLI ships
19//! `AVAROK_*` names and no supported release still emits `ATLAS_*`.
20
21use std::ffi::OsString;
22
23/// The name every legacy variable starts with. Matched as an exact ASCII
24/// prefix, so sibling namespaces such as `ATLASCTL_*` are left alone.
25const LEGACY_PREFIX: &str = "ATLAS_";
26
27/// The name the renamed tree reads.
28const CURRENT_PREFIX: &str = "AVAROK_";
29
30/// Mirror every `ATLAS_*` variable onto its `AVAROK_*` name when the new
31/// name is unset. Returns the mirrored keys (new names). Must run before any
32/// thread is spawned; call it first thing in `main`.
33///
34/// An `AVAROK_*` value already present always wins: an operator who sets the
35/// new name explicitly is never overridden by a stale legacy export.
36///
37/// # Example
38///
39/// ```no_run
40/// // First statement of `main`, before any runtime, logging or thread init.
41/// let mirrored = avarok_core::env_compat::mirror_legacy_env();
42/// if !mirrored.is_empty() {
43/// eprintln!("mirrored {} legacy variables", mirrored.len());
44/// }
45/// ```
46pub fn mirror_legacy_env() -> Vec<String> {
47 // Snapshot the environment before touching it. `vars_os` walks the live
48 // environ block, and `set_var` may reallocate that block, so collecting
49 // first keeps the iterator and the mutation strictly separated.
50 //
51 // Keys must be UTF-8 to be rewritten (every variable in play is ASCII);
52 // values stay `OsString` so a non-UTF-8 path is mirrored byte for byte.
53 let candidates: Vec<(String, OsString)> = std::env::vars_os()
54 .filter_map(|(key, value)| {
55 let key = key.into_string().ok()?;
56 let rest = key.strip_prefix(LEGACY_PREFIX)?;
57 Some((format!("{CURRENT_PREFIX}{rest}"), value))
58 })
59 .collect();
60
61 let mut mirrored = Vec::new();
62 for (current_key, value) in candidates {
63 if std::env::var_os(¤t_key).is_some() {
64 // The new name is already set. Never clobber it.
65 continue;
66 }
67 // SAFETY: `std::env::set_var` is unsafe in edition 2024 because the
68 // underlying `setenv` is not thread safe: it can reallocate the
69 // environ block while another thread is inside `getenv`, and the C
70 // library gives no lock to share. The contract that makes this call
71 // sound is that it runs as the very first statement of `main`, before
72 // any runtime, logging subscriber, GPU context or `std::thread::spawn`
73 // exists, so this thread is the only one that can observe the
74 // environment. Callers must not call it from anywhere else.
75 unsafe { std::env::set_var(¤t_key, &value) };
76 mirrored.push(current_key);
77 }
78
79 mirrored.sort();
80 mirrored
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use std::sync::Mutex;
87
88 /// These tests mutate the process environment, which is global to the test
89 /// binary, and `mirror_legacy_env` reads all of it. Serialize them so one
90 /// test's half-built key pair is never visible to another's mirror pass.
91 static ENV_LOCK: Mutex<()> = Mutex::new(());
92
93 /// A failed assertion poisons the lock; the data is `()`, so recovering the
94 /// guard keeps one failure from cascading into unrelated test failures.
95 fn env_guard() -> std::sync::MutexGuard<'static, ()> {
96 ENV_LOCK
97 .lock()
98 .unwrap_or_else(|poisoned| poisoned.into_inner())
99 }
100
101 /// Key names are unique per test and carry the pid so a stray variable
102 /// inherited from the surrounding shell cannot collide with them.
103 fn unique(suffix: &str) -> String {
104 format!("ZZ_TEST_{}_{suffix}", std::process::id())
105 }
106
107 /// SAFETY: same contract as [`mirror_legacy_env`]. Every test that calls
108 /// this holds `ENV_LOCK`, and the crate spawns no threads in its tests, so
109 /// no other thread can read the environment concurrently.
110 fn set(key: &str, value: &str) {
111 unsafe { std::env::set_var(key, value) };
112 }
113
114 /// SAFETY: as [`set`] above.
115 fn unset(key: &str) {
116 unsafe { std::env::remove_var(key) };
117 }
118
119 #[test]
120 fn legacy_key_is_mirrored_onto_the_new_name() {
121 let _guard = env_guard();
122 let legacy = format!("ATLAS_{}", unique("MIRRORED"));
123 let current = format!("AVAROK_{}", unique("MIRRORED"));
124 unset(¤t);
125 set(&legacy, "recipe-value");
126
127 let mirrored = mirror_legacy_env();
128
129 assert_eq!(
130 std::env::var(¤t).ok().as_deref(),
131 Some("recipe-value"),
132 "{legacy} should have been copied onto {current}"
133 );
134 assert!(
135 mirrored.contains(¤t),
136 "{current} should be reported as mirrored, got {mirrored:?}"
137 );
138
139 unset(&legacy);
140 unset(¤t);
141 }
142
143 #[test]
144 fn existing_new_name_is_not_overwritten() {
145 let _guard = env_guard();
146 let legacy = format!("ATLAS_{}", unique("KEPT"));
147 let current = format!("AVAROK_{}", unique("KEPT"));
148 // Set the winner first so no interleaving can mirror the legacy name
149 // before the new name exists.
150 set(¤t, "explicit-new-value");
151 set(&legacy, "stale-legacy-value");
152
153 let mirrored = mirror_legacy_env();
154
155 assert_eq!(
156 std::env::var(¤t).ok().as_deref(),
157 Some("explicit-new-value"),
158 "an explicitly set {current} must win over {legacy}"
159 );
160 assert!(
161 !mirrored.contains(¤t),
162 "{current} was already set and must not be reported, got {mirrored:?}"
163 );
164
165 unset(&legacy);
166 unset(¤t);
167 }
168
169 #[test]
170 fn atlasctl_prefix_is_left_alone() {
171 let _guard = env_guard();
172 // `ATLASCTL_` shares the first five letters but is a different
173 // namespace: the prefix match is on `ATLAS_`, underscore included.
174 let legacy = format!("ATLASCTL_{}", unique("UNTOUCHED"));
175 let would_be = format!("AVAROKCTL_{}", unique("UNTOUCHED"));
176 set(&legacy, "control-plane-value");
177
178 let mirrored = mirror_legacy_env();
179
180 assert!(
181 std::env::var_os(&would_be).is_none(),
182 "{legacy} must not produce {would_be}"
183 );
184 assert!(
185 !mirrored.iter().any(|key| key.contains("UNTOUCHED")),
186 "no ATLASCTL_ key should be mirrored, got {mirrored:?}"
187 );
188
189 unset(&legacy);
190 }
191}