mirror of
https://github.com/opnsense/plugins.git
synced 2026-04-29 11:06:53 -05:00
sysutils/nextcloud-backup: sync with master
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
PLUGIN_NAME= nextcloud-backup
|
||||
PLUGIN_VERSION= 1.1
|
||||
PLUGIN_VERSION= 1.2
|
||||
PLUGIN_COMMENT= Track config changes using NextCloud
|
||||
|
||||
.include "../../Mk/plugins.mk"
|
||||
|
||||
@@ -6,6 +6,14 @@ strongly advise to not use a public service to send backups to.
|
||||
Plugin Changelog
|
||||
================
|
||||
|
||||
1.2
|
||||
|
||||
* Add option to upload to one file each day instead of syncing the contents of /conf/backup
|
||||
* Add support for having backing up to a subdirectory instead of the root backup dir
|
||||
* Skip non-files when enumerating local entries to backup
|
||||
* Only back up when local file is newer than remote
|
||||
* Switch to UpdateOnlyTextField from TextField
|
||||
|
||||
1.1
|
||||
|
||||
* Back up the content of /conf/backup (contributed by Daniel Lysfjor)
|
||||
|
||||
@@ -84,7 +84,34 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
"type" => "text",
|
||||
"label" => gettext("Directory Name without leading slash, starting from user's root"),
|
||||
"value" => 'OPNsense-Backup'
|
||||
)
|
||||
),
|
||||
array(
|
||||
"name" => "strategy",
|
||||
"type" => "checkbox",
|
||||
"help" => gettext("Select this one to back up to a file named config-YYYYMMDD instead of syncing contents of /conf/backup"),
|
||||
"label" => gettext("Daily file instead of sync all"),
|
||||
),
|
||||
array(
|
||||
"name" => "addhostname",
|
||||
"type" => "checkbox",
|
||||
"label" => gettext("Backup to directory named after hostname"),
|
||||
"help" => gettext("Create subdirectory under backupdir for this host"),
|
||||
"value" => null
|
||||
),
|
||||
array(
|
||||
"name" => "numdays",
|
||||
"type" => "text",
|
||||
"label" => gettext("Number of days worth of backups to keep"),
|
||||
"help" => gettext("This works in collaboration with Number of backups below, the one with the oldest/most will win"),
|
||||
"value" => null
|
||||
),
|
||||
array(
|
||||
"name" => "numbackups",
|
||||
"type" => "text",
|
||||
"label" => gettext("Number of backups to keep"),
|
||||
"help" => gettext("This works in collaboration with Number of days above, the one with the oldest/most will win"),
|
||||
"value" => null
|
||||
),
|
||||
);
|
||||
$nextcloud = new NextcloudSettings();
|
||||
foreach ($fields as &$field) {
|
||||
@@ -121,6 +148,302 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
return $validation_messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* check remote file last modified tag
|
||||
* @param string $remote_filename filename to check on server
|
||||
* @param string $username username for login to server
|
||||
* @param string $password password for authentication
|
||||
* @return int unix timestamp or 0 if errors occour
|
||||
*/
|
||||
public function get_remote_file_lastmodified(
|
||||
$remote_filename,
|
||||
$username,
|
||||
$password
|
||||
) {
|
||||
$reply = $this->curl_request_nothrow($remote_filename, $username, $password, 'PROPFIND', 'Cannot get remote fileinfo');
|
||||
$http_code = $reply['info']['http_code'];
|
||||
if ($http_code >= 200 && $http_code < 300) {
|
||||
$xml_data = $reply['response'];
|
||||
if ($xml_data == null) {
|
||||
syslog(LOG_ERR, 'Data was NULL');
|
||||
return 0;
|
||||
}
|
||||
$xml_data = str_replace(['<d:', '</d:'], ['<', '</'], $xml_data);
|
||||
$xml = simplexml_load_string($xml_data);
|
||||
foreach ($xml->children() as $response) {
|
||||
if ($response->getName() == 'response') {
|
||||
$lastmodifiedstr = $response->propstat->prop->getlastmodified;
|
||||
$filedate = strtotime($lastmodifiedstr);
|
||||
return $filedate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* backup with strategy 0 (copy everything in /conf/backup/ to $backupdir/)
|
||||
* @param string $internal_username the returnvalue from $this->getInternalUsername
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $url server protocol and hostname
|
||||
* @param string $backupdir
|
||||
* @param string $crypto_password
|
||||
*/
|
||||
public function backupstrat_zero(
|
||||
$internal_username,
|
||||
$username,
|
||||
$password,
|
||||
$url,
|
||||
$backupdir,
|
||||
$crypto_password
|
||||
) {
|
||||
// Get list of files from local backup system
|
||||
$local_files = array();
|
||||
$tmp_local_files = scandir('/conf/backup/');
|
||||
// Remove '.' and '..', skip directories
|
||||
foreach ($tmp_local_files as $tmp_local_file) {
|
||||
if ($tmp_local_file === '.' || $tmp_local_file === '..') {
|
||||
continue;
|
||||
}
|
||||
if (!is_file("/conf/backup/" . $tmp_local_file)) {
|
||||
continue;
|
||||
}
|
||||
$local_files[] = $tmp_local_file;
|
||||
}
|
||||
|
||||
// Get list of filenames (without path) on remote location
|
||||
$remote_files = array();
|
||||
$tmp_remote_files = $this->listfiles($url, $username, $password, $internal_username, "/$backupdir/", false);
|
||||
foreach ($tmp_remote_files as $tmp_remote_file) {
|
||||
$remote_files[] = pathinfo($tmp_remote_file)['basename'];
|
||||
}
|
||||
|
||||
|
||||
$uploaded_files = array();
|
||||
|
||||
// Loop over each local file,
|
||||
// see if it's in $remote_files,
|
||||
// if not, optionally encrypt, and upload
|
||||
foreach ($local_files as $file_to_upload) {
|
||||
if (!in_array($file_to_upload, $remote_files)) {
|
||||
$confdata = file_get_contents("/conf/backup/$file_to_upload");
|
||||
if (!empty($crypto_password)) {
|
||||
$confdata = $this->encrypt($confdata, $crypto_password);
|
||||
}
|
||||
try {
|
||||
$this->upload_file_content(
|
||||
$url,
|
||||
$username,
|
||||
$password,
|
||||
$internal_username,
|
||||
$backupdir,
|
||||
$file_to_upload,
|
||||
$confdata
|
||||
);
|
||||
$uploaded_files[] = $file_to_upload;
|
||||
} catch (\Exception $e) {
|
||||
return $uploaded_files;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $uploaded_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* backup with strategy 1 (copy /conf/config.xml to $backupdir/conf-YYYYMMDD.xml)
|
||||
* @param string $internal_username the returnvalue from $this->getInternalUsername
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string url server protocol and hostname
|
||||
* @param string $backupdir
|
||||
* @param string $crypto_password
|
||||
*/
|
||||
public function backupstrat_one(
|
||||
$internal_username,
|
||||
$username,
|
||||
$password,
|
||||
$url,
|
||||
$backupdir,
|
||||
$crypto_password
|
||||
) {
|
||||
$confdata = file_get_contents('/conf/config.xml');
|
||||
$mdate = filemtime('/conf/config.xml');
|
||||
$datestring = date('Ymd', $mdate);
|
||||
$target_filename = 'config-' . $datestring . '.xml';
|
||||
// Find the same filename @ remote
|
||||
$remote_filename = $url . '/remote.php/dav/files/' . $internal_username . '/' . $backupdir . '/' . $target_filename;
|
||||
$remote_file_date = $this->get_remote_file_lastmodified($remote_filename, $username, $password);
|
||||
if ($remote_file_date >= $mdate) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Optionally encrypt
|
||||
if (!empty($crypto_password)) {
|
||||
$confdata = $this->encrypt($confdata, $crypto_password);
|
||||
}
|
||||
// Finally, upload some data
|
||||
try {
|
||||
$this->upload_file_content(
|
||||
$url,
|
||||
$username,
|
||||
$password,
|
||||
$internal_username,
|
||||
$backupdir,
|
||||
$target_filename,
|
||||
$confdata
|
||||
);
|
||||
return array($backupdir . '/' . $target_filename);
|
||||
} catch (\Exception $e) {
|
||||
syslog(LOG_ERR, 'Backup to ' . $url . ' failed: ' . $e);
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timestamp value from filename in list
|
||||
* @param $filelist array of files in remote location
|
||||
* @return array($filedata => $filename)
|
||||
*/
|
||||
public function get_filelist_dates($filelist)
|
||||
{
|
||||
// Save as associative array
|
||||
// key = lastmodified
|
||||
// value = filename
|
||||
$files = array();
|
||||
foreach ($filelist as $target_filename) {
|
||||
// Find suggested creation date
|
||||
// Base this on the filename. Either it is a unix timestamp, or it should be YYYYMMDD
|
||||
// Either way, it's the part between "config-" and ".xml"
|
||||
$filestr_no_xml = explode(".xml", $target_filename)[0];
|
||||
$filedatestr = intval(explode("-", $filestr_no_xml)[1]);
|
||||
if (($filedate = strtotime($filedatestr)) === false) {
|
||||
// Cannot convert string to time.. probably already a unix-timestamp
|
||||
// Try to convert with date()
|
||||
$date = date(DATE_ATOM, $filedatestr);
|
||||
// Then to a UNIX timestamp again
|
||||
$maybedate = strtotime($date);
|
||||
if ($maybedate === $filedatestr) {
|
||||
// They represent the same time, this is good
|
||||
// Just copy the intval() and be done with this
|
||||
$filedate = $filedatestr;
|
||||
}
|
||||
}
|
||||
if ($filedate) {
|
||||
$files[(string)$filedate] = $target_filename;
|
||||
} else {
|
||||
syslog(LOG_ERR, "Skipping file " . $target_filename . ", cannot determine date");
|
||||
}
|
||||
}
|
||||
ksort($files);
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* housekeeping
|
||||
* @param $internal_username returnvalue from $this->getInternalUsername
|
||||
* @param $username
|
||||
* @param $password
|
||||
* @param $url protocol and hostname of server
|
||||
* @param $backupdir directory to operate in
|
||||
* @param $keep_days number of days to keep backups for
|
||||
* @param $keep_num number of backups to keep
|
||||
*/
|
||||
public function retention(
|
||||
$internal_username,
|
||||
$username,
|
||||
$password,
|
||||
$url,
|
||||
$backupdir,
|
||||
$keep_days,
|
||||
$keep_num
|
||||
) {
|
||||
// Are we configured to run at all?
|
||||
// Short circuit in case none of our options are set
|
||||
if (!strlen($keep_days) && !strlen($keep_num)) {
|
||||
return;
|
||||
}
|
||||
// Get list of filenames (without path) on remote location
|
||||
$remote_files = array();
|
||||
$tmp_remote_files = $this->listfiles($url, $username, $password, $internal_username, "/$backupdir/", false);
|
||||
foreach ($tmp_remote_files as $tmp_remote_file) {
|
||||
if (!($tmp_remote_file === "")) {
|
||||
if (!($tmp_remote_file == "/$backupdir/")) {
|
||||
// No idea why the root directory is in the list..
|
||||
$remote_files[] = pathinfo($tmp_remote_file)['basename'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$num_remote_files = count($remote_files);
|
||||
// Short-circuit, if too few files, no need to check no more
|
||||
if (strlen($keep_num)) {
|
||||
if ($keep_num > $num_remote_files) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$date = new \DateTime();
|
||||
$files = $this->get_filelist_dates($remote_files);
|
||||
if (strlen($keep_days)) {
|
||||
// Admin has specified number of days to keep
|
||||
$dateinterval = \DateInterval::createFromDateString($keep_days . " day");
|
||||
$target_timestamp = date_sub($date, $dateinterval)->format('U');
|
||||
// $files is an associative array with key=creation_time, value=filename
|
||||
// should be sorted by ksort, hopefully that is a numerical sort:)
|
||||
$new_files = array();
|
||||
$old_files = array();
|
||||
foreach (array_keys($files) as $file_timestamp) {
|
||||
if ($file_timestamp > $target_timestamp) {
|
||||
$new_files[(string)$file_timestamp] = $files[$file_timestamp];
|
||||
} else {
|
||||
// file is "old", aka ripe for deletion
|
||||
$old_files[(string)$file_timestamp] = $files[$file_timestamp];
|
||||
}
|
||||
}
|
||||
if (strlen($keep_num)) {
|
||||
$num_new_files = count($new_files);
|
||||
if ($num_new_files < $keep_num) {
|
||||
// Not enough new files to satisfy $keep_num
|
||||
$missing_num = $keep_num - $num_new_files;
|
||||
// Can we slice some files from the $old_files list to satisfy $keep_num?
|
||||
$total_files = count($files);
|
||||
if ($total_files >= $keep_num) {
|
||||
// Yes, we can
|
||||
$tmp_files = array_slice($old_files, 0, $missing_num * -1);
|
||||
foreach (array_keys($tmp_files) as $filetodelete) {
|
||||
$this->delete_file($url, $username, $password, $internal_username, $backupdir, $tmp_files[$filetodelete]);
|
||||
}
|
||||
}
|
||||
// No, we can't. Keep all things as is
|
||||
} else {
|
||||
// We have more new files than what we need to satisfy $keep_num
|
||||
foreach (array_keys($old_files) as $filetodelete) {
|
||||
$this->delete_file($url, $username, $password, $internal_username, $backupdir, $old_files[$filetodelete]);
|
||||
}
|
||||
// We do not delete files from new_files, as they are covered by the $keep_days
|
||||
}
|
||||
} else {
|
||||
// We have not been told to keep N items,
|
||||
// delete everything in $old_files
|
||||
foreach (array_keys($old_files) as $filetodelete) {
|
||||
$this->delete_file($url, $username, $password, $internal_username, $backupdir, $old_files[$filetodelete]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No $keep_days specified
|
||||
if (strlen($keep_num)) {
|
||||
// keep_num is some number
|
||||
// Delete filenames based on their creation time
|
||||
if (count($files) > $keep_num) {
|
||||
$tmp_files = array_slice($files, 0, $keep_num * -1);
|
||||
foreach (array_keys($tmp_files) as $filetodelete) {
|
||||
$this->delete_file($url, $username, $password, $internal_username, $backupdir, $tmp_files[$filetodelete]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* perform backup
|
||||
* @return array filelist
|
||||
@@ -129,15 +452,24 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
*/
|
||||
public function backup()
|
||||
{
|
||||
date_default_timezone_set('UTC');
|
||||
$cnf = Config::getInstance();
|
||||
$nextcloud = new NextcloudSettings();
|
||||
if ($cnf->isValid() && !empty((string)$nextcloud->enabled)) {
|
||||
$config = $cnf->object();
|
||||
$url = (string)$nextcloud->url;
|
||||
$username = (string)$nextcloud->user;
|
||||
$password = (string)$nextcloud->password;
|
||||
$backupdir = (string)$nextcloud->backupdir;
|
||||
$crypto_password = (string)$nextcloud->password_encryption;
|
||||
$url = $nextcloud->url->getValue();
|
||||
$username = $nextcloud->user->getValue();
|
||||
$password = $nextcloud->password->getValue();
|
||||
$backupdir = $nextcloud->backupdir->getValue();
|
||||
$crypto_password = $nextcloud->password_encryption->getValue();
|
||||
$strategy = $nextcloud->strategy->getValue();
|
||||
// Strategy 0 = Sync /conf/backup
|
||||
// Strategy 1 = Copy /conf/config.xml to $backupdir/conf-YYYYMMDD.xml
|
||||
$keep_days = $nextcloud->numdays->getValue();
|
||||
$keep_num = $nextcloud->numbackups->getValue();
|
||||
|
||||
if ($nextcloud->addhostname->isEqual('1')) {
|
||||
$backupdir .= "/" . gethostname();
|
||||
}
|
||||
|
||||
// Check if destination directory exists, create (full path) if not
|
||||
try {
|
||||
@@ -147,53 +479,13 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
return array();
|
||||
}
|
||||
|
||||
// Get list of files from local backup system
|
||||
$local_files = array();
|
||||
$tmp_local_files = scandir('/conf/backup/');
|
||||
// Remove '.' and '..'
|
||||
foreach ($tmp_local_files as $tmp_local_file) {
|
||||
if ($tmp_local_file === '.' || $tmp_local_file === '..') {
|
||||
continue;
|
||||
}
|
||||
$local_files[] = $tmp_local_file;
|
||||
if ($strategy) {
|
||||
$list_of_files = $this->backupstrat_one($internal_username, $username, $password, $url, $backupdir, $crypto_password);
|
||||
} else {
|
||||
$list_of_files = $this->backupstrat_zero($internal_username, $username, $password, $url, $backupdir, $crypto_password);
|
||||
}
|
||||
|
||||
// Get list of filenames (without path) on remote location
|
||||
$remote_files = array();
|
||||
$tmp_remote_files = $this->listfiles($url, $username, $password, $internal_username, "/$backupdir/", false);
|
||||
foreach ($tmp_remote_files as $tmp_remote_file) {
|
||||
$remote_files[] = pathinfo($tmp_remote_file)['basename'];
|
||||
}
|
||||
|
||||
|
||||
$uploaded_files = array();
|
||||
|
||||
// Loop over each local file,
|
||||
// see if it's in $remote_files,
|
||||
// if not, optionally encrypt, and upload
|
||||
foreach ($local_files as $file_to_upload) {
|
||||
if (!in_array($file_to_upload, $remote_files)) {
|
||||
$confdata = file_get_contents("/conf/backup/$file_to_upload");
|
||||
if (!empty($crypto_password)) {
|
||||
$confdata = $this->encrypt($confdata, $crypto_password);
|
||||
}
|
||||
try {
|
||||
$this->upload_file_content(
|
||||
$url,
|
||||
$username,
|
||||
$password,
|
||||
$internal_username,
|
||||
$backupdir,
|
||||
$file_to_upload,
|
||||
$confdata
|
||||
);
|
||||
$uploaded_files[] = $file_to_upload;
|
||||
} catch (\Exception $e) {
|
||||
return $uploaded_files;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $uploaded_files;
|
||||
$this->retention($internal_username, $username, $password, $url, $backupdir, $keep_days, $keep_num);
|
||||
return $list_of_files;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +536,7 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
* @param string $url remote location
|
||||
* @param string $username remote user
|
||||
* @param string $password password to use
|
||||
* @param string $internal_username UUID
|
||||
* @param string $backupdir remote directory
|
||||
* @param string $filename filename to use
|
||||
* @param string $local_file_content contents to save
|
||||
@@ -251,14 +544,44 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
*/
|
||||
public function upload_file_content($url, $username, $password, $internal_username, $backupdir, $filename, $local_file_content)
|
||||
{
|
||||
$this->curl_request(
|
||||
$url . "/remote.php/dav/files/$internal_username/$backupdir/$filename",
|
||||
$url = $url . "/remote.php/dav/files/$internal_username/$backupdir/$filename";
|
||||
$reply = $this->curl_request_nothrow(
|
||||
$url,
|
||||
$username,
|
||||
$password,
|
||||
'PUT',
|
||||
'cannot execute PUT',
|
||||
$local_file_content
|
||||
);
|
||||
$http_code = $reply['info']['http_code'];
|
||||
// Accepted http codes for upload is 200-299
|
||||
if (!($http_code >= 200 && $http_code < 300)) {
|
||||
syslog(LOG_ERR, 'Could not PUT ' . $url);
|
||||
throw new \Exception();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a file
|
||||
* @param string $url remote location
|
||||
* @param string $username remote user
|
||||
* @param string $password password to use
|
||||
* @param strign $internal_username UUID
|
||||
* @param string $backupdir remote directory
|
||||
* @param string $filename filename to use
|
||||
*/
|
||||
public function delete_file($url, $username, $password, $internal_username, $backupdir, $filename)
|
||||
{
|
||||
$url = $url . "/remote.php/dav/files/$internal_username/$backupdir/$filename";
|
||||
$reply = $this->curl_request_nothrow(
|
||||
$url,
|
||||
$username,
|
||||
$password,
|
||||
'DELETE',
|
||||
'cannot delete file'
|
||||
);
|
||||
$http_code = $reply['info']['http_code'];
|
||||
syslog(LOG_ERR, "Deleting " . $url . " returned " . $http_code);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,6 +665,31 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
$error_message,
|
||||
$postdata = null,
|
||||
$headers = array("User-Agent: OPNsense Firewall")
|
||||
) {
|
||||
$result = $this->curl_request_nothrow($url, $username, $password, $method, $error_message, $postdata, $headers);
|
||||
$info = $result['info'];
|
||||
$err = $result['err'];
|
||||
$response = $result['response'];
|
||||
if (!($info['http_code'] == 200 || $info['http_code'] == 207 || $info['http_code'] == 201) || $err) {
|
||||
syslog(LOG_ERR, $error_message);
|
||||
syslog(LOG_ERR, json_encode($info));
|
||||
throw new \Exception();
|
||||
}
|
||||
return array('response' => $response, 'info' => $info);
|
||||
}
|
||||
|
||||
|
||||
// Add this here, since I'm fundamentally opposed to throwing exceptions
|
||||
// if http codes aren't to your liking in a generic function.
|
||||
// Delegate that to upper functions, where it belongs.
|
||||
public function curl_request_nothrow(
|
||||
$url,
|
||||
$username,
|
||||
$password,
|
||||
$method,
|
||||
$error_message,
|
||||
$postdata = null,
|
||||
$headers = array("User-Agent: OPNsense Firewall")
|
||||
) {
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
@@ -361,13 +709,8 @@ class Nextcloud extends Base implements IBackupProvider
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
$info = curl_getinfo($curl);
|
||||
if (!($info['http_code'] == 200 || $info['http_code'] == 207 || $info['http_code'] == 201) || $err) {
|
||||
syslog(LOG_ERR, $error_message);
|
||||
syslog(LOG_ERR, json_encode($info));
|
||||
throw new \Exception();
|
||||
}
|
||||
curl_close($curl);
|
||||
return array('response' => $response, 'info' => $info);
|
||||
return array('response' => $response, 'info' => $info, 'err' => $err);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<model>
|
||||
<mount>//system/backup/nextcloud</mount>
|
||||
<version>1.0.0</version>
|
||||
<version>1.0.2</version>
|
||||
<description>OPNsense Nextcloud Backup Settings</description>
|
||||
<items>
|
||||
<enabled type="BooleanField">
|
||||
@@ -31,7 +31,7 @@
|
||||
</check001>
|
||||
</Constraints>
|
||||
</user>
|
||||
<password type="TextField">
|
||||
<password type="UpdateOnlyTextField">
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>A password for a Nextcloud server must be set.</ValidationMessage>
|
||||
@@ -42,12 +42,26 @@
|
||||
</check001>
|
||||
</Constraints>
|
||||
</password>
|
||||
<password_encryption type="TextField"/>
|
||||
<password_encryption type="UpdateOnlyTextField"/>
|
||||
<backupdir type="TextField">
|
||||
<Required>Y</Required>
|
||||
<Mask>/^([\w%+\-]+\/)*[\w+%\-]+$/</Mask>
|
||||
<Default>OPNsense-Backup</Default>
|
||||
<ValidationMessage>The Backup Directory can only consist of alphanumeric characters, dash, underscores and slash. No leading or trailing slash.</ValidationMessage>
|
||||
</backupdir>
|
||||
<strategy type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<Default>0</Default>
|
||||
</strategy>
|
||||
<addhostname type="BooleanField">
|
||||
<Default>1</Default>
|
||||
<Required>Y</Required>
|
||||
</addhostname>
|
||||
<numdays type="IntegerField">
|
||||
<MinimumValue>1</MinimumValue>
|
||||
</numdays>
|
||||
<numbackups type="IntegerField">
|
||||
<MinimumValue>1</MinimumValue>
|
||||
</numbackups>
|
||||
</items>
|
||||
</model>
|
||||
|
||||
Reference in New Issue
Block a user