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 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 Ok(())
119}
120
121#[doc(hidden)]
122fn setup() -> anyhow::Result<(Settings, Option<WorkerGuard>)> {
123 let args = Args::parse();
124
125 let settings = Settings::get_settings(args.config).context("failed to load settings");
126
127 match settings {
128 Ok(settings) => {
129 let guard = setup_logs(
130 &settings.app.log_level,
131 &settings.opts.log_file,
132 &(&settings.opts.log_file_rollover).into(),
133 settings.app.api_logging,
134 )?;
135
136 Ok((settings, guard))
137 }
138 Err(e) => {
139 setup_logs(
140 &autopulse_utils::LogLevel::Info,
141 &None,
142 &Rotation::NEVER,
143 false,
144 )?;
145
146 Err(e)
147 }
148 }
149}
150
151#[doc(hidden)]
152pub fn main() -> anyhow::Result<()> {
153 match setup() {
154 Ok((settings, guard)) => {
155 info!("💫 autopulse v{} starting up...", env!("CARGO_PKG_VERSION"),);
156
157 if let Err(e) = run(settings, guard) {
158 error!("{:?}", e);
159 }
160 }
161 Err(e) => {
162 error!("{:?}", e);
163 }
164 }
165
166 Ok(())
167}