fix(maestro): drop SHARED_ prefix from [shared] env vars
ci / rust (pull_request) Successful in 4m13s
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:
@@ -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`.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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:?}"),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user