feat(#1897): hivectl peer-config verb to generate a federation peer block
Add `hivectl peer-config --domain <this-hive-domain>`: prints the `services.hyperhive.swarm.peers."<domain>"` nix block a peer operator pastes to federate with this hive. Emits `caCert = ./<hive>-ca.pem` plus a `cp /var/lib/hive-tls/ca.pem ./<hive>-ca.pem` line when this hive serves a self-signed CA (the cert file exists); omits caCert for ACME/public-CA hives (trusted by the default bundle). Includes the WireGuard public key when the mesh key exists, and the wireguardAddress/Endpoint passed via flags. `wg init` gains an optional --domain; when set it calls peer-config at the end, so a fresh mesh setup prints the hand-over block in one command. Pure output — reads local state (TLS CA cert presence, wg key), never mutates. Regenerated docs/tools/hivectl-cli.md. Closes #1897.
This commit is contained in:
parent
156ca70b1e
commit
8464cb95cb
2 changed files with 117 additions and 3 deletions
|
|
@ -24,6 +24,7 @@ This document contains the help content for the `hivectl` command-line program.
|
|||
* [`hivectl wg init`↴](#hivectl-wg-init)
|
||||
* [`hivectl wg peer`↴](#hivectl-wg-peer)
|
||||
* [`hivectl wg status`↴](#hivectl-wg-status)
|
||||
* [`hivectl peer-config`↴](#hivectl-peer-config)
|
||||
* [`hivectl choom`↴](#hivectl-choom)
|
||||
* [`hivectl stop`↴](#hivectl-stop)
|
||||
* [`hivectl start`↴](#hivectl-start)
|
||||
|
|
@ -49,6 +50,7 @@ Sibling to the `hive-c0re` daemon binary. Covers host-side admin operations that
|
|||
* `gateway` — Gateway htpasswd user management. Add, remove, or list users in an htpasswd file used by the gateway's HTTP Basic auth (`services.hyperhive.gateway.auth`). Credentials are stored as `BCrypt` hashes — no extra service or PAM required
|
||||
* `agents` — Agent container management. Requires the hive-c0re daemon to be running (connects to the host admin socket)
|
||||
* `wg` — WireGuard inter-hive mesh setup helpers (`services.hyperhive.swarm`)
|
||||
* `peer-config` — Generate the federation peer-config block for THIS hive — the nix a peer operator pastes into their `services.hyperhive.swarm.peers` to trust + reach this hive. Emits `caCert` (+ a `cp` line for the cert) when this hive serves a self-signed CA, the WireGuard public key when the mesh key exists, and the `wireguard{Address,Endpoint}` you pass. Pure output — reads local state (the TLS CA cert, the wg key), never mutates. `wg init` calls this at the end when given a `--domain`, so a fresh mesh setup prints the hand-over block too
|
||||
* `choom` — Open an interactive Claude session inside an agent container
|
||||
* `stop` — Stop containers hive-wide in one operator action. Bare `hivectl stop` stops **everything** — all sub-agents plus the ci, forge, gateway, and matrix infra containers. Narrow it with scope flags: `--agents` (all sub-agents), `--ci` / `--forge` / `--gateway` / `--matrix` (named infra), and `--agent <name>` (repeatable) for specific sub-agents. Flags are additive (e.g. `--agents --matrix`). Requires the hive-c0re daemon (connects to the host admin socket). hive-c0re itself is never stopped — it services the request
|
||||
* `start` — Start containers hive-wide — the inverse of `hivectl stop`. Bare `hivectl start` starts everything back up; the same scope flags as `stop` narrow it (`--agents`, `--ci`, `--forge`, `--gateway`, `--matrix`, `--agent <name>`). Requires the hive-c0re daemon
|
||||
|
|
@ -315,6 +317,7 @@ Generate (if absent) this hive's WireGuard private key, print its public key, an
|
|||
###### **Options:**
|
||||
|
||||
* `--address <ADDRESS>` — This hive's mesh address (e.g. `10.42.0.1/32`) to bake into the printed snippet. Omit to get a placeholder you fill in
|
||||
* `--domain <DOMAIN>` — This hive's DNS domain. When set, `init` also prints the `peer-config` block peers paste to federate with this hive (CA + this mesh key), so setup is one command. Omit to skip that and just enable the mesh locally
|
||||
|
||||
|
||||
|
||||
|
|
@ -344,6 +347,20 @@ Show the live mesh interface state (`wg show wg-hive`). Requires the mesh to be
|
|||
|
||||
|
||||
|
||||
## `hivectl peer-config`
|
||||
|
||||
Generate the federation peer-config block for THIS hive — the nix a peer operator pastes into their `services.hyperhive.swarm.peers` to trust + reach this hive. Emits `caCert` (+ a `cp` line for the cert) when this hive serves a self-signed CA, the WireGuard public key when the mesh key exists, and the `wireguard{Address,Endpoint}` you pass. Pure output — reads local state (the TLS CA cert, the wg key), never mutates. `wg init` calls this at the end when given a `--domain`, so a fresh mesh setup prints the hand-over block too
|
||||
|
||||
**Usage:** `hivectl peer-config [OPTIONS] --domain <DOMAIN>`
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `--domain <DOMAIN>` — This hive's DNS domain — the `swarm.peers` attrset key the peer declares. Required: hivectl has no other source for it
|
||||
* `--wg-address <WG_ADDRESS>` — This hive's WireGuard mesh address (e.g. `10.42.0.1/32`), emitted as `wireguardAddress`. Omit when not running the mesh
|
||||
* `--wg-endpoint <WG_ENDPOINT>` — This hive's public WireGuard endpoint (`host:port`), emitted as `wireguardEndpoint`. Omit when peers dial in / no mesh
|
||||
|
||||
|
||||
|
||||
## `hivectl choom`
|
||||
|
||||
Open an interactive Claude session inside an agent container.
|
||||
|
|
|
|||
|
|
@ -89,6 +89,28 @@ enum Cmd {
|
|||
#[command(subcommand)]
|
||||
cmd: WgCmd,
|
||||
},
|
||||
/// Generate the federation peer-config block for THIS hive — the nix
|
||||
/// a peer operator pastes into their `services.hyperhive.swarm.peers`
|
||||
/// to trust + reach this hive. Emits `caCert` (+ a `cp` line for the
|
||||
/// cert) when this hive serves a self-signed CA, the WireGuard public
|
||||
/// key when the mesh key exists, and the `wireguard{Address,Endpoint}`
|
||||
/// you pass. Pure output — reads local state (the TLS CA cert, the wg
|
||||
/// key), never mutates. `wg init` calls this at the end when given a
|
||||
/// `--domain`, so a fresh mesh setup prints the hand-over block too.
|
||||
PeerConfig {
|
||||
/// This hive's DNS domain — the `swarm.peers` attrset key the peer
|
||||
/// declares. Required: hivectl has no other source for it.
|
||||
#[arg(long)]
|
||||
domain: String,
|
||||
/// This hive's WireGuard mesh address (e.g. `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>,
|
||||
},
|
||||
/// Open an interactive Claude session inside an agent container.
|
||||
///
|
||||
/// Replaces the current process with `machinectl shell
|
||||
|
|
@ -429,6 +451,12 @@ enum WgCmd {
|
|||
/// printed snippet. Omit to get a placeholder you fill in.
|
||||
#[arg(long)]
|
||||
address: Option<String>,
|
||||
/// This hive's DNS domain. When set, `init` also prints the
|
||||
/// `peer-config` block peers paste to federate with this hive
|
||||
/// (CA + this mesh key), so setup is one command. Omit to skip
|
||||
/// that and just enable the mesh locally.
|
||||
#[arg(long)]
|
||||
domain: Option<String>,
|
||||
},
|
||||
/// Print the nix snippet to add a peer hive to the mesh. Pure output —
|
||||
/// paste it into this hive's config. Get `<pubkey>` from the peer's
|
||||
|
|
@ -562,7 +590,7 @@ async fn main() -> Result<()> {
|
|||
AgentsCmd::RestartAll => agents_restart_all(&socket).await,
|
||||
},
|
||||
Cmd::Wg { cmd } => match cmd {
|
||||
WgCmd::Init { address } => wg_init(address.as_deref()),
|
||||
WgCmd::Init { address, domain } => wg_init(address.as_deref(), domain.as_deref()),
|
||||
WgCmd::Peer {
|
||||
domain,
|
||||
pubkey,
|
||||
|
|
@ -574,6 +602,14 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
WgCmd::Status => wg_status(),
|
||||
},
|
||||
Cmd::PeerConfig {
|
||||
domain,
|
||||
wg_address,
|
||||
wg_endpoint,
|
||||
} => {
|
||||
peer_config(&domain, wg_address.as_deref(), wg_endpoint.as_deref());
|
||||
Ok(())
|
||||
}
|
||||
Cmd::Stop { scope, graceful } => stop(&socket, scope.to_scope(), graceful).await,
|
||||
Cmd::Start { scope } => start(&socket, scope.to_scope()).await,
|
||||
Cmd::Restart { scope, graceful } => restart(&socket, scope.to_scope(), graceful).await,
|
||||
|
|
@ -612,9 +648,18 @@ const WG_KEY_PATH: &str = "/etc/wireguard/hive.key";
|
|||
/// The mesh interface name hive-c0re's nix module brings up.
|
||||
const WG_INTERFACE: &str = "wg-hive";
|
||||
|
||||
/// Host path of this hive's self-signed CA cert (matches the
|
||||
/// `services.hyperhive.tls.stateDir` default in hive-tls.nix). Its
|
||||
/// existence means the gateway serves a self-signed, hive-CA-signed leaf,
|
||||
/// so a federating peer needs this CA via `swarm.peers.<d>.caCert`. Absent
|
||||
/// = ACME / operator cert (trusted by the default CA bundle, no `caCert`).
|
||||
const HIVE_TLS_CA_PATH: &str = "/var/lib/hive-tls/ca.pem";
|
||||
|
||||
/// `wg init` — generate (if absent) the hive's WireGuard key, print its
|
||||
/// public key + the nix snippet to enable the mesh.
|
||||
fn wg_init(address: Option<&str>) -> Result<()> {
|
||||
/// public key + the nix snippet to enable the mesh. When `domain` is set,
|
||||
/// also prints the `peer-config` block peers paste to federate with this
|
||||
/// hive (so a fresh setup is one command).
|
||||
fn wg_init(address: Option<&str>, domain: Option<&str>) -> Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
let key_path = Path::new(WG_KEY_PATH);
|
||||
if key_path.exists() {
|
||||
|
|
@ -657,6 +702,13 @@ fn wg_init(address: Option<&str>) -> Result<()> {
|
|||
println!(" address = \"{addr}\";");
|
||||
println!(" # listenPort = 51820; # default");
|
||||
println!(" }};");
|
||||
|
||||
// When the operator names this hive's domain, also print the block a
|
||||
// peer pastes to federate with us (CA + this mesh key) — one-stop setup.
|
||||
if let Some(d) = domain {
|
||||
println!();
|
||||
peer_config(d, address, None);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -702,6 +754,51 @@ fn wg_peer(domain: &str, pubkey: &str, address: &str, endpoint: Option<&str>) {
|
|||
println!(" }};");
|
||||
}
|
||||
|
||||
/// `peer-config` — print the `swarm.peers."<domain>"` block a peer
|
||||
/// operator pastes to federate with THIS hive, plus a `cp` line for the
|
||||
/// CA when this hive is self-signed. Reads local state only (the TLS CA
|
||||
/// cert presence + the wg key); prints, never mutates.
|
||||
fn peer_config(domain: &str, wg_address: Option<&str>, wg_endpoint: Option<&str>) {
|
||||
let self_signed = Path::new(HIVE_TLS_CA_PATH).exists();
|
||||
// CA filename derived from the first DNS label so multiple peers'
|
||||
// certs don't collide in the operator's config dir.
|
||||
let ca_file = format!("{}-ca.pem", domain.split('.').next().unwrap_or("peer"));
|
||||
// WireGuard public key, when this hive has a mesh key. Best-effort:
|
||||
// a missing key or absent `wg` binary just omits the mesh lines.
|
||||
let wg_pub = std::fs::read(WG_KEY_PATH)
|
||||
.ok()
|
||||
.and_then(|k| wg_pubkey(&k).ok());
|
||||
|
||||
if self_signed {
|
||||
println!("# 1. copy this hive's CA cert next to the peer's config:");
|
||||
println!("cp {HIVE_TLS_CA_PATH} ./{ca_file}");
|
||||
println!();
|
||||
println!("# 2. paste into the peer hive's NixOS config:");
|
||||
} else {
|
||||
println!("# paste into the peer hive's NixOS config:");
|
||||
}
|
||||
println!("services.hyperhive.swarm.peers.\"{domain}\" = {{");
|
||||
if self_signed {
|
||||
println!(" caCert = ./{ca_file};");
|
||||
}
|
||||
if let Some(pk) = &wg_pub {
|
||||
println!(" wireguardPublicKey = \"{pk}\";");
|
||||
}
|
||||
if let Some(addr) = wg_address {
|
||||
println!(" wireguardAddress = \"{addr}\";");
|
||||
}
|
||||
if let Some(ep) = wg_endpoint {
|
||||
println!(" wireguardEndpoint = \"{ep}\";");
|
||||
}
|
||||
println!("}};");
|
||||
if !self_signed {
|
||||
println!(
|
||||
"# (this hive's cert chains to a public CA — no `caCert` needed; \
|
||||
it's trusted by the default bundle.)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `wg status` — show the live mesh interface (`wg show wg-hive`),
|
||||
/// inheriting stdout so the operator sees it directly.
|
||||
fn wg_status() -> Result<()> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue