1use std::{fmt, sync::Arc, time::Duration};
2
3pub use bestool_canopy as canopy;
4pub use bestool_canopy::Redacted;
5
6pub mod backup;
7pub mod commands;
8mod context;
9mod daemon;
10pub mod doctor;
11pub mod http_server;
12mod metrics;
13pub mod tasks;
14
15#[cfg(windows)]
16pub mod windows_service;
17
18pub use backup::{BackupRegistry, BackupRunner, BackupTask, RunningBackup};
19pub use context::InternalContext;
20pub use daemon::{RestartTrigger, run, run_with_shutdown};
21pub use tasks::{BackgroundTask, TaskContext, TaskEndpoint, TaskEndpointResponse};
22
23pub const VERSION: &str = env!("CARGO_PKG_VERSION");
25
26pub fn http_builder() -> reqwest::ClientBuilder {
30 reqwest::Client::builder().user_agent(concat!("bestool-alertd/", env!("CARGO_PKG_VERSION")))
31}
32
33pub fn http_client() -> reqwest::Client {
35 http_builder()
36 .build()
37 .expect("failed to build alertd HTTP client")
38}
39#[derive(Clone)]
41pub struct DaemonConfig {
42 pub pg_pool: Option<bestool_postgres::pool::PgPool>,
50
51 pub database_url: Option<String>,
53
54 pub device_key_pem: Option<Redacted<String>>,
60
61 pub no_server: bool,
63
64 pub server_addrs: Vec<std::net::SocketAddr>,
66
67 pub watchdog_timeout: Option<Duration>,
73
74 pub background_tasks: Vec<Arc<dyn BackgroundTask>>,
79
80 pub backups: Option<Arc<BackupRegistry>>,
83
84 pub binary_version: String,
89}
90
91impl fmt::Debug for DaemonConfig {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.debug_struct("DaemonConfig")
94 .field("database_url", &self.database_url)
95 .field("device_key_pem", &self.device_key_pem)
96 .field("binary_version", &self.binary_version)
97 .field("no_server", &self.no_server)
98 .field("server_addrs", &self.server_addrs)
99 .field("watchdog_timeout", &self.watchdog_timeout)
100 .field(
101 "background_tasks",
102 &self
103 .background_tasks
104 .iter()
105 .map(|t| t.name())
106 .collect::<Vec<_>>(),
107 )
108 .finish()
109 }
110}
111
112impl DaemonConfig {
113 pub fn new(
114 pg_pool: Option<bestool_postgres::pool::PgPool>,
115 database_url: Option<String>,
116 ) -> Self {
117 Self {
118 pg_pool,
119 database_url,
120 device_key_pem: None,
121 no_server: false,
122 server_addrs: Vec::new(),
123 watchdog_timeout: Some(Duration::from_secs(10 * 60)),
124 background_tasks: Vec::new(),
125 backups: None,
126 binary_version: VERSION.to_string(),
129 }
130 }
131
132 pub fn with_binary_version(mut self, version: String) -> Self {
134 self.binary_version = version;
135 self
136 }
137
138 pub fn with_task(mut self, task: Arc<dyn BackgroundTask>) -> Self {
139 self.background_tasks.push(task);
140 self
141 }
142
143 pub fn with_backups(mut self, registry: Arc<BackupRegistry>) -> Self {
145 self.backups = Some(registry);
146 self
147 }
148
149 pub fn with_device_key_pem(mut self, pem: String) -> Self {
150 self.device_key_pem = Some(Redacted(pem));
151 self
152 }
153
154 pub fn with_no_server(mut self, no_server: bool) -> Self {
155 self.no_server = no_server;
156 self
157 }
158
159 pub fn with_server_addrs(mut self, server_addrs: Vec<std::net::SocketAddr>) -> Self {
160 self.server_addrs = server_addrs;
161 self
162 }
163
164 pub fn with_watchdog_timeout(mut self, watchdog_timeout: Option<Duration>) -> Self {
165 self.watchdog_timeout = watchdog_timeout;
166 self
167 }
168}
169
170pub(crate) struct LogError<'a>(pub &'a miette::Report);
172
173impl fmt::Display for LogError<'_> {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 use miette::ReportHandler;
176
177 let handler = miette::NarratableReportHandler::new();
178
179 if let Err(e) = handler.debug(self.0.as_ref(), f) {
180 write!(f, "{}: {}", self.0, e)
181 } else {
182 Ok(())
183 }
184 }
185}