Skip to main content

autopulse/
main.rs

1//! automated scanning tool that integrates widely-used media management services with various media servers for seamless media organization
2//!
3//! ## Quick docs
4//!
5//! - **[Settings](autopulse_service::settings)**: Settings handler
6//!   - **[Triggers](autopulse_service::settings::triggers)**: Create triggers that will be executed by a service when a certain event occurs
7//!   - **[Targets](autopulse_service::settings::targets)**: Create targets that will be scanned by a service
8//!   - **[Webhooks](autopulse_service::settings::webhooks)**: Send webhooks to services to notify them of an event
9//! - **[Database](autopulse_database::conn::AnyConnection)**: Database handler
10//!
11//! ## About
12#![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/// Arguments for CLI
25///
26/// ```
27/// $ autopulse --help
28/// ```
29#[derive(Parser, Debug)]
30#[command(version, about, long_about = None)]
31pub struct Args {
32    /// Location of configuration file
33    #[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}