examples/install-knut-flight.rs
//! N5 — Skidbladnir-driven **system install** of the knut Arrow Flight server.
//!
//! Proves the [`crate::product::ComponentSpec::knut_flight`] spec end-to-end through
//! the REAL installer path (no hand-written unit): stage the pre-built binary into a
//! bundle with the [`skidbladnir::archive::TarZstd`] engine, then hand it to
//! [`skidbladnir::product::SystemdInstaller`], which verifies the bundle, unpacks it
//! under the [`skidbladnir::layout::Layout`], writes the unit + env file, and
//! enables+starts `knut-flight.service` on the system bus.
//!
//! ```sh
//! # 1. build the server binary (in the knut repo):
//! cargo build --release -p knut-flight --bin knut-flight-server
//! # 2. install it as a systemd service (needs root — creates the `_knut` user
//! # and writes /etc/systemd/system/knut-flight.service):
//! sudo cargo run --example install-knut-flight -- \
//! ~/git/knut/target/release/knut-flight-server
//! # 3. prove it: `systemctl status knut-flight` + a Flight `list_flights` on :50055.
//! ```
//!
//! Roots default to `/var/lib/lake`, `/var/log/lake`, `/opt/lake` (override with the
//! 2nd/3rd/4th args). Idempotent: re-running just fills any gaps.
use std::path::PathBuf;
use skidbladnir::archive::{Archive, TarZstd};
use skidbladnir::layout::Layout;
use skidbladnir::product::{ComponentSpec, Installer, SystemdInstaller};
fn main() -> anyhow::Result<()> {
let mut args = std::env::args().skip(1);
let bin = PathBuf::from(args.next().ok_or_else(|| {
anyhow::anyhow!(
"usage: install-knut-flight <knut-flight-server> [data_dir log_dir software_dir]"
)
})?);
anyhow::ensure!(bin.exists(), "binary not found: {}", bin.display());
let data = args.next().unwrap_or_else(|| "/var/lib/lake".into());
let log = args.next().unwrap_or_else(|| "/var/log/lake".into());
let software = args.next().unwrap_or_else(|| "/opt/lake".into());
let spec = ComponentSpec::knut_flight();
// 1. Stage the binary at the spec's `bundle_binary` path, then pack a real
// TarZstd bundle (content-addressed manifest — the same format install verifies).
let stage = std::env::temp_dir().join("knut-flight-stage");
let _ = std::fs::remove_dir_all(&stage);
let entry = stage.join(&spec.bundle_binary);
std::fs::create_dir_all(entry.parent().unwrap())?;
std::fs::copy(&bin, &entry)?;
let engine = TarZstd::new();
let bundle = std::env::temp_dir().join("knut-flight.bundle.tar.zst");
let manifest = engine.pack(&stage, &spec.name, &bundle)?;
println!(
"packed bundle {} ({} entries, root_sha256={})",
bundle.display(),
manifest.entries.len(),
&manifest.root_sha256[..16]
);
// 2. Install through the REAL SystemdInstaller (verify → unpack → unit → firewall).
// trust_root=None ⇒ integrity-only verify (no signing key in this local proof).
let layout = Layout::dated(&data, &log, &software);
println!(
"installing `{}` via SystemdInstaller (system scope)…",
spec.name
);
SystemdInstaller.install(&spec, &layout, &bundle, &engine, None)?;
println!(
"installed: unit=knut-flight.service user={} exec={}",
spec.user,
spec.installed_binary(&layout).display()
);
Ok(())
}