139 lines
5.2 KiB
Rust
139 lines
5.2 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 (`agent <name> <spawn|kill|
|
|
//! rebuild|restart|choom|…>`, `list-agents`, `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 couple of verbs work off local host state
|
|
//! directly instead (`wg` / `peer-config` read the mesh key + TLS CA), so
|
|
//! they don't need the socket. `agent <name> choom` execs into a container
|
|
//! rather than asking the daemon to do anything, but still uses the socket
|
|
//! for its "is this an agent?" pre-flight — that answer lives in a
|
|
//! directory only the daemon's user can read.
|
|
//!
|
|
//! 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 node progress rendering (`wait_for_nodes` + 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;
|
|
mod github;
|
|
mod watch;
|
|
use github::github_set_token;
|
|
mod forge;
|
|
use forge::{forge_create_user, forge_reconcile_config};
|
|
mod agents;
|
|
use agents::{agents_list, run_agent};
|
|
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::Agent { name, cmd } => run_agent(&socket, &name, cmd).await,
|
|
Cmd::ListAgents { json } => agents_list(&socket, json).await,
|
|
Cmd::QuotaEnable => quota::quota_enable(&socket).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::MarkdownDocs => {
|
|
print!("{}", clap_markdown::help_markdown::<Cli>());
|
|
Ok(())
|
|
}
|
|
Cmd::Open { target } => open_url(&socket, target).await,
|
|
Cmd::Completions { shell } => {
|
|
generate_completions(shell);
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|