fix(maestro): drop SHARED_ prefix from [shared] env vars
ci / rust (pull_request) Successful in 4m13s

The shared section is the implicit global scope, so its fields use
the bare `MAESTRO_<FIELD>` env-var spelling. Only `[daemon]` and
`[registry]` sections get section-prefixed env vars.

Renames:
- MAESTRO_SHARED_TOKEN        → MAESTRO_TOKEN
- MAESTRO_SHARED_REGISTRY_URL → MAESTRO_REGISTRY_URL

`MAESTRO_DAEMON_*` and `MAESTRO_REGISTRY_BIND_ADDR` are unchanged.

This restores the canonical spec name (`MAESTRO_TOKEN`) that operators
already use in their deployment scripts — NAP-233 had renamed it to
`MAESTRO_SHARED_TOKEN`, which silently broke daemon auth against
existing registry deployments still setting `MAESTRO_TOKEN` in env.

Constants renamed alongside: `ENV_SHARED_TOKEN` → `ENV_TOKEN`,
`ENV_SHARED_REGISTRY_URL` → `ENV_REGISTRY_URL`. `EnvOverrides` fields
shed their `shared_` prefix to match.

Touches: daemon/cli/registry crates, e2e harness, design spec.
This commit is contained in:
2026-05-26 13:24:29 +00:00
parent 05bff366f8
commit 094cd1ffce
12 changed files with 39 additions and 44 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ use uuid::Uuid;
pub struct Cli {
/// Override the registry URL for this invocation (e.g. `https://maestro.example.com`).
/// If omitted, `[shared].registry_url` from the unified config file is used.
#[arg(long, global = true, env = "MAESTRO_SHARED_REGISTRY_URL")]
#[arg(long, global = true, env = "MAESTRO_REGISTRY_URL")]
pub registry_url: Option<String>,
/// Path to the unified Maestro config file. Default `~/.config/maestro/config.toml`.
+1 -1
View File
@@ -26,7 +26,7 @@ pub enum ConfigError {
Inner(#[from] maestro_config::ConfigError),
#[error(
"no registry URL configured — pass --registry-url, set \
MAESTRO_SHARED_REGISTRY_URL, or run `maestro set-registry <url>`"
MAESTRO_REGISTRY_URL, or run `maestro set-registry <url>`"
)]
NoRegistryUrl,
}
+24 -29
View File
@@ -26,9 +26,9 @@ pub use maestro_config::{
pub const DEFAULT_BIND_ADDR: &str = "127.0.0.1:7070";
/// Env var that overrides `[shared].registry_url`.
pub const ENV_SHARED_REGISTRY_URL: &str = "MAESTRO_SHARED_REGISTRY_URL";
pub const ENV_REGISTRY_URL: &str = "MAESTRO_REGISTRY_URL";
/// Env var that overrides `[shared].token`.
pub const ENV_SHARED_TOKEN: &str = "MAESTRO_SHARED_TOKEN";
pub const ENV_TOKEN: &str = "MAESTRO_TOKEN";
/// Env var that overrides `[daemon].daemon_id`.
pub const ENV_DAEMON_ID: &str = "MAESTRO_DAEMON_ID";
/// Env var that overrides `[daemon].daemon_url`.
@@ -90,10 +90,10 @@ impl DaemonConfig {
#[derive(Debug, Clone, Default)]
pub struct EnvOverrides {
pub daemon_id: Option<String>,
pub shared_registry_url: Option<String>,
pub registry_url: Option<String>,
pub bind_addr: Option<String>,
pub daemon_url: Option<String>,
pub shared_token: Option<String>,
pub token: Option<String>,
}
impl EnvOverrides {
@@ -101,10 +101,10 @@ impl EnvOverrides {
pub fn from_env() -> Self {
Self {
daemon_id: std::env::var(ENV_DAEMON_ID).ok(),
shared_registry_url: std::env::var(ENV_SHARED_REGISTRY_URL).ok(),
registry_url: std::env::var(ENV_REGISTRY_URL).ok(),
bind_addr: std::env::var(ENV_DAEMON_BIND_ADDR).ok(),
daemon_url: std::env::var(ENV_DAEMON_URL).ok(),
shared_token: std::env::var(ENV_SHARED_TOKEN).ok(),
token: std::env::var(ENV_TOKEN).ok(),
}
}
}
@@ -139,15 +139,13 @@ pub fn load_config(path: &Path, env: EnvOverrides) -> Result<DaemonConfig, Confi
.or(daemon.daemon_id)
.unwrap_or_else(|| hostname.clone());
let shared_registry_url =
env.shared_registry_url
.or(shared.registry_url)
.ok_or(ConfigError::Missing {
field: "shared.registry_url",
env: ENV_SHARED_REGISTRY_URL,
path: path.to_path_buf(),
})?;
let registry_url = derive_daemon_ws_url(&shared_registry_url)?;
let registry_url = derive_daemon_ws_url(&env.registry_url.or(shared.registry_url).ok_or(
ConfigError::Missing {
field: "shared.registry_url",
env: ENV_REGISTRY_URL,
path: path.to_path_buf(),
},
)?)?;
let daemon_url = env
.daemon_url
@@ -157,14 +155,11 @@ pub fn load_config(path: &Path, env: EnvOverrides) -> Result<DaemonConfig, Confi
env: ENV_DAEMON_URL,
path: path.to_path_buf(),
})?;
let token = env
.shared_token
.or(shared.token)
.ok_or(ConfigError::Missing {
field: "shared.token",
env: ENV_SHARED_TOKEN,
path: path.to_path_buf(),
})?;
let token = env.token.or(shared.token).ok_or(ConfigError::Missing {
field: "shared.token",
env: ENV_TOKEN,
path: path.to_path_buf(),
})?;
let bind_addr = match env.bind_addr {
Some(raw) => raw.parse().map_err(|source| ConfigError::InvalidBindAddr {
@@ -196,7 +191,7 @@ pub fn load_config(path: &Path, env: EnvOverrides) -> Result<DaemonConfig, Confi
}
fn env_has_all_required(env: &EnvOverrides) -> bool {
env.shared_registry_url.is_some() && env.daemon_url.is_some() && env.shared_token.is_some()
env.registry_url.is_some() && env.daemon_url.is_some() && env.token.is_some()
}
pub fn default_state_dir() -> Result<PathBuf, ConfigError> {
@@ -255,9 +250,9 @@ daemon_url = "ws://localhost:7070"
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("does-not-exist.toml");
let env = EnvOverrides {
shared_registry_url: Some("http://r.example".into()),
registry_url: Some("http://r.example".into()),
daemon_url: Some("ws://x:7070".into()),
shared_token: Some("t".into()),
token: Some("t".into()),
..EnvOverrides::default()
};
let cfg = load_config(&path, env).unwrap();
@@ -296,10 +291,10 @@ bind_addr = "127.0.0.1:1"
.unwrap();
let env = EnvOverrides {
daemon_id: Some("from-env".into()),
shared_registry_url: Some("https://from-env".into()),
registry_url: Some("https://from-env".into()),
bind_addr: Some("0.0.0.0:9999".into()),
daemon_url: Some("ws://from-env:1".into()),
shared_token: Some("from-env".into()),
token: Some("from-env".into()),
};
let cfg = load_config(&path, env).unwrap();
assert_eq!(cfg.daemon_id, "from-env");
@@ -332,7 +327,7 @@ daemon_url = "ws://x"
path: p,
} => {
assert_eq!(field, "shared.token");
assert_eq!(env, ENV_SHARED_TOKEN);
assert_eq!(env, ENV_TOKEN);
assert_eq!(p, path);
}
other => panic!("expected Missing(shared.token), got {other:?}"),
+1 -1
View File
@@ -92,7 +92,7 @@ pub fn run(
)?;
let token = resolve_required(
"token",
"Shared token (MAESTRO_SHARED_TOKEN; paste from the registry)",
"Shared token (MAESTRO_TOKEN; paste from the registry)",
inputs.token,
&mut handle,
&stdin,
+1 -1
View File
@@ -52,7 +52,7 @@ struct ConfigureCmd {
/// Reachable URL clients dial for this daemon's attach WS.
#[arg(long)]
daemon_url: Option<String>,
/// Shared token (MAESTRO_SHARED_TOKEN). Tip: pipe from `pass` / `op` / `secret-tool`.
/// Shared token (MAESTRO_TOKEN). Tip: pipe from `pass` / `op` / `secret-tool`.
#[arg(long)]
token: Option<String>,
/// Stable daemon ID. Defaults to the system hostname.
+2 -2
View File
@@ -152,9 +152,9 @@ fn launch(
.env("XDG_CONFIG_HOME", home.join(".config"))
.env("XDG_STATE_HOME", home.join(".local/state"))
.env("TMUX_TMPDIR", tmux_tmpdir)
.env("MAESTRO_SHARED_TOKEN", token)
.env("MAESTRO_TOKEN", token)
.env("MAESTRO_DAEMON_ID", daemon_id)
.env("MAESTRO_SHARED_REGISTRY_URL", registry_url)
.env("MAESTRO_REGISTRY_URL", registry_url)
.env("MAESTRO_DAEMON_BIND_ADDR", bind.to_string())
.env("MAESTRO_DAEMON_URL", daemon_url)
.env("RUST_LOG", "warn")
+1 -1
View File
@@ -38,7 +38,7 @@ impl Registry {
.env("HOME", home.path())
.env("XDG_CONFIG_HOME", home.path().join(".config"))
.env("XDG_STATE_HOME", home.path().join(".local/state"))
.env("MAESTRO_SHARED_TOKEN", token)
.env("MAESTRO_TOKEN", token)
.env("MAESTRO_REGISTRY_BIND_ADDR", addr.to_string())
.env("RUST_LOG", "warn")
.stdout(Stdio::null())
+1 -1
View File
@@ -1,7 +1,7 @@
//! Cookie-based auth for the client-facing HTTP/WS surface.
//!
//! On the first request the registry sets an httpOnly cookie whose value is a
//! JWT signed with the shared token (`MAESTRO_SHARED_TOKEN`, HS256). Subsequent requests
//! JWT signed with the shared token (`MAESTRO_TOKEN`, HS256). Subsequent requests
//! must present a cookie whose signature verifies; missing or tampered
//! cookies get a fresh cookie issued (effectively re-bootstrapping a
//! first-time client) and are still allowed through. The cookie is the second
+1 -1
View File
@@ -28,5 +28,5 @@ pub use server::{router, router_full, router_with_timeout, RegistryServer};
pub use state::{DaemonView, Registry, RegistryError, RpcError, HEARTBEAT_TIMEOUT, RPC_TIMEOUT};
pub use token::{
default_config_path, ensure_token, load_config, resolve_config_path, RegistryConfig,
TokenError, CONFIG_PATH_ENV, ENV_REGISTRY_BIND_ADDR, ENV_SHARED_TOKEN,
TokenError, CONFIG_PATH_ENV, ENV_REGISTRY_BIND_ADDR, ENV_TOKEN,
};
+1 -1
View File
@@ -27,7 +27,7 @@ async fn run() -> Result<(), Box<dyn Error>> {
Some(p) => PathBuf::from(p),
None => default_config_path()?,
};
let env_token = std::env::var(maestro_registry::ENV_SHARED_TOKEN).ok();
let env_token = std::env::var(maestro_registry::ENV_TOKEN).ok();
let env_bind = std::env::var(maestro_registry::ENV_REGISTRY_BIND_ADDR).ok();
let token_from_env = env_token.is_some();
let bind_from_env = env_bind.is_some();
+4 -4
View File
@@ -11,7 +11,7 @@
//!
//! Env-var overrides (applied by [`load_config`]):
//!
//! - `MAESTRO_SHARED_TOKEN` — when set, bypasses the file entirely (no read,
//! - `MAESTRO_TOKEN` — when set, bypasses the file entirely (no read,
//! no write). Lets the binary run in a container with no writable volume.
//! - `MAESTRO_REGISTRY_BIND_ADDR` — when set, overrides `bind_addr`.
//!
@@ -30,7 +30,7 @@ pub use maestro_config::{default_config_path, resolve_config_path, CONFIG_PATH_E
pub const DEFAULT_BIND_ADDR: &str = "127.0.0.1:8080";
/// Env var that overrides `[shared].token`.
pub const ENV_SHARED_TOKEN: &str = "MAESTRO_SHARED_TOKEN";
pub const ENV_TOKEN: &str = "MAESTRO_TOKEN";
/// Env var that overrides `[registry].bind_addr`.
pub const ENV_REGISTRY_BIND_ADDR: &str = "MAESTRO_REGISTRY_BIND_ADDR";
@@ -78,7 +78,7 @@ pub fn ensure_token(config_path: &Path) -> Result<RegistryConfig, TokenError> {
file.shared.token = Some(token.clone());
file.save(config_path)?;
eprintln!(
"maestro-registry: generated new MAESTRO_SHARED_TOKEN; deploy this to every daemon:\n {token}"
"maestro-registry: generated new MAESTRO_TOKEN; deploy this to every daemon:\n {token}"
);
Ok(RegistryConfig { token, bind_addr })
}
@@ -88,7 +88,7 @@ pub fn ensure_token(config_path: &Path) -> Result<RegistryConfig, TokenError> {
///
/// Precedence per field: env > file > default.
///
/// `env_token` (`MAESTRO_SHARED_TOKEN`): when `Some`, the file is neither
/// `env_token` (`MAESTRO_TOKEN`): when `Some`, the file is neither
/// read nor created — useful in container deployments without a writable
/// volume.
/// `env_bind_addr` (`MAESTRO_REGISTRY_BIND_ADDR`): when `Some`, overrides
@@ -244,7 +244,7 @@ struct Session {
## Auth
- Registry generates the shared token at first run, printed once, stored as `[shared].token` in `~/.config/maestro/config.toml` (env var name: `MAESTRO_SHARED_TOKEN`).
- Registry generates the shared token at first run, printed once, stored as `[shared].token` in `~/.config/maestro/config.toml` (env var name: `MAESTRO_TOKEN`).
- Same token deployed to each daemon (config file or env var) and used in `Hello`.
- Web client gets a cookie from the registry on first visit. All registry API calls use it.
- Per-attach JWT issued by registry, signed with the shared secret, validated locally by the daemon. ~5 min expiry; client refreshes by re-requesting `GET /api/sessions/:id/connect-info`.