feat(hostd): varlink interfaced host controller to manage machine configuration and boot mgmt

This commit is contained in:
Lars Sjöström 2025-01-08 11:09:34 +01:00
parent 8e99ab4555
commit dd55bc50ed
No known key found for this signature in database
12 changed files with 726 additions and 17 deletions

1
hostd/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

24
hostd/Cargo.toml Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "hostd"
version.workspace = true
edition.workspace = true
[dependencies]
anyhow.workspace = true
tokio.workspace = true
sqlx = { version = "0.8.3", default-features = false, features = [
"macros", "migrate", "postgres", "runtime-tokio", "tls-rustls", "time", "uuid"
] }
dropshot.workspace = true
clap.workspace = true
slog.workspace = true
slog-async.workspace = true
tracing-slog.workspace = true
tracing.workspace = true
trace-request = { path = "../trace-request" }
schemars.workspace = true
serde.workspace = true
http.workspace = true
zbus_systemd = { version = "0.25701.0", features = ["hostname1", "sysupdate1", "network1", "portable1", "resolve1", "systemd1"] }
zbus = "5.4.0"
openssl = { version = "0.10.63", features = ["vendored"] }

17
hostd/src/api.rs Normal file
View file

@ -0,0 +1,17 @@
use anyhow::Result;
use dropshot::ApiDescription;
use std::sync::Arc;
use crate::context::ControllerContext;
use crate::machine;
use crate::sysupdate;
type ControllerApiDescription = ApiDescription<Arc<ControllerContext>>;
pub fn api() -> Result<ControllerApiDescription> {
let mut api = ControllerApiDescription::new();
api.register(machine::describe)?;
api.register(sysupdate::list_versions)?;
Ok(api)
}

75
hostd/src/bin/hostd.rs Normal file
View file

@ -0,0 +1,75 @@
use anyhow::{anyhow, Result};
use clap::Parser;
use dropshot::{ConfigDropshot, ServerBuilder};
use slog::Drain;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;
use tracing_slog::TracingSlogDrain;
use hostd::api;
use hostd::context::ControllerContext;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(
long = "telemetry-otlp-endpoint",
default_value = "http://localhost:4317",
env = "OTEL_EXPORTER_OTLP_ENDPOINT"
)]
otlp_endpoint: Option<String>,
#[arg(
long = "log-stderr",
short = 'v',
default_value = "false",
env = "LOG_TO_STDERR"
)]
log_stderr: bool,
#[arg(
long = "listen-address",
default_value = "127.0.0.1:9478",
env = "LISTEN_ADDRESS"
)]
listen_address: String,
#[arg(
long = "database-url",
default_value = "postgresql://localhost/patagia",
env = "DATABASE_URL"
)]
database_url: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
let config = ConfigDropshot {
bind_address: SocketAddr::from_str(&args.listen_address).unwrap(),
..Default::default()
};
let logger = {
let level_drain = slog::LevelFilter(TracingSlogDrain, slog::Level::Debug).fuse();
let async_drain = slog_async::Async::new(level_drain).build().fuse();
slog::Logger::root(async_drain, slog::o!())
};
let dbus = zbus::Connection::system().await.unwrap();
let ctx = ControllerContext::new(dbus);
let api = api::api()?;
println!("Listening on http://{}", config.bind_address);
ServerBuilder::new(api, Arc::new(ctx), logger)
.config(config)
.start()
.map_err(|e| anyhow!("Error starting server: {:?}", e))?
.await
.map_err(|e| anyhow!(e))
}

9
hostd/src/context.rs Normal file
View file

@ -0,0 +1,9 @@
pub struct ControllerContext {
pub dbus: zbus::Connection,
}
impl ControllerContext {
pub fn new(dbus: zbus::Connection) -> ControllerContext {
ControllerContext { dbus }
}
}

4
hostd/src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod api;
pub mod context;
pub mod machine;
pub mod sysupdate;

43
hostd/src/machine.rs Normal file
View file

@ -0,0 +1,43 @@
use dropshot::{endpoint, HttpError, HttpResponseOk, RequestContext};
use schemars::JsonSchema;
use serde::Serialize;
use std::sync::Arc;
use trace_request::trace_request;
use crate::context::ControllerContext;
/// Machine information
#[derive(Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
struct MachineInfo {
machine_id: String,
}
/// Fetch machine info
#[endpoint {
method = GET,
path = "/machine_info",
}]
#[trace_request]
pub async fn describe(
rqctx: RequestContext<Arc<ControllerContext>>,
) -> Result<HttpResponseOk<MachineInfo>, HttpError> {
let hostnamed = zbus_systemd::hostname1::HostnamedProxy::new(&rqctx.context().dbus)
.await
.unwrap();
let machine_id = hostnamed
.machine_id()
.await
.map_err(|e| match e {
err => HttpError::for_internal_error(format!("Error: {}", err)),
})?
// convert bytes to hex string
.iter()
.map(|&b| format!("{:02x}", b))
.collect();
let machine_info = MachineInfo { machine_id };
Ok(HttpResponseOk(machine_info))
}

52
hostd/src/sysupdate.rs Normal file
View file

@ -0,0 +1,52 @@
use dropshot::{endpoint, HttpError, HttpResponseOk, RequestContext};
use schemars::JsonSchema;
use serde::Serialize;
use std::sync::Arc;
use trace_request::trace_request;
use crate::context::ControllerContext;
const SYSUPDATE_HOST_PATH: &str = "/org/freedesktop/sysupdate1/target/host";
#[derive(Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
struct SysUpdate {
current_version: String,
versions: Vec<String>,
}
#[endpoint {
method = GET,
path = "/list_versions",
}]
#[trace_request]
pub async fn list_versions(
rqctx: RequestContext<Arc<ControllerContext>>,
) -> Result<HttpResponseOk<SysUpdate>, HttpError> {
let sysupdate_target = zbus_systemd::sysupdate1::TargetProxy::builder(&rqctx.context().dbus)
.path(SYSUPDATE_HOST_PATH)
.unwrap()
.build()
.await
.map_err(|e| match e {
err => HttpError::for_internal_error(format!("Error: {}", err)),
})?;
let versions = sysupdate_target.list(0).await.map_err(|e| match e {
err => {
println!("Error: {}", err);
HttpError::for_internal_error(format!("Error: {}", err))
}
})?;
let current_version = sysupdate_target.get_version().await.map_err(|e| match e {
err => HttpError::for_internal_error(format!("Error: {}", err)),
})?;
let sysupdate = SysUpdate {
versions,
current_version,
};
Ok(HttpResponseOk(sysupdate))
}