1#![doc = include_str!("../README.md")]
13
14use anyhow::Context;
15use autopulse_database::conn::{get_conn, get_pool, AnyConnection};
16use autopulse_server::get_server;
17use autopulse_service::manager::PulseManager;
18use autopulse_service::settings::Settings;
19use autopulse_utils::tracing_appender::non_blocking::WorkerGuard;
20use autopulse_utils::{setup_logs, Rotation};
21use clap::Parser;
22use std::sync::Arc;
23use tokio::signal::unix::{signal, SignalKind};
24use tracing::{debug, error, info};
25
26#[derive(Parser, Debug)]
32#[command(version, about, long_about = None)]
33pub struct Args {
34 #[arg(short, long)]
36 pub config: Option<String>,
37}
38
39#[doc(hidden)]
40#[tokio::main]
41async fn run(settings: Settings, _guard: Option<WorkerGuard>) -> anyhow::Result<()> {
42 let hostname = settings.app.hostname.clone();
43 let port = settings.app.port;
44 let database_url = settings.app.database_url.clone();
45
46 AnyConnection::pre_init(&database_url)?;
47
48 let pool = get_pool(&database_url)?;
49 let mut conn = get_conn(&pool)?;
50
51 conn.migrate()?;
52
53 drop(conn);
55
56 let manager = PulseManager::new(settings, pool.clone());
57 let manager = Arc::new(manager);
58
59 manager.start().await;
60 manager.start_webhooks().await;
61 manager.start_notify().await;
62
63 let manager_clone = manager.clone();
64
65 let server = get_server(hostname.clone(), port, manager_clone)?;
66
67 info!("🚀 listening on {}:{}", hostname, port);
68
69 let server_task = tokio::spawn(server);
70
71 let shutdown: tokio::task::JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
72 let mut sigterm = signal(SignalKind::terminate())?;
73 let mut sigint = signal(SignalKind::interrupt())?;
74
75 tokio::select! {
76 _ = sigterm.recv() => {
77 debug!("Received SIGTERM");
78 }
79 _ = sigint.recv() => {
80 debug!("Received SIGINT");
81 }
82 }
83
84 info!("💤 shutting down...");
85
86 Ok(())
87 });
88
89 shutdown.await??;
90
91 manager.shutdown().await?;
92 server_task.abort();
93
94 Ok(())
95}
96
97#[doc(hidden)]
98fn setup() -> anyhow::Result<(Settings, Option<WorkerGuard>)> {
99 let args = Args::parse();
100
101 let settings = Settings::get_settings(args.config).context("failed to load settings");
102
103 match settings {
104 Ok(settings) => {
105 let guard = setup_logs(
106 &settings.app.log_level,
107 &settings.opts.log_file,
108 settings.opts.log_file_rollover.clone().into(),
109 settings.app.api_logging,
110 )?;
111
112 Ok((settings, guard))
113 }
114 Err(e) => {
115 setup_logs(
117 &autopulse_utils::LogLevel::Info,
118 &None,
119 Rotation::NEVER,
120 false,
121 )?;
122
123 Err(e)
124 }
125 }
126}
127
128#[doc(hidden)]
129pub fn main() -> anyhow::Result<()> {
130 match setup() {
131 Ok((settings, guard)) => {
132 info!("💫 autopulse v{} starting up...", env!("CARGO_PKG_VERSION"),);
133
134 if let Err(e) = run(settings, guard) {
135 error!("{:?}", e);
136 }
137 }
138 Err(e) => {
139 error!("{:?}", e);
140 }
141 }
142
143 Ok(())
144}