4 use handlebars
::{Handlebars, Helper, Context, RenderError, RenderContext, Output, HelperResult}
;
6 use proxmox
::tools
::email
::sendmail
;
9 config
::verify
::VerificationJobConfig
,
10 config
::sync
::SyncJobConfig
,
13 GarbageCollectionStatus
,
16 tools
::format
::HumanByte
,
19 const GC_OK_TEMPLATE
: &str = r
###"
21 Datastore: {{datastore}}
22 Task ID: {{status.upid}}
23 Index file count: {{status.index-file-count}}
25 Removed garbage: {{human-bytes status.removed-bytes}}
26 Removed chunks: {{status.removed-chunks}}
27 Removed bad chunks: {{status.removed-bad}}
29 Leftover bad chunks: {{status.still-bad}}
30 Pending removals: {{human-bytes status.pending-bytes}} (in {{status.pending-chunks}} chunks)
32 Original Data usage: {{human-bytes status.index-data-bytes}}
33 On-Disk usage: {{human-bytes status.disk-bytes}} ({{relative-percentage status.disk-bytes status.index-data-bytes}})
34 On-Disk chunks: {{status.disk-chunks}}
36 Deduplication Factor: {{deduplication-factor}}
38 Garbage collection successful.
43 const GC_ERR_TEMPLATE
: &str = r
###"
45 Datastore: {{datastore}}
47 Garbage collection failed: {{error}}
51 const VERIFY_OK_TEMPLATE
: &str = r
###"
54 Datastore: {{job.store}}
56 Verification successful.
60 const VERIFY_ERR_TEMPLATE
: &str = r
###"
63 Datastore: {{job.store}}
65 Verification failed on these snapshots:
73 const SYNC_OK_TEMPLATE
: &str = r
###"
76 Datastore: {{job.store}}
77 Remote: {{job.remote}}
78 Remote Store: {{job.remote-store}}
80 Synchronization successful.
84 const SYNC_ERR_TEMPLATE
: &str = r
###"
87 Datastore: {{job.store}}
88 Remote: {{job.remote}}
89 Remote Store: {{job.remote-store}}
91 Synchronization failed: {{error}}
95 const PACKAGE_UPDATES_TEMPLATE
: &str = r
###"
96 Proxmox Backup Server has the following updates available:
98 {{Package}}: {{OldVersion}} -> {{Version~}}
101 To upgrade visit the webinderface: <https://{{fqdn}}:{{port}}/#pbsServerAdministration:updates>
105 lazy_static
::lazy_static
!{
107 static ref HANDLEBARS
: Handlebars
<'
static> = {
108 let mut hb
= Handlebars
::new();
110 hb
.set_strict_mode(true);
112 hb
.register_helper("human-bytes", Box
::new(handlebars_humam_bytes_helper
));
113 hb
.register_helper("relative-percentage", Box
::new(handlebars_relative_percentage_helper
));
115 hb
.register_template_string("gc_ok_template", GC_OK_TEMPLATE
).unwrap();
116 hb
.register_template_string("gc_err_template", GC_ERR_TEMPLATE
).unwrap();
118 hb
.register_template_string("verify_ok_template", VERIFY_OK_TEMPLATE
).unwrap();
119 hb
.register_template_string("verify_err_template", VERIFY_ERR_TEMPLATE
).unwrap();
121 hb
.register_template_string("sync_ok_template", SYNC_OK_TEMPLATE
).unwrap();
122 hb
.register_template_string("sync_err_template", SYNC_ERR_TEMPLATE
).unwrap();
124 hb
.register_template_string("package_update_template", PACKAGE_UPDATES_TEMPLATE
).unwrap();
130 fn send_job_status_mail(
134 ) -> Result
<(), Error
> {
136 // Note: OX has serious problems displaying text mails,
137 // so we include html as well
138 let html
= format
!("<html><body><pre>\n{}\n<pre>", handlebars
::html_escape(text
));
140 let nodename
= proxmox
::tools
::nodename();
142 let author
= format
!("Proxmox Backup Server - {}", nodename
);
156 pub fn send_gc_status(
159 status
: &GarbageCollectionStatus
,
160 result
: &Result
<(), Error
>,
161 ) -> Result
<(), Error
> {
163 let text
= match result
{
165 let deduplication_factor
= if status
.disk_bytes
> 0 {
166 (status
.index_data_bytes
as f64)/(status
.disk_bytes
as f64)
173 "datastore": datastore
,
174 "deduplication-factor": format
!("{:.2}", deduplication_factor
),
177 HANDLEBARS
.render("gc_ok_template", &data
)?
181 "error": err
.to_string(),
182 "datastore": datastore
,
184 HANDLEBARS
.render("gc_err_template", &data
)?
188 let subject
= match result
{
190 "Garbage Collect Datastore '{}' successful",
194 "Garbage Collect Datastore '{}' failed",
199 send_job_status_mail(email
, &subject
, &text
)?
;
204 pub fn send_verify_status(
206 job
: VerificationJobConfig
,
207 result
: &Result
<Vec
<String
>, Error
>,
208 ) -> Result
<(), Error
> {
211 let text
= match result
{
212 Ok(errors
) if errors
.is_empty() => {
213 let data
= json
!({ "job": job }
);
214 HANDLEBARS
.render("verify_ok_template", &data
)?
217 let data
= json
!({ "job": job, "errors": errors }
);
218 HANDLEBARS
.render("verify_err_template", &data
)?
221 // aborted job - do not send any email
226 let subject
= match result
{
227 Ok(errors
) if errors
.is_empty() => format
!(
228 "Verify Datastore '{}' successful",
232 "Verify Datastore '{}' failed",
237 send_job_status_mail(email
, &subject
, &text
)?
;
242 pub fn send_sync_status(
245 result
: &Result
<(), Error
>,
246 ) -> Result
<(), Error
> {
248 let text
= match result
{
250 let data
= json
!({ "job": job }
);
251 HANDLEBARS
.render("sync_ok_template", &data
)?
254 let data
= json
!({ "job": job, "error": err.to_string() }
);
255 HANDLEBARS
.render("sync_err_template", &data
)?
259 let subject
= match result
{
261 "Sync remote '{}' datastore '{}' successful",
266 "Sync remote '{}' datastore '{}' failed",
272 send_job_status_mail(email
, &subject
, &text
)?
;
277 pub fn send_updates_available(
278 updates
: &Vec
<&APTUpdateInfo
>,
279 ) -> Result
<(), Error
> {
280 // update mails always go to the root@pam configured email..
281 if let Some(email
) = lookup_user_email(Userid
::root_userid()) {
282 let nodename
= proxmox
::tools
::nodename();
283 let subject
= format
!("New software packages available ({})", nodename
);
285 let text
= HANDLEBARS
.render("package_update_template", &json
!({
286 "fqdn": nix
::sys
::utsname
::uname().nodename(), // FIXME: add get_fqdn helper like PVE?
287 "port": 8007, // user will surely request that they can change this
291 send_job_status_mail(&email
, &subject
, &text
)?
;
296 /// Lookup users email address
298 /// For "backup@pam", this returns the address from "root@pam".
299 pub fn lookup_user_email(userid
: &Userid
) -> Option
<String
> {
301 use crate::config
::user
::{self, User}
;
303 if userid
== Userid
::backup_userid() {
304 return lookup_user_email(Userid
::root_userid());
307 if let Ok(user_config
) = user
::cached_config() {
308 if let Ok(user
) = user_config
.lookup
::<User
>("user", userid
.as_str()) {
309 return user
.email
.clone();
316 // Handlerbar helper functions
318 fn handlebars_humam_bytes_helper(
322 _rc
: &mut RenderContext
,
325 let param
= h
.param(0).map(|v
| v
.value().as_u64())
327 .ok_or(RenderError
::new("human-bytes: param not found"))?
;
329 out
.write(&HumanByte
::from(param
).to_string())?
;
334 fn handlebars_relative_percentage_helper(
338 _rc
: &mut RenderContext
,
341 let param0
= h
.param(0).map(|v
| v
.value().as_f64())
343 .ok_or(RenderError
::new("relative-percentage: param0 not found"))?
;
344 let param1
= h
.param(1).map(|v
| v
.value().as_f64())
346 .ok_or(RenderError
::new("relative-percentage: param1 not found"))?
;
351 out
.write(&format
!("{:.2}%", (param0
*100.0)/param1
))?
;