backend/
main.rs

1//! The backend of `PermaplanT`.
2
3#![recursion_limit = "1024"]
4// Enable all lints apart from clippy::restriction by default.
5// See https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints for as to why restriction is not enabled.
6#![warn(clippy::pedantic)]
7#![warn(clippy::nursery)]
8#![warn(clippy::cargo)]
9// Lints in clippy::restriction which seem useful.
10#![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// Cannot fix some errors because dependencies import them.
50#![allow(clippy::multiple_crate_versions)]
51// Clippy suggests lots of false "x.get(0)" => "x.first()"
52#![allow(clippy::get_first)]
53// We often want the same name per module (for instance every enum).
54#![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/// Auto generated by diesel.
83#[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
91/// OpenTelemetry resource describing this service.
92static 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/// Main function.
109#[actix_web::main]
110async fn main() -> std::io::Result<()> {
111    env_logger::init();
112
113    log::debug!("Initializing telemetry ...");
114
115    // Initialize OpenTelemetry
116    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
195/// Create a CORS configuration for the server.
196fn 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
222/// Start all scheduled jobs that get run in the backend.
223fn start_cronjobs(pool: Arc<Pool>) {
224    tokio::spawn(cleanup_maps(Arc::clone(&pool)));
225    tokio::spawn(cleanup_layers(pool));
226}
227
228/// Initialize OpenTelemetry tracing.
229fn 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
250/// Initialize OpenTelemetry metrics.
251fn 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}