The hive no longer sets an agent's CPU or memory cap: caps come from the swarm, else the hive-wide defaults. The container load tab was the only hive UI that set them, and its figures are already exported as OTEL metrics, so with the tab gone nothing wrote meta/resource-limits.json.
Removed: the file's reader and writer, the `hivectl agent set-limits` verb with its host-socket request, the `GET /api/container-resources` and `POST /api/resource-limits/{name}` dashboard endpoints, and the `cpu_quota`/`memory_max` fields of the container view that only the tab read. A leftover resource-limits.json is ignored.
518 lines
19 KiB
Rust
518 lines
19 KiB
Rust
//! hivectl clap command tree: the `Cli` root, the top-level `Cmd`
|
|
//! verb enum, and every subcommand enum + shared args struct.
|
|
|
|
use clap::{Args, Parser, Subcommand};
|
|
use std::path::PathBuf;
|
|
|
|
#[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 matrix container, plus future \
|
|
recovery / debugging verbs.\
|
|
"
|
|
)]
|
|
pub struct Cli {
|
|
/// Path to the hive-c0re host admin socket, used by the daemon-assisted
|
|
/// verbs (`agent`, `list-agents`, `stop`, `start`). Global: accepted
|
|
/// before or after the subcommand. Verbs that don't talk to the daemon
|
|
/// ignore it.
|
|
#[arg(long, global = true, default_value = DEFAULT_HOST_SOCKET)]
|
|
pub(crate) socket: PathBuf,
|
|
#[command(subcommand)]
|
|
pub(crate) cmd: Cmd,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum Cmd {
|
|
/// Reconcile an agent's config between this hive and the forge.
|
|
///
|
|
/// A human's first SSO login to the forge makes their forge
|
|
/// account.
|
|
Forge {
|
|
#[command(subcommand)]
|
|
cmd: ForgeCmd,
|
|
},
|
|
/// matrix-tuwunel invites.
|
|
///
|
|
/// Manual invites into the hive Space and its rooms; c0re's periodic
|
|
/// matrix sweep provisions the Space, the chat room and the agents'
|
|
/// invites on its own.
|
|
Matrix {
|
|
#[command(subcommand)]
|
|
cmd: MatrixCmd,
|
|
},
|
|
/// Gateway htpasswd user management.
|
|
///
|
|
/// Add, remove, or list users for the gateway's HTTP Basic auth.
|
|
Gateway {
|
|
#[command(subcommand)]
|
|
cmd: GatewayCmd,
|
|
},
|
|
/// Lifecycle actions on ONE managed agent container. Needs the
|
|
/// hive-c0re daemon running.
|
|
///
|
|
/// Everything here targets a single named agent (`hivectl agent foo
|
|
/// restart`, `hivectl agent foo choom`, …) — anything that acts
|
|
/// hive-wide lives at the top level instead (`list-agents`,
|
|
/// `restart`/`stop`/`start` with a scope, `quota-enable`).
|
|
Agent {
|
|
/// Agent name (for example `damocles`, `iris`).
|
|
name: String,
|
|
#[command(subcommand)]
|
|
cmd: AgentCmd,
|
|
},
|
|
/// Show all managed agents with their status and technical state.
|
|
///
|
|
/// Global — not scoped to one agent, so it lives at the top level
|
|
/// rather than under `hivectl agent <name>`. Needs the hive-c0re
|
|
/// daemon running.
|
|
ListAgents {
|
|
/// Emit the raw JSON rows instead of the padded table (for
|
|
/// scripting). The table is the default human-readable shape.
|
|
#[arg(long)]
|
|
json: bool,
|
|
},
|
|
/// Enable btrfs qgroup accounting on the agent-state filesystem.
|
|
///
|
|
/// Global one-shot toggle (not per-agent), hence top-level rather than
|
|
/// under `hivectl agent <name>`. Idempotent — safe to re-run. Once
|
|
/// enabled, `hivectl agent <name> quota show`/`quota set` work.
|
|
QuotaEnable,
|
|
/// WireGuard inter-hive mesh setup helpers.
|
|
///
|
|
/// Generate this hive's mesh key and print the nix to enable the mesh,
|
|
/// add a peer, or inspect live interface state.
|
|
Wg {
|
|
#[command(subcommand)]
|
|
cmd: WgCmd,
|
|
},
|
|
/// Generate the federation peer-config block for THIS hive.
|
|
///
|
|
/// Prints the nix a peer operator pastes into their swarm config to
|
|
/// trust and reach this hive.
|
|
PeerConfig {
|
|
/// This hive's WireGuard mesh address (for example `10.42.0.1/32`),
|
|
/// emitted as `wireguardAddress`. Omit when not running the mesh.
|
|
#[arg(long)]
|
|
wg_address: Option<String>,
|
|
/// This hive's public WireGuard endpoint (`host:port`), emitted as
|
|
/// `wireguardEndpoint`. Omit when peers dial in / no mesh.
|
|
#[arg(long)]
|
|
wg_endpoint: Option<String>,
|
|
},
|
|
/// Stop containers hive-wide in one operator action.
|
|
///
|
|
/// Bare `hivectl stop` stops everything; scope flags narrow it to
|
|
/// specific sub-agents or infra containers.
|
|
Stop {
|
|
#[command(flatten)]
|
|
scope: ScopeArgs,
|
|
/// Gracefully quiesce each agent before stopping, instead of a
|
|
/// hard stop. Each agent gets a graceful-stop DAG on the job
|
|
/// queue: it signals the harness, which runs one stop-checkpoint
|
|
/// turn to flush durable `/state` and drains, then the DAG stops
|
|
/// the container (bounded by a 3-min timeout that falls back to a
|
|
/// hard stop). All drains overlap. Applies to agents only.
|
|
#[arg(long)]
|
|
graceful: bool,
|
|
/// Return immediately after hivectl queues the stop DAGs instead of
|
|
/// waiting for them with live per-node progress.
|
|
#[arg(long)]
|
|
no_wait: bool,
|
|
},
|
|
/// Start containers hive-wide — the inverse of `hivectl stop`.
|
|
///
|
|
/// Bare `hivectl start` restores the agents stopped by the last
|
|
/// broad-scope `stop` (or starts everything if none); scope flags
|
|
/// narrow it.
|
|
Start {
|
|
#[command(flatten)]
|
|
scope: ScopeArgs,
|
|
/// Return immediately after hivectl queues the start DAGs instead
|
|
/// of waiting for them with live per-node progress.
|
|
#[arg(long)]
|
|
no_wait: bool,
|
|
},
|
|
/// Restart containers hive-wide — `stop` then `start` over one scope.
|
|
///
|
|
/// Bare `hivectl restart` restarts everything; scope flags narrow it.
|
|
Restart {
|
|
#[command(flatten)]
|
|
scope: ScopeArgs,
|
|
/// Gracefully quiesce each agent on the stop half (see
|
|
/// `stop --graceful`). Applies to agents only.
|
|
#[arg(long)]
|
|
graceful: bool,
|
|
},
|
|
/// Print (and best-effort open in a browser) a hive web surface URL.
|
|
///
|
|
/// Resolves the URL from the running daemon so custom forge / matrix
|
|
/// domains work. Bare `hivectl open` opens the operator dashboard.
|
|
Open {
|
|
/// Which surface to open. Defaults to the operator dashboard.
|
|
#[arg(value_enum, default_value_t = OpenTarget::Home)]
|
|
target: OpenTarget,
|
|
},
|
|
/// Emit the full CLI reference as `CommonMark` to stdout.
|
|
///
|
|
/// Hidden tooling command used by the docs build to keep the published
|
|
/// `hivectl` reference in lockstep with the code.
|
|
#[command(hide = true)]
|
|
MarkdownDocs,
|
|
/// Generate a shell completion script for `hivectl` and print it to
|
|
/// stdout.
|
|
///
|
|
/// Supports bash, zsh, fish, elvish, and powershell. The NixOS module
|
|
/// already installs the zsh script system-wide; this is for ad-hoc or
|
|
/// other-shell use.
|
|
Completions {
|
|
/// Shell to emit completions for.
|
|
shell: clap_complete::Shell,
|
|
},
|
|
}
|
|
|
|
/// Which hive web surface `hivectl open` targets.
|
|
#[derive(Copy, Clone, Debug, clap::ValueEnum)]
|
|
pub enum OpenTarget {
|
|
/// The operator dashboard (`https://<domain>/`).
|
|
Home,
|
|
/// The forge (Forgejo) web UI.
|
|
Forge,
|
|
/// The matrix GUI (fluffychat).
|
|
Matrix,
|
|
}
|
|
|
|
/// Shared scope flags for `hivectl stop` / `hivectl start`. With no flag
|
|
/// set the verb targets **everything** (all sub-agents + every controllable
|
|
/// infra container). Setting any flag restricts to the selected classes,
|
|
/// additively.
|
|
// One bool per selectable container class, each mapping 1:1 to a clap flag;
|
|
// orthogonal toggles, not a state machine — hence the bools allow (mirrors
|
|
// `hive_host_sock::LifecycleScope`).
|
|
#[allow(clippy::struct_excessive_bools)]
|
|
#[derive(Args)]
|
|
pub struct ScopeArgs {
|
|
/// All sub-agent containers.
|
|
#[arg(long)]
|
|
agents: bool,
|
|
/// A specific sub-agent by name. Repeatable: `--agent a --agent b`.
|
|
#[arg(long = "agent", value_name = "NAME")]
|
|
agent: Vec<String>,
|
|
/// The forge container (`hive-forge`).
|
|
#[arg(long)]
|
|
forge: bool,
|
|
/// The gateway (`hive-gateway`) — nginx on the host, not a container.
|
|
#[arg(long)]
|
|
gateway: bool,
|
|
/// The matrix container (`hive-matrix`).
|
|
#[arg(long)]
|
|
matrix: bool,
|
|
}
|
|
|
|
impl ScopeArgs {
|
|
pub(crate) fn to_scope(&self) -> hive_host_sock::LifecycleScope {
|
|
hive_host_sock::LifecycleScope {
|
|
agents: self.agents,
|
|
agent_names: self.agent.clone(),
|
|
forge: self.forge,
|
|
gateway: self.gateway,
|
|
matrix: self.matrix,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum ForgeCmd {
|
|
/// Show + reconcile the divergence between an agent's local applied
|
|
/// config checkout and its forge `agent-configs/<agent>` main.
|
|
///
|
|
/// Always prints the diff first. `--from forge` resets the local
|
|
/// checkout to forge main (effective on the next deploy); `--from local`
|
|
/// isn't supported yet. With no `--from`, prompts for the direction.
|
|
ReconcileConfig {
|
|
/// Agent whose config branches to reconcile.
|
|
agent: String,
|
|
/// Which side to reconcile from. Omit it and hivectl prompts you after the diff.
|
|
#[arg(long, value_enum)]
|
|
from: Option<ReconcileFrom>,
|
|
/// Include the full diff (not just `--stat`) in the report.
|
|
#[arg(long)]
|
|
verbose: bool,
|
|
},
|
|
}
|
|
|
|
/// Which side to reconcile config branches from (`hivectl forge
|
|
/// reconcile-config --from`). Maps to [`hive_host_sock::ReconcileDirection`].
|
|
#[derive(Clone, Copy, clap::ValueEnum)]
|
|
pub enum ReconcileFrom {
|
|
/// Reset the local applied checkout to forge main.
|
|
Forge,
|
|
/// Advance forge main from local — not supported yet.
|
|
Local,
|
|
}
|
|
|
|
impl From<ReconcileFrom> for hive_host_sock::ReconcileDirection {
|
|
fn from(from: ReconcileFrom) -> Self {
|
|
match from {
|
|
ReconcileFrom::Forge => Self::Forge,
|
|
ReconcileFrom::Local => Self::Local,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum MatrixCmd {
|
|
/// Invite a matrix user to the hive Space, or a specific room with
|
|
/// `--room`. Idempotent.
|
|
Invite {
|
|
/// User to invite: a full id (`@mara:server`) or a bare
|
|
/// localpart (qualified with the homeserver's `server_name`).
|
|
user: String,
|
|
/// Target room id (`!abc:server`) or alias (`#name:server`).
|
|
/// Omit to invite to the hive Space.
|
|
#[arg(long)]
|
|
room: Option<String>,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum GatewayCmd {
|
|
/// Add a user or update an existing user's password in the gateway
|
|
/// htpasswd.
|
|
///
|
|
/// Use `--password-stdin` to keep the password out of shell history.
|
|
CreateUser {
|
|
/// Username to add or update.
|
|
username: String,
|
|
/// Set the password inline. WARNING: visible in shell history and
|
|
/// process listings — prefer `--password-stdin` for sensitive input.
|
|
/// Mutually exclusive with `--password-stdin`.
|
|
#[arg(long, conflicts_with = "password_stdin")]
|
|
password: Option<String>,
|
|
/// Read the password from stdin (single line, trailing newline
|
|
/// stripped). Mutually exclusive with `--password`.
|
|
#[arg(long)]
|
|
password_stdin: bool,
|
|
},
|
|
/// Remove a user from the gateway htpasswd.
|
|
DeleteUser {
|
|
/// Username to remove.
|
|
username: String,
|
|
},
|
|
/// List all gateway htpasswd usernames, one per line.
|
|
ListUsers,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum WgCmd {
|
|
/// Generate this hive's WireGuard key (if absent) and print its public
|
|
/// key plus the nix to enable the mesh.
|
|
Init {
|
|
/// This hive's mesh address (for example `10.42.0.1/32`) to bake into the
|
|
/// printed snippet. Omit to get a placeholder you fill in.
|
|
#[arg(long)]
|
|
address: Option<String>,
|
|
},
|
|
/// Print the nix to add a peer hive to the mesh.
|
|
Peer {
|
|
/// Peer hive's DNS domain (its `swarm.hives` entry's `domain`)
|
|
domain: String,
|
|
/// Peer's WireGuard public key (from its `hivectl wg init`).
|
|
#[arg(long)]
|
|
pubkey: String,
|
|
/// Peer's mesh address (for example `10.42.0.2/32`).
|
|
#[arg(long)]
|
|
address: String,
|
|
/// Peer's `host:port` endpoint (omit for a peer that only dials out,
|
|
/// for example one behind NAT — it must set an endpoint pointing back here).
|
|
#[arg(long)]
|
|
endpoint: Option<String>,
|
|
},
|
|
/// Show the live mesh interface state.
|
|
Status,
|
|
}
|
|
|
|
// Default host admin socket path. Shared with `hive-c0re`'s `main.rs`
|
|
// default via `hive_host_sock::HOST_SOCKET` — the daemon binds there
|
|
// and `hivectl` connects to it.
|
|
pub(crate) use hive_host_sock::HOST_SOCKET as DEFAULT_HOST_SOCKET;
|
|
|
|
/// Verbs under `hivectl agent <name> <verb>` — every one of these targets
|
|
/// the single agent named on the parent command, so none of them carry
|
|
/// their own `name` field.
|
|
#[derive(Subcommand)]
|
|
pub enum AgentCmd {
|
|
/// Stop and start this agent container without rebuilding config.
|
|
Restart {
|
|
/// Return immediately after hivectl queues the restart DAG.
|
|
#[arg(long)]
|
|
no_wait: bool,
|
|
},
|
|
/// Park this agent's turn loop, leaving the container running.
|
|
///
|
|
/// The harness stops driving turns but keeps serving its web UI and
|
|
/// MCP daemons, so the container, its mounts and its warm caches stay
|
|
/// up while it burns no tokens. Inbox messages queue unacked and the
|
|
/// backlog drains on `resume`. Sticky: it survives a restart, and
|
|
/// pausing a stopped agent makes it come up paused.
|
|
Pause {
|
|
/// Return immediately after hivectl queues the pause DAG instead of
|
|
/// waiting for the harness to acknowledge it.
|
|
#[arg(long)]
|
|
no_wait: bool,
|
|
},
|
|
/// Resume this paused agent — it drains whatever queued up while parked.
|
|
Resume,
|
|
/// Start this EXISTING agent container. Fails immediately if `name`
|
|
/// has no config/topology entry at all — it never attempts
|
|
/// first-time creation, which is swarm-level (`swarmctl agent create`).
|
|
Start {
|
|
/// Start (or leave) the agent paused: if it's currently down, hivectl
|
|
/// writes the pause marker before the container boots, so it comes
|
|
/// up paused instead of driving turns immediately. If it's already
|
|
/// running, this pauses it in place and doesn't attempt a
|
|
/// (re)start.
|
|
#[arg(long)]
|
|
paused: bool,
|
|
},
|
|
/// Gracefully stop this agent container: signal → drain → reconcile.
|
|
/// Never escalates to a hard kill — use `kill` for that.
|
|
Stop,
|
|
/// Hard-stop this managed container.
|
|
Kill,
|
|
/// Tear down this sub-agent container, keeping its state by default.
|
|
/// No undo.
|
|
Destroy {
|
|
/// Also wipe the agent's state dirs (config + creds + notes).
|
|
#[arg(long)]
|
|
purge: bool,
|
|
},
|
|
/// Apply pending config to this managed container.
|
|
Rebuild,
|
|
/// Open an interactive Claude session inside this agent's container.
|
|
///
|
|
/// A fresh session by default, or resume a prior one. Requires root
|
|
/// or `hive-admin` group membership, plus a running container.
|
|
Choom {
|
|
/// Resume a prior claude session by its session id, passed
|
|
/// through as `claude --resume <value>` (claude's `--continue`
|
|
/// takes no value — it resumes the cwd's latest session, which
|
|
/// is the harness's, so choom never uses it; this flag matches
|
|
/// the claude flag it maps to). Omit for a fresh blank session.
|
|
/// Requires a value.
|
|
#[arg(long = "resume", value_name = "SESSION")]
|
|
resume_session: Option<String>,
|
|
},
|
|
/// Follow this agent's live turn/tool-call event stream from the CLI.
|
|
///
|
|
/// Prints one compact line per event as they happen. `Ctrl-C` to stop.
|
|
/// Requires the agent to be running.
|
|
Watch,
|
|
/// This agent's disk accounting + optional quota via btrfs qgroups.
|
|
///
|
|
/// Needs `hivectl quota-enable` run once hive-wide first. No-op on
|
|
/// non-btrfs hosts.
|
|
Quota {
|
|
#[command(subcommand)]
|
|
cmd: AgentQuotaCmd,
|
|
},
|
|
/// btrfs subvolume management for this agent's state dir.
|
|
///
|
|
/// Upgrade an existing plain-dir agent's state into a btrfs subvolume
|
|
/// so it gains snapshots and per-subvol usage/quota.
|
|
Subvol {
|
|
#[command(subcommand)]
|
|
cmd: SubvolCmd,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum AgentQuotaCmd {
|
|
/// Report this agent's disk usage from btrfs qgroups.
|
|
Show,
|
|
/// Set or clear this agent's disk-usage quota.
|
|
Set {
|
|
/// Size cap (`5G`, `500M`, `1073741824`) or `none` to clear.
|
|
size: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum SubvolCmd {
|
|
/// Convert a plain-dir agent state root into a btrfs subvolume in
|
|
/// place, so it gains snapshots and per-subvol usage/quota.
|
|
///
|
|
/// Bounces the agent to migrate its state, so it requires `--yes`.
|
|
Upgrade {
|
|
/// Confirm: this stops the agent, migrates its state dir, and
|
|
/// restarts it. Required — the command refuses without it.
|
|
#[arg(long)]
|
|
yes: bool,
|
|
},
|
|
/// Read-only snapshots of this agent's state subvolume.
|
|
Snapshot {
|
|
#[command(subcommand)]
|
|
cmd: SnapshotCmd,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub enum SnapshotCmd {
|
|
/// Create a read-only snapshot (agent must already be a subvolume).
|
|
Create {
|
|
/// Snapshot label. Mandatory, and must start with `hive-` — the
|
|
/// prefix doubles as an allow-list hive-priv checks so only
|
|
/// hivectl-issued snapshot names can reach the `btrfs subvolume
|
|
/// snapshot` shellout.
|
|
#[arg(long)]
|
|
label: String,
|
|
},
|
|
/// Delete a snapshot created by `subvol snapshot create`.
|
|
Delete {
|
|
/// Snapshot label passed to `subvol snapshot create --label`.
|
|
label: String,
|
|
},
|
|
/// Export a snapshot to a local file via `btrfs send` — the
|
|
/// local-file half of the inter-hive migration transport (`push`
|
|
/// is the network half). Also useful standalone as a point-in-time
|
|
/// backup: a full send with no `--parent` produces a
|
|
/// self-contained archive of the snapshot.
|
|
Send {
|
|
/// Snapshot label passed to `subvol snapshot create --label`.
|
|
label: String,
|
|
/// Optional parent snapshot label for an incremental send
|
|
/// (`btrfs send -p`) — must be an existing, older snapshot of the
|
|
/// same agent. Omit for a full send.
|
|
#[arg(long)]
|
|
parent: Option<String>,
|
|
/// Destination filename (not a path) under the migrate-staging
|
|
/// dir. Refused if it already exists.
|
|
#[arg(long)]
|
|
dest: String,
|
|
},
|
|
/// Stream a snapshot to the swarm's snapshot store over the
|
|
/// WireGuard mesh — the network half of the migration transport.
|
|
///
|
|
/// Stages nothing locally: `btrfs send` writes straight into the
|
|
/// connection, so a multi-gigabyte agent needs no scratch space on
|
|
/// this host. The mesh is the authentication (cryptokey routing
|
|
/// binds the sender's address to its key), so there is no credential
|
|
/// to pass here.
|
|
///
|
|
/// No destination argument exists: a swarm has one store, read
|
|
/// from `services.hyperhive.swarm.snapshotStore`.
|
|
Push {
|
|
/// Snapshot label passed to `subvol snapshot create --label`.
|
|
label: String,
|
|
/// Optional parent snapshot label for an incremental send
|
|
/// (`btrfs send -p`) — must be an existing, older snapshot of the
|
|
/// same agent, and must already be present on the receiver.
|
|
/// Omit for a full send.
|
|
#[arg(long)]
|
|
parent: Option<String>,
|
|
},
|
|
}
|