mirror of
https://github.com/unraid/api.git
synced 2025-12-21 08:39:38 -06:00
feat(notifications): add notify script for backup and restore on plugin installation
This commit is contained in:
@@ -181,6 +181,7 @@ echo "Backing up original files..."
|
||||
|
||||
# Define files to backup in a shell variable
|
||||
FILES_TO_BACKUP=(
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify"
|
||||
"/usr/local/emhttp/plugins/dynamix/DisplaySettings.page"
|
||||
"/usr/local/emhttp/plugins/dynamix/Registration.page"
|
||||
"/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php"
|
||||
@@ -324,6 +325,7 @@ exit 0
|
||||
|
||||
# Define files to restore in a shell variable - must match backup list
|
||||
FILES_TO_RESTORE=(
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify"
|
||||
"/usr/local/emhttp/plugins/dynamix/DisplaySettings.page"
|
||||
"/usr/local/emhttp/plugins/dynamix/Registration.page"
|
||||
"/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php"
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, Bergware International.
|
||||
* Copyright 2012, Andrew Hamer-Adams, http://www.pixeleyes.co.nz.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/webGui/include/Encryption.php";
|
||||
|
||||
function usage() {
|
||||
echo <<<EOT
|
||||
notify [-e "event"] [-s "subject"] [-d "description"] [-i "normal|warning|alert"] [-m "message"] [-x] [-t] [-b] [add]
|
||||
create a notification
|
||||
use -e to specify the event
|
||||
use -s to specify a subject
|
||||
use -d to specify a short description
|
||||
use -i to specify the severity
|
||||
use -m to specify a message (long description)
|
||||
use -l to specify a link (clicking the notification will take you to that location)
|
||||
use -x to create a single notification ticket
|
||||
use -r to specify recipients and not use default
|
||||
use -t to force send email only (for testing)
|
||||
use -b to NOT send a browser notification
|
||||
use -u to specify a custom filename (API use only)
|
||||
all options are optional
|
||||
|
||||
notify init
|
||||
Initialize the notification subsystem.
|
||||
|
||||
notify smtp-init
|
||||
Initialize sendmail configuration (ssmtp in our case).
|
||||
|
||||
notify get
|
||||
Output a json-encoded list of all the unread notifications.
|
||||
|
||||
notify archive file
|
||||
Move file from 'unread' state to 'archive' state.
|
||||
|
||||
EOT;
|
||||
return 1;
|
||||
}
|
||||
|
||||
function generate_email($event, $subject, $description, $importance, $message, $recipients, $fqdnlink) {
|
||||
global $ssmtp;
|
||||
$rcpt = $ssmtp['RcptTo'];
|
||||
if (!$recipients)
|
||||
$to = implode(',', explode(' ', trim($rcpt)));
|
||||
else
|
||||
$to = $recipients;
|
||||
if (empty($to)) return;
|
||||
$subj = "{$ssmtp['Subject']}$subject";
|
||||
$headers = [];
|
||||
$headers[] = "MIME-Version: 1.0";
|
||||
$headers[] = "X-Mailer: PHP/".phpversion();
|
||||
$headers[] = "Content-type: text/plain; charset=utf-8";
|
||||
$headers[] = "From: {$ssmtp['root']}";
|
||||
$headers[] = "Reply-To: {$ssmtp['root']}";
|
||||
if (($importance == "warning" || $importance == "alert") && $ssmtp['SetEmailPriority']=="True") {
|
||||
$headers[] = "X-Priority: 1 (highest)";
|
||||
$headers[] = "X-Mms-Priority: High";
|
||||
}
|
||||
$headers[] = "";
|
||||
$body = [];
|
||||
if (!empty($fqdnlink)) {
|
||||
$body[] = "Link: $fqdnlink";
|
||||
$body[] = "";
|
||||
}
|
||||
$body[] = "Event: $event";
|
||||
$body[] = "Subject: $subject";
|
||||
$body[] = "Description: $description";
|
||||
$body[] = "Importance: $importance";
|
||||
if (!empty($message)) {
|
||||
$body[] = "";
|
||||
foreach (explode('\n',$message) as $line)
|
||||
$body[] = $line;
|
||||
}
|
||||
$body[] = "";
|
||||
return mail($to, $subj, implode("\n", $body), implode("\n", $headers));
|
||||
}
|
||||
|
||||
function safe_filename($string, $maxLength=255) {
|
||||
$special_chars = ["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"];
|
||||
$string = trim(str_replace($special_chars, "", $string));
|
||||
$string = preg_replace('~[^0-9a-z _\-]~i', '', $string);
|
||||
$string = preg_replace('~[- ]~i', '_', $string);
|
||||
// limit filename length to $maxLength characters
|
||||
return substr(trim($string), 0, $maxLength);
|
||||
}
|
||||
|
||||
/*
|
||||
Call this when using the subject field in email or agents. Do not use when showing the subject in a browser.
|
||||
Removes all HTML entities from subject line, is specifically targetting the my_temp() function, which adds ' °'
|
||||
*/
|
||||
function clean_subject($subject) {
|
||||
$subject = preg_replace("/&#?[a-z0-9]{2,8};/i"," ",$subject);
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap string values in double quotes for INI compatibility and escape quotes/backslashes.
|
||||
* Numeric types remain unquoted so they can be parsed as-is.
|
||||
*/
|
||||
function ini_encode_value($value) {
|
||||
if (is_int($value) || is_float($value)) return $value;
|
||||
if (is_bool($value)) return $value ? 'true' : 'false';
|
||||
$value = (string)$value;
|
||||
return '"'.strtr($value, ["\\"=>"\\\\", '"' => '\\"']).'"';
|
||||
}
|
||||
|
||||
function build_ini_string(array $data) {
|
||||
$lines = [];
|
||||
foreach ($data as $key => $value) {
|
||||
$lines[] = "{$key}=".ini_encode_value($value);
|
||||
}
|
||||
return implode("\n", $lines)."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims and unescapes strings (eg quotes, backslashes) if necessary.
|
||||
*/
|
||||
function ini_decode_value($value) {
|
||||
$value = trim($value);
|
||||
$length = strlen($value);
|
||||
if ($length >= 2 && $value[0] === '"' && $value[$length-1] === '"') {
|
||||
return stripslashes(substr($value, 1, -1));
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
// start
|
||||
if ($argc == 1) exit(usage());
|
||||
|
||||
extract(parse_plugin_cfg("dynamix",true));
|
||||
|
||||
$path = _var($notify,'path','/tmp/notifications');
|
||||
$unread = "$path/unread";
|
||||
$archive = "$path/archive";
|
||||
$agents_dir = "/boot/config/plugins/dynamix/notifications/agents";
|
||||
if (is_dir($agents_dir)) {
|
||||
$agents = [];
|
||||
foreach (array_diff(scandir($agents_dir), ['.','..']) as $p) {
|
||||
if (file_exists("{$agents_dir}/{$p}")) $agents[] = "{$agents_dir}/{$p}";
|
||||
}
|
||||
} else {
|
||||
$agents = NULL;
|
||||
}
|
||||
|
||||
switch ($argv[1][0]=='-' ? 'add' : $argv[1]) {
|
||||
case 'init':
|
||||
$files = glob("$unread/*.notify", GLOB_NOSORT);
|
||||
foreach ($files as $file) if (!is_readable($file)) chmod($file,0666);
|
||||
break;
|
||||
|
||||
case 'smtp-init':
|
||||
@mkdir($unread,0755,true);
|
||||
@mkdir($archive,0755,true);
|
||||
$conf = [];
|
||||
$conf[] = "# Generated settings:";
|
||||
$conf[] = "Root={$ssmtp['root']}";
|
||||
$domain = strtok($ssmtp['root'],'@');
|
||||
$domain = strtok('@');
|
||||
$conf[] = "rewriteDomain=$domain";
|
||||
$conf[] = "FromLineOverride=YES";
|
||||
$conf[] = "Mailhub={$ssmtp['server']}:{$ssmtp['port']}";
|
||||
$conf[] = "UseTLS={$ssmtp['UseTLS']}";
|
||||
$conf[] = "UseSTARTTLS={$ssmtp['UseSTARTTLS']}";
|
||||
if ($ssmtp['AuthMethod'] != "none") {
|
||||
$conf[] = "AuthMethod={$ssmtp['AuthMethod']}";
|
||||
$conf[] = "AuthUser={$ssmtp['AuthUser']}";
|
||||
$conf[] = "AuthPass=".base64_decrypt($ssmtp['AuthPass']);
|
||||
}
|
||||
$conf[] = "";
|
||||
file_put_contents("/etc/ssmtp/ssmtp.conf", implode("\n", $conf));
|
||||
break;
|
||||
|
||||
case 'cron-init':
|
||||
@mkdir($unread,0755,true);
|
||||
@mkdir($archive,0755,true);
|
||||
$text = empty($notify['status']) ? "" : "# Generated array status check schedule:\n{$notify['status']} $docroot/plugins/dynamix/scripts/statuscheck &> /dev/null\n\n";
|
||||
parse_cron_cfg("dynamix", "status-check", $text);
|
||||
$text = empty($notify['unraidos']) ? "" : "# Generated Unraid OS update check schedule:\n{$notify['unraidos']} $docroot/plugins/dynamix.plugin.manager/scripts/unraidcheck &> /dev/null\n\n";
|
||||
parse_cron_cfg("dynamix", "unraid-check", $text);
|
||||
$text = empty($notify['version']) ? "" : "# Generated plugins version check schedule:\n{$notify['version']} $docroot/plugins/dynamix.plugin.manager/scripts/plugincheck &> /dev/null\n\n";
|
||||
parse_cron_cfg("dynamix", "plugin-check", $text);
|
||||
$text = empty($notify['system']) ? "" : "# Generated system monitoring schedule:\n{$notify['system']} $docroot/plugins/dynamix/scripts/monitor &> /dev/null\n\n";
|
||||
parse_cron_cfg("dynamix", "monitor", $text);
|
||||
$text = empty($notify['docker_update']) ? "" : "# Generated docker monitoring schedule:\n{$notify['docker_update']} $docroot/plugins/dynamix.docker.manager/scripts/dockerupdate check &> /dev/null\n\n";
|
||||
parse_cron_cfg("dynamix", "docker-update", $text);
|
||||
$text = empty($notify['language_update']) ? "" : "# Generated languages version check schedule:\n{$notify['language_update']} $docroot/plugins/dynamix.plugin.manager/scripts/languagecheck &> /dev/null\n\n";
|
||||
parse_cron_cfg("dynamix", "language-check", $text);
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
$event = 'Unraid Status';
|
||||
$subject = 'Notification';
|
||||
$description = 'No description';
|
||||
$importance = 'normal';
|
||||
$message = $recipients = $link = $fqdnlink = '';
|
||||
$timestamp = time();
|
||||
$ticket = $timestamp;
|
||||
$mailtest = false;
|
||||
$overrule = false;
|
||||
$noBrowser = false;
|
||||
$customFilename = false;
|
||||
|
||||
$options = getopt("l:e:s:d:i:m:r:u:xtb");
|
||||
foreach ($options as $option => $value) {
|
||||
switch ($option) {
|
||||
case 'e':
|
||||
$event = $value;
|
||||
break;
|
||||
case 's':
|
||||
$subject = $value;
|
||||
break;
|
||||
case 'd':
|
||||
$description = $value;
|
||||
break;
|
||||
case 'i':
|
||||
$importance = strtok($value,' ');
|
||||
$overrule = strtok(' ');
|
||||
break;
|
||||
case 'm':
|
||||
$message = $value;
|
||||
break;
|
||||
case 'r':
|
||||
$recipients = $value;
|
||||
break;
|
||||
case 'x':
|
||||
$ticket = 'ticket';
|
||||
break;
|
||||
case 't':
|
||||
$mailtest = true;
|
||||
break;
|
||||
case 'b':
|
||||
$noBrowser = true;
|
||||
break;
|
||||
case 'l':
|
||||
$nginx = (array)@parse_ini_file('/var/local/emhttp/nginx.ini');
|
||||
$link = $value;
|
||||
$fqdnlink = (strpos($link,"http") === 0) ? $link : ($nginx['NGINX_DEFAULTURL']??'').$link;
|
||||
break;
|
||||
case 'u':
|
||||
$customFilename = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($customFilename) {
|
||||
$filename = safe_filename($customFilename);
|
||||
} else {
|
||||
// suffix length: _{timestamp}.notify = 1+10+7 = 18 chars.
|
||||
$suffix = "_{$ticket}.notify";
|
||||
$max_name_len = 255 - strlen($suffix);
|
||||
// sanitize event, truncating it to leave room for suffix
|
||||
$clean_name = safe_filename($event, $max_name_len);
|
||||
// construct filename with suffix (underscore separator matches safe_filename behavior)
|
||||
$filename = "{$clean_name}{$suffix}";
|
||||
}
|
||||
|
||||
$unread = "{$unread}/{$filename}";
|
||||
$archive = "{$archive}/{$filename}";
|
||||
if (file_exists($archive)) break;
|
||||
$entity = $overrule===false ? $notify[$importance] : $overrule;
|
||||
$cleanSubject = clean_subject($subject);
|
||||
$archiveData = [
|
||||
'timestamp' => $timestamp,
|
||||
'event' => $event,
|
||||
'subject' => $cleanSubject,
|
||||
'description' => $description,
|
||||
'importance' => $importance,
|
||||
];
|
||||
if ($message) $archiveData['message'] = str_replace('\n','<br>',$message);
|
||||
if (!$mailtest) file_put_contents($archive, build_ini_string($archiveData));
|
||||
if (($entity & 1)==1 && !$mailtest && !$noBrowser) {
|
||||
$unreadData = [
|
||||
'timestamp' => $timestamp,
|
||||
'event' => $event,
|
||||
'subject' => $cleanSubject,
|
||||
'description' => $description,
|
||||
'importance' => $importance,
|
||||
'link' => $link,
|
||||
];
|
||||
file_put_contents($unread, build_ini_string($unreadData));
|
||||
}
|
||||
if (($entity & 2)==2 || $mailtest) generate_email($event, $cleanSubject, str_replace('<br>','. ',$description), $importance, $message, $recipients, $fqdnlink);
|
||||
if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg($cleanSubject)." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}};
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
$output = [];
|
||||
$json = [];
|
||||
$files = glob("$unread/*.notify", GLOB_NOSORT);
|
||||
usort($files, function($a,$b){return filemtime($a)-filemtime($b);});
|
||||
$i = 0;
|
||||
foreach ($files as $file) {
|
||||
$fields = file($file,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
|
||||
$time = true;
|
||||
$output[$i]['file'] = basename($file);
|
||||
$output[$i]['show'] = (fileperms($file) & 0x0FFF)==0400 ? 0 : 1;
|
||||
foreach ($fields as $field) {
|
||||
if (!$field) continue;
|
||||
# limit the explode('=', …) used during reads to two pieces so values containing = remain intact
|
||||
[$key,$val] = array_pad(explode('=', $field, 2),2,'');
|
||||
if ($time) {$val = date($notify['date'].' '.$notify['time'], $val); $time = false;}
|
||||
# unescape the value before emitting JSON, so the browser UI
|
||||
# and any scripts calling `notify get` still see plain strings
|
||||
$output[$i][trim($key)] = ini_decode_value($val);
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
echo json_encode($output, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
case 'archive':
|
||||
if ($argc != 3) exit(usage());
|
||||
$file = $argv[2];
|
||||
if (strpos(realpath("$unread/$file"),$unread.'/')===0) @unlink("$unread/$file");
|
||||
break;
|
||||
}
|
||||
|
||||
exit(0);
|
||||
?>
|
||||
Reference in New Issue
Block a user