Skip to main content

hatchery_server/
harness.rs

1//! Harness + observation host: supervises the `hatchery-harness` binary
2//! (the observation engine and store run inside it). Arguments come from the
3//! operator's configuration; tokens stay in the environment the child
4//! inherits (`GATE4AGENT_C2_TOKEN`, operator token), never in arguments.
5
6use crate::error::ServerError;
7use crate::subservice::{ManagedProcess, State, Status, Subservice};
8use std::path::PathBuf;
9use tokio::process::Command;
10
11pub struct HarnessSubservice {
12    bin: PathBuf,
13    args: Vec<String>,
14    process: ManagedProcess,
15    failed: Option<String>,
16}
17
18impl HarnessSubservice {
19    pub fn new(bin: PathBuf, args: Vec<String>) -> Self {
20        Self { bin, args, process: ManagedProcess::new(), failed: None }
21    }
22}
23
24impl Subservice for HarnessSubservice {
25    fn name(&self) -> &'static str {
26        "harness"
27    }
28
29    fn start(&mut self) -> Result<(), ServerError> {
30        if self.process.alive() {
31            return Ok(());
32        }
33        let mut command = Command::new(&self.bin);
34        command.args(&self.args);
35        match self.process.spawn(command) {
36            Ok(()) => {
37                self.failed = None;
38                Ok(())
39            }
40            Err(error) => {
41                self.failed = Some(error.to_string());
42                Err(ServerError::sub("harness", error.to_string()))
43            }
44        }
45    }
46
47    fn stop(&mut self) {
48        self.process.stop();
49    }
50
51    fn status(&mut self) -> Status {
52        let state = if self.process.alive() {
53            State::Running
54        } else if self.failed.is_some() {
55            State::Failed
56        } else {
57            State::Stopped
58        };
59        Status { name: "harness", state, port: None, detail: self.failed.clone() }
60    }
61}