Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion plist
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/ServiceController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/SnapshotsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/TunablesController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/backup_local.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/backup_remote.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/hasyncSettings.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/snapshot.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/forms/tunable.xml
Expand Down Expand Up @@ -678,6 +680,8 @@
/usr/local/opnsense/mvc/app/models/OPNsense/Core/ACL.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Backup.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Backup.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Core/FieldTypes/TunableField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Firmware.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Firmware.xml
Expand All @@ -688,6 +692,7 @@
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Migrations/M1_0_0.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Migrations/M1_0_1.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Migrations/M1_0_2.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Migrations/MHA1_0_0.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Migrations/MHA1_0_1.php
/usr/local/opnsense/mvc/app/models/OPNsense/Core/Migrations/TUN1_0_2.php
Expand Down Expand Up @@ -937,6 +942,7 @@
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/clients.volt
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/index.volt
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/vouchers.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Core/backup.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Core/backup_history.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Core/dashboard.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Core/defaults.volt
Expand Down Expand Up @@ -1385,6 +1391,8 @@
/usr/local/opnsense/scripts/syslog/queryLog.py
/usr/local/opnsense/scripts/syslog/streamLog.py
/usr/local/opnsense/scripts/system/activity.py
/usr/local/opnsense/scripts/system/backup_download.php
/usr/local/opnsense/scripts/system/backup_restore.php
/usr/local/opnsense/scripts/system/bectl.py
/usr/local/opnsense/scripts/system/cert_fetch_local.py
/usr/local/opnsense/scripts/system/certctl.py
Expand Down Expand Up @@ -2522,7 +2530,6 @@
/usr/local/www/crash_reporter.php
/usr/local/www/csrf.inc
/usr/local/www/diag_authentication.php
/usr/local/www/diag_backup.php
/usr/local/www/fbegin.inc
/usr/local/www/firewall_nat_out.php
/usr/local/www/firewall_nat_out_edit.php
Expand Down
12 changes: 11 additions & 1 deletion src/etc/inc/plugins.inc.d/core.inc
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,19 @@ function core_cron()
$jobs[]['autocron'] = ['/usr/local/etc/rc.syshook.d/backup/20-netflow', '0', '*/' . $config['system']['netflowbackup']];
}

$cron_minute = 0;
$cron_hour = 2;

$push_time = $config['system']['backup']['settings']['pushtime'] ?? '';

if (!empty($push_time) && preg_match('/^([01]?[0-9]|2[0-3]):([0-5]?[0-9])$/', $push_time, $matches)) {
$cron_hour = (int)$matches[1];
$cron_minute = (int)$matches[2];
}

