greentic_runner_host/
lib.rs

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