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, warn};
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 if manager.settings.auth.is_default_credentials() {
93 warn!("using default credentials (admin/password), change them in your config");
94 }
95
96 let handle_events_task = manager.start();
97 let handle_webhooks_task = manager.start_webhooks();
98 let handle_notify_task = manager.start_notify();
99
100 let server = get_server(&hostname, &port, manager.clone())?;
101
102 info!("🚀 listening on {}:{}", hostname, port);
103
104 tokio::select! {
105 res = on_shutdown() => {
106 res??;
107 }
108 res = handle_events_task => {
109 res?;
110 }
111 res = handle_webhooks_task => {
112 res?;
113 }
114 res = handle_notify_task => {
115 res?;
116 }
117 res = server => {
118 res?;
119 }
120 }
121
122 close_pool(&manager.pool);
123
124 Ok(())
125}
126
127#[doc(hidden)]
128fn setup() -> anyhow::Result<(Settings, Option<WorkerGuard>)> {
129 let args = Args::parse();
130
131 let loaded = Settings::get_settings(args.config).context("failed to load settings");
132
133 match loaded {
134 Ok(loaded) => {
135 let log_file_rollover = Rotation::from(&loaded.settings.opts.log_file_rollover);
136 let guard = setup_logs(
137 &loaded.settings.app.log_level,
138 &loaded.settings.opts.log_file,
139 &log_file_rollover,
140 loaded.settings.app.api_logging,
141 )?;
142 loaded.log_diagnostics();
143
144 Ok((loaded.settings, guard))
145 }
146 Err(e) => {
147 setup_logs(
148 &autopulse_utils::LogLevel::Info,
149 &None,
150 &Rotation::NEVER,
151 false,
152 )?;
153
154 Err(e)
155 }
156 }
157}
158
159#[doc(hidden)]
160pub fn main() -> anyhow::Result<()> {
161 match setup() {
162 Ok((settings, guard)) => {
163 info!("💫 autopulse v{} starting up...", env!("CARGO_PKG_VERSION"),);
164 settings.log_summary();
165
166 if let Err(e) = run(settings, guard) {
167 error!("{:?}", e);
168 }
169 }
170 Err(e) => {
171 error!("{:?}", e);
172 }
173 }
174
175 Ok(())
176}