greentic_runner_host/
lib.rs1#![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#[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 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 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
104pub 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}