huskies: merge 1144 story Gateway trampoline-restart: detached helper survives the gateway's own death
This commit is contained in:
@@ -19,6 +19,28 @@ use super::super::verification::check_sender_verified;
|
||||
|
||||
use super::handle_message;
|
||||
|
||||
/// Return `true` when the message is a "rebuild gateway" command addressed to the bot.
|
||||
///
|
||||
/// The command is recognised case-insensitively as `rebuild gateway` after stripping
|
||||
/// the bot mention prefix so both `@Timmy rebuild gateway` and `Timmy rebuild gateway`
|
||||
/// match.
|
||||
fn extract_rebuild_gateway_command(message: &str, bot_name: &str, bot_user_id: &str) -> bool {
|
||||
let stripped = crate::chat::util::strip_bot_mention(message, bot_name, bot_user_id);
|
||||
let trimmed = stripped
|
||||
.trim()
|
||||
.trim_start_matches(|c: char| !c.is_alphanumeric());
|
||||
let (cmd, rest) = match trimmed.split_once(char::is_whitespace) {
|
||||
Some((c, r)) => (c, r.trim()),
|
||||
None => return false,
|
||||
};
|
||||
cmd.eq_ignore_ascii_case("rebuild")
|
||||
&& rest
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.map(|w| w.eq_ignore_ascii_case("gateway"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Evaluate a `switch <arg>` command against the live project store.
|
||||
///
|
||||
/// Reads valid project names from the store at call time so newly added
|
||||
@@ -657,6 +679,87 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
return;
|
||||
}
|
||||
|
||||
// In gateway mode, intercept "rebuild gateway" and route it through the
|
||||
// detached trampoline so the process swap survives any bash-tool kill cascade.
|
||||
if ctx.gateway_active_project.is_some()
|
||||
&& extract_rebuild_gateway_command(
|
||||
&user_message,
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
)
|
||||
{
|
||||
slog!("[matrix-bot] Handling 'rebuild gateway' command from {sender}");
|
||||
let ack = "Rebuilding gateway\u{2026} this may take a moment.";
|
||||
let ack_html = markdown_to_html(ack);
|
||||
if let Ok(msg_id) = ctx
|
||||
.transport
|
||||
.send_message(&room_id_str, ack, &ack_html)
|
||||
.await
|
||||
&& let Ok(event_id) = msg_id.parse()
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
let config_dir = ctx.services.project_root.clone();
|
||||
let gateway_port: u16 = ctx.gateway_port.unwrap_or(3000);
|
||||
match crate::gateway::rebuild::rebuild_gateway(&config_dir, gateway_port).await {
|
||||
Ok(()) => {
|
||||
// Trampoline is running detached — it kills this gateway and starts
|
||||
// the new one, which will post "gateway X.Y.Z ready" on startup.
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("Gateway rebuild failed: {e}");
|
||||
let html = markdown_to_html(&msg);
|
||||
if let Ok(msg_id) = ctx.transport.send_message(&room_id_str, &msg, &html).await
|
||||
&& let Ok(event_id) = msg_id.parse()
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// In gateway mode, intercept "rebuild gateway" before the plain "rebuild"
|
||||
// handler so the trampoline path is used instead of a direct re-exec.
|
||||
if ctx.gateway_port.is_some()
|
||||
&& super::super::super::rebuild::extract_rebuild_gateway_command(
|
||||
&user_message,
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
slog!("[matrix-bot] Handling rebuild-gateway command from {sender}");
|
||||
let ack = "Rebuilding gateway… this may take a moment. \
|
||||
The gateway will announce itself when the new version is ready.";
|
||||
let ack_html = markdown_to_html(ack);
|
||||
if let Ok(msg_id) = ctx
|
||||
.transport
|
||||
.send_message(&room_id_str, ack, &ack_html)
|
||||
.await
|
||||
&& let Ok(event_id) = msg_id.parse()
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
let port = ctx.gateway_port.unwrap_or(3000);
|
||||
match crate::gateway::rebuild::rebuild_gateway(&ctx.services.project_root, port).await {
|
||||
Ok(()) => {
|
||||
// Trampoline is running — this gateway will be killed shortly.
|
||||
// No further reply needed; the new gateway posts "gateway X.Y.Z ready".
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("Gateway rebuild failed: {e}");
|
||||
let html = markdown_to_html(&msg);
|
||||
if let Ok(msg_id) = ctx.transport.send_message(&room_id_str, &msg, &html).await
|
||||
&& let Ok(event_id) = msg_id.parse()
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for the rebuild command, which requires async agent and process ops
|
||||
// and cannot be handled by the sync command registry.
|
||||
if super::super::super::rebuild::extract_rebuild_command(
|
||||
|
||||
Reference in New Issue
Block a user