The hive applies one `agentCpuQuota` / `agentMemoryMax` to every
container. That's the right default and the wrong ceiling: a build-heavy
agent needs headroom the other twelve don't, and raising the hive-wide
value to suit it hands that headroom to everyone.
Adds a per-agent override, persisted host-side and resolved per-field
against the hive defaults.
Follows the existing `meta/*.json` pattern (`capabilities.json`,
`tool-groups.json`): a host-side map read by `hive-c0re`, staged and
committed in the meta repo so every change lands in the audit trail.
```json
{ "sock": { "cpu_quota": "400%", "memory_max": "8G" } }
```
Fallback is **per field**, not per agent: an entry with only
`memory_max` leaves that agent on the hive-wide CPU quota. Absent file,
absent agent and absent field all resolve to the hive default, so the
feature is inert until someone opts an agent in.
Unlike the other meta files this one is **not** injected into the
container — a limit is something done *to* an agent, not something it
reads about itself.
```
hivectl agents set-limits sock --cpu-quota 400% --memory-max 8G
hivectl agents set-limits sock --reset
```
Values are validated before they're persisted: they go into a systemd
drop-in verbatim, and a typo there makes the unit fail to *start* —
turning a fat-fingered quota into a container that won't come back.
The command is declarative: each call replaces the agent's whole entry.
That makes a forgotten flag a silent revert, so a bare `set-limits
<name>` is rejected at the clap layer and clearing needs an explicit
`--reset`.
`ContainerView` gains `cpu_quota` / `memory_max`, both always populated:
there's no "unset" state to render, only "same as everyone else". They
reflect what the drop-in *says* — what the next start will enforce — not
a live cgroup reading.
The write goes through `meta::commit_resource_limits` rather than the
bare setter, so it's staged and committed under `META_LOCK`. Writing
without committing would leave the meta working tree dirty for the next
`prepare_deploy` to trip over.
Docs: `persistence.md` (the new meta file, and why it isn't injected),
`tools/hivectl.md` (the prose guide), `tools/hivectl-cli.md`
(regenerated clap dump).
Closes: internal/requests issue 25
564 lines
25 KiB
Rust
564 lines
25 KiB
Rust
//! Host admin socket wire types (`/run/hyperhive/host.sock`).
|
|
//!
|
|
//! The host-control protocol spoken between `hivectl` and the `hive-c0re`
|
|
//! daemon. Re-homed out of `hive-sh4re` so a standalone `hivectl` can depend
|
|
//! on just this protocol crate instead of the whole daemon crate. The shared
|
|
//! payload types it references (`Approval`, `AgentStatusRow`, `jobs::DagView`)
|
|
//! stay in `hive-sh4re`.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use hive_sh4re::{AgentStatusRow, Approval, jobs};
|
|
use hive_types::Ident;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// ── Shared hive layout facts ──────────────────────────────────────────────
|
|
// Paths + names both the `hive-c0re` daemon and the host-side `hivectl` CLI
|
|
// must agree on. Homed here (the protocol crate both sides already depend on)
|
|
// so a standalone `hivectl` can reach them without linking the whole daemon
|
|
// crate. `hive-c0re`'s `paths` / `lifecycle` modules re-export these, staying
|
|
// the daemon's single-source facade for its own callsites.
|
|
|
|
/// Default host admin socket (`/run/hyperhive/host.sock`). Used as the
|
|
/// `--socket` / `--host-socket` clap `default_value` in the daemon and
|
|
/// `hivectl`.
|
|
pub const HOST_SOCKET: &str = "/run/hyperhive/host.sock";
|
|
|
|
/// `agents/` — per-agent persistent state root (one subdir per agent,
|
|
/// bind-mounted into each container as `/agents/<name>`).
|
|
// nix: agent container bind-mount source (harness modules / agent.nix template) — must match.
|
|
// priv-sock: `hive_priv_sock::AGENT_STATE_ROOT` is the same value and must stay in sync;
|
|
// the privsep boundary prevents importing across the crate.
|
|
pub const AGENTS_ROOT: &str = "/var/lib/hyperhive/agents";
|
|
|
|
/// `agents/<name>` — one agent's persistent state root.
|
|
///
|
|
/// Takes a validated [`Ident`] (not a raw `&str`) so a per-agent state path
|
|
/// can never be built from an unvalidated name — the `../` traversal guard is
|
|
/// the type, enforced at the one place every agent path is rooted.
|
|
#[must_use]
|
|
pub fn agent_state_dir(name: &Ident) -> PathBuf {
|
|
PathBuf::from(AGENTS_ROOT).join(name.as_str())
|
|
}
|
|
|
|
/// `gateway/gateway.htpasswd` — nginx basic-auth credential store for the
|
|
/// operator dashboard vhost. `hivectl`'s `--htpasswd-file` clap default.
|
|
// nix: read by the gateway container's nginx (hive-gateway.nix) — must match.
|
|
pub const GATEWAY_HTPASSWD: &str = "/var/lib/hyperhive/gateway/gateway.htpasswd";
|
|
|
|
/// nspawn machine-name prefix for agent containers (`h-<name>`). A single
|
|
/// `starts_with(AGENT_PREFIX)` filter enumerates managed containers.
|
|
pub const AGENT_PREFIX: &str = "h-";
|
|
|
|
/// Map an agent's logical name to its nspawn machine name (`h-<name>`).
|
|
#[must_use]
|
|
pub fn container_name(name: &str) -> String {
|
|
format!("{AGENT_PREFIX}{name}")
|
|
}
|
|
|
|
/// Which way to reconcile an agent's config branches
|
|
/// ([`HostRequest::ReconcileConfigApply`]).
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ReconcileDirection {
|
|
/// Reset the local applied checkout to forge `main`.
|
|
Forge,
|
|
/// Advance forge `main` from local — not supported yet.
|
|
Local,
|
|
}
|
|
|
|
/// Requests on the host admin socket.
|
|
///
|
|
/// Wire format: one JSON object per line.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "cmd", rename_all = "snake_case")]
|
|
pub enum HostRequest {
|
|
/// Create and start a sub-agent container directly, bypassing the
|
|
/// approval queue. Privileged-context only. See
|
|
/// `docs/approvals.md::Approval kinds (wire shapes)`.
|
|
Spawn { name: Ident },
|
|
/// Submit a spawn request for the operator to approve. See
|
|
/// `docs/approvals.md::Approval kinds (wire shapes)` (`Spawn`).
|
|
RequestSpawn { name: Ident },
|
|
/// Stop a managed container (graceful).
|
|
Kill { name: Ident },
|
|
/// Tear down a sub-agent container, optionally purging state.
|
|
/// See `docs/approvals.md::Destroy semantics`.
|
|
Destroy {
|
|
name: Ident,
|
|
#[serde(default)]
|
|
purge: bool,
|
|
},
|
|
/// Stop and start a managed container without rebuilding config.
|
|
/// For "kick the container" operations that don't touch the flake or
|
|
/// nspawn flags. Mirrors `lifecycle::restart` (kill + start).
|
|
Restart { name: Ident },
|
|
/// Park (or un-park) an agent's turn loop without touching its
|
|
/// container: `hivectl pause|resume <name>` and the dashboard
|
|
/// toggle. Writes/removes the marker file the harness polls, so the
|
|
/// container stays up and keeps serving its web UI and MCP daemons
|
|
/// while burning no tokens. Inbox messages queue unacked and the
|
|
/// backlog drains on resume. Not a lifecycle DAG — it's a single
|
|
/// marker write, so it applies immediately and works on a stopped
|
|
/// container too (the pause is sticky and takes effect at next boot).
|
|
SetPaused {
|
|
name: Ident,
|
|
/// `true` pauses, `false` resumes. Idempotent either way.
|
|
paused: bool,
|
|
},
|
|
/// Stop and restart all managed containers in sequence. Convenience
|
|
/// wrapper for `hivectl agents restart-all`; iterates the live
|
|
/// container list and restarts each one.
|
|
RestartAll,
|
|
/// Restart containers hive-wide (`hivectl restart`), scoped like
|
|
/// `Stop`/`Start`. Each targeted agent rides exactly one DAG
|
|
/// server-side — the `Restart` template (mechanical stop + reconcile),
|
|
/// or, when `graceful` is set, the `GracefulRestart` template (signal →
|
|
/// drain → mechanical stop → reconcile) — rather than the old
|
|
/// client-side stop-then-start composition (and, briefly, a server-side
|
|
/// "submit stop DAG, await it, submit start DAG" composition): a
|
|
/// dropped `hivectl` connection mid-way, or a crash between the two
|
|
/// submits, used to leave the agent stopped with no automatic
|
|
/// follow-up, since nothing durable remembered "finish the restart"
|
|
/// once the calling process/turn was gone. `GracefulRestart` closes
|
|
/// that gap the same way `Restart` already does — one DAG, queued up
|
|
/// front, that owns the whole sequence. Infra containers have no
|
|
/// lease/DAG and restart synchronously (stop then start), same as
|
|
/// before. Scope semantics match `Stop`/`Start` (all-false =
|
|
/// everything).
|
|
RestartScoped {
|
|
#[serde(default)]
|
|
scope: LifecycleScope,
|
|
#[serde(default)]
|
|
graceful: bool,
|
|
},
|
|
/// Apply pending config to a managed container.
|
|
Rebuild { name: Ident },
|
|
/// List managed containers.
|
|
List,
|
|
/// List managed agents with their full status + technical state
|
|
/// (running / needs-login / needs-update / deployed sha / parent /
|
|
/// pending reminders) — the `hivectl agents list` roster view.
|
|
/// Reuses the dashboard's per-agent `ContainerView` aggregation.
|
|
AgentStatus,
|
|
/// Report this hive's canonical DNS domain
|
|
/// (`services.hyperhive.domain`) plus the browser-facing home /
|
|
/// forge / matrix URLs, daemon-sourced so custom forge/matrix
|
|
/// domains resolve correctly. Each URL is `None` when its subsystem
|
|
/// is unreachable from a browser (e.g. forge not behind the gateway,
|
|
/// matrix GUI disabled). Backs `hivectl open` + the federation
|
|
/// peer-config block (which reads the bare `domain`).
|
|
Urls,
|
|
/// Fetch one job-queue DAG by id — the polling surface behind
|
|
/// `hivectl`'s wait/progress loop. A multi-step op is a single DAG
|
|
/// (its whole graph in `nodes`). Result: [`HostResponse::dags`].
|
|
QueueDag { id: u64 },
|
|
/// List pending approval requests.
|
|
Pending,
|
|
/// Approve a pending request by id; the action runs immediately.
|
|
Approve { id: i64 },
|
|
/// Deny a pending request by id.
|
|
Deny { id: i64 },
|
|
/// Move an agent in the topology tree. `new_parent = None`
|
|
/// promotes the agent to root, `Some(name)` sets a new parent.
|
|
/// Validation rules + bind-mount caveat documented in
|
|
/// `docs/agent-hierarchy.md::Current state`.
|
|
SetParent {
|
|
child: Ident,
|
|
new_parent: Option<Ident>,
|
|
},
|
|
/// Declare an agent's CPU/memory overrides for the per-container
|
|
/// systemd drop-in, persisted to `meta/resource-limits.json`.
|
|
///
|
|
/// **Replace, not merge** — the pair given here becomes the agent's
|
|
/// entire entry, matching how tool-groups/capabilities are set. A
|
|
/// `None` field falls back to the hive-wide
|
|
/// `agentCpuQuota` / `agentMemoryMax`, so passing both as `None`
|
|
/// removes the entry entirely (reset to hive defaults).
|
|
///
|
|
/// Values are passed verbatim to systemd, so the server validates
|
|
/// their shape before persisting: a malformed `CPUQuota=` makes
|
|
/// systemd reject the unit, which would stop the container starting.
|
|
SetResourceLimits {
|
|
name: Ident,
|
|
cpu_quota: Option<String>,
|
|
memory_max: Option<String>,
|
|
},
|
|
/// Stop managed containers hive-wide in one operator action
|
|
/// (`hivectl stop`): agents plus the selected infra containers. `scope`
|
|
/// selects which classes; an all-false scope means **everything** (the
|
|
/// bare `hivectl stop`). `graceful` runs the per-agent quiesce (graceful
|
|
/// agent stop, issue tracker `graceful agent stop`) instead of a hard
|
|
/// stop. Agents stop via the lifecycle path; infra containers via the
|
|
/// host `container@<name>` units.
|
|
Stop {
|
|
#[serde(default)]
|
|
scope: LifecycleScope,
|
|
#[serde(default)]
|
|
graceful: bool,
|
|
},
|
|
/// Start managed containers hive-wide — the inverse of `Stop`
|
|
/// (`hivectl start`). Same `scope` semantics (all-false = everything);
|
|
/// no graceful flag (start is unconditional).
|
|
Start {
|
|
#[serde(default)]
|
|
scope: LifecycleScope,
|
|
},
|
|
/// Create or refresh a matrix account + access token for `name`.
|
|
/// The daemon runs the provisioning (it holds the register + admin
|
|
/// tokens and the matrix creds dir) and returns the operator-facing
|
|
/// results (persisted-token path for agents, or the freshly-minted
|
|
/// token + password for non-agent accounts) in
|
|
/// [`HostResponse::messages`]. `password` is resolved by the client
|
|
/// (inline flag or stdin) and `None` requests a random throwaway.
|
|
MatrixCreateUser {
|
|
name: Ident,
|
|
#[serde(default)]
|
|
password: Option<String>,
|
|
},
|
|
/// Provision (or re-provision) the hive system admin matrix account.
|
|
/// Daemon-side equivalent of `hivectl matrix sync-admin`.
|
|
MatrixSyncAdmin,
|
|
/// Promote a matrix user to homeserver admin via the admin API.
|
|
/// Uses the daemon's system admin token; `server_name` is discovered
|
|
/// from the running homeserver.
|
|
MatrixPromoteUser { name: Ident },
|
|
/// Reset a matrix user's password via the admin API and persist the
|
|
/// new password to the matrix creds dir so a later token mint can
|
|
/// re-login. Returns the outcome in [`HostResponse::messages`].
|
|
MatrixResetPassword { name: Ident },
|
|
/// Invite a matrix user to the hive Space (default) or a specific
|
|
/// `room`. Uses the daemon's admin token; idempotent
|
|
/// (already-member / already-invited is a no-op).
|
|
MatrixInvite {
|
|
user: String,
|
|
#[serde(default)]
|
|
room: Option<String>,
|
|
},
|
|
/// Create or refresh a forge account + API token for `name`. Daemon-side
|
|
/// equivalent of `hivectl forge create-user`: for an existing agent it
|
|
/// provisions the account and persists the token to `<notes>/forge-token`;
|
|
/// for a non-agent (operator/human) it mints a user and returns the token
|
|
/// in [`HostResponse::messages`]. `password` is resolved client-side
|
|
/// (inline flag or stdin) and only meaningful for non-agent accounts.
|
|
ForgeCreateUser {
|
|
name: Ident,
|
|
#[serde(default)]
|
|
password: Option<String>,
|
|
},
|
|
/// Report the divergence between agent `agent`'s local applied config
|
|
/// checkout and its forge `agent-configs/<agent>` `main`. The daemon
|
|
/// fetches forge `main` read-only and returns a human-readable report
|
|
/// (ahead/behind counts, commit-range summary, `git diff --stat`, and
|
|
/// the full diff when `verbose`) in [`HostResponse::messages`]. Read-only
|
|
/// — never mutates either side. Backs `hivectl forge reconcile-config`
|
|
/// (the diff it always shows first).
|
|
ReconcileConfigStatus {
|
|
agent: Ident,
|
|
#[serde(default)]
|
|
verbose: bool,
|
|
},
|
|
/// Reconcile agent `agent`'s config branches in `direction`.
|
|
/// `Forge` resets the local applied checkout to forge `main` (takes
|
|
/// effect on the next deploy); `Local` is not supported yet and returns
|
|
/// an [`HostResponse::error`] (advancing the protected forge `main` from
|
|
/// local needs lifting branch protection — resolve via a config PR).
|
|
/// Backs `hivectl forge reconcile-config --from <forge|local>`.
|
|
ReconcileConfigApply {
|
|
agent: Ident,
|
|
direction: ReconcileDirection,
|
|
},
|
|
/// Add or update a gateway HTTP-Basic user in the daemon's htpasswd file
|
|
/// (`paths::GATEWAY_HTPASSWD`). Daemon-side equivalent of `hivectl gateway
|
|
/// create-user`: the daemon bcrypt-hashes `password` (cost 12, remapped to
|
|
/// the `$2y$` prefix nginx accepts) and writes the entry, so hivectl never
|
|
/// touches the file. `password` is read client-side (inline flag or stdin).
|
|
/// Returns a confirmation line in [`HostResponse::messages`].
|
|
GatewayCreateUser { username: String, password: String },
|
|
/// Remove a gateway HTTP-Basic user from the daemon's htpasswd file.
|
|
/// Daemon-side equivalent of `hivectl gateway delete-user`. Errors if the
|
|
/// user isn't present so a no-op is detectable.
|
|
GatewayDeleteUser { username: String },
|
|
/// List the gateway HTTP-Basic usernames in the daemon's htpasswd file.
|
|
/// Daemon-side equivalent of `hivectl gateway list-users`; the usernames
|
|
/// come back in [`HostResponse::messages`], one per line.
|
|
GatewayListUsers,
|
|
/// Write (or overwrite) an agent's GitHub PAT under its state dir, via
|
|
/// the privileged helper. Daemon-side equivalent of `hivectl github
|
|
/// set-token`. `token` is resolved + non-empty-validated client-side
|
|
/// (inline flag or stdin); the daemon just persists it. Read live by
|
|
/// the agent's `gh` wrapper / git credential helper — no rebuild needed.
|
|
SetAgentGithubToken { agent: Ident, token: String },
|
|
/// Turn on btrfs qgroup accounting on the agent-state filesystem, via
|
|
/// the privileged helper. Daemon-side equivalent of `hivectl quota
|
|
/// enable`. Returns advisory lines in [`HostResponse::messages`].
|
|
QuotaEnable,
|
|
/// Set (or, with `limit = None`, clear) an agent's btrfs disk quota via
|
|
/// the privileged helper. Daemon-side equivalent of `hivectl quota
|
|
/// limit`; the size string is parsed to bytes client-side. Returns a
|
|
/// bare success — the client prints the confirmation from the value it
|
|
/// sent.
|
|
QuotaLimit {
|
|
name: Ident,
|
|
#[serde(default)]
|
|
limit: Option<u64>,
|
|
},
|
|
/// Report per-agent btrfs qgroup usage (`hivectl quota show [name]`).
|
|
/// The daemon resolves the agent set (all kept state dirs when `name`
|
|
/// is absent) and reads each subvolume's referenced/exclusive usage via
|
|
/// the privileged helper. Result rows land in [`HostResponse::quota`];
|
|
/// a "btrfs quota not enabled" error short-circuits the whole sweep as a
|
|
/// plain [`HostResponse::error`] so the client can print the enable hint.
|
|
QuotaShow {
|
|
#[serde(default)]
|
|
name: Option<Ident>,
|
|
},
|
|
/// Migrate an agent's plain state dir to a btrfs subvolume via the
|
|
/// privileged helper (`hivectl subvol upgrade`). The agent MUST already
|
|
/// be stopped — the client orchestrates stop → this → start. Returns a
|
|
/// bare success; the client prints its own progress lines.
|
|
UpgradeSubvolume { name: Ident },
|
|
/// Create a read-only btrfs snapshot of an agent's state subvolume
|
|
/// (`hivectl subvol snapshot create`). `label` is validated client-side
|
|
/// AND by hive-priv. Returns the snapshot's host path in
|
|
/// [`HostResponse::messages`].
|
|
SnapshotSubvolume { name: Ident, label: String },
|
|
/// Delete a snapshot created by `SnapshotSubvolume` (`hivectl subvol
|
|
/// snapshot delete`). Bare success; the client prints the confirmation.
|
|
DeleteSnapshot { name: Ident, label: String },
|
|
/// Export a snapshot to a local file via `btrfs send` (`hivectl subvol
|
|
/// snapshot send`). `dest` is a bare filename (hive-priv rejects paths);
|
|
/// `parent` names an optional parent snapshot for an incremental send.
|
|
/// Returns the written file's host path in [`HostResponse::messages`].
|
|
SendSnapshot {
|
|
name: Ident,
|
|
label: String,
|
|
#[serde(default)]
|
|
parent: Option<String>,
|
|
dest: String,
|
|
},
|
|
}
|
|
|
|
/// One agent's btrfs qgroup usage row — the [`HostRequest::QuotaShow`]
|
|
/// result unit. `referenced` / `exclusive` are byte counts when the agent
|
|
/// has a live qgroup; both are `None` (with an explanatory `note`) for a
|
|
/// plain-dir agent that has no subvolume to account. The client formats the
|
|
/// byte counts into human-readable columns.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QuotaRow {
|
|
pub agent: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub referenced: Option<u64>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub exclusive: Option<u64>,
|
|
/// Set instead of the byte counts when the agent has no qgroup data
|
|
/// (plain dir, or a non-"quota not enabled" read error), so the client
|
|
/// can print an inline explanation and keep going.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub note: Option<String>,
|
|
}
|
|
|
|
/// Selects which container classes a hive-wide [`HostRequest::Stop`] /
|
|
/// [`HostRequest::Start`] touches. An all-false scope means **everything**
|
|
/// (the bare `hivectl stop` / `start`); set individual fields to restrict
|
|
/// (e.g. only `agents` → just the sub-agent containers). `agents` covers
|
|
/// every managed sub-agent container; the rest are the named infra
|
|
/// containers (`hive-ci`, `hive-forge`, `hive-gateway`, `hive-matrix`).
|
|
//
|
|
// A flat bag of independent flag toggles — one bool per selectable
|
|
// container class — is exactly the right shape here: each maps 1:1 to a
|
|
// `hivectl` `--ci` / `--forge` / `--gateway` / `--matrix` flag, and they're
|
|
// orthogonal (any subset is valid), so a state machine or two-variant enums
|
|
// would only obscure the mapping. Hence the `struct_excessive_bools` allow.
|
|
#[allow(clippy::struct_excessive_bools)]
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct LifecycleScope {
|
|
/// All sub-agent containers (`--agents`).
|
|
#[serde(default)]
|
|
pub agents: bool,
|
|
/// Specific sub-agents by logical name (`--agent <name>`, repeatable).
|
|
/// Additive with the rest of the scope; redundant when `agents` is set
|
|
/// (which already covers every sub-agent).
|
|
#[serde(default)]
|
|
pub agent_names: Vec<String>,
|
|
#[serde(default)]
|
|
pub ci: bool,
|
|
#[serde(default)]
|
|
pub forge: bool,
|
|
#[serde(default)]
|
|
pub gateway: bool,
|
|
#[serde(default)]
|
|
pub matrix: bool,
|
|
}
|
|
|
|
impl LifecycleScope {
|
|
/// True when nothing is explicitly selected — interpreted as "all
|
|
/// classes" (the bare `hivectl stop` / `start` with no scope flags).
|
|
pub fn is_everything(&self) -> bool {
|
|
!(self.agents || self.ci || self.forge || self.gateway || self.matrix)
|
|
&& self.agent_names.is_empty()
|
|
}
|
|
}
|
|
|
|
/// This hive's canonical domain plus the browser-facing URLs for its
|
|
/// web surfaces — the `Urls` request result. Every field is `None` when
|
|
/// the corresponding surface can't be reached from a browser (domain
|
|
/// unset, forge not behind the gateway, matrix GUI disabled), so the CLI
|
|
/// can give a precise hint instead of opening a dead link.
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct HiveUrls {
|
|
/// Canonical hive domain (`services.hyperhive.domain`).
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub domain: Option<String>,
|
|
/// Operator dashboard root (`https://<domain>/`).
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub home: Option<String>,
|
|
/// Forge browser URL (`HIVE_FORGE_PUBLIC_URL`) — only the
|
|
/// behind-gateway public URL; `None` on direct-port forge deploys.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub forge: Option<String>,
|
|
/// Matrix GUI (fluffychat) browser URL — `None` when the matrix GUI
|
|
/// is disabled.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub matrix: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct HostResponse {
|
|
pub ok: bool,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub error: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub agents: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub approvals: Option<Vec<Approval>>,
|
|
/// `Urls` result — this hive's domain plus the browser-facing
|
|
/// home / forge / matrix URLs. `None` for every other request kind.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub urls: Option<HiveUrls>,
|
|
/// `AgentStatus` result — one row per managed agent with its
|
|
/// running/health flags + technical state. `None` for every other
|
|
/// request kind.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub agent_statuses: Option<Vec<AgentStatusRow>>,
|
|
/// Ids of the job-queue DAGs this request submitted (rebuild /
|
|
/// restart / power ops). Clients poll them via
|
|
/// [`HostRequest::QueueDag`]; `None` for non-submitting requests.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub queued_dags: Option<Vec<u64>>,
|
|
/// `QueueDag` result — the requested DAG followed by its live
|
|
/// fan-out children ([`jobs::DagView`]). Empty when the DAG has
|
|
/// been evicted from the queue's history tail.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub dags: Option<Vec<jobs::DagView>>,
|
|
/// Free-form operator-facing output lines the client prints verbatim
|
|
/// (one per line). Carries results a request produced daemon-side that
|
|
/// have no structured home — e.g. a freshly-minted matrix token, a
|
|
/// reset password, or an invited room id from the `Matrix*` requests.
|
|
/// Empty for requests that produce no such output.
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
pub messages: Vec<String>,
|
|
/// `QuotaShow` result — one row per agent with its btrfs qgroup usage.
|
|
/// `None` for every other request kind.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub quota: Option<Vec<QuotaRow>>,
|
|
}
|
|
|
|
impl HostResponse {
|
|
#[must_use]
|
|
pub fn success() -> Self {
|
|
Self {
|
|
ok: true,
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn error(message: impl Into<String>) -> Self {
|
|
Self {
|
|
ok: false,
|
|
error: Some(message.into()),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn list(agents: Vec<String>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
agents: Some(agents),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn pending(approvals: Vec<Approval>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
approvals: Some(approvals),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
/// `Urls` result — this hive's domain + browser-facing web URLs.
|
|
#[must_use]
|
|
pub fn urls(urls: HiveUrls) -> Self {
|
|
Self {
|
|
ok: true,
|
|
urls: Some(urls),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
/// `AgentStatus` result — one row per managed agent.
|
|
#[must_use]
|
|
pub fn agent_statuses(rows: Vec<AgentStatusRow>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
agent_statuses: Some(rows),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
/// A request that submitted job-queue DAGs — carries their ids for
|
|
/// the client's wait/progress loop.
|
|
#[must_use]
|
|
pub fn queued(ids: Vec<u64>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
queued_dags: Some(ids),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
/// `QueueDag` result — the polled DAG + its live children.
|
|
#[must_use]
|
|
pub fn dags(dags: Vec<jobs::DagView>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
dags: Some(dags),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
/// A success carrying operator-facing output lines the client prints
|
|
/// verbatim — the result shape for the `Matrix*` provisioning requests.
|
|
#[must_use]
|
|
pub fn messages(messages: Vec<String>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
messages,
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
/// `QuotaShow` result — one usage row per agent.
|
|
#[must_use]
|
|
pub fn quota(rows: Vec<QuotaRow>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
quota: Some(rows),
|
|
..Self::default()
|
|
}
|
|
}
|
|
}
|