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