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