use std::{sync::Arc, time::Duration};
use ::pingora::{server::Server, services::background::background_service};
use anyhow::anyhow;
use config::load_proxy_config;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use pingora::listeners::TlsSettings;
use pingora_load_balancing::{health_check::TcpHealthCheck, LoadBalancer};
use pingora_proxy::http_proxy_service;
use services::{
letsencrypt::http01::LetsencryptService,
logger::{ProxyLogger, ProxyLoggerReceiver},
};
use stores::{certificates::CertificateStore, routes::RouteStore};
use tokio::sync::mpsc;
mod config;
mod docker;
mod proxy_server;
mod services;
mod stores;
mod tools;
pub static ROUTE_STORE: Lazy<RouteStore> = Lazy::new(|| Arc::new(DashMap::new()));
pub static CERT_STORE: Lazy<CertificateStore> = Lazy::new(|| Arc::new(DashMap::new()));
fn main() -> Result<(), anyhow::Error> {
let proxy_config = Arc::new(
load_proxy_config("/etc/proksi/configs")
.map_err(|e| anyhow!("Failed to load configuration: {}", e))?,
);
let (log_sender, log_receiver) = mpsc::unbounded_channel::<Vec<u8>>();
let proxy_logger = ProxyLogger::new(log_sender);
tracing_subscriber::fmt()
.with_max_level(&proxy_config.logging.level)
.compact()
.with_writer(proxy_logger)
.init();
let mut pingora_server = Server::new(None)?;
for route in &proxy_config.routes {
let addr_upstreams = route
.upstreams
.iter()
.map(|upstr| format!("{}:{}", upstr.ip, upstr.port));
let mut upstreams = LoadBalancer::try_from_iter(addr_upstreams)?;
let tcp_health_check = TcpHealthCheck::new();
upstreams.set_health_check(tcp_health_check);
upstreams.health_check_frequency = Some(Duration::from_secs(15));
let health_check_service = background_service(&route.host, upstreams);
let upstreams = health_check_service.task();
ROUTE_STORE.insert(route.host.clone(), upstreams);
pingora_server.add_service(health_check_service);
}
let certificate_store = proxy_server::cert_store::CertStore::new();
let mut tls_settings = TlsSettings::with_callbacks(certificate_store).unwrap();
tls_settings.enable_h2();
tls_settings.set_max_proto_version(Some(pingora::tls::ssl::SslVersion::TLS1_3))?;
let client = docker::client::create_client();
let docker_service = background_service("docker", client);
let challenge_store = Arc::new(DashMap::<String, (String, String)>::new());
let domains: Vec<String> = ROUTE_STORE
.as_ref()
.into_iter()
.map(|k| k.key().to_string())
.collect();
let letsencrypt_service =
LetsencryptService::new(domains, proxy_config.clone(), challenge_store.clone());
let mut http_service = http_proxy_service(
&pingora_server.configuration,
proxy_server::http_proxy::HttpLB { challenge_store },
);
let router = proxy_server::https_proxy::Router {};
let mut https_service = http_proxy_service(&pingora_server.configuration, router);
http_service.add_tcp("0.0.0.0:80");
https_service.threads = proxy_config.worker_threads;
https_service.add_tls_with_settings("0.0.0.0:443", None, tls_settings);
pingora_server.add_service(http_service);
pingora_server.add_service(https_service);
pingora_server.add_service(docker_service);
pingora_server.add_service(letsencrypt_service);
pingora_server.add_service(ProxyLoggerReceiver(log_receiver));
pingora_server.bootstrap();
pingora_server.run_forever();
}