auto-install claude plugins at harness boot

new hyperhive.claudePlugins NixOS option (list of strings) rendered
to /etc/hyperhive/claude-plugins.json. both hive-ag3nt and hive-m1nd
shell out 'claude plugin install <spec>' for each entry once at
startup before the turn loop opens. failures log a warning but don't
abort boot.
This commit is contained in:
müde 2026-05-16 15:17:34 +02:00
parent 8e7405db13
commit 6dd17864ac
5 changed files with 72 additions and 2 deletions

View file

@ -6,7 +6,7 @@ use anyhow::Result;
use clap::{Parser, Subcommand};
use hive_ag3nt::events::{Bus, LiveEvent, TurnState};
use hive_ag3nt::login::{self, LoginState};
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, mcp, turn, web_ui};
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, mcp, plugins, turn, web_ui};
use hive_sh4re::{AgentRequest, AgentResponse};
#[derive(Parser)]
@ -71,6 +71,7 @@ async fn main() -> Result<()> {
let login_state = Arc::new(Mutex::new(initial));
let bus = Bus::new();
let files = turn::TurnFiles::prepare(&cli.socket, &label, mcp::Flavor::Agent).await?;
plugins::install_configured().await;
tokio::spawn(web_ui::serve(
label,
port,

View file

@ -10,7 +10,7 @@ use anyhow::Result;
use clap::{Parser, Subcommand};
use hive_ag3nt::events::{Bus, LiveEvent, TurnState};
use hive_ag3nt::login::{self, LoginState};
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, mcp, turn, web_ui};
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, mcp, plugins, turn, web_ui};
use hive_sh4re::{HelperEvent, ManagerRequest, ManagerResponse, SYSTEM_SENDER};
#[derive(Parser)]
@ -61,6 +61,7 @@ async fn main() -> Result<()> {
let login_state = Arc::new(Mutex::new(initial));
let bus = Bus::new();
let files = turn::TurnFiles::prepare(&cli.socket, &label, mcp::Flavor::Manager).await?;
plugins::install_configured().await;
tokio::spawn(web_ui::serve(
label,
port,

View file

@ -6,6 +6,7 @@ pub mod events;
pub mod login;
pub mod login_session;
pub mod mcp;
pub mod plugins;
pub mod turn;
pub mod web_ui;

48
hive-ag3nt/src/plugins.rs Normal file
View file

@ -0,0 +1,48 @@
//! Boot-time `claude plugin install` driver. Reads the list declared
//! via the `hyperhive.claudePlugins` NixOS option (rendered to
//! `/etc/hyperhive/claude-plugins.json` by the harness module) and
//! shells out `claude plugin install <spec>` for each entry. Runs once
//! per harness boot before the turn loop; `claude plugin install`
//! is expected to be idempotent so reinstalling on each container
//! recreate is fine. Failures log a warning but do not abort boot —
//! we'd rather start without a plugin than refuse to serve.
use tokio::process::Command;
const PLUGINS_PATH: &str = "/etc/hyperhive/claude-plugins.json";
pub async fn install_configured() {
let raw = match tokio::fs::read_to_string(PLUGINS_PATH).await {
Ok(s) => s,
Err(_) => return,
};
let specs: Vec<String> = match serde_json::from_str(&raw) {
Ok(v) => v,
Err(e) => {
tracing::warn!(path = PLUGINS_PATH, error = ?e, "claude-plugins spec parse failed; skipping");
return;
}
};
for spec in specs {
match Command::new("claude")
.args(["plugin", "install", &spec])
.output()
.await
{
Ok(out) if out.status.success() => {
tracing::info!(spec = %spec, "claude plugin install ok");
}
Ok(out) => {
tracing::warn!(
spec = %spec,
status = ?out.status,
stderr = %String::from_utf8_lossy(&out.stderr),
"claude plugin install failed",
);
}
Err(e) => {
tracing::warn!(spec = %spec, error = ?e, "claude plugin install spawn failed");
}
}
}
}