Skip to main content

orch/
webhook_status.rs

1//! Webhook server health state, persisted to `~/.orch/webhook_status.json`.
2//!
3//! The engine writes this file during startup and each health-check cycle.
4//! The `orch webhook status` CLI reads it so operators can inspect webhook
5//! health without tailing logs.
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::fs as std_fs;
10use std::path::PathBuf;
11use tokio::fs;
12
13use crate::home;
14
15/// Persisted webhook health state.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct WebhookStatus {
18    /// Whether webhooks are enabled in config.
19    pub configured: bool,
20    /// Listening port (None when disabled or not yet bound).
21    pub port: Option<u16>,
22    /// Whether the last health-check ping succeeded.
23    pub healthy: bool,
24    /// True when the engine has fallen back to polling due to startup failure.
25    pub fallback_mode: bool,
26    /// UTC timestamp of the last health-check attempt.
27    pub last_check_utc: Option<DateTime<Utc>>,
28    /// Human-readable reason for the last failure (cleared on recovery).
29    pub last_failure_reason: Option<String>,
30    /// How many bind attempts were made at startup.
31    pub startup_attempts: u32,
32}
33
34impl Default for WebhookStatus {
35    fn default() -> Self {
36        Self {
37            configured: false,
38            port: None,
39            healthy: false,
40            fallback_mode: true,
41            last_check_utc: None,
42            last_failure_reason: None,
43            startup_attempts: 0,
44        }
45    }
46}
47
48/// Path to `~/.orch/webhook_status.json`.
49pub fn status_path() -> anyhow::Result<PathBuf> {
50    Ok(home::orch_home()?.join("webhook_status.json"))
51}
52
53impl WebhookStatus {
54    /// Persist the status to disk. Non-fatal: logs a warning on write failure.
55    pub async fn save(&self) {
56        match status_path().and_then(|p| {
57            serde_json::to_string_pretty(self)
58                .map(|j| (p, j))
59                .map_err(anyhow::Error::from)
60        }) {
61            Ok((p, json)) => {
62                if let Err(e) = fs::write(&p, json).await {
63                    tracing::warn!(error = ?e, "failed to persist webhook status");
64                }
65            }
66            Err(e) => tracing::warn!(?e, "failed to persist webhook status"),
67        }
68    }
69
70    /// Load the last-known status from disk. Returns `None` if the file does
71    /// not exist or cannot be parsed.
72    pub fn load() -> Option<Self> {
73        let path = status_path().ok()?;
74        let json = std_fs::read_to_string(path).ok()?;
75        serde_json::from_str(&json).ok()
76    }
77}