foreach ((new OPNsense\Backup\BackupFactory())->listProviders() as $classname => $provider) {
if ($provider['handle']->isEnabled()) {
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d system remote backup 3600', 0, 1];
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d system remote backup 30', $cron_minute, $cron_hour, '*', '*', '*'];
Comment thread
sopex marked this conversation as resolved.
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

/*
* Copyright (C) 2026 Konstantinos Spartalis <cspartalis@potatonetworks.com>
* Copyright (C) 2023 Deciso B.V.
* All rights reserved.
*
Expand Down Expand Up @@ -29,10 +30,12 @@
namespace OPNsense\Core\Api;

use OPNsense\Base\ApiControllerBase;
use OPNsense\Base\UserException;
use OPNsense\Core\ACL;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
use OPNsense\Core\Shell;
use OPNsense\Backup\Local;

/**
* Class BackupController
Expand Down Expand Up @@ -213,4 +216,211 @@ public function downloadAction($host, $backup = null)
}
}
}

public function getSettingsAction()
{
$mdlBackup = new \OPNsense\Core\Backup();
$nodes = $mdlBackup->getNodes();
return [
'backup' => [
'pushtime' => $nodes['pushtime'],
'backupcount' => $nodes['backupcount'],
]
];
}

public function setSettingsAction()
{
$result = ['status' => 'failed'];
if ($this->request->isPost()) {
$post = $this->request->getPost('backup');
$mdlBackup = new \OPNsense\Core\Backup();

if (isset($post['pushtime'])) {
$mdlBackup->pushtime = trim($post['pushtime']);
}
if (isset($post['backupcount'])) {
$mdlBackup->backupcount = trim($post['backupcount']) === '' ? null : trim($post['backupcount']);
}

$valMsgs = $mdlBackup->performValidation();
if (count($valMsgs) > 0) {
$validations = [];
foreach ($valMsgs as $msg) {
$field = $msg->getField();
if ($field === 'pushtime') {
$validations['backup.pushtime'] = $msg->getMessage();
} else {
$validations['backup.' . $field] = $msg->getMessage();
}
}
return ['status' => 'failed', 'validations' => $validations];
}

$mdlBackup->serializeToConfig();
Config::getInstance()->save('Changed backup settings');

// CRON restart
if (isset($post['pushtime'])) {
$backend = new \OPNsense\Core\Backend();
$backend->configdRun('template reload OPNsense/Cron');
$backend->configdRun('cron restart');
}

$result = ['status' => 'success'];
}
return $result;
}

public function downloadConfigAction()
{
if ($this->request->isPost()) {
$config = Config::getInstance()->object();
$hostname = "OPNsense";
if (isset($config->system->hostname)) {
$hostname = (string)$config->system->hostname . "." . (string)$config->system->domain;
}
$name = "config-" . $hostname . "-" . date("YmdHis") . ".xml";
$tmpfile = tempnam(sys_get_temp_dir(), 'opn_bck_');
$rrd_arg = empty($this->request->getPost('donotbackuprrd')) ? "rrd" : "norrd";
$backend = new Backend();
$response = json_decode(trim($backend->configdpRun('system config export', [$tmpfile, $rrd_arg])), true);

if ($response !== null && $response['status'] === 'success' && file_exists($tmpfile)) {
$data = file_get_contents($tmpfile);
@unlink($tmpfile);

if (!empty($this->request->getPost('encrypt'))) {
$password = $this->request->getPost('encrypt_password');
$crypter = new Local();
$data = $crypter->encrypt($data, $password);
}

$size = strlen($data);
$this->response->setContentType('application/octet-stream');
$this->response->setRawHeader("Content-Disposition: attachment; filename={$name}");
$this->response->setRawHeader("Content-Length: $size");
$this->response->setRawHeader("Pragma: private");
$this->response->setRawHeader("Cache-Control: private, must-revalidate");
$this->response->setContent($data);
return null;
} else if ($response !== null && isset($response['message'])) {
@unlink($tmpfile);
return $response;
}
}
return ['status' => 'failed'];
}

public function restoreAction()
{
if ($this->request->isPost() && isset($_FILES['conffile']) && is_uploaded_file($_FILES['conffile']['tmp_name'])) {
if ((new ACL())->hasPrivilege($this->getUserName(), 'user-config-readonly')) {
return ['status' => 'failed', 'message' => gettext('You do not have sufficient privileges to restore the configuration.')];
}

$data = file_get_contents($_FILES['conffile']['tmp_name']);

if (empty($data)) {
return ['status' => 'failed', 'message' => sprintf(gettext("Warning, could not read file %s"), $_FILES['conffile']['name'])];
}

if (!empty($this->request->getPost('decrypt'))) {
$password = $this->request->getPost('decrypt_password');
$crypter = new Local();
$data = $crypter->decrypt($data, $password);
if (empty($data)) {
return ['status' => 'failed', 'message' => gettext('The uploaded file could not be decrypted.')];
}
}

$post = $this->request->getPost();
$restoreareas = !empty($post['restorearea']) ? $post['restorearea'] : [];
$do_reboot = !empty($post['rebootafterrestore']);

$tmpfile = tempnam(sys_get_temp_dir(), 'opn_bck_');
file_put_contents($tmpfile, $data);

$params = [
'conffile' => $tmpfile,
'restorearea' => $restoreareas,
'rebootafterrestore' => $do_reboot,
'keepconsole' => !empty($post['keepconsole']),
'flush_history' => !empty($post['flush_history'])
];

$paramfile = tempnam(sys_get_temp_dir(), 'opn_bck_par_');
file_put_contents($paramfile, json_encode($params));

$backend = new Backend();
$response = json_decode(trim($backend->configdpRun('system config restore', [$paramfile])), true);

@unlink($tmpfile);
@unlink($paramfile);

if ($response !== null && $response['status'] === 'success') {
if (!empty($response['reboot'])) {
$backend->configdRun('system reboot', true);
}
return $response;
}

return $response ?? ['status' => 'failed', 'message' => gettext("The configuration could not be restored.")];
}
return ['status' => 'failed', 'message' => 'No files uploaded'];
}

public function setupProviderAction($providerName)
{
if ($this->request->isPost()) {
$backupFactory = new \OPNsense\Backup\BackupFactory();
$provider = $backupFactory->getProvider($providerName);
if (!$provider) {
return ['status' => 'failed', 'message' => 'Provider not found.'];
}

$providerSet = array();
$post = $this->request->getPost();

foreach ($provider['handle']->getConfigurationFields() as $field) {
if ($field['type'] == 'file') {
if (isset($_FILES[$field['name']]) && is_uploaded_file($_FILES[$field['name']]['tmp_name'])) {
$providerSet[$field['name']] = file_get_contents($_FILES[$field['name']]['tmp_name']);
} else {
$providerSet[$field['name']] = null;
}
} else {
$providerSet[$field['name']] = isset($post[$field['name']]) ? $post[$field['name']] : '';
}
}

$input_errors = $provider['handle']->setConfiguration($providerSet);

if (count($input_errors) == 0) {
$backend = new Backend();
$backend->configdRun('template reload OPNsense/Cron');
$backend->configdRun('cron restart');

if ($provider['handle']->isEnabled()) {
try {
$filesInBackup = $provider['handle']->backup();
} catch (\Exception $e) {
return ['status' => 'failed', 'message' => $e->getMessage()];
}
if (count($filesInBackup) == 0) {
return ['status' => 'success', 'message' => gettext('Saved settings, but remote backup returned no files.')];
} else {
$msg = gettext("Backup successful. Current file list: ") . implode(", ", $filesInBackup);
return ['status' => 'success', 'message' => $msg];
}
}

return ['status' => 'success', 'message' => gettext("Settings configured.")];
} else {
return ['status' => 'failed', 'message' => implode(", ", $input_errors)];
}
}
return ['status' => 'failed', 'message' => 'Invalid request'];
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

/*
* Copyright (C) 2026 Konstantinos Spartalis <cspartalis@potatonetworks.com>
* Copyright (C) 2023 Deciso B.V.
* All rights reserved.
*
Expand Down Expand Up @@ -34,6 +35,40 @@
*/
class BackupController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$backupFactory = new \OPNsense\Backup\BackupFactory();
$this->view->providers = $backupFactory->listProviders();

$this->view->backupLocalForm = $this->getForm("backup_local");
$this->view->backupRemoteForm = $this->getForm("backup_remote");

$this->view->backupSize = \OPNsense\Core\Config::getInstance()->getBackupSize();

$areas = [
'bridges' => gettext('Bridge Devices'),
'gifs' => gettext('GIF Devices'),
'interfaces' => gettext('Interfaces'),
'laggs' => gettext('LAGG Devices'),
'ppps' => gettext('Point-to-Point Devices'),
'rrddata' => gettext('RRD Data'),
'vlans' => gettext('VLAN Devices'),
'wireless' => gettext('Wireless Devices'),
];
$xmlrpc_options = @json_decode((new \OPNsense\Core\Backend())->configdRun('system xmlrpc options'), true);
if (is_array($xmlrpc_options)) {
foreach ($xmlrpc_options as $area) {
if (!empty($area['section'])) {
$areas[$area['section']] = $area['description'];
}
}
}
natcasesort($areas);
$this->view->areas = $areas;

$this->view->pick('OPNsense/Core/backup');
}

public function historyAction($selected_host = null)
{
$this->view->selected_host = $selected_host;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<form>
<field>
<id>backup.backupcount</id>
<label>Backup Count</label>
<type>text</type>
<help>Enter the number of older configurations to keep in the local backup cache.</help>
</field>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<form>
<field>
<id>backup.pushtime</id>
<label>Push Time</label>
<type>text</type>
<help>This determines the backup time used for the main backup of the day. Additional backups can be set up on the cron page.</help>
</field>
</form>
Loading