1#![recursion_limit = "1024"]
4#![warn(clippy::pedantic)]
7#![warn(clippy::nursery)]
8#![warn(clippy::cargo)]
9#![warn(
11 clippy::clone_on_ref_ptr,
12 clippy::empty_structs_with_brackets,
13 clippy::exit,
14 clippy::expect_used,
15 clippy::format_push_string,
16 clippy::get_unwrap,
17 clippy::if_then_some_else_none,
18 clippy::indexing_slicing,
19 clippy::integer_division,
20 clippy::implicit_clone,
21 clippy::large_include_file,
22 clippy::missing_docs_in_private_items,
23 clippy::mixed_read_write_in_expression,
24 clippy::multiple_inherent_impl,
25 clippy::mutex_atomic,
26 clippy::panic_in_result_fn,
27 clippy::partial_pub_fields,
28 clippy::print_stderr,
29 clippy::print_stdout,
30 clippy::rc_buffer,
31 clippy::rc_mutex,
32 clippy::rest_pat_in_fully_bound_structs,
33 clippy::same_name_method,
34 clippy::shadow_unrelated,
35 clippy::str_to_string,
36 clippy::suspicious_xor_used_as_pow,
37 clippy::todo,
38 clippy::try_err,
39 clippy::unimplemented,
40 clippy::unnecessary_self_imports,
41 clippy::unneeded_field_pattern,
42 clippy::unreachable,
43 clippy::unseparated_literal_suffix,
44 clippy::unwrap_in_result,
45 clippy::unwrap_used,
46 clippy::use_debug,
47 clippy::verbose_file_reads
48)]
49#![allow(clippy::multiple_crate_versions)]
51#![allow(clippy::get_first)]
53#![allow(clippy::module_name_repetitions)]
55
56use actix_cors::Cors;
57use actix_web::{http, middleware::Logger, App, HttpServer};
58use config::{api_doc, auth::Config, routes};
59use db::{
60 connection::Pool,
61 cronjobs::{cleanup_layers, cleanup_maps},
62};
63use std::sync::Arc;
64
65use opentelemetry::propagation::TextMapCompositePropagator;
66use opentelemetry::{global, KeyValue};
67use opentelemetry_instrumentation_actix_web::{RequestMetrics, RequestTracing};
68use opentelemetry_resource_detectors::{
69 HostResourceDetector, OsResourceDetector, ProcessResourceDetector,
70};
71use opentelemetry_sdk::metrics::SdkMeterProvider;
72use opentelemetry_sdk::propagation::{BaggagePropagator, TraceContextPropagator};
73use opentelemetry_sdk::trace::SdkTracerProvider;
74use opentelemetry_sdk::Resource;
75
76pub mod config;
77pub mod controller;
78pub mod db;
79pub mod error;
80pub mod keycloak_api;
81pub mod model;
82#[allow(clippy::wildcard_imports)]
84#[allow(clippy::missing_docs_in_private_items)]
85pub mod schema;
86pub mod service;
87pub mod sse;
88#[cfg(test)]
89pub mod test;
90
91static RESOURCE: std::sync::LazyLock<Resource> = std::sync::LazyLock::new(|| {
93 const NAME: &str = env!("CARGO_PKG_NAME");
94 const VERSION: &str = env!("CARGO_PKG_VERSION");
95 const BUILD_DATE: &str = env!("BUILD_DATE");
96 Resource::builder()
97 .with_detector(Box::new(OsResourceDetector))
98 .with_detector(Box::new(HostResourceDetector::default()))
99 .with_detector(Box::new(ProcessResourceDetector))
100 .with_attributes(vec![
101 KeyValue::new("service.name", NAME),
102 KeyValue::new("service.version", VERSION),
103 KeyValue::new("service.build_date", BUILD_DATE),
104 ])
105 .build()
106});
107
108#[actix_web::main]
110async fn main() -> std::io::Result<()> {
111 env_logger::init();
112
113 log::debug!("Initializing telemetry ...");
114
115 let tracer_provider = init_trace();
117 let tracer_provider = match tracer_provider {
118 Ok(tp) => tp,
119 Err(e) => {
120 log::error!("Error initializing OpenTelemetry tracing: {e}");
121 std::process::exit(1);
122 }
123 };
124
125 global::set_tracer_provider(tracer_provider.clone());
126
127 let meter_provider = init_metrics();
128 let meter_provider = match meter_provider {
129 Ok(mp) => mp,
130 Err(e) => {
131 log::error!("Error initializing OpenTelemetry metrics: {e}");
132 std::process::exit(1);
133 }
134 };
135
136 global::set_meter_provider(meter_provider.clone());
137
138 opentelemetry_instrumentation_tokio::observe_current_runtime();
139
140 log::debug!("Starting PermaplanT backend...");
141
142 let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");
143 let build_date = option_env!("BUILD_DATE").unwrap_or("unknown");
144
145 let config = match config::app::Config::from_env() {
146 Ok(config) => config,
147 Err(e) => {
148 return Err(std::io::Error::other(format!(
149 "Error reading configuration: {e}"
150 )));
151 }
152 };
153
154 log::info!("Configuration loaded: {config:#?}");
155
156 Config::init(&config).await;
157
158 log::info!("Version : {version}");
159 log::info!("Build date: {build_date}");
160
161 let data_init = config::data::init(&config);
162 let pool = data_init.pool.clone().into_inner();
163 start_cronjobs(pool);
164
165 log::info!(
166 "Binding to: {}:{}",
167 config.bind_address.0,
168 config.bind_address.1
169 );
170 HttpServer::new(move || {
171 App::new()
172 .wrap(Logger::default())
173 .wrap(RequestTracing::new())
174 .wrap(RequestMetrics::default())
175 .wrap(cors_configuration())
176 .app_data(data_init.pool.clone())
177 .app_data(data_init.broadcaster.clone())
178 .app_data(data_init.http_client.clone())
179 .app_data(data_init.keycloak_api.clone())
180 .app_data(data_init.mode.clone())
181 .configure(routes::config)
182 .configure(api_doc::config)
183 })
184 .shutdown_timeout(5)
185 .bind(config.bind_address)?
186 .run()
187 .await?;
188
189 let _ = tracer_provider.shutdown();
190 let _ = meter_provider.shutdown();
191
192 Ok(())
193}
194
195fn cors_configuration() -> Cors {
197 Cors::default()
198 .allowed_origin("http://localhost:5173")
199 .allowed_origin("http://local1.permaplant.net:5173")
200 .allowed_origin("http://local2.permaplant.net:5173")
201 .allowed_origin("http://local3.permaplant.net:5173")
202 .allowed_origin("http://local4.permaplant.net:5173")
203 .allowed_origin("http://local5.permaplant.net:5173")
204 .allowed_origin("https://mr.permaplant.net")
205 .allowed_origin("https://mr.staging.permaplant.net")
206 .allowed_origin("https://dev.permaplant.net")
207 .allowed_origin("https://master.permaplant.net")
208 .allowed_origin("https://master.staging.permaplant.net")
209 .allowed_origin("https://www.permaplant.net")
210 .allowed_origin("https://www.staging.permaplant.net")
211 .allowed_origin("https://cloud.permaplant.net")
212 .allowed_origin("https://telemetry.permaplant.net")
213 .allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE"])
214 .allowed_headers(vec![
215 http::header::AUTHORIZATION,
216 http::header::ACCEPT,
217 http::header::CONTENT_TYPE,
218 ])
219 .max_age(3600)
220}
221
222fn start_cronjobs(pool: Arc<Pool>) {
224 tokio::spawn(cleanup_maps(Arc::clone(&pool)));
225 tokio::spawn(cleanup_layers(pool));
226}
227
228fn init_trace() -> Result<SdkTracerProvider, Box<dyn std::error::Error + Send + Sync>> {
230 let baggage_propagator = BaggagePropagator::new();
231 let trace_context_propagator = TraceContextPropagator::new();
232 let composite_propagator = TextMapCompositePropagator::new(vec![
233 Box::new(baggage_propagator),
234 Box::new(trace_context_propagator),
235 ]);
236
237 global::set_text_map_propagator(composite_propagator);
238
239 let exporter = opentelemetry_otlp::SpanExporter::builder()
240 .with_http()
241 .build()?;
242
243 let tr = SdkTracerProvider::builder()
244 .with_batch_exporter(exporter)
245 .with_resource(RESOURCE.clone())
246 .build();
247 Ok(tr)
248}
249
250fn init_metrics() -> Result<SdkMeterProvider, Box<dyn std::error::Error + Send + Sync>> {
252 let exporter = opentelemetry_otlp::MetricExporter::builder()
253 .with_http()
254 .build()?;
255
256 let mt = SdkMeterProvider::builder()
257 .with_periodic_exporter(exporter)
258 .with_resource(RESOURCE.clone())
259 .build();
260 Ok(mt)
261}