Add release chat command — build sled binary and publish artifact

Finds the registered project carrying the huskies source tree, runs
`cargo build --release` inside its container (dedicated
CARGO_TARGET_DIR=target/sled-release so container builds stop
clobbering host target/release), then atomically publishes the binary
to ~/.huskies/artifacts/ with a .hash sidecar for convergence checks.

Full fleet redeploy is now chat-only: `release` then `upgrade all` —
no laptop access needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fHdm92yjvguPi2LiXfLB9
This commit is contained in:
Timmy
2026-07-15 16:27:32 +01:00
co-authored by Claude Fable 5
parent 18ba57a7b0
commit e8253a06b7
3 changed files with 322 additions and 0 deletions
@@ -503,6 +503,49 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
return;
}
// In gateway mode, handle the `release` command: build the sled binary in
// the builder container and publish it to the artifact store.
if ctx.is_gateway()
&& super::super::super::release::extract_release_command(
&user_message,
&ctx.services.bot_name,
ctx.matrix_user_id.as_str(),
)
{
slog!("[matrix-bot] Handling 'release' from {sender}");
let response = if let Some(ref store) = ctx.gateway_projects_store {
let transport = Arc::clone(&ctx.transport);
let bot_sent = Arc::clone(&ctx.bot_sent_event_ids);
let room = room_id_str.clone();
super::super::super::release::handle_release(store, |phase_msg| {
let transport = Arc::clone(&transport);
let bot_sent = Arc::clone(&bot_sent);
let room = room.clone();
async move {
let html = markdown_to_html(&phase_msg);
if let Ok(msg_id) = transport.send_message(&room, &phase_msg, &html).await
&& let Ok(event_id) = msg_id.parse()
{
bot_sent.lock().await.insert(event_id);
}
}
})
.await
} else {
"Gateway projects store unavailable — cannot release.".to_string()
};
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
.transport
.send_message(&room_id_str, &response, &html)
.await
&& let Ok(event_id) = msg_id.parse()
{
ctx.bot_sent_event_ids.lock().await.insert(event_id);
}
return;
}
// In gateway mode, handle the `upgrade [<project>]` command to upgrade a
// sled's binary in-container, streaming phase markers to the room.
if ctx.is_gateway()