hivectl: add operator-facing host CLI with forge + matrix create-user verbs (#655)
This commit is contained in:
parent
2e40e1782a
commit
53447842bc
6 changed files with 214 additions and 41 deletions
133
hive-c0re/src/bin/hivectl.rs
Normal file
133
hive-c0re/src/bin/hivectl.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
//! `hivectl` — operator-facing host CLI for hyperhive (#655).
|
||||
//!
|
||||
//! Sibling binary to the `hive-c0re` daemon. Where `hive-c0re`'s
|
||||
//! subcommands focus on the broker / approval / topology surface
|
||||
//! (`spawn`, `kill`, `rebuild`, `approve` …), `hivectl` covers
|
||||
//! host-side administration that doesn't need the daemon running —
|
||||
//! starting with manual user provisioning on the bundled forge +
|
||||
//! matrix containers when c0re's automatic boot-time sweep is
|
||||
//! inappropriate (recovery, debugging, single-shot reprovisioning,
|
||||
//! verifying the registration token path post-#644).
|
||||
//!
|
||||
//! Verbs read configuration off the same on-disk paths c0re uses
|
||||
//! (`/var/lib/hyperhive/forge-core-token`,
|
||||
//! `/var/lib/hyperhive/matrix-register-token`, per-agent state
|
||||
//! dirs) and reuse the `forge` / `matrix` modules from the
|
||||
//! `hive-c0re` lib — single source of truth, no duplication.
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
name = "hivectl",
|
||||
about = "hyperhive host CLI — operator-facing administration",
|
||||
long_about = "\
|
||||
Sibling to the `hive-c0re` daemon binary. Covers host-side admin \
|
||||
operations that don't go through the broker — manual user \
|
||||
provisioning on the bundled forge + matrix containers, plus future \
|
||||
recovery / debugging verbs.\
|
||||
"
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
cmd: Cmd,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Cmd {
|
||||
/// Forgejo user provisioning. Manual entry point to the same
|
||||
/// idempotent flow c0re runs automatically at boot
|
||||
/// (`forge::ensure_all`) — useful for recovery, ad-hoc reprovisioning,
|
||||
/// or single-agent fixes without bouncing the daemon.
|
||||
Forge {
|
||||
#[command(subcommand)]
|
||||
cmd: ForgeCmd,
|
||||
},
|
||||
/// matrix-tuwunel user provisioning. Manual entry point to the same
|
||||
/// idempotent flow c0re runs automatically at boot
|
||||
/// (`matrix::ensure_all`) — useful when the boot-time sweep skipped
|
||||
/// an agent (e.g. matrix container wasn't up yet) or to re-register
|
||||
/// after wiping a token file.
|
||||
Matrix {
|
||||
#[command(subcommand)]
|
||||
cmd: MatrixCmd,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum ForgeCmd {
|
||||
/// Create or refresh the Forgejo account + token for `<name>`.
|
||||
/// Idempotent: skips user creation when the account exists,
|
||||
/// skips token mint when the token file is already populated.
|
||||
/// To force re-minting, delete the token file at
|
||||
/// `/var/lib/hyperhive/agents/<name>/state/forge-token`.
|
||||
CreateUser {
|
||||
/// Container/agent name (the `<name>` in `h-<name>`; manager
|
||||
/// agent uses the literal `manager`).
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum MatrixCmd {
|
||||
/// Create or refresh the matrix account + access token for `<name>`.
|
||||
/// Idempotent: skips registration entirely when the token file is
|
||||
/// already populated. To force re-registration, delete the token
|
||||
/// file at `/var/lib/hyperhive/agents/<name>/state/matrix-token`.
|
||||
CreateUser {
|
||||
/// Container/agent name.
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[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();
|
||||
match cli.cmd {
|
||||
Cmd::Forge { cmd } => match cmd {
|
||||
ForgeCmd::CreateUser { name } => forge_create_user(&name).await,
|
||||
},
|
||||
Cmd::Matrix { cmd } => match cmd {
|
||||
MatrixCmd::CreateUser { name } => matrix_create_user(&name).await,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn forge_create_user(name: &str) -> Result<()> {
|
||||
if !hive_c0re::forge::is_present().await {
|
||||
bail!(
|
||||
"hive-forge container not running — start it (services.hyperhive.forge.enable = true) before provisioning forge users"
|
||||
);
|
||||
}
|
||||
hive_c0re::forge::ensure_user_for(name)
|
||||
.await
|
||||
.with_context(|| format!("forge create-user {name}"))?;
|
||||
println!("forge: provisioned user '{name}' (idempotent)");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn matrix_create_user(name: &str) -> Result<()> {
|
||||
if !hive_c0re::matrix::is_present().await {
|
||||
bail!(
|
||||
"hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) before provisioning matrix users"
|
||||
);
|
||||
}
|
||||
let register_token =
|
||||
hive_c0re::matrix::ensure_register_token().context("read matrix register token")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.context("build reqwest client")?;
|
||||
hive_c0re::matrix::ensure_user_for(&client, name, ®ister_token)
|
||||
.await
|
||||
.with_context(|| format!("matrix create-user {name}"))?;
|
||||
println!("matrix: provisioned user '{name}' (idempotent)");
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Reference in a new issue