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