feat(#2014): hivectl open verb + Urls host request for web surfaces
This commit is contained in:
parent
c2ba841932
commit
cae1dd8147
6 changed files with 200 additions and 32 deletions
|
|
@ -204,6 +204,19 @@ enum Cmd {
|
|||
#[command(subcommand)]
|
||||
cmd: SubvolCmd,
|
||||
},
|
||||
/// Print (and best-effort open in a browser) a hive web surface URL.
|
||||
///
|
||||
/// Resolves the URL from the running daemon (`HostRequest::Urls`), so
|
||||
/// custom forge / matrix domains work without guessing `forge.<domain>`.
|
||||
/// Prints the URL unconditionally — the reliable core, since the host
|
||||
/// is usually headless / driven over SSH where `xdg-open` is a no-op —
|
||||
/// then tries `xdg-open` as a convenience. 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 (not part of day-to-day operator admin):
|
||||
|
|
@ -227,6 +240,17 @@ enum Cmd {
|
|||
},
|
||||
}
|
||||
|
||||
/// Which hive web surface `hivectl open` targets.
|
||||
#[derive(Copy, Clone, Debug, clap::ValueEnum)]
|
||||
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,
|
||||
|
|
@ -628,6 +652,7 @@ async fn main() -> Result<()> {
|
|||
print!("{}", clap_markdown::help_markdown::<Cli>());
|
||||
Ok(())
|
||||
}
|
||||
Cmd::Open { target } => open_url(&socket, target).await,
|
||||
Cmd::Completions { shell } => {
|
||||
generate_completions(shell);
|
||||
Ok(())
|
||||
|
|
@ -635,6 +660,41 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
/// `open <home|forge|matrix>` — resolve the surface URL from the daemon,
|
||||
/// print it, then best-effort `xdg-open` it. Printing is the reliable
|
||||
/// core (headless / SSH hosts where no browser opener exists); the open
|
||||
/// is convenience on top, so a missing/failed `xdg-open` is not an error.
|
||||
async fn open_url(socket: &Path, target: OpenTarget) -> Result<()> {
|
||||
let urls = query_hive_urls(socket).await.context(
|
||||
"could not reach the hive-c0re daemon for URLs — is hive-c0re running? \
|
||||
(the socket is at /run/hyperhive/host.sock)",
|
||||
)?;
|
||||
let (url, hint) = match target {
|
||||
OpenTarget::Home => (
|
||||
urls.home,
|
||||
"the dashboard URL needs `services.hyperhive.domain` to be set",
|
||||
),
|
||||
OpenTarget::Forge => (
|
||||
urls.forge,
|
||||
"the public forge URL needs `services.hyperhive.forge.behindGateway = true`",
|
||||
),
|
||||
OpenTarget::Matrix => (
|
||||
urls.matrix,
|
||||
"the matrix GUI URL needs `services.hyperhive.matrix.gui.enable = true`",
|
||||
),
|
||||
};
|
||||
let url = url.with_context(|| format!("no URL available for this surface — {hint}"))?;
|
||||
println!("{url}");
|
||||
// Best-effort: many hosts are headless, so a missing opener or a
|
||||
// non-zero exit is fine — the URL is already printed.
|
||||
match std::process::Command::new("xdg-open").arg(&url).status() {
|
||||
Ok(status) if status.success() => {}
|
||||
Ok(status) => eprintln!("note: xdg-open exited with {status} (URL printed above)"),
|
||||
Err(e) => eprintln!("note: could not run xdg-open ({e}) (URL printed above)"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emit a shell completion script for `hivectl` to stdout. Walks the clap
|
||||
/// command tree (the single source of truth — same tree `markdown-docs`
|
||||
/// renders) so completions never drift from the actual verbs/flags.
|
||||
|
|
@ -658,14 +718,20 @@ const WG_INTERFACE: &str = "wg-hive";
|
|||
const HIVE_TLS_CA_PATH: &str = "/var/lib/hive-tls/ca.pem";
|
||||
|
||||
/// Best-effort query for this hive's domain from the running daemon
|
||||
/// (`HostRequest::HiveDomain`, which reads `HYPERHIVE_HIVE_DOMAIN` from
|
||||
/// c0re's service env). `None` when the daemon is unreachable or the
|
||||
/// domain is unset — callers decide whether that's fatal.
|
||||
/// (`HostRequest::Urls`, which reads `HYPERHIVE_HIVE_DOMAIN` from c0re's
|
||||
/// service env). `None` when the daemon is unreachable or the domain is
|
||||
/// unset — callers decide whether that's fatal.
|
||||
async fn query_hive_domain(socket: &Path) -> Option<String> {
|
||||
hive_c0re::client::request(socket, hive_sh4re::HostRequest::HiveDomain)
|
||||
query_hive_urls(socket).await.and_then(|u| u.domain)
|
||||
}
|
||||
|
||||
/// Best-effort query for this hive's domain + browser-facing web URLs
|
||||
/// (`HostRequest::Urls`). `None` when the daemon is unreachable.
|
||||
async fn query_hive_urls(socket: &Path) -> Option<hive_sh4re::HiveUrls> {
|
||||
hive_c0re::client::request(socket, hive_sh4re::HostRequest::Urls)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|r| r.domain)
|
||||
.and_then(|r| r.urls)
|
||||
}
|
||||
|
||||
/// Require this hive's domain from the daemon for snippet generation.
|
||||
|
|
|
|||
Loading…
Reference in a new issue