probe/src/bin/skidbladnir-testsvc.rs
//! skidbladnir-testsvc — a deliberately trivial "product" the install-matrix
//! installs as a real service (via the ServiceSpec renderer's unit) inside each
//! KVM guest. It does exactly enough for the probe to assert "installed + started
//! + healthy": it binds a TCP port and answers `GET /health` with `200 OK`.
//!
//! std-only, static musl → the SAME binary runs under systemd (Ubuntu/Debian/
//! Fedora) and OpenRC (Alpine).
//!
//! Usage: `skidbladnir-testsvc [--port N] [--name S]` (or env SKID_PORT).
use std::io::{Read, Write};
use std::net::TcpListener;
fn arg_val(flag: &str) -> Option<String> {
let args: Vec<String> = std::env::args().collect();
args.iter().position(|a| a == flag).and_then(|i| args.get(i + 1).cloned())
}
/// `<version> (<git-hash>)` — stamped by the pure-fs `build.rs`
/// (build-version-githash-law).
pub const VERSION_LINE: &str =
concat!(env!("CARGO_PKG_VERSION"), " (", env!("SKIDBLADNIR_PROBE_GIT_HASH"), ")");
fn main() {
// Answer `--version` before binding a port: a test service that cannot say
// which build it is turns a matrix failure into a guessing game.
if std::env::args().skip(1).any(|a| a == "--version" || a == "-V") {
println!("skidbladnir-testsvc {VERSION_LINE}");
return;
}
let port: u16 = arg_val("--port")
.or_else(|| std::env::var("SKID_PORT").ok())
.and_then(|s| s.parse().ok())
.unwrap_or(7070);
let name = arg_val("--name").unwrap_or_else(|| "skidtest".to_string());
let addr = format!("127.0.0.1:{port}");
let listener = match TcpListener::bind(&addr) {
Ok(l) => l,
Err(e) => {
eprintln!("skidbladnir-testsvc: bind {addr} failed: {e}");
std::process::exit(1);
}
};
println!("skidbladnir-testsvc `{name}` listening on {addr} (pid {})", std::process::id());
for stream in listener.incoming() {
let mut s = match stream {
Ok(s) => s,
Err(_) => continue,
};
// Read (and ignore) the request line; respond to anything with a health body.
let mut buf = [0u8; 1024];
let _ = s.read(&mut buf);
let body = format!("OK {name}\n");
let resp = format!(
"HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
let _ = s.write_all(resp.as_bytes());
let _ = s.flush();
}
}