huskies: merge 898
This commit is contained in:
@@ -19,6 +19,12 @@ pub struct GatewayConfig {
|
||||
/// Map of project name → container URL.
|
||||
#[serde(default)]
|
||||
pub projects: BTreeMap<String, ProjectEntry>,
|
||||
/// Map of sled_id → shared secret token for sled-uplink authentication.
|
||||
///
|
||||
/// Each entry allows a sled identified by `sled_id` to connect to
|
||||
/// `/api/sled-uplink` using the given secret token as a bearer credential.
|
||||
#[serde(default)]
|
||||
pub sled_tokens: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
/// Validate that a gateway config has at least one project.
|
||||
@@ -113,6 +119,7 @@ url = "http://localhost:3002"
|
||||
fn validate_config_rejects_empty() {
|
||||
let config = GatewayConfig {
|
||||
projects: BTreeMap::new(),
|
||||
sled_tokens: BTreeMap::new(),
|
||||
};
|
||||
assert!(validate_config(&config).is_err());
|
||||
}
|
||||
@@ -132,7 +139,10 @@ url = "http://localhost:3002"
|
||||
url: "http://a".into(),
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let config = GatewayConfig {
|
||||
projects,
|
||||
sled_tokens: BTreeMap::new(),
|
||||
};
|
||||
assert_eq!(validate_config(&config).unwrap(), "alpha");
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,23 @@ pub fn load_config(path: &Path) -> Result<GatewayConfig, String> {
|
||||
|
||||
/// Persist the current projects map to `<config_dir>/projects.toml`.
|
||||
/// Silently ignores write errors or skips when `config_dir` is empty.
|
||||
///
|
||||
/// Existing `[sled_tokens]` entries are preserved so that adding or removing
|
||||
/// projects via the UI does not wipe the sled authentication tokens.
|
||||
pub async fn save_config(projects: &BTreeMap<String, ProjectEntry>, config_dir: &Path) {
|
||||
if config_dir.as_os_str().is_empty() {
|
||||
return;
|
||||
}
|
||||
let path = config_dir.join("projects.toml");
|
||||
let sled_tokens = tokio::fs::read_to_string(&path)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|data| toml::from_str::<GatewayConfig>(&data).ok())
|
||||
.map(|c| c.sled_tokens)
|
||||
.unwrap_or_default();
|
||||
let config = GatewayConfig {
|
||||
projects: projects.clone(),
|
||||
sled_tokens,
|
||||
};
|
||||
if let Ok(data) = toml::to_string_pretty(&config) {
|
||||
let _ = tokio::fs::write(&path, data).await;
|
||||
@@ -518,27 +528,20 @@ pub fn spawn_gateway_bot(
|
||||
gateway_project_urls: BTreeMap<String, String>,
|
||||
port: u16,
|
||||
gateway_event_tx: Option<tokio::sync::broadcast::Sender<super::GatewayStatusEvent>>,
|
||||
perm_rx: std::sync::Arc<
|
||||
tokio::sync::Mutex<
|
||||
tokio::sync::mpsc::UnboundedReceiver<crate::http::context::PermissionForward>,
|
||||
>,
|
||||
>,
|
||||
) -> (
|
||||
Option<tokio::task::AbortHandle>,
|
||||
tokio::sync::watch::Sender<Option<crate::rebuild::ShutdownReason>>,
|
||||
) {
|
||||
use crate::agents::AgentPool;
|
||||
use crate::services::Services;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
|
||||
let (watcher_tx, _) = broadcast::channel(16);
|
||||
let (perm_tx, perm_rx) = mpsc::unbounded_channel();
|
||||
// Keep the sender alive for the gateway's lifetime so the matrix bot's
|
||||
// `permission_listener` task doesn't exit immediately with
|
||||
// "perm_rx channel closed". Previously `_perm_tx` was dropped when
|
||||
// `spawn_gateway_bot` returned, closing the channel before the
|
||||
// listener could even register. Story 898 (sled→gateway WS uplink)
|
||||
// will eventually wire in a real sender; for now the leak keeps the
|
||||
// channel open with no senders writing to it, matching the original
|
||||
// intent of "listener watches forever, waiting for requests".
|
||||
std::mem::forget(perm_tx);
|
||||
let perm_rx = std::sync::Arc::new(tokio::sync::Mutex::new(perm_rx));
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
let (watcher_tx, _) = broadcast::channel::<crate::io::watcher::WatcherEvent>(16);
|
||||
let (shutdown_tx, shutdown_rx) =
|
||||
tokio::sync::watch::channel::<Option<crate::rebuild::ShutdownReason>>(None);
|
||||
// shutdown_tx is intentionally NOT forgotten — the caller holds it and
|
||||
@@ -611,6 +614,9 @@ mod tests {
|
||||
let active = std::sync::Arc::new(tokio::sync::RwLock::new("proj".to_string()));
|
||||
let (event_tx, _) = tokio::sync::broadcast::channel(4);
|
||||
|
||||
let (_perm_tx, perm_rx) =
|
||||
tokio::sync::mpsc::unbounded_channel::<crate::http::context::PermissionForward>();
|
||||
let perm_rx = std::sync::Arc::new(tokio::sync::Mutex::new(perm_rx));
|
||||
let (handle, shutdown_tx) = spawn_gateway_bot(
|
||||
tmp.path(),
|
||||
active,
|
||||
@@ -618,6 +624,7 @@ mod tests {
|
||||
std::collections::BTreeMap::new(),
|
||||
3001,
|
||||
Some(event_tx),
|
||||
perm_rx,
|
||||
);
|
||||
|
||||
// No bot.toml in tmp → no abort handle spawned.
|
||||
|
||||
@@ -22,6 +22,7 @@ pub use io::{
|
||||
spawn_gateway_notification_poller,
|
||||
};
|
||||
|
||||
use crate::http::context::PermissionForward;
|
||||
use crate::rebuild::ShutdownReason;
|
||||
use io::Client;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
@@ -29,6 +30,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex as TokioMutex;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub use crate::crdt_state::NodePresenceView;
|
||||
|
||||
@@ -122,6 +124,22 @@ pub struct GatewayState {
|
||||
///
|
||||
/// Call `event_tx.subscribe()` to obtain a receiver for outbound fan-out.
|
||||
pub event_tx: tokio::sync::broadcast::Sender<GatewayStatusEvent>,
|
||||
/// Sender end of the gateway's permission channel.
|
||||
///
|
||||
/// The sled-uplink handler uses this to inject `perm_request` messages
|
||||
/// received from connected sleds into the gateway's Matrix bot permission
|
||||
/// pipeline.
|
||||
pub perm_tx: mpsc::UnboundedSender<PermissionForward>,
|
||||
/// Receiver end of the gateway's permission channel (shared with the Matrix bot).
|
||||
///
|
||||
/// The Matrix bot's `permission_listener` holds this locked for its lifetime;
|
||||
/// the sled-uplink WS handler sends requests via `perm_tx`.
|
||||
pub perm_rx: Arc<TokioMutex<mpsc::UnboundedReceiver<PermissionForward>>>,
|
||||
/// Reversed sled-token map: token → sled_id.
|
||||
///
|
||||
/// Built at startup from [`GatewayConfig::sled_tokens`] (which maps
|
||||
/// sled_id → token). The handler looks up incoming tokens in O(1).
|
||||
pub sled_tokens: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl GatewayState {
|
||||
@@ -141,6 +159,12 @@ impl GatewayState {
|
||||
.filter(|p| gateway_config.projects.contains_key(p))
|
||||
.unwrap_or(first_from_config);
|
||||
let (event_tx, _) = tokio::sync::broadcast::channel(EVENT_CHANNEL_CAPACITY);
|
||||
let (perm_tx, perm_rx) = mpsc::unbounded_channel::<PermissionForward>();
|
||||
let sled_tokens: HashMap<String, String> = gateway_config
|
||||
.sled_tokens
|
||||
.iter()
|
||||
.map(|(sled_id, token)| (token.clone(), sled_id.clone()))
|
||||
.collect();
|
||||
Ok(Self {
|
||||
projects: Arc::new(RwLock::new(gateway_config.projects)),
|
||||
active_project: Arc::new(RwLock::new(first)),
|
||||
@@ -151,6 +175,9 @@ impl GatewayState {
|
||||
bot_handle: Arc::new(TokioMutex::new(None)),
|
||||
bot_shutdown_tx: Arc::new(TokioMutex::new(None)),
|
||||
event_tx,
|
||||
perm_tx,
|
||||
perm_rx: Arc::new(TokioMutex::new(perm_rx)),
|
||||
sled_tokens,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -477,6 +504,7 @@ pub async fn save_bot_config_and_restart(state: &GatewayState, content: &str) ->
|
||||
gateway_project_urls,
|
||||
state.port,
|
||||
Some(state.event_tx.clone()),
|
||||
Arc::clone(&state.perm_rx),
|
||||
);
|
||||
*handle = new_handle;
|
||||
*state.bot_shutdown_tx.lock().await = Some(new_shutdown_tx);
|
||||
@@ -502,13 +530,17 @@ mod tests {
|
||||
},
|
||||
);
|
||||
}
|
||||
GatewayConfig { projects }
|
||||
GatewayConfig {
|
||||
projects,
|
||||
sled_tokens: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_state_rejects_empty_config() {
|
||||
let config = GatewayConfig {
|
||||
projects: BTreeMap::new(),
|
||||
sled_tokens: BTreeMap::new(),
|
||||
};
|
||||
assert!(GatewayState::new(config, PathBuf::from("."), 3000).is_err());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user