Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hivectl/src/main.rs
atlas 766b1f71fb refactor(hivectl): move quota + subvol under agents
Both groups only ever act on a single managed agent's state dir, so
they belong in the `agents` namespace rather than as top-level verbs
next to `forge` / `matrix` / `wg`.

Renames `quota limit` -> `quota set`: the enclosing group already
carries the noun, so the bare verb matches the flat `set-parent` /
`set-limits` spelling without stuttering, and it removes the
`set-limits` (cpu/mem) vs `quota limit` (disk) ambiguity. Adds a
cross-pointer from `set-limits` to `agents quota`.

Handlers stay in their own modules; `run_agents` gains the reparenting
glue. Regenerates docs/tools/hivectl-cli.md.

Refs #2724
2026-07-26 18:26:27 +02:00

137 lines
4.9 KiB
Rust

//! `hivectl` — operator-facing host CLI for hyperhive.
//!
//! A thin client for the `hive-c0re` daemon: it speaks the host admin
//! socket protocol (`hive-host-sock`) and does NOT link the daemon crate.
//! Container lifecycle + the approval queue (`agents <spawn|kill|rebuild|
//! restart|…>`, `approvals <pending|approve|deny>`, `stop` / `start`) and
//! provisioning (`forge` / `matrix` / `github` / `gateway`) all forward to
//! the daemon, which owns the broker, the credentials, and the provisioning
//! logic — a running daemon is required for those. A few verbs work off
//! local host state directly instead (`wg` / `peer-config` read the mesh key
//! + TLS CA; `choom` execs into a container), so they don't need the socket.
//!
//! One module per subcommand family (see the `mod` list below); `main` is
//! just the clap parse + the top-level dispatch match.
use anyhow::Result;
use clap::Parser;
mod cli;
/// The host admin socket client (`request`), split out so it lives with
/// hivectl rather than in the daemon crate.
mod client;
/// Rebuild-queue DAG progress rendering (`wait_for_dags` + the spinner /
/// plain renderers), split out to keep this file manageable.
mod dag_progress;
use cli::{Cli, Cmd, ForgeCmd, GatewayCmd, GithubCmd, WgCmd};
mod completions;
mod quota;
mod util;
use completions::generate_completions;
mod gateway;
use gateway::{gateway_create_user, gateway_delete_user, gateway_list_users};
mod matrix;
use matrix::run_matrix_cmd;
mod open;
use open::open_url;
mod wg;
use wg::{peer_config, require_hive_domain, wg_init, wg_peer, wg_status};
mod choom;
use choom::choom;
mod github;
use github::github_set_token;
mod forge;
use forge::{forge_create_user, forge_reconcile_config};
mod agents;
use agents::run_agents;
mod power;
use power::{restart, start, stop};
mod approvals;
mod subvol;
use approvals::run_approvals;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
let cli = Cli::parse();
let socket = cli.socket;
match cli.cmd {
Cmd::Forge { cmd } => match cmd {
ForgeCmd::CreateUser {
name,
password,
password_stdin,
} => forge_create_user(&socket, &name, password.as_deref(), password_stdin).await,
ForgeCmd::ReconcileConfig {
agent,
from,
verbose,
} => forge_reconcile_config(&socket, &agent, from, verbose).await,
},
Cmd::Matrix { cmd } => run_matrix_cmd(&socket, cmd).await,
Cmd::Github { cmd } => match cmd {
GithubCmd::SetToken {
agent,
token,
token_stdin,
} => github_set_token(&socket, &agent, token, token_stdin).await,
},
Cmd::Gateway { cmd } => match cmd {
GatewayCmd::CreateUser {
username,
password,
password_stdin,
} => gateway_create_user(&socket, &username, password.as_deref(), password_stdin).await,
GatewayCmd::DeleteUser { username } => gateway_delete_user(&socket, &username).await,
GatewayCmd::ListUsers => gateway_list_users(&socket).await,
},
Cmd::Agents { cmd } => run_agents(&socket, cmd).await,
Cmd::Approvals { cmd } => run_approvals(&socket, cmd).await,
Cmd::Wg { cmd } => match cmd {
WgCmd::Init { address } => wg_init(&socket, address.as_deref()).await,
WgCmd::Peer {
domain,
pubkey,
address,
endpoint,
} => {
wg_peer(&domain, &pubkey, &address, endpoint.as_deref());
Ok(())
}
WgCmd::Status => wg_status(),
},
Cmd::PeerConfig {
wg_address,
wg_endpoint,
} => {
let domain = require_hive_domain(&socket).await?;
peer_config(&domain, wg_address.as_deref(), wg_endpoint.as_deref());
Ok(())
}
Cmd::Stop {
scope,
graceful,
no_wait,
} => stop(&socket, scope.to_scope(), graceful, no_wait).await,
Cmd::Start { scope, no_wait } => start(&socket, scope.to_scope(), no_wait).await,
Cmd::Restart { scope, graceful } => restart(&socket, scope.to_scope(), graceful).await,
Cmd::Choom {
name,
resume_session,
} => choom(&name, resume_session.as_deref()),
Cmd::MarkdownDocs => {
print!("{}", clap_markdown::help_markdown::<Cli>());
Ok(())
}
Cmd::Open { target } => open_url(&socket, target).await,
Cmd::Completions { shell } => {
generate_completions(shell);
Ok(())
}
}
}