refactor(#1474): extract dispatch arm logic in manager_server + hive-priv
This commit is contained in:
parent
c6d9f59c4d
commit
02dcf4d028
2 changed files with 336 additions and 261 deletions
|
|
@ -74,11 +74,6 @@ async fn serve(stream: UnixStream, coord: Arc<Coordinator>) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
reason = "flat dispatch table: one match arm per manager-socket request \
|
||||
variant; splitting it would scatter the routing logic"
|
||||
)]
|
||||
async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResponse {
|
||||
// Delegate all variants shared with the agent socket to the common handler.
|
||||
if let Some(resp) = crate::agent_server::dispatch_shared(req, MANAGER_AGENT, coord).await {
|
||||
|
|
@ -86,112 +81,16 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
|||
}
|
||||
match req {
|
||||
ManagerRequest::RequestInitConfig { name, description } => {
|
||||
tracing::info!(%name, "manager: request_init_config");
|
||||
match submit_init_config(coord, name, description.clone()) {
|
||||
Ok(_id) => ManagerResponse::Ok,
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
ManagerRequest::Kill { name } => {
|
||||
tracing::info!(%name, "manager: kill");
|
||||
let result: Result<()> = async {
|
||||
lifecycle::kill(name).await?;
|
||||
coord.unregister_agent(name);
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
match result {
|
||||
Ok(()) => {
|
||||
coord.notify_manager(&hive_sh4re::HelperEvent::Killed {
|
||||
agent: name.clone(),
|
||||
});
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
ManagerRequest::Start { name } => {
|
||||
tracing::info!(%name, "manager: start");
|
||||
match lifecycle::start(name).await {
|
||||
Ok(()) => {
|
||||
coord.kick_agent(name, "container started");
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
ManagerRequest::Restart { name } => {
|
||||
tracing::info!(%name, "manager: enqueue restart");
|
||||
coord.rebuild_queue.enqueue(
|
||||
crate::rebuild_queue::QueueKind::Restart,
|
||||
name.to_owned(),
|
||||
crate::rebuild_queue::QueueSource::Manual,
|
||||
"manager `restart` tool".to_owned(),
|
||||
None,
|
||||
);
|
||||
coord.emit_rebuild_queue_snapshot();
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
ManagerRequest::Update { name } => {
|
||||
tracing::info!(%name, "manager: enqueue update");
|
||||
coord.rebuild_queue.enqueue(
|
||||
crate::rebuild_queue::QueueKind::Rebuild,
|
||||
name.to_owned(),
|
||||
crate::rebuild_queue::QueueSource::Manual,
|
||||
"manager `update` tool".to_owned(),
|
||||
None,
|
||||
);
|
||||
coord.emit_rebuild_queue_snapshot();
|
||||
ManagerResponse::Ok
|
||||
handle_manager_init_config(coord, name, description.clone())
|
||||
}
|
||||
ManagerRequest::Kill { name } => handle_manager_kill(coord, name).await,
|
||||
ManagerRequest::Start { name } => handle_manager_start(coord, name).await,
|
||||
ManagerRequest::Restart { name } => handle_manager_restart(coord, name),
|
||||
ManagerRequest::Update { name } => handle_manager_update(coord, name),
|
||||
ManagerRequest::RequestUpdateMetaInputs {
|
||||
inputs,
|
||||
description,
|
||||
} => {
|
||||
let label = if inputs.is_empty() {
|
||||
"all inputs".to_string()
|
||||
} else {
|
||||
inputs.join(", ")
|
||||
};
|
||||
tracing::info!(%label, "manager: request_update_meta_inputs");
|
||||
// Encode the inputs list as JSON and store it in commit_ref
|
||||
// (there's no git commit involved; the field carries the
|
||||
// payload for the approval handler to decode at run time).
|
||||
let commit_ref = serde_json::to_string(inputs).unwrap_or_default();
|
||||
let id = match coord
|
||||
.approvals
|
||||
.submit_kind(
|
||||
MANAGER_AGENT,
|
||||
hive_sh4re::ApprovalKind::UpdateMetaInputs,
|
||||
&commit_ref,
|
||||
description.as_deref(),
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("{e:#}"))
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return ManagerResponse::Err {
|
||||
message: format!("queue update_meta_inputs approval: {e:#}"),
|
||||
};
|
||||
}
|
||||
};
|
||||
tracing::info!(%id, %label, "update_meta_inputs approval queued");
|
||||
coord.emit_approval_added(
|
||||
id,
|
||||
MANAGER_AGENT,
|
||||
"update_meta_inputs",
|
||||
None,
|
||||
None,
|
||||
description.clone(),
|
||||
);
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
} => handle_request_update_meta_inputs(coord, inputs, description.as_deref()),
|
||||
ManagerRequest::RequestSchedulePrompt(payload) => {
|
||||
handle_request_schedule_prompt(coord, MANAGER_AGENT, payload)
|
||||
}
|
||||
|
|
@ -219,101 +118,24 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
|||
targets_remove: targets_remove.clone(),
|
||||
},
|
||||
),
|
||||
ManagerRequest::ListSchedules => match coord.scheduled_prompts.list() {
|
||||
Ok(schedules) => ManagerResponse::Schedules {
|
||||
schedules: schedules.into_iter().map(schedule_to_wire).collect(),
|
||||
},
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("list scheduled prompts: {e:#}"),
|
||||
},
|
||||
},
|
||||
ManagerRequest::ListSchedules => handle_list_schedules(coord),
|
||||
ManagerRequest::FireScheduleNow { id } => {
|
||||
handle_fire_schedule_now(coord, MANAGER_AGENT, *id).await
|
||||
}
|
||||
ManagerRequest::GetLogs { agent, lines } => {
|
||||
let n = lines.unwrap_or(50);
|
||||
// `journalctl -M` wants the container name (`h-<name>`),
|
||||
// not the logical agent name. `container_name` adds the prefix.
|
||||
// The `-M` read needs root, so it goes through hive-priv.
|
||||
let machine = crate::lifecycle::container_name(agent);
|
||||
tracing::info!(%agent, %machine, %n, "manager: get_logs");
|
||||
match crate::priv_client::read_container_journal(
|
||||
&machine,
|
||||
hive_sh4re::priv_proto::JournalQuery {
|
||||
lines: n,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((stdout, stderr)) => {
|
||||
let content = if stdout.is_empty() { stderr } else { stdout };
|
||||
ManagerResponse::Logs { content }
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("get_logs: {e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
ManagerRequest::GetLogs { agent, lines } => handle_get_logs(agent, *lines).await,
|
||||
ManagerRequest::RequestApplyCommit {
|
||||
agent,
|
||||
commit_ref,
|
||||
description,
|
||||
} => {
|
||||
tracing::info!(%agent, %commit_ref, "manager: request_apply_commit");
|
||||
match submit_apply_commit(coord, agent, commit_ref, description.as_deref()).await {
|
||||
Ok((id, sha)) => {
|
||||
tracing::info!(%id, %agent, manager_ref = %commit_ref, %sha, "approval queued + proposal tag planted");
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
} => handle_manager_apply_commit(coord, agent, commit_ref, description.as_deref()).await,
|
||||
ManagerRequest::GetLooseEnds { agent } => {
|
||||
let result = match agent.as_deref() {
|
||||
Some("*") => {
|
||||
// Hive-wide query requires query_agent_state capability.
|
||||
if !crate::capabilities::has_cap(
|
||||
MANAGER_AGENT,
|
||||
hive_sh4re::Capability::QueryAgentState,
|
||||
) {
|
||||
return ManagerResponse::Err {
|
||||
message:
|
||||
"query_agent_state capability required for hive-wide loose ends"
|
||||
.into(),
|
||||
};
|
||||
}
|
||||
crate::loose_ends::hive_wide(coord)
|
||||
}
|
||||
Some(name) => crate::loose_ends::for_agent(coord, name),
|
||||
None => crate::loose_ends::for_agent(coord, MANAGER_AGENT),
|
||||
};
|
||||
match result {
|
||||
Ok(loose_ends) => ManagerResponse::LooseEnds { loose_ends },
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
handle_manager_loose_ends(coord, agent.as_deref())
|
||||
}
|
||||
ManagerRequest::CountPendingReminders { agent } => {
|
||||
let target = agent.as_deref().unwrap_or(MANAGER_AGENT);
|
||||
match coord.broker.count_pending_reminders_for(target) {
|
||||
Ok(count) => ManagerResponse::PendingRemindersCount { count },
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
handle_manager_count_pending_reminders(coord, agent.as_deref())
|
||||
}
|
||||
ManagerRequest::ReminderRollup { since_secs, agent } => {
|
||||
let target = agent.as_deref().unwrap_or(MANAGER_AGENT);
|
||||
match coord.broker.reminder_rollup_for(target, *since_secs) {
|
||||
Ok(stats) => ManagerResponse::ReminderRollup(stats),
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
handle_manager_reminder_rollup(coord, agent.as_deref(), *since_secs)
|
||||
}
|
||||
_ => ManagerResponse::Err {
|
||||
message: "request not handled on manager socket".to_owned(),
|
||||
|
|
@ -321,6 +143,245 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
|||
}
|
||||
}
|
||||
|
||||
/// `RequestInitConfig` (manager socket) — queue an `InitConfig`
|
||||
/// approval. No topology check: the manager can act on any agent.
|
||||
fn handle_manager_init_config(
|
||||
coord: &Arc<Coordinator>,
|
||||
name: &str,
|
||||
description: Option<String>,
|
||||
) -> ManagerResponse {
|
||||
tracing::info!(%name, "manager: request_init_config");
|
||||
match submit_init_config(coord, name, description) {
|
||||
Ok(_id) => ManagerResponse::Ok,
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `Kill` (manager socket) — kill the container, unregister it, notify.
|
||||
async fn handle_manager_kill(coord: &Arc<Coordinator>, name: &str) -> ManagerResponse {
|
||||
tracing::info!(%name, "manager: kill");
|
||||
let result: Result<()> = async {
|
||||
lifecycle::kill(name).await?;
|
||||
coord.unregister_agent(name);
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
match result {
|
||||
Ok(()) => {
|
||||
coord.notify_manager(&hive_sh4re::HelperEvent::Killed {
|
||||
agent: name.to_owned(),
|
||||
});
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `Start` (manager socket) — start the container, kick its next turn.
|
||||
async fn handle_manager_start(coord: &Arc<Coordinator>, name: &str) -> ManagerResponse {
|
||||
tracing::info!(%name, "manager: start");
|
||||
match lifecycle::start(name).await {
|
||||
Ok(()) => {
|
||||
coord.kick_agent(name, "container started");
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `Restart` (manager socket) — enqueue a restart.
|
||||
fn handle_manager_restart(coord: &Arc<Coordinator>, name: &str) -> ManagerResponse {
|
||||
tracing::info!(%name, "manager: enqueue restart");
|
||||
coord.rebuild_queue.enqueue(
|
||||
crate::rebuild_queue::QueueKind::Restart,
|
||||
name.to_owned(),
|
||||
crate::rebuild_queue::QueueSource::Manual,
|
||||
"manager `restart` tool".to_owned(),
|
||||
None,
|
||||
);
|
||||
coord.emit_rebuild_queue_snapshot();
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
|
||||
/// `Update` (manager socket) — enqueue a rebuild.
|
||||
fn handle_manager_update(coord: &Arc<Coordinator>, name: &str) -> ManagerResponse {
|
||||
tracing::info!(%name, "manager: enqueue update");
|
||||
coord.rebuild_queue.enqueue(
|
||||
crate::rebuild_queue::QueueKind::Rebuild,
|
||||
name.to_owned(),
|
||||
crate::rebuild_queue::QueueSource::Manual,
|
||||
"manager `update` tool".to_owned(),
|
||||
None,
|
||||
);
|
||||
coord.emit_rebuild_queue_snapshot();
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
|
||||
/// `RequestUpdateMetaInputs` — queue an `UpdateMetaInputs` approval
|
||||
/// carrying the JSON-encoded input list in `commit_ref` (no git commit
|
||||
/// is involved; the field is the payload the approval handler decodes).
|
||||
fn handle_request_update_meta_inputs(
|
||||
coord: &Arc<Coordinator>,
|
||||
inputs: &[String],
|
||||
description: Option<&str>,
|
||||
) -> ManagerResponse {
|
||||
let label = if inputs.is_empty() {
|
||||
"all inputs".to_string()
|
||||
} else {
|
||||
inputs.join(", ")
|
||||
};
|
||||
tracing::info!(%label, "manager: request_update_meta_inputs");
|
||||
let commit_ref = serde_json::to_string(inputs).unwrap_or_default();
|
||||
let id = match coord
|
||||
.approvals
|
||||
.submit_kind(
|
||||
MANAGER_AGENT,
|
||||
hive_sh4re::ApprovalKind::UpdateMetaInputs,
|
||||
&commit_ref,
|
||||
description,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("{e:#}"))
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return ManagerResponse::Err {
|
||||
message: format!("queue update_meta_inputs approval: {e:#}"),
|
||||
};
|
||||
}
|
||||
};
|
||||
tracing::info!(%id, %label, "update_meta_inputs approval queued");
|
||||
coord.emit_approval_added(
|
||||
id,
|
||||
MANAGER_AGENT,
|
||||
"update_meta_inputs",
|
||||
None,
|
||||
None,
|
||||
description.map(str::to_owned),
|
||||
);
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
|
||||
/// `ListSchedules` — snapshot every scheduled prompt onto the wire.
|
||||
fn handle_list_schedules(coord: &Arc<Coordinator>) -> ManagerResponse {
|
||||
match coord.scheduled_prompts.list() {
|
||||
Ok(schedules) => ManagerResponse::Schedules {
|
||||
schedules: schedules.into_iter().map(schedule_to_wire).collect(),
|
||||
},
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("list scheduled prompts: {e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `GetLogs` — read a child container's journal via hive-priv (the
|
||||
/// `-M` read needs root). `journalctl -M` wants the `h-<name>` machine
|
||||
/// name, which `container_name` derives.
|
||||
async fn handle_get_logs(agent: &str, lines: Option<u32>) -> ManagerResponse {
|
||||
let n = lines.unwrap_or(50);
|
||||
let machine = crate::lifecycle::container_name(agent);
|
||||
tracing::info!(%agent, %machine, %n, "manager: get_logs");
|
||||
match crate::priv_client::read_container_journal(
|
||||
&machine,
|
||||
hive_sh4re::priv_proto::JournalQuery {
|
||||
lines: n,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((stdout, stderr)) => {
|
||||
let content = if stdout.is_empty() { stderr } else { stdout };
|
||||
ManagerResponse::Logs { content }
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("get_logs: {e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `RequestApplyCommit` (manager socket) — queue an apply-commit
|
||||
/// approval + plant the `proposal/<id>` tag.
|
||||
async fn handle_manager_apply_commit(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
commit_ref: &str,
|
||||
description: Option<&str>,
|
||||
) -> ManagerResponse {
|
||||
tracing::info!(%agent, %commit_ref, "manager: request_apply_commit");
|
||||
match submit_apply_commit(coord, agent, commit_ref, description).await {
|
||||
Ok((id, sha)) => {
|
||||
tracing::info!(%id, %agent, manager_ref = %commit_ref, %sha, "approval queued + proposal tag planted");
|
||||
ManagerResponse::Ok
|
||||
}
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `GetLooseEnds` (manager socket) — manager's own threads by default,
|
||||
/// a named agent's when given, or hive-wide for `"*"` (which requires
|
||||
/// the `query_agent_state` capability).
|
||||
fn handle_manager_loose_ends(coord: &Arc<Coordinator>, agent: Option<&str>) -> ManagerResponse {
|
||||
let result = match agent {
|
||||
Some("*") => {
|
||||
if !crate::capabilities::has_cap(MANAGER_AGENT, hive_sh4re::Capability::QueryAgentState)
|
||||
{
|
||||
return ManagerResponse::Err {
|
||||
message: "query_agent_state capability required for hive-wide loose ends"
|
||||
.into(),
|
||||
};
|
||||
}
|
||||
crate::loose_ends::hive_wide(coord)
|
||||
}
|
||||
Some(name) => crate::loose_ends::for_agent(coord, name),
|
||||
None => crate::loose_ends::for_agent(coord, MANAGER_AGENT),
|
||||
};
|
||||
match result {
|
||||
Ok(loose_ends) => ManagerResponse::LooseEnds { loose_ends },
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `CountPendingReminders` (manager socket) — count pending reminders
|
||||
/// for the target (defaults to the manager itself).
|
||||
fn handle_manager_count_pending_reminders(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: Option<&str>,
|
||||
) -> ManagerResponse {
|
||||
let target = agent.unwrap_or(MANAGER_AGENT);
|
||||
match coord.broker.count_pending_reminders_for(target) {
|
||||
Ok(count) => ManagerResponse::PendingRemindersCount { count },
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `ReminderRollup` (manager socket) — roll up reminders fired in the
|
||||
/// last `since_secs` for the target (defaults to the manager itself).
|
||||
fn handle_manager_reminder_rollup(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: Option<&str>,
|
||||
since_secs: u64,
|
||||
) -> ManagerResponse {
|
||||
let target = agent.unwrap_or(MANAGER_AGENT);
|
||||
match coord.broker.reminder_rollup_for(target, since_secs) {
|
||||
Ok(stats) => ManagerResponse::ReminderRollup(stats),
|
||||
Err(e) => ManagerResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `request_apply_commit` takes a commit SHA only — not a branch or
|
||||
/// tag name. A branch is mutable; pinning the proposal to a concrete
|
||||
/// sha keeps "what the manager asked to deploy" unambiguous and means
|
||||
|
|
|
|||
|
|
@ -157,8 +157,10 @@ async fn write_line_event(writer: &mut OwnedWriteHalf, stream: PrivStream, data:
|
|||
/// the returned strings are empty.
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
reason = "flat dispatch table: one match arm per privileged request variant; \
|
||||
splitting it would scatter the routing logic"
|
||||
reason = "flat routing table over the privileged request variants — the \
|
||||
logic-bearing arms (resource-limits / daemon-reload / \
|
||||
restart-matrix / create+update) are extracted to helpers; the \
|
||||
rest are one-line validate-then-delegate dispatches kept inline"
|
||||
)]
|
||||
async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String, String)> {
|
||||
match req {
|
||||
|
|
@ -178,35 +180,11 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
|||
}
|
||||
|
||||
PrivRequest::UpdateContainer { ref name, stream } => {
|
||||
validate_container_name(name)?;
|
||||
let flake_ref = agent_flake_ref(name);
|
||||
let args = [
|
||||
"update",
|
||||
&container_system_name(name),
|
||||
"--flake",
|
||||
&flake_ref,
|
||||
];
|
||||
if stream {
|
||||
container_run_streaming(&args, writer).await
|
||||
} else {
|
||||
container_run(&args).await
|
||||
}
|
||||
container_flake_action("update", name, stream, writer).await
|
||||
}
|
||||
|
||||
PrivRequest::CreateContainer { ref name, stream } => {
|
||||
validate_container_name(name)?;
|
||||
let flake_ref = agent_flake_ref(name);
|
||||
let args = [
|
||||
"create",
|
||||
&container_system_name(name),
|
||||
"--flake",
|
||||
&flake_ref,
|
||||
];
|
||||
if stream {
|
||||
container_run_streaming(&args, writer).await
|
||||
} else {
|
||||
container_run(&args).await
|
||||
}
|
||||
container_flake_action("create", name, stream, writer).await
|
||||
}
|
||||
|
||||
PrivRequest::DestroyContainer { ref name } => {
|
||||
|
|
@ -242,15 +220,7 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
|||
ref container,
|
||||
ref memory_max,
|
||||
ref cpu_quota,
|
||||
} => {
|
||||
validate_container_system_name(container)?;
|
||||
let dir = format!("/run/systemd/system/container@{container}.service.d");
|
||||
std::fs::create_dir_all(&dir).with_context(|| format!("create {dir}"))?;
|
||||
let path = format!("{dir}/hyperhive-limits.conf");
|
||||
let content = format!("[Service]\nMemoryMax={memory_max}\nCPUQuota={cpu_quota}\n");
|
||||
std::fs::write(&path, content).with_context(|| format!("write {path}"))?;
|
||||
Ok((String::new(), String::new()))
|
||||
}
|
||||
} => write_resource_limits(container, memory_max, cpu_quota),
|
||||
|
||||
PrivRequest::RemoveServiceDropin { ref container } => {
|
||||
validate_container_system_name(container)?;
|
||||
|
|
@ -261,21 +231,7 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
|||
Ok((String::new(), String::new()))
|
||||
}
|
||||
|
||||
PrivRequest::DaemonReload => {
|
||||
let out = Command::new("systemctl")
|
||||
.arg("daemon-reload")
|
||||
.output()
|
||||
.await
|
||||
.context("invoke systemctl daemon-reload")?;
|
||||
if !out.status.success() {
|
||||
bail!(
|
||||
"systemctl daemon-reload failed ({}): {}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
);
|
||||
}
|
||||
Ok((String::new(), String::new()))
|
||||
}
|
||||
PrivRequest::DaemonReload => daemon_reload().await,
|
||||
|
||||
PrivRequest::ReloadGatewayNginx => sync_gateway_nginx().await,
|
||||
|
||||
|
|
@ -327,29 +283,87 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
|||
}
|
||||
|
||||
PrivRequest::RestartMatrixDaemon { ref agent_name } => {
|
||||
validate_agent_name(agent_name)?;
|
||||
let machine = format!("--machine=h-{agent_name}");
|
||||
let unit = "hive-matrix-daemon.service";
|
||||
let out = Command::new("systemctl")
|
||||
.args([&machine, "restart", unit])
|
||||
.output()
|
||||
.await
|
||||
.with_context(|| format!("systemctl restart {unit} in container h-{agent_name}"))?;
|
||||
if !out.status.success() {
|
||||
bail!(
|
||||
"systemctl restart {unit} in h-{agent_name} exited {}: {}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
);
|
||||
}
|
||||
Ok((
|
||||
String::from_utf8_lossy(&out.stdout).into_owned(),
|
||||
String::from_utf8_lossy(&out.stderr).into_owned(),
|
||||
))
|
||||
restart_matrix_daemon(agent_name).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared body for `CreateContainer` / `UpdateContainer`: validate the
|
||||
/// name, build the `nixos-container <verb> … --flake <ref>` argv, and
|
||||
/// run it (streaming line events to `writer` when `stream` is set).
|
||||
async fn container_flake_action(
|
||||
verb: &str,
|
||||
name: &str,
|
||||
stream: bool,
|
||||
writer: &mut OwnedWriteHalf,
|
||||
) -> Result<(String, String)> {
|
||||
validate_container_name(name)?;
|
||||
let flake_ref = agent_flake_ref(name);
|
||||
let args = [verb, &container_system_name(name), "--flake", &flake_ref];
|
||||
if stream {
|
||||
container_run_streaming(&args, writer).await
|
||||
} else {
|
||||
container_run(&args).await
|
||||
}
|
||||
}
|
||||
|
||||
/// `WriteResourceLimits` — drop a systemd `MemoryMax`/`CPUQuota`
|
||||
/// override into the container service's drop-in dir.
|
||||
fn write_resource_limits(
|
||||
container: &str,
|
||||
memory_max: &str,
|
||||
cpu_quota: &str,
|
||||
) -> Result<(String, String)> {
|
||||
validate_container_system_name(container)?;
|
||||
let dir = format!("/run/systemd/system/container@{container}.service.d");
|
||||
std::fs::create_dir_all(&dir).with_context(|| format!("create {dir}"))?;
|
||||
let path = format!("{dir}/hyperhive-limits.conf");
|
||||
let content = format!("[Service]\nMemoryMax={memory_max}\nCPUQuota={cpu_quota}\n");
|
||||
std::fs::write(&path, content).with_context(|| format!("write {path}"))?;
|
||||
Ok((String::new(), String::new()))
|
||||
}
|
||||
|
||||
/// `DaemonReload` — `systemctl daemon-reload` on the host.
|
||||
async fn daemon_reload() -> Result<(String, String)> {
|
||||
let out = Command::new("systemctl")
|
||||
.arg("daemon-reload")
|
||||
.output()
|
||||
.await
|
||||
.context("invoke systemctl daemon-reload")?;
|
||||
if !out.status.success() {
|
||||
bail!(
|
||||
"systemctl daemon-reload failed ({}): {}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
);
|
||||
}
|
||||
Ok((String::new(), String::new()))
|
||||
}
|
||||
|
||||
/// `RestartMatrixDaemon` — restart the matrix daemon unit inside the
|
||||
/// agent's container.
|
||||
async fn restart_matrix_daemon(agent_name: &str) -> Result<(String, String)> {
|
||||
validate_agent_name(agent_name)?;
|
||||
let machine = format!("--machine=h-{agent_name}");
|
||||
let unit = "hive-matrix-daemon.service";
|
||||
let out = Command::new("systemctl")
|
||||
.args([&machine, "restart", unit])
|
||||
.output()
|
||||
.await
|
||||
.with_context(|| format!("systemctl restart {unit} in container h-{agent_name}"))?;
|
||||
if !out.status.success() {
|
||||
bail!(
|
||||
"systemctl restart {unit} in h-{agent_name} exited {}: {}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
);
|
||||
}
|
||||
Ok((
|
||||
String::from_utf8_lossy(&out.stdout).into_owned(),
|
||||
String::from_utf8_lossy(&out.stderr).into_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Shared helper for `WriteAgentForgeToken` and `WriteAgentMatrixToken`.
|
||||
/// Writes `content` to `AGENT_STATE_ROOT/<agent_name>/state/<filename>`,
|
||||
/// chowns to the agent user (derived from the state dir's existing owner),
|
||||
|
|
|
|||
Loading…
Reference in a new issue