autopulse/
main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! automated scanning tool that integrates widely-used media management services with various media servers for seamless media organization
//!
//! ## Quick docs
//!
//! - **[Settings](autopulse_service::settings)**: Settings handler
//!   - **[Triggers](autopulse_service::settings::triggers)**: Create triggers that will be executed by a service when a certain event occurs
//!   - **[Targets](autopulse_service::settings::targets)**: Create targets that will be scanned by a service
//!   - **[Webhooks](autopulse_service::settings::webhooks)**: Send webhooks to services to notify them of an event
//! - **[Database](autopulse_database::conn::AnyConnection)**: Database handler
//!
//! ## About
#![doc = include_str!("../README.md")]

use anyhow::Context;
use autopulse_database::conn::{get_conn, get_pool, AnyConnection};
use autopulse_server::get_server;
use autopulse_service::manager::PulseManager;
use autopulse_service::settings::Settings;
use autopulse_utils::setup_logs;
use autopulse_utils::tracing_appender::non_blocking::WorkerGuard;
use clap::Parser;
use std::sync::Arc;
use tokio::signal::unix::{signal, SignalKind};
use tracing::{debug, error, info};

/// Arguments for CLI
///
/// ```
/// $ autopulse --help
/// ```
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Args {
    /// Location of configuration file
    #[arg(short, long)]
    pub config: Option<String>,
}

#[doc(hidden)]
#[tokio::main]
async fn run(settings: Settings, _guard: Option<WorkerGuard>) -> anyhow::Result<()> {
    let hostname = settings.app.hostname.clone();
    let port = settings.app.port;
    let database_url = settings.app.database_url.clone();

    AnyConnection::pre_init(&database_url)?;

    let pool = get_pool(&database_url)?;
    let mut conn = get_conn(&pool)?;

    conn.migrate()?;

    // drop conn to prevent deadlocks
    drop(conn);

    let manager = PulseManager::new(settings, pool.clone());
    let manager = Arc::new(manager);

    manager.start().await;
    manager.start_webhooks().await;
    manager.start_notify().await;

    let manager_clone = manager.clone();

    let server = get_server(hostname.clone(), port, manager_clone)?;

    info!("🚀 listening on {}:{}", hostname, port);

    let server_task = tokio::spawn(server);

    let shutdown: tokio::task::JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
        let mut sigterm = signal(SignalKind::terminate())?;
        let mut sigint = signal(SignalKind::interrupt())?;

        tokio::select! {
            _ = sigterm.recv() => {
                debug!("Received SIGTERM");
            }
            _ = sigint.recv() => {
                debug!("Received SIGINT");
            }
        }

        info!("💤 shutting down...");

        Ok(())
    });

    shutdown.await??;

    manager.shutdown().await?;
    server_task.abort();

    Ok(())
}

#[doc(hidden)]
fn setup() -> anyhow::Result<(Settings, Option<WorkerGuard>)> {
    let args = Args::parse();

    let settings = Settings::get_settings(args.config).context("failed to load settings");

    match settings {
        Ok(settings) => {
            let guard = setup_logs(&settings.app.log_level, &settings.opts.log_file)?;

            Ok((settings, guard))
        }
        Err(e) => {
            // still setup logs if settings failed to load
            setup_logs(&autopulse_utils::LogLevel::Info, &None)?;

            Err(e)
        }
    }
}

#[doc(hidden)]
pub fn main() -> anyhow::Result<()> {
    match setup() {
        Ok((settings, guard)) => {
            info!("💫 autopulse v{} starting up...", env!("CARGO_PKG_VERSION"),);

            if let Err(e) = run(settings, guard) {
                error!("{:?}", e);
            }
        }
        Err(e) => {
            error!("{:?}", e);
        }
    }

    Ok(())
}