]>
Commit | Line | Data |
---|---|---|
b9e7bcc2 | 1 | use anyhow::Error; |
57570bda | 2 | use const_format::concatcp; |
04f35d0e | 3 | use serde_json::json; |
57570bda LW |
4 | use std::collections::HashMap; |
5 | use std::path::Path; | |
6 | use std::time::{Duration, Instant}; | |
b9e7bcc2 | 7 | |
04f35d0e | 8 | use handlebars::{Handlebars, TemplateError}; |
57570bda | 9 | use nix::unistd::Uid; |
b9e7bcc2 | 10 | |
6ef1b649 | 11 | use proxmox_lang::try_block; |
57570bda | 12 | use proxmox_notify::context::pbs::PBS_CONTEXT; |
9fa3026a | 13 | use proxmox_schema::ApiType; |
ee0ea735 | 14 | use proxmox_sys::email::sendmail; |
57570bda | 15 | use proxmox_sys::fs::{create_path, CreateOptions}; |
b9e7bcc2 | 16 | |
e3619d41 | 17 | use pbs_api_types::{ |
04f35d0e LW |
18 | APTUpdateInfo, DataStoreConfig, DatastoreNotify, GarbageCollectionStatus, NotificationMode, |
19 | Notify, SyncJobConfig, TapeBackupJobSetup, User, Userid, VerificationJobConfig, | |
b9e7bcc2 | 20 | }; |
04f35d0e LW |
21 | use proxmox_notify::endpoints::sendmail::{SendmailConfig, SendmailEndpoint}; |
22 | use proxmox_notify::{Endpoint, Notification, Severity}; | |
b9e7bcc2 | 23 | |
57570bda | 24 | const SPOOL_DIR: &str = concatcp!(pbs_buildcfg::PROXMOX_BACKUP_STATE_DIR, "/notifications"); |
b9e7bcc2 | 25 | |
8703a68a DC |
26 | const TAPE_BACKUP_OK_TEMPLATE: &str = r###" |
27 | ||
28 | {{#if id ~}} | |
29 | Job ID: {{id}} | |
30 | {{/if~}} | |
31 | Datastore: {{job.store}} | |
32 | Tape Pool: {{job.pool}} | |
33 | Tape Drive: {{job.drive}} | |
34 | ||
4abd4dbe DC |
35 | {{#if snapshot-list ~}} |
36 | Snapshots included: | |
37 | ||
38 | {{#each snapshot-list~}} | |
39 | {{this}} | |
40 | {{/each~}} | |
41 | {{/if}} | |
42 | Duration: {{duration}} | |
36156038 DC |
43 | {{#if used-tapes }} |
44 | Used Tapes: | |
45 | {{#each used-tapes~}} | |
46 | {{this}} | |
47 | {{/each~}} | |
48 | {{/if}} | |
8703a68a DC |
49 | Tape Backup successful. |
50 | ||
51 | ||
d1d74c43 | 52 | Please visit the web interface for further details: |
8703a68a DC |
53 | |
54 | <https://{{fqdn}}:{{port}}/#DataStore-{{job.store}}> | |
55 | ||
56 | "###; | |
57 | ||
58 | const TAPE_BACKUP_ERR_TEMPLATE: &str = r###" | |
59 | ||
60 | {{#if id ~}} | |
61 | Job ID: {{id}} | |
62 | {{/if~}} | |
63 | Datastore: {{job.store}} | |
64 | Tape Pool: {{job.pool}} | |
65 | Tape Drive: {{job.drive}} | |
66 | ||
4ca3f0c6 DC |
67 | {{#if snapshot-list ~}} |
68 | Snapshots included: | |
8703a68a | 69 | |
4ca3f0c6 DC |
70 | {{#each snapshot-list~}} |
71 | {{this}} | |
72 | {{/each~}} | |
73 | {{/if}} | |
36156038 DC |
74 | {{#if used-tapes }} |
75 | Used Tapes: | |
76 | {{#each used-tapes~}} | |
77 | {{this}} | |
78 | {{/each~}} | |
79 | {{/if}} | |
8703a68a DC |
80 | Tape Backup failed: {{error}} |
81 | ||
82 | ||
d1d74c43 | 83 | Please visit the web interface for further details: |
8703a68a DC |
84 | |
85 | <https://{{fqdn}}:{{port}}/#pbsServerAdministration:tasks> | |
86 | ||
87 | "###; | |
86d60245 | 88 | |
ee0ea735 | 89 | lazy_static::lazy_static! { |
b9e7bcc2 DM |
90 | |
91 | static ref HANDLEBARS: Handlebars<'static> = { | |
92 | let mut hb = Handlebars::new(); | |
25b4d52d | 93 | let result: Result<(), TemplateError> = try_block!({ |
b9e7bcc2 | 94 | |
25b4d52d | 95 | hb.set_strict_mode(true); |
f24cbee7 | 96 | hb.register_escape_fn(handlebars::no_escape); |
b9e7bcc2 | 97 | |
25b4d52d DC |
98 | hb.register_template_string("tape_backup_ok_template", TAPE_BACKUP_OK_TEMPLATE)?; |
99 | hb.register_template_string("tape_backup_err_template", TAPE_BACKUP_ERR_TEMPLATE)?; | |
8703a68a | 100 | |
25b4d52d DC |
101 | Ok(()) |
102 | }); | |
103 | ||
104 | if let Err(err) = result { | |
dd06b7f1 | 105 | eprintln!("error during template registration: {err}"); |
25b4d52d | 106 | } |
86d60245 | 107 | |
b9e7bcc2 DM |
108 | hb |
109 | }; | |
110 | } | |
111 | ||
57570bda LW |
112 | /// Initialize the notification system by setting context in proxmox_notify |
113 | pub fn init() -> Result<(), Error> { | |
114 | proxmox_notify::context::set_context(&PBS_CONTEXT); | |
115 | Ok(()) | |
116 | } | |
117 | ||
118 | /// Create the directory which will be used to temporarily store notifications | |
119 | /// which were sent from an unprivileged process. | |
120 | pub fn create_spool_dir() -> Result<(), Error> { | |
121 | let backup_user = pbs_config::backup_user()?; | |
122 | let opts = CreateOptions::new() | |
123 | .owner(backup_user.uid) | |
124 | .group(backup_user.gid); | |
125 | ||
126 | create_path(SPOOL_DIR, None, Some(opts))?; | |
127 | Ok(()) | |
128 | } | |
129 | ||
130 | async fn send_queued_notifications() -> Result<(), Error> { | |
131 | let mut read_dir = tokio::fs::read_dir(SPOOL_DIR).await?; | |
132 | ||
133 | let mut notifications = Vec::new(); | |
134 | ||
135 | while let Some(entry) = read_dir.next_entry().await? { | |
136 | let path = entry.path(); | |
137 | ||
138 | if let Some(ext) = path.extension() { | |
139 | if ext == "json" { | |
140 | let p = path.clone(); | |
141 | ||
142 | let bytes = tokio::fs::read(p).await?; | |
143 | let notification: Notification = serde_json::from_slice(&bytes)?; | |
144 | notifications.push(notification); | |
145 | ||
146 | // Currently, there is no retry-mechanism in case of failure... | |
147 | // For retries, we'd have to keep track of which targets succeeded/failed | |
148 | // to send, so we do not retry notifying a target which succeeded before. | |
149 | tokio::fs::remove_file(path).await?; | |
150 | } | |
151 | } | |
152 | } | |
153 | ||
154 | // Make sure that we send the oldest notification first | |
155 | notifications.sort_unstable_by_key(|n| n.timestamp()); | |
156 | ||
157 | let res = tokio::task::spawn_blocking(move || { | |
158 | let config = pbs_config::notifications::config()?; | |
159 | for notification in notifications { | |
160 | if let Err(err) = proxmox_notify::api::common::send(&config, ¬ification) { | |
161 | log::error!("failed to send notification: {err}"); | |
162 | } | |
163 | } | |
164 | ||
165 | Ok::<(), Error>(()) | |
166 | }) | |
167 | .await?; | |
168 | ||
169 | if let Err(e) = res { | |
170 | log::error!("could not read notification config: {e}"); | |
171 | } | |
172 | ||
173 | Ok::<(), Error>(()) | |
174 | } | |
175 | ||
176 | /// Worker task to periodically send any queued notifications. | |
177 | pub async fn notification_worker() { | |
178 | loop { | |
179 | let delay_target = Instant::now() + Duration::from_secs(5); | |
180 | ||
181 | if let Err(err) = send_queued_notifications().await { | |
182 | log::error!("notification worker task error: {err}"); | |
183 | } | |
184 | ||
185 | tokio::time::sleep_until(tokio::time::Instant::from_std(delay_target)).await; | |
186 | } | |
187 | } | |
188 | ||
189 | fn send_notification(notification: Notification) -> Result<(), Error> { | |
190 | if nix::unistd::ROOT == Uid::current() { | |
191 | let config = pbs_config::notifications::config()?; | |
192 | proxmox_notify::api::common::send(&config, ¬ification)?; | |
193 | } else { | |
194 | let ser = serde_json::to_vec(¬ification)?; | |
195 | let path = Path::new(SPOOL_DIR).join(format!("{id}.json", id = notification.id())); | |
196 | ||
197 | let backup_user = pbs_config::backup_user()?; | |
198 | let opts = CreateOptions::new() | |
199 | .owner(backup_user.uid) | |
200 | .group(backup_user.gid); | |
201 | proxmox_sys::fs::replace_file(path, &ser, opts, true)?; | |
202 | log::info!("queued notification (id={id})", id = notification.id()) | |
203 | } | |
204 | ||
205 | Ok(()) | |
206 | } | |
207 | ||
04f35d0e LW |
208 | fn send_sendmail_legacy_notification(notification: Notification, email: &str) -> Result<(), Error> { |
209 | let endpoint = SendmailEndpoint { | |
210 | config: SendmailConfig { | |
211 | mailto: vec![email.into()], | |
212 | ..Default::default() | |
213 | }, | |
214 | }; | |
215 | ||
216 | endpoint.send(¬ification)?; | |
217 | ||
218 | Ok(()) | |
219 | } | |
220 | ||
4abd4dbe DC |
221 | /// Summary of a successful Tape Job |
222 | #[derive(Default)] | |
223 | pub struct TapeBackupJobSummary { | |
224 | /// The list of snaphots backed up | |
225 | pub snapshot_list: Vec<String>, | |
226 | /// The total time of the backup job | |
227 | pub duration: std::time::Duration, | |
36156038 DC |
228 | /// The labels of the used tapes of the backup job |
229 | pub used_tapes: Option<Vec<String>>, | |
4abd4dbe DC |
230 | } |
231 | ||
ee0ea735 | 232 | fn send_job_status_mail(email: &str, subject: &str, text: &str) -> Result<(), Error> { |
e4665261 | 233 | let (config, _) = crate::config::node::config()?; |
ee0ea735 | 234 | let from = config.email_from; |
e4665261 | 235 | |
dd06b7f1 TL |
236 | // NOTE: some (web)mailers have big problems displaying text mails, so include html as well |
237 | let escaped_text = handlebars::html_escape(text); | |
238 | let html = format!("<html><body><pre>\n{escaped_text}\n<pre>"); | |
b9e7bcc2 | 239 | |
25877d05 | 240 | let nodename = proxmox_sys::nodename(); |
b9e7bcc2 | 241 | |
dd06b7f1 | 242 | let author = format!("Proxmox Backup Server - {nodename}"); |
b9e7bcc2 DM |
243 | |
244 | sendmail( | |
245 | &[email], | |
9a37bd6c FG |
246 | subject, |
247 | Some(text), | |
b9e7bcc2 | 248 | Some(&html), |
e4665261 | 249 | from.as_deref(), |
b9e7bcc2 DM |
250 | Some(&author), |
251 | )?; | |
252 | ||
253 | Ok(()) | |
254 | } | |
255 | ||
256 | pub fn send_gc_status( | |
b9e7bcc2 DM |
257 | datastore: &str, |
258 | status: &GarbageCollectionStatus, | |
259 | result: &Result<(), Error>, | |
260 | ) -> Result<(), Error> { | |
3066f564 DM |
261 | let (fqdn, port) = get_server_url(); |
262 | let mut data = json!({ | |
263 | "datastore": datastore, | |
264 | "fqdn": fqdn, | |
265 | "port": port, | |
266 | }); | |
267 | ||
04f35d0e | 268 | let (severity, template) = match result { |
b9e7bcc2 | 269 | Ok(()) => { |
d6373f35 | 270 | let deduplication_factor = if status.disk_bytes > 0 { |
ee0ea735 | 271 | (status.index_data_bytes as f64) / (status.disk_bytes as f64) |
d6373f35 DM |
272 | } else { |
273 | 1.0 | |
274 | }; | |
275 | ||
3066f564 DM |
276 | data["status"] = json!(status); |
277 | data["deduplication-factor"] = format!("{:.2}", deduplication_factor).into(); | |
d6373f35 | 278 | |
04f35d0e | 279 | (Severity::Info, "gc-ok") |
b9e7bcc2 DM |
280 | } |
281 | Err(err) => { | |
3066f564 | 282 | data["error"] = err.to_string().into(); |
04f35d0e | 283 | (Severity::Error, "gc-err") |
b9e7bcc2 DM |
284 | } |
285 | }; | |
04f35d0e LW |
286 | let metadata = HashMap::from([ |
287 | ("datastore".into(), datastore.into()), | |
288 | ("hostname".into(), proxmox_sys::nodename().into()), | |
289 | ("type".into(), "gc".into()), | |
290 | ]); | |
b9e7bcc2 | 291 | |
04f35d0e | 292 | let notification = Notification::from_template(severity, template, data, metadata); |
b9e7bcc2 | 293 | |
04f35d0e LW |
294 | let (email, notify, mode) = lookup_datastore_notify_settings(datastore); |
295 | match mode { | |
296 | NotificationMode::LegacySendmail => { | |
297 | let notify = notify.gc.unwrap_or(Notify::Always); | |
298 | ||
299 | if notify == Notify::Never || (result.is_ok() && notify == Notify::Error) { | |
300 | return Ok(()); | |
301 | } | |
302 | ||
303 | if let Some(email) = email { | |
304 | send_sendmail_legacy_notification(notification, &email)?; | |
305 | } | |
306 | } | |
307 | NotificationMode::NotificationSystem => { | |
308 | send_notification(notification)?; | |
309 | } | |
310 | } | |
b9e7bcc2 DM |
311 | |
312 | Ok(()) | |
313 | } | |
314 | ||
315 | pub fn send_verify_status( | |
b9e7bcc2 | 316 | job: VerificationJobConfig, |
a4915dfc | 317 | result: &Result<Vec<String>, Error>, |
b9e7bcc2 | 318 | ) -> Result<(), Error> { |
3066f564 DM |
319 | let (fqdn, port) = get_server_url(); |
320 | let mut data = json!({ | |
321 | "job": job, | |
322 | "fqdn": fqdn, | |
323 | "port": port, | |
324 | }); | |
b9e7bcc2 | 325 | |
2432775c LW |
326 | let (template, severity) = match result { |
327 | Ok(errors) if errors.is_empty() => ("verify-ok", Severity::Info), | |
a4915dfc | 328 | Ok(errors) => { |
3066f564 | 329 | data["errors"] = json!(errors); |
2432775c | 330 | ("verify-err", Severity::Error) |
b9e7bcc2 | 331 | } |
a4915dfc | 332 | Err(_) => { |
2432775c | 333 | // aborted job - do not send any notification |
a4915dfc DM |
334 | return Ok(()); |
335 | } | |
b9e7bcc2 DM |
336 | }; |
337 | ||
2432775c LW |
338 | let metadata = HashMap::from([ |
339 | ("job-id".into(), job.id.clone()), | |
340 | ("datastore".into(), job.store.clone()), | |
341 | ("hostname".into(), proxmox_sys::nodename().into()), | |
342 | ("type".into(), "verify".into()), | |
343 | ]); | |
344 | ||
345 | let notification = Notification::from_template(severity, template, data, metadata); | |
346 | ||
347 | let (email, notify, mode) = lookup_datastore_notify_settings(&job.store); | |
348 | match mode { | |
349 | NotificationMode::LegacySendmail => { | |
350 | let notify = notify.verify.unwrap_or(Notify::Always); | |
351 | ||
352 | if notify == Notify::Never || (result.is_ok() && notify == Notify::Error) { | |
c26c9390 DM |
353 | return Ok(()); |
354 | } | |
2432775c LW |
355 | |
356 | if let Some(email) = email { | |
357 | send_sendmail_legacy_notification(notification, &email)?; | |
358 | } | |
359 | } | |
360 | NotificationMode::NotificationSystem => { | |
361 | send_notification(notification)?; | |
c26c9390 DM |
362 | } |
363 | } | |
364 | ||
b9e7bcc2 DM |
365 | Ok(()) |
366 | } | |
367 | ||
cf91a072 DC |
368 | pub fn send_prune_status( |
369 | store: &str, | |
370 | jobname: &str, | |
371 | result: &Result<(), Error>, | |
372 | ) -> Result<(), Error> { | |
cf91a072 DC |
373 | let (fqdn, port) = get_server_url(); |
374 | let mut data = json!({ | |
375 | "jobname": jobname, | |
376 | "store": store, | |
377 | "fqdn": fqdn, | |
378 | "port": port, | |
379 | }); | |
380 | ||
3ca03c05 LW |
381 | let (template, severity) = match result { |
382 | Ok(()) => ("prune-ok", Severity::Info), | |
cf91a072 DC |
383 | Err(err) => { |
384 | data["error"] = err.to_string().into(); | |
3ca03c05 | 385 | ("prune-err", Severity::Error) |
cf91a072 DC |
386 | } |
387 | }; | |
388 | ||
3ca03c05 LW |
389 | let metadata = HashMap::from([ |
390 | ("job-id".into(), jobname.to_string()), | |
391 | ("datastore".into(), store.into()), | |
392 | ("hostname".into(), proxmox_sys::nodename().into()), | |
393 | ("type".into(), "prune".into()), | |
394 | ]); | |
cf91a072 | 395 | |
3ca03c05 LW |
396 | let notification = Notification::from_template(severity, template, data, metadata); |
397 | ||
398 | let (email, notify, mode) = lookup_datastore_notify_settings(store); | |
399 | match mode { | |
400 | NotificationMode::LegacySendmail => { | |
401 | let notify = notify.prune.unwrap_or(Notify::Error); | |
402 | ||
403 | if notify == Notify::Never || (result.is_ok() && notify == Notify::Error) { | |
404 | return Ok(()); | |
405 | } | |
406 | ||
407 | if let Some(email) = email { | |
408 | send_sendmail_legacy_notification(notification, &email)?; | |
409 | } | |
410 | } | |
411 | NotificationMode::NotificationSystem => { | |
412 | send_notification(notification)?; | |
413 | } | |
414 | } | |
cf91a072 DC |
415 | |
416 | Ok(()) | |
417 | } | |
418 | ||
5b23a707 | 419 | pub fn send_sync_status(job: &SyncJobConfig, result: &Result<(), Error>) -> Result<(), Error> { |
3066f564 DM |
420 | let (fqdn, port) = get_server_url(); |
421 | let mut data = json!({ | |
422 | "job": job, | |
423 | "fqdn": fqdn, | |
424 | "port": port, | |
425 | }); | |
426 | ||
5b23a707 LW |
427 | let (template, severity) = match result { |
428 | Ok(()) => ("sync-ok", Severity::Info), | |
9e733dae | 429 | Err(err) => { |
3066f564 | 430 | data["error"] = err.to_string().into(); |
5b23a707 | 431 | ("sync-err", Severity::Error) |
9e733dae DM |
432 | } |
433 | }; | |
434 | ||
5b23a707 LW |
435 | let metadata = HashMap::from([ |
436 | ("job-id".into(), job.id.clone()), | |
437 | ("datastore".into(), job.store.clone()), | |
438 | ("hostname".into(), proxmox_sys::nodename().into()), | |
439 | ("type".into(), "sync".into()), | |
440 | ]); | |
4ec73327 | 441 | |
5b23a707 | 442 | let notification = Notification::from_template(severity, template, data, metadata); |
9e733dae | 443 | |
5b23a707 LW |
444 | let (email, notify, mode) = lookup_datastore_notify_settings(&job.store); |
445 | match mode { | |
446 | NotificationMode::LegacySendmail => { | |
447 | let notify = notify.prune.unwrap_or(Notify::Error); | |
448 | ||
449 | if notify == Notify::Never || (result.is_ok() && notify == Notify::Error) { | |
450 | return Ok(()); | |
451 | } | |
452 | ||
453 | if let Some(email) = email { | |
454 | send_sendmail_legacy_notification(notification, &email)?; | |
455 | } | |
456 | } | |
457 | NotificationMode::NotificationSystem => { | |
458 | send_notification(notification)?; | |
459 | } | |
460 | } | |
9e733dae DM |
461 | |
462 | Ok(()) | |
463 | } | |
464 | ||
8703a68a DC |
465 | pub fn send_tape_backup_status( |
466 | email: &str, | |
467 | id: Option<&str>, | |
468 | job: &TapeBackupJobSetup, | |
469 | result: &Result<(), Error>, | |
4abd4dbe | 470 | summary: TapeBackupJobSummary, |
8703a68a | 471 | ) -> Result<(), Error> { |
8703a68a | 472 | let (fqdn, port) = get_server_url(); |
15cc41b6 | 473 | let duration: proxmox_time::TimeSpan = summary.duration.into(); |
8703a68a DC |
474 | let mut data = json!({ |
475 | "job": job, | |
476 | "fqdn": fqdn, | |
477 | "port": port, | |
478 | "id": id, | |
4abd4dbe | 479 | "snapshot-list": summary.snapshot_list, |
36156038 | 480 | "used-tapes": summary.used_tapes, |
4abd4dbe | 481 | "duration": duration.to_string(), |
8703a68a DC |
482 | }); |
483 | ||
484 | let text = match result { | |
ee0ea735 | 485 | Ok(()) => HANDLEBARS.render("tape_backup_ok_template", &data)?, |
8703a68a DC |
486 | Err(err) => { |
487 | data["error"] = err.to_string().into(); | |
488 | HANDLEBARS.render("tape_backup_err_template", &data)? | |
489 | } | |
490 | }; | |
491 | ||
492 | let subject = match (result, id) { | |
dd06b7f1 | 493 | (Ok(()), Some(id)) => format!("Tape Backup '{id}' datastore '{}' successful", job.store,), |
ee0ea735 | 494 | (Ok(()), None) => format!("Tape Backup datastore '{}' successful", job.store,), |
dd06b7f1 | 495 | (Err(_), Some(id)) => format!("Tape Backup '{id}' datastore '{}' failed", job.store,), |
ee0ea735 | 496 | (Err(_), None) => format!("Tape Backup datastore '{}' failed", job.store,), |
8703a68a DC |
497 | }; |
498 | ||
499 | send_job_status_mail(email, &subject, &text)?; | |
500 | ||
501 | Ok(()) | |
502 | } | |
503 | ||
28926247 DC |
504 | /// Send email to a person to request a manual media change |
505 | pub fn send_load_media_email( | |
bdce7fa1 DC |
506 | changer: bool, |
507 | device: &str, | |
28926247 DC |
508 | label_text: &str, |
509 | to: &str, | |
510 | reason: Option<String>, | |
511 | ) -> Result<(), Error> { | |
5574114a WB |
512 | use std::fmt::Write as _; |
513 | ||
bdce7fa1 DC |
514 | let device_type = if changer { "changer" } else { "drive" }; |
515 | ||
516 | let subject = format!("Load Media '{label_text}' request for {device_type} '{device}'"); | |
28926247 DC |
517 | |
518 | let mut text = String::new(); | |
519 | ||
520 | if let Some(reason) = reason { | |
5574114a WB |
521 | let _ = write!( |
522 | text, | |
bdce7fa1 | 523 | "The {device_type} has the wrong or no tape(s) inserted. Error:\n{reason}\n\n" |
5574114a | 524 | ); |
28926247 DC |
525 | } |
526 | ||
bdce7fa1 DC |
527 | if changer { |
528 | text.push_str("Please insert the requested media into the changer.\n\n"); | |
529 | let _ = writeln!(text, "Changer: {device}"); | |
530 | } else { | |
531 | text.push_str("Please insert the requested media into the backup drive.\n\n"); | |
532 | let _ = writeln!(text, "Drive: {device}"); | |
533 | } | |
dd06b7f1 | 534 | let _ = writeln!(text, "Media: {label_text}"); |
28926247 DC |
535 | |
536 | send_job_status_mail(to, &subject, &text) | |
537 | } | |
538 | ||
3066f564 | 539 | fn get_server_url() -> (String, usize) { |
3066f564 DM |
540 | // user will surely request that they can change this |
541 | ||
25877d05 | 542 | let nodename = proxmox_sys::nodename(); |
3066f564 DM |
543 | let mut fqdn = nodename.to_owned(); |
544 | ||
545 | if let Ok(resolv_conf) = crate::api2::node::dns::read_etc_resolv_conf() { | |
546 | if let Some(search) = resolv_conf["search"].as_str() { | |
547 | fqdn.push('.'); | |
548 | fqdn.push_str(search); | |
549 | } | |
550 | } | |
551 | ||
552 | let port = 8007; | |
553 | ||
554 | (fqdn, port) | |
555 | } | |
556 | ||
ee0ea735 | 557 | pub fn send_updates_available(updates: &[&APTUpdateInfo]) -> Result<(), Error> { |
823314c7 LW |
558 | let (fqdn, port) = get_server_url(); |
559 | let hostname = proxmox_sys::nodename().to_string(); | |
86d60245 | 560 | |
823314c7 LW |
561 | let data = json!({ |
562 | "fqdn": fqdn, | |
563 | "hostname": &hostname, | |
564 | "port": port, | |
565 | "updates": updates, | |
566 | }); | |
3066f564 | 567 | |
823314c7 LW |
568 | let metadata = HashMap::from([ |
569 | ("hostname".into(), hostname), | |
570 | ("type".into(), "package-updates".into()), | |
571 | ]); | |
86d60245 | 572 | |
823314c7 LW |
573 | let notification = |
574 | Notification::from_template(Severity::Info, "package-updates", data, metadata); | |
575 | ||
576 | send_notification(notification)?; | |
86d60245 TL |
577 | Ok(()) |
578 | } | |
579 | ||
9e8daa1d | 580 | /// send email on certificate renewal failure. |
9e8daa1d SS |
581 | pub fn send_certificate_renewal_mail(result: &Result<(), Error>) -> Result<(), Error> { |
582 | let error: String = match result { | |
e1db0670 | 583 | Err(e) => e.to_string(), |
9e8daa1d SS |
584 | _ => return Ok(()), |
585 | }; | |
586 | ||
1d2069d1 | 587 | let (fqdn, port) = get_server_url(); |
9e8daa1d | 588 | |
1d2069d1 LW |
589 | let data = json!({ |
590 | "fqdn": fqdn, | |
591 | "port": port, | |
592 | "error": error, | |
593 | }); | |
9e8daa1d | 594 | |
1d2069d1 LW |
595 | let metadata = HashMap::from([ |
596 | ("hostname".into(), proxmox_sys::nodename().into()), | |
597 | ("type".into(), "acme".into()), | |
598 | ]); | |
9e8daa1d | 599 | |
1d2069d1 | 600 | let notification = Notification::from_template(Severity::Info, "acme-err", data, metadata); |
9e8daa1d | 601 | |
1d2069d1 | 602 | send_notification(notification)?; |
9e8daa1d SS |
603 | Ok(()) |
604 | } | |
605 | ||
b9e7bcc2 | 606 | /// Lookup users email address |
c9793d47 | 607 | pub fn lookup_user_email(userid: &Userid) -> Option<String> { |
ba3d7e19 | 608 | if let Ok(user_config) = pbs_config::user::cached_config() { |
b9e7bcc2 | 609 | if let Ok(user) = user_config.lookup::<User>("user", userid.as_str()) { |
44288184 | 610 | return user.email; |
b9e7bcc2 DM |
611 | } |
612 | } | |
613 | ||
614 | None | |
615 | } | |
616 | ||
f47c1d3a | 617 | /// Lookup Datastore notify settings |
04f35d0e LW |
618 | pub fn lookup_datastore_notify_settings( |
619 | store: &str, | |
620 | ) -> (Option<String>, DatastoreNotify, NotificationMode) { | |
f47c1d3a DM |
621 | let mut email = None; |
622 | ||
ee0ea735 TL |
623 | let notify = DatastoreNotify { |
624 | gc: None, | |
625 | verify: None, | |
626 | sync: None, | |
cf91a072 | 627 | prune: None, |
ee0ea735 | 628 | }; |
c26c9390 | 629 | |
e7d4be9d | 630 | let (config, _digest) = match pbs_config::datastore::config() { |
f47c1d3a | 631 | Ok(result) => result, |
04f35d0e | 632 | Err(_) => return (email, notify, NotificationMode::default()), |
f47c1d3a DM |
633 | }; |
634 | ||
635 | let config: DataStoreConfig = match config.lookup("datastore", store) { | |
636 | Ok(result) => result, | |
04f35d0e | 637 | Err(_) => return (email, notify, NotificationMode::default()), |
f47c1d3a DM |
638 | }; |
639 | ||
640 | email = match config.notify_user { | |
641 | Some(ref userid) => lookup_user_email(userid), | |
ad54df31 | 642 | None => lookup_user_email(Userid::root_userid()), |
f47c1d3a DM |
643 | }; |
644 | ||
04f35d0e | 645 | let notification_mode = config.notification_mode.unwrap_or_default(); |
17c7b46a | 646 | let notify_str = config.notify.unwrap_or_default(); |
c26c9390 | 647 | |
9fa3026a | 648 | if let Ok(value) = DatastoreNotify::API_SCHEMA.parse_property_string(¬ify_str) { |
c26c9390 | 649 | if let Ok(notify) = serde_json::from_value(value) { |
04f35d0e | 650 | return (email, notify, notification_mode); |
c26c9390 | 651 | } |
f47c1d3a DM |
652 | } |
653 | ||
04f35d0e | 654 | (email, notify, notification_mode) |
b9e7bcc2 | 655 | } |
25b4d52d DC |
656 | |
657 | #[test] | |
658 | fn test_template_register() { | |
25b4d52d DC |
659 | assert!(HANDLEBARS.has_template("tape_backup_ok_template")); |
660 | assert!(HANDLEBARS.has_template("tape_backup_err_template")); | |
25b4d52d | 661 | } |