149 lines
4.9 KiB
Rust
149 lines
4.9 KiB
Rust
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use anyhow::{Context, Result};
|
|
use axum::routing::{get, post};
|
|
use axum::Router;
|
|
use tokio::sync::Mutex;
|
|
use tracing::info;
|
|
|
|
mod api;
|
|
mod db;
|
|
mod poller;
|
|
mod types;
|
|
mod webhook;
|
|
|
|
use types::WebhookTarget;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
|
.init();
|
|
|
|
let port: u16 = std::env::var("NOISEBELL_CACHE_PORT")
|
|
.unwrap_or_else(|_| "3000".into())
|
|
.parse()
|
|
.context("NOISEBELL_CACHE_PORT must be a valid u16")?;
|
|
|
|
let pi_address = std::env::var("NOISEBELL_CACHE_PI_ADDRESS")
|
|
.context("NOISEBELL_CACHE_PI_ADDRESS is required")?;
|
|
|
|
let pi_api_key = std::env::var("NOISEBELL_CACHE_PI_API_KEY")
|
|
.context("NOISEBELL_CACHE_PI_API_KEY is required")?;
|
|
|
|
let inbound_api_key = std::env::var("NOISEBELL_CACHE_INBOUND_API_KEY")
|
|
.context("NOISEBELL_CACHE_INBOUND_API_KEY is required")?;
|
|
|
|
let data_dir =
|
|
std::env::var("NOISEBELL_CACHE_DATA_DIR").unwrap_or_else(|_| "/var/lib/noisebell-cache".into());
|
|
|
|
let status_poll_interval_secs: u64 = std::env::var("NOISEBELL_CACHE_STATUS_POLL_INTERVAL_SECS")
|
|
.unwrap_or_else(|_| "60".into())
|
|
.parse()
|
|
.context("NOISEBELL_CACHE_STATUS_POLL_INTERVAL_SECS must be a valid u64")?;
|
|
|
|
let info_poll_interval_secs: u64 = std::env::var("NOISEBELL_CACHE_INFO_POLL_INTERVAL_SECS")
|
|
.unwrap_or_else(|_| "300".into())
|
|
.parse()
|
|
.context("NOISEBELL_CACHE_INFO_POLL_INTERVAL_SECS must be a valid u64")?;
|
|
|
|
let offline_threshold: u32 = std::env::var("NOISEBELL_CACHE_OFFLINE_THRESHOLD")
|
|
.unwrap_or_else(|_| "3".into())
|
|
.parse()
|
|
.context("NOISEBELL_CACHE_OFFLINE_THRESHOLD must be a valid u32")?;
|
|
|
|
let retry_attempts: u32 = std::env::var("NOISEBELL_CACHE_RETRY_ATTEMPTS")
|
|
.unwrap_or_else(|_| "3".into())
|
|
.parse()
|
|
.context("NOISEBELL_CACHE_RETRY_ATTEMPTS must be a valid u32")?;
|
|
|
|
let retry_base_delay_secs: u64 = std::env::var("NOISEBELL_CACHE_RETRY_BASE_DELAY_SECS")
|
|
.unwrap_or_else(|_| "1".into())
|
|
.parse()
|
|
.context("NOISEBELL_CACHE_RETRY_BASE_DELAY_SECS must be a valid u64")?;
|
|
|
|
let http_timeout_secs: u64 = std::env::var("NOISEBELL_CACHE_HTTP_TIMEOUT_SECS")
|
|
.unwrap_or_else(|_| "10".into())
|
|
.parse()
|
|
.context("NOISEBELL_CACHE_HTTP_TIMEOUT_SECS must be a valid u64")?;
|
|
|
|
// Parse outbound webhooks from NOISEBELL_CACHE_WEBHOOK_<n>_URL and _SECRET env vars
|
|
let mut webhooks = Vec::new();
|
|
for i in 0.. {
|
|
let url_key = format!("NOISEBELL_CACHE_WEBHOOK_{i}_URL");
|
|
match std::env::var(&url_key) {
|
|
Ok(url) => {
|
|
let secret_key = format!("NOISEBELL_CACHE_WEBHOOK_{i}_SECRET");
|
|
let secret = std::env::var(&secret_key).ok();
|
|
webhooks.push(WebhookTarget { url, secret });
|
|
}
|
|
Err(_) => break,
|
|
}
|
|
}
|
|
|
|
info!(
|
|
port,
|
|
%pi_address,
|
|
webhook_count = webhooks.len(),
|
|
"starting noisebell-cache"
|
|
);
|
|
|
|
let db_path = format!("{data_dir}/noisebell.db");
|
|
let conn = db::init(&db_path)?;
|
|
let db = Arc::new(Mutex::new(conn));
|
|
|
|
let client = reqwest::Client::builder()
|
|
.timeout(Duration::from_secs(http_timeout_secs))
|
|
.build()
|
|
.context("failed to build HTTP client")?;
|
|
|
|
let poller_config = Arc::new(poller::PollerConfig {
|
|
pi_address,
|
|
pi_api_key,
|
|
status_poll_interval: Duration::from_secs(status_poll_interval_secs),
|
|
info_poll_interval: Duration::from_secs(info_poll_interval_secs),
|
|
offline_threshold,
|
|
retry_attempts,
|
|
retry_base_delay_secs,
|
|
http_timeout_secs,
|
|
webhooks: webhooks.clone(),
|
|
});
|
|
|
|
poller::spawn_status_poller(poller_config.clone(), db.clone(), client.clone());
|
|
poller::spawn_info_poller(poller_config, db.clone(), client.clone());
|
|
|
|
let app_state = Arc::new(api::AppState {
|
|
db,
|
|
client,
|
|
inbound_api_key,
|
|
webhooks,
|
|
retry_attempts,
|
|
retry_base_delay_secs,
|
|
});
|
|
|
|
let app = Router::new()
|
|
.route("/webhook", post(api::post_webhook))
|
|
.route("/status", get(api::get_status))
|
|
.route("/info", get(api::get_info))
|
|
.route("/history", get(api::get_history))
|
|
.with_state(app_state);
|
|
|
|
let listener = tokio::net::TcpListener::bind(("0.0.0.0", port))
|
|
.await
|
|
.context(format!("failed to bind to 0.0.0.0:{port}"))?;
|
|
|
|
info!(port, "listening");
|
|
|
|
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
|
.context("failed to register SIGTERM handler")?;
|
|
axum::serve(listener, app)
|
|
.with_graceful_shutdown(async move {
|
|
sigterm.recv().await;
|
|
})
|
|
.await
|
|
.context("server error")?;
|
|
|
|
info!("shutdown complete");
|
|
Ok(())
|
|
}
|