greentic_runner_host/
lib.rs

1#![deny(unsafe_code)]
2//! Canonical Greentic host runtime.
3//!
4//! This crate owns tenant bindings, pack ingestion/watchers, ingress adapters,
5//! Wasmtime glue, session/state storage, and the HTTP server used by the
6//! `greentic-runner` CLI. Downstream crates embed it either through
7//! [`RunnerConfig`] + [`run`] (HTTP host) or [`HostBuilder`] (direct API access).
8
9use std::path::PathBuf;
10use std::sync::Arc;
11use std::time::Duration;
12
13use crate::secrets::SecretsBackend;
14use anyhow::{Context, Result, anyhow};
15use runner_core::env::PackConfig;
16use tokio::signal;
17
18pub mod boot;
19pub mod config;
20pub mod engine;
21pub mod http;
22pub mod imports;
23pub mod ingress;
24pub mod pack;
25pub mod routing;
26pub mod runner;
27pub mod runtime;
28pub mod runtime_wasmtime;
29pub mod secrets;
30pub mod storage;
31pub mod telemetry;
32pub mod verify;
33pub mod wasi;
34pub mod watcher;
35
36mod activity;
37mod host;
38pub mod oauth;
39
40pub use activity::{Activity, ActivityKind};
41pub use config::HostConfig;
42pub use host::TelemetryCfg;
43pub use host::{HostBuilder, RunnerHost, TenantHandle};
44pub use wasi::{PreopenSpec, RunnerWasiPolicy};
45
46pub use greentic_types::{EnvId, FlowId, PackId, TenantCtx, TenantId};
47
48pub use http::auth::AdminAuth;
49pub use routing::RoutingConfig;
50use routing::TenantRouting;
51pub use runner::HostServer;
52
53/// User-facing configuration for running the unified host.
54#[derive(Clone)]
55pub struct RunnerConfig {
56    pub bindings: Vec<PathBuf>,
57    pub pack: PackConfig,
58    pub port: u16,
59    pub refresh_interval: Duration,
60    pub routing: RoutingConfig,
61    pub admin: AdminAuth,
62    pub telemetry: Option<TelemetryCfg>,
63    pub secrets_backend: SecretsBackend,
64    pub wasi_policy: RunnerWasiPolicy,
65}
66
67impl RunnerConfig {
68    /// Build a [`RunnerConfig`] from environment variables and the provided binding files.
69    pub fn from_env(bindings: Vec<PathBuf>) -> Result<Self> {
70        if bindings.is_empty() {
71            anyhow::bail!("at least one bindings file is required");
72        }
73        let pack = PackConfig::from_env()?;
74        let refresh = parse_refresh_interval(std::env::var("PACK_REFRESH_INTERVAL").ok())?;
75        let port = std::env::var("PORT")
76            .ok()
77            .and_then(|value| value.parse().ok())
78            .unwrap_or(8080);
79        let routing = RoutingConfig::from_env();
80        let admin = AdminAuth::from_env();
81        let secrets_backend = SecretsBackend::from_env(std::env::var("SECRETS_BACKEND").ok())?;
82        Ok(Self {
83            bindings,
84            pack,
85            port,
86            refresh_interval: refresh,
87            routing,
88            admin,
89            telemetry: None,
90            secrets_backend,
91            wasi_policy: RunnerWasiPolicy::default(),
92        })
93    }
94
95    /// Override the HTTP port used by the host server.
96    pub fn with_port(mut self, port: u16) -> Self {
97        self.port = port;
98        self
99    }
100
101    pub fn with_wasi_policy(mut self, policy: RunnerWasiPolicy) -> Self {
102        self.wasi_policy = policy;
103        self
104    }
105}
106
107fn parse_refresh_interval(value: Option<String>) -> Result<Duration> {
108    let raw = value.unwrap_or_else(|| "30s".into());
109    humantime::parse_duration(&raw).map_err(|err| anyhow!("invalid PACK_REFRESH_INTERVAL: {err}"))
110}
111
112/// Run the unified Greentic runner host until shutdown.
113pub async fn run(cfg: RunnerConfig) -> Result<()> {
114    let RunnerConfig {
115        bindings,
116        pack,
117        port,
118        refresh_interval,
119        routing,
120        admin,
121        telemetry,
122        secrets_backend,
123        wasi_policy,
124    } = cfg;
125    #[cfg(not(feature = "telemetry"))]
126    let _ = telemetry;
127
128    let mut builder = HostBuilder::new();
129    for path in &bindings {
130        let host_config = HostConfig::load_from_path(path)
131            .with_context(|| format!("failed to load host bindings {}", path.display()))?;
132        builder = builder.with_config(host_config);
133    }
134    #[cfg(feature = "telemetry")]
135    if let Some(telemetry) = telemetry.clone() {
136        builder = builder.with_telemetry(telemetry);
137    }
138    builder = builder
139        .with_wasi_policy(wasi_policy.clone())
140        .with_secrets_manager(
141            secrets_backend
142                .build_manager()
143                .context("failed to initialise secrets backend")?,
144        );
145
146    let host = Arc::new(builder.build()?);
147    host.start().await?;
148
149    let (watcher, reload_handle) =
150        watcher::start_pack_watcher(Arc::clone(&host), pack.clone(), refresh_interval).await?;
151
152    let routing = TenantRouting::new(routing.clone());
153    let server = HostServer::new(
154        port,
155        host.active_packs(),
156        routing,
157        host.health_state(),
158        Some(reload_handle),
159        admin.clone(),
160    )?;
161
162    tokio::select! {
163        result = server.serve() => {
164            result?;
165        }
166        _ = signal::ctrl_c() => {
167            tracing::info!("received shutdown signal");
168        }
169    }
170
171    drop(watcher);
172    host.stop().await?;
173    Ok(())
174}