-
Notifications
You must be signed in to change notification settings - Fork 952
System: Configuration: Backups migrate to MVC #10098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 13 commits
4655d0f
799706d
0484a5a
3f41d03
d659cfd
f086d94
648f686
8e13ee9
a009e37
6a157b9
0c6b134
31ce916
fa15c18
bc5eebd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
| * | ||
|
|
@@ -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 | ||
|
|
@@ -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"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't know this but we were talking about deprecating this part of the backup functionality for a while now to ease the MVC migration while at it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like rrd :(
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just the backups into the config.xml really unfortunately schedules are quite tight at least until after next week
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I understood that but still :)
Of course, take your time. In any case, this is in working condition and probably fine by MVC standards. Surely there are improvements to be made, but I will make a small commit today and let it sit fot a bit. Let's see if there is some kind of major feedback that requires rebuilding it from scratch, and if not, I will focus on the minor stuff. |
||
| $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> | ||
|
sopex marked this conversation as resolved.
|
||
| * Copyright (C) 2023 Deciso B.V. | ||
| * All rights reserved. | ||
| * | ||
|
|
@@ -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'), | ||
| ]; | ||
|
Comment on lines
+48
to
+57
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we have to discuss where we want to anchor this |
||
| $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; | ||
|
|
||
| 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> |
Uh oh!
There was an error while loading. Please reload this page.