1use std::{fmt, time::Duration};
2
3mod alert;
4pub mod canopy;
5pub mod commands;
6mod daemon;
7mod events;
8mod glob_resolver;
9pub mod http_server;
10mod loader;
11mod metrics;
12
13pub mod scheduler;
14pub mod state_file;
15mod targets;
16pub mod templates;
17
18#[cfg(windows)]
19pub mod windows_service;
20
21pub use alert::{AlertDefinition, AlwaysSend, InternalContext, TicketSource, WhenChanged};
22pub use daemon::{run, run_with_shutdown, run_with_shutdown_and_reload};
23pub use events::EventType;
24pub use targets::{
25 AlertTargets, ExternalTarget, ResolvedTarget, SendTarget, TargetConnection, TargetEmail,
26};
27
28pub const VERSION: &str = env!("CARGO_PKG_VERSION");
30
31#[derive(Debug, Clone)]
33pub struct EmailConfig {
34 pub from: String,
35 pub mailgun_api_key: String,
36 pub mailgun_domain: String,
37}
38
39#[derive(Clone)]
41pub struct DaemonConfig {
42 pub alert_globs: Vec<String>,
47
48 pub database_url: String,
50
51 pub email: Option<EmailConfig>,
53
54 pub device_key_pem: Option<Redacted<String>>,
60
61 pub tamanu_version: String,
65
66 pub dry_run: bool,
68
69 pub no_server: bool,
71
72 pub server_addrs: Vec<std::net::SocketAddr>,
74
75 pub watchdog_timeout: Option<Duration>,
81}
82
83impl fmt::Debug for DaemonConfig {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 f.debug_struct("DaemonConfig")
86 .field("alert_globs", &self.alert_globs)
87 .field("database_url", &self.database_url)
88 .field("email", &self.email)
89 .field("device_key_pem", &self.device_key_pem)
90 .field("tamanu_version", &self.tamanu_version)
91 .field("dry_run", &self.dry_run)
92 .field("no_server", &self.no_server)
93 .field("server_addrs", &self.server_addrs)
94 .field("watchdog_timeout", &self.watchdog_timeout)
95 .finish()
96 }
97}
98
99#[derive(Clone)]
101pub struct Redacted<T>(pub T);
102
103impl<T> fmt::Debug for Redacted<T> {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 f.write_str("<redacted>")
106 }
107}
108
109impl<T> std::ops::Deref for Redacted<T> {
110 type Target = T;
111 fn deref(&self) -> &T {
112 &self.0
113 }
114}
115
116impl DaemonConfig {
117 pub fn new(alert_globs: Vec<String>, database_url: String, tamanu_version: String) -> Self {
118 Self {
119 alert_globs,
120 database_url,
121 email: None,
122 device_key_pem: None,
123 tamanu_version,
124 dry_run: false,
125 no_server: false,
126 server_addrs: Vec::new(),
127 watchdog_timeout: Some(Duration::from_secs(10 * 60)),
128 }
129 }
130
131 pub fn with_email(mut self, email: EmailConfig) -> Self {
132 self.email = Some(email);
133 self
134 }
135
136 pub fn with_device_key_pem(mut self, pem: String) -> Self {
137 self.device_key_pem = Some(Redacted(pem));
138 self
139 }
140
141 pub fn with_dry_run(mut self, dry_run: bool) -> Self {
142 self.dry_run = dry_run;
143 self
144 }
145
146 pub fn with_no_server(mut self, no_server: bool) -> Self {
147 self.no_server = no_server;
148 self
149 }
150
151 pub fn with_server_addrs(mut self, server_addrs: Vec<std::net::SocketAddr>) -> Self {
152 self.server_addrs = server_addrs;
153 self
154 }
155
156 pub fn with_watchdog_timeout(mut self, watchdog_timeout: Option<Duration>) -> Self {
157 self.watchdog_timeout = watchdog_timeout;
158 self
159 }
160}
161
162pub(crate) struct LogError<'a>(pub &'a miette::Report);
164
165impl fmt::Display for LogError<'_> {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 use miette::ReportHandler;
168
169 let handler = miette::NarratableReportHandler::new();
170
171 if let Err(e) = handler.debug(self.0.as_ref(), f) {
172 write!(f, "{}: {}", self.0, e)
173 } else {
174 Ok(())
175 }
176 }
177}