4 use handlebars
::{Handlebars, Helper, Context, RenderError, RenderContext, Output, HelperResult}
;
6 use proxmox
::tools
::email
::sendmail
;
7 use proxmox
::api
::schema
::parse_property_string
;
10 config
::datastore
::DataStoreConfig
,
11 config
::verify
::VerificationJobConfig
,
12 config
::sync
::SyncJobConfig
,
13 config
::tape_job
::TapeBackupJobSetup
,
16 GarbageCollectionStatus
,
21 tools
::format
::HumanByte
,
24 const GC_OK_TEMPLATE
: &str = r
###"
26 Datastore: {{datastore}}
27 Task ID: {{status.upid}}
28 Index file count: {{status.index-file-count}}
30 Removed garbage: {{human-bytes status.removed-bytes}}
31 Removed chunks: {{status.removed-chunks}}
32 Removed bad chunks: {{status.removed-bad}}
34 Leftover bad chunks: {{status.still-bad}}
35 Pending removals: {{human-bytes status.pending-bytes}} (in {{status.pending-chunks}} chunks)
37 Original Data usage: {{human-bytes status.index-data-bytes}}
38 On-Disk usage: {{human-bytes status.disk-bytes}} ({{relative-percentage status.disk-bytes status.index-data-bytes}})
39 On-Disk chunks: {{status.disk-chunks}}
41 Deduplication Factor: {{deduplication-factor}}
43 Garbage collection successful.
46 Please visit the web interface for further details:
48 <https://{{fqdn}}:{{port}}/#DataStore-{{datastore}}>
53 const GC_ERR_TEMPLATE
: &str = r
###"
55 Datastore: {{datastore}}
57 Garbage collection failed: {{error}}
60 Please visit the web interface for further details:
62 <https://{{fqdn}}:{{port}}/#pbsServerAdministration:tasks>
66 const VERIFY_OK_TEMPLATE
: &str = r
###"
69 Datastore: {{job.store}}
71 Verification successful.
74 Please visit the web interface for further details:
76 <https://{{fqdn}}:{{port}}/#DataStore-{{job.store}}>
80 const VERIFY_ERR_TEMPLATE
: &str = r
###"
83 Datastore: {{job.store}}
85 Verification failed on these snapshots/groups:
92 Please visit the web interface for further details:
94 <https://{{fqdn}}:{{port}}/#pbsServerAdministration:tasks>
98 const SYNC_OK_TEMPLATE
: &str = r
###"
101 Datastore: {{job.store}}
102 Remote: {{job.remote}}
103 Remote Store: {{job.remote-store}}
105 Synchronization successful.
108 Please visit the web interface for further details:
110 <https://{{fqdn}}:{{port}}/#DataStore-{{job.store}}>
114 const SYNC_ERR_TEMPLATE
: &str = r
###"
117 Datastore: {{job.store}}
118 Remote: {{job.remote}}
119 Remote Store: {{job.remote-store}}
121 Synchronization failed: {{error}}
124 Please visit the web interface for further details:
126 <https://{{fqdn}}:{{port}}/#pbsServerAdministration:tasks>
130 const PACKAGE_UPDATES_TEMPLATE
: &str = r
###"
131 Proxmox Backup Server has the following updates available:
133 {{Package}}: {{OldVersion}} -> {{Version~}}
136 To upgrade visit the web interface:
138 <https://{{fqdn}}:{{port}}/#pbsServerAdministration:updates>
142 const TAPE_BACKUP_OK_TEMPLATE
: &str = r
###"
147 Datastore: {{job.store}}
148 Tape Pool: {{job.pool}}
149 Tape Drive: {{job.drive}}
152 Tape Backup successful.
155 Please visit the web interface for further details:
157 <https://{{fqdn}}:{{port}}/#DataStore-{{job.store}}>
161 const TAPE_BACKUP_ERR_TEMPLATE
: &str = r
###"
166 Datastore: {{job.store}}
167 Tape Pool: {{job.pool}}
168 Tape Drive: {{job.drive}}
171 Tape Backup failed: {{error}}
174 Please visit the web interface for further details:
176 <https://{{fqdn}}:{{port}}/#pbsServerAdministration:tasks>
180 lazy_static
::lazy_static
!{
182 static ref HANDLEBARS
: Handlebars
<'
static> = {
183 let mut hb
= Handlebars
::new();
185 hb
.set_strict_mode(true);
187 hb
.register_helper("human-bytes", Box
::new(handlebars_humam_bytes_helper
));
188 hb
.register_helper("relative-percentage", Box
::new(handlebars_relative_percentage_helper
));
190 hb
.register_template_string("gc_ok_template", GC_OK_TEMPLATE
).unwrap();
191 hb
.register_template_string("gc_err_template", GC_ERR_TEMPLATE
).unwrap();
193 hb
.register_template_string("verify_ok_template", VERIFY_OK_TEMPLATE
).unwrap();
194 hb
.register_template_string("verify_err_template", VERIFY_ERR_TEMPLATE
).unwrap();
196 hb
.register_template_string("sync_ok_template", SYNC_OK_TEMPLATE
).unwrap();
197 hb
.register_template_string("sync_err_template", SYNC_ERR_TEMPLATE
).unwrap();
199 hb
.register_template_string("tape_backup_ok_template", TAPE_BACKUP_OK_TEMPLATE
).unwrap();
200 hb
.register_template_string("tape_backup_err_template", TAPE_BACKUP_ERR_TEMPLATE
).unwrap();
202 hb
.register_template_string("package_update_template", PACKAGE_UPDATES_TEMPLATE
).unwrap();
208 fn send_job_status_mail(
212 ) -> Result
<(), Error
> {
214 // Note: OX has serious problems displaying text mails,
215 // so we include html as well
216 let html
= format
!("<html><body><pre>\n{}\n<pre>", handlebars
::html_escape(text
));
218 let nodename
= proxmox
::tools
::nodename();
220 let author
= format
!("Proxmox Backup Server - {}", nodename
);
234 pub fn send_gc_status(
236 notify
: DatastoreNotify
,
238 status
: &GarbageCollectionStatus
,
239 result
: &Result
<(), Error
>,
240 ) -> Result
<(), Error
> {
243 None
=> { /* send notifications by default */ }
,
245 if notify
== Notify
::Never
|| (result
.is_ok() && notify
== Notify
::Error
) {
251 let (fqdn
, port
) = get_server_url();
252 let mut data
= json
!({
253 "datastore": datastore
,
258 let text
= match result
{
260 let deduplication_factor
= if status
.disk_bytes
> 0 {
261 (status
.index_data_bytes
as f64)/(status
.disk_bytes
as f64)
266 data
["status"] = json
!(status
);
267 data
["deduplication-factor"] = format
!("{:.2}", deduplication_factor
).into();
269 HANDLEBARS
.render("gc_ok_template", &data
)?
272 data
["error"] = err
.to_string().into();
273 HANDLEBARS
.render("gc_err_template", &data
)?
277 let subject
= match result
{
279 "Garbage Collect Datastore '{}' successful",
283 "Garbage Collect Datastore '{}' failed",
288 send_job_status_mail(email
, &subject
, &text
)?
;
293 pub fn send_verify_status(
295 notify
: DatastoreNotify
,
296 job
: VerificationJobConfig
,
297 result
: &Result
<Vec
<String
>, Error
>,
298 ) -> Result
<(), Error
> {
300 let (fqdn
, port
) = get_server_url();
301 let mut data
= json
!({
307 let mut result_is_ok
= false;
309 let text
= match result
{
310 Ok(errors
) if errors
.is_empty() => {
312 HANDLEBARS
.render("verify_ok_template", &data
)?
315 data
["errors"] = json
!(errors
);
316 HANDLEBARS
.render("verify_err_template", &data
)?
319 // aborted job - do not send any email
324 match notify
.verify
{
325 None
=> { /* send notifications by default */ }
,
327 if notify
== Notify
::Never
|| (result_is_ok
&& notify
== Notify
::Error
) {
333 let subject
= match result
{
334 Ok(errors
) if errors
.is_empty() => format
!(
335 "Verify Datastore '{}' successful",
339 "Verify Datastore '{}' failed",
344 send_job_status_mail(email
, &subject
, &text
)?
;
349 pub fn send_sync_status(
351 notify
: DatastoreNotify
,
353 result
: &Result
<(), Error
>,
354 ) -> Result
<(), Error
> {
357 None
=> { /* send notifications by default */ }
,
359 if notify
== Notify
::Never
|| (result
.is_ok() && notify
== Notify
::Error
) {
365 let (fqdn
, port
) = get_server_url();
366 let mut data
= json
!({
372 let text
= match result
{
374 HANDLEBARS
.render("sync_ok_template", &data
)?
377 data
["error"] = err
.to_string().into();
378 HANDLEBARS
.render("sync_err_template", &data
)?
382 let subject
= match result
{
384 "Sync remote '{}' datastore '{}' successful",
389 "Sync remote '{}' datastore '{}' failed",
395 send_job_status_mail(email
, &subject
, &text
)?
;
400 pub fn send_tape_backup_status(
403 job
: &TapeBackupJobSetup
,
404 result
: &Result
<(), Error
>,
405 ) -> Result
<(), Error
> {
407 let (fqdn
, port
) = get_server_url();
408 let mut data
= json
!({
415 let text
= match result
{
417 HANDLEBARS
.render("tape_backup_ok_template", &data
)?
420 data
["error"] = err
.to_string().into();
421 HANDLEBARS
.render("tape_backup_err_template", &data
)?
425 let subject
= match (result
, id
) {
426 (Ok(()), Some(id
)) => format
!(
427 "Tape Backup '{}' datastore '{}' successful",
431 (Ok(()), None
) => format
!(
432 "Tape Backup datastore '{}' successful",
435 (Err(_
), Some(id
)) => format
!(
436 "Tape Backup '{}' datastore '{}' failed",
440 (Err(_
), None
) => format
!(
441 "Tape Backup datastore '{}' failed",
446 send_job_status_mail(email
, &subject
, &text
)?
;
451 /// Send email to a person to request a manual media change
452 pub fn send_load_media_email(
456 reason
: Option
<String
>,
457 ) -> Result
<(), Error
> {
459 let subject
= format
!("Load Media '{}' request for drive '{}'", label_text
, drive
);
461 let mut text
= String
::new();
463 if let Some(reason
) = reason
{
464 text
.push_str(&format
!("The drive has the wrong or no tape inserted. Error:\n{}\n\n", reason
));
467 text
.push_str("Please insert the requested media into the backup drive.\n\n");
469 text
.push_str(&format
!("Drive: {}\n", drive
));
470 text
.push_str(&format
!("Media: {}\n", label_text
));
472 send_job_status_mail(to
, &subject
, &text
)
475 fn get_server_url() -> (String
, usize) {
477 // user will surely request that they can change this
479 let nodename
= proxmox
::tools
::nodename();
480 let mut fqdn
= nodename
.to_owned();
482 if let Ok(resolv_conf
) = crate::api2
::node
::dns
::read_etc_resolv_conf() {
483 if let Some(search
) = resolv_conf
["search"].as_str() {
485 fqdn
.push_str(search
);
494 pub fn send_updates_available(
495 updates
: &[&APTUpdateInfo
],
496 ) -> Result
<(), Error
> {
497 // update mails always go to the root@pam configured email..
498 if let Some(email
) = lookup_user_email(Userid
::root_userid()) {
499 let nodename
= proxmox
::tools
::nodename();
500 let subject
= format
!("New software packages available ({})", nodename
);
502 let (fqdn
, port
) = get_server_url();
504 let text
= HANDLEBARS
.render("package_update_template", &json
!({
510 send_job_status_mail(&email
, &subject
, &text
)?
;
515 /// Lookup users email address
516 pub fn lookup_user_email(userid
: &Userid
) -> Option
<String
> {
518 use crate::config
::user
::{self, User}
;
520 if let Ok(user_config
) = user
::cached_config() {
521 if let Ok(user
) = user_config
.lookup
::<User
>("user", userid
.as_str()) {
529 /// Lookup Datastore notify settings
530 pub fn lookup_datastore_notify_settings(
532 ) -> (Option
<String
>, DatastoreNotify
) {
534 let mut email
= None
;
536 let notify
= DatastoreNotify { gc: None, verify: None, sync: None }
;
538 let (config
, _digest
) = match crate::config
::datastore
::config() {
539 Ok(result
) => result
,
540 Err(_
) => return (email
, notify
),
543 let config
: DataStoreConfig
= match config
.lookup("datastore", store
) {
544 Ok(result
) => result
,
545 Err(_
) => return (email
, notify
),
548 email
= match config
.notify_user
{
549 Some(ref userid
) => lookup_user_email(userid
),
550 None
=> lookup_user_email(Userid
::root_userid()),
553 let notify_str
= config
.notify
.unwrap_or_default();
555 if let Ok(value
) = parse_property_string(¬ify_str
, &DatastoreNotify
::API_SCHEMA
) {
556 if let Ok(notify
) = serde_json
::from_value(value
) {
557 return (email
, notify
);
564 // Handlerbar helper functions
566 fn handlebars_humam_bytes_helper(
570 _rc
: &mut RenderContext
,
573 let param
= h
.param(0).map(|v
| v
.value().as_u64())
575 .ok_or_else(|| RenderError
::new("human-bytes: param not found"))?
;
577 out
.write(&HumanByte
::from(param
).to_string())?
;
582 fn handlebars_relative_percentage_helper(
586 _rc
: &mut RenderContext
,
589 let param0
= h
.param(0).map(|v
| v
.value().as_f64())
591 .ok_or_else(|| RenderError
::new("relative-percentage: param0 not found"))?
;
592 let param1
= h
.param(1).map(|v
| v
.value().as_f64())
594 .ok_or_else(|| RenderError
::new("relative-percentage: param1 not found"))?
;
599 out
.write(&format
!("{:.2}%", (param0
*100.0)/param1
))?
;