diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/FirmwareController.php b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/FirmwareController.php index 8c3a5ce3faa..cbb4e282fcf 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/FirmwareController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/FirmwareController.php @@ -323,7 +323,7 @@ public function statusAction() $response['status_msg'] = sprintf( '%s %s', $response['status_msg'], - gettext('This update requires a reboot.') + gettext('This update requires a reboot/ power off.') ); } $response['status_reboot'] = $active_reboot; @@ -466,12 +466,15 @@ public function updateAction() if ($this->request->isPost()) { $this->getLogger('audit')->notice(sprintf("[Firmware] User %s executed a firmware update", $this->getUserName())); $backend->configdRun('firmware flush'); - $response['msg_uuid'] = trim($backend->configdRun('firmware update', true)); + + $cmd = 'firmware update'; + if ($this->request->getPost('shutdown') === '1') { + $cmd .= ' shutdown'; + } + + $response['msg_uuid'] = trim($backend->configdRun($cmd, true)); $response['status'] = 'ok'; - } else { - $response['status'] = 'failure'; } - return $response; } @@ -487,12 +490,15 @@ public function upgradeAction() if ($this->request->isPost()) { $this->getLogger('audit')->notice(sprintf("[Firmware] User %s executed a firmware upgrade", $this->getUserName())); $backend->configdRun('firmware flush'); - $response['msg_uuid'] = trim($backend->configdRun('firmware upgrade', true)); + + $cmd = 'firmware upgrade'; + if ($this->request->getPost('shutdown') === '1') { + $cmd .= ' shutdown'; + } + + $response['msg_uuid'] = trim($backend->configdRun($cmd, true)); $response['status'] = 'ok'; - } else { - $response['status'] = 'failure'; } - return $response; } @@ -769,6 +775,8 @@ public function upgradestatusAction() $result['status'] = 'done'; } elseif (strpos($cmd_result, '***REBOOT***') !== false) { $result['status'] = 'reboot'; + } elseif (strpos($cmd_result, '***POWER OFF***') !== false) { + $result['status'] = 'shutdown'; } return $result; diff --git a/src/opnsense/mvc/app/views/OPNsense/Core/firmware.volt b/src/opnsense/mvc/app/views/OPNsense/Core/firmware.volt index 7acd66bd63d..0322bc1116f 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Core/firmware.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Core/firmware.volt @@ -131,8 +131,11 @@ /** * perform backend action and install poller to update status */ - function backend(type) { + function backend(type, data) { $.upgrade_check = type == 'check'; + if (data === undefined) { + data = {}; + } $('#update_status').html(''); $('#updatelist').hide(); @@ -140,7 +143,7 @@ $('#updatetab > a').tab('show'); $('#updatetab_progress').addClass("fa fa-spinner fa-pulse"); - ajaxCall('/api/core/firmware/' + type, {}, function () { + ajaxCall('/api/core/firmware/' + type, data, function () { setTimeout(trackStatus, 500); }); } @@ -247,21 +250,43 @@ if (major === true) { reboot_msg = "{{ lang._('The firewall will download all firmware sets and reboot multiple times for this upgrade. All operating system files and packages will be reinstalled as a consequence. This may take several minutes to complete.') }}"; } + reboot_msg += '

'; // reboot required, inform the user. + let countdownSeconds = 30; + let countdownTimer = null; BootstrapDialog.show({ type:BootstrapDialog.TYPE_WARNING, - title: "{{ lang._('Reboot required') }}", + title: "{{ lang._('Reboot/ Power off required') }}", message: reboot_msg, + onshown: function(dialogRef) { + let $btn = dialogRef.getButton('btn-reboot'); + countdownTimer = setInterval(function () { + countdownSeconds--; + $btn.text('{{ lang._("Confirm") }} (' + countdownSeconds + ')'); + if (countdownSeconds <= 0) { + clearInterval(countdownTimer); + $btn.trigger('click'); + } + }, 1000); + }, + onhidden: function() { + if (countdownTimer) clearInterval(countdownTimer); + }, buttons: [{ - label: "{{ lang._('OK') }}", + id: 'btn-reboot', + label: '{{ lang._("Confirm") }} (' + countdownSeconds + ')', cssClass: 'btn-warning', action: function(dialogRef){ + if (countdownTimer) clearInterval(countdownTimer); + let doShutdown = $('#upgrade_shutdown_cb').is(':checked') ? '1' : '0'; dialogRef.close(); - backend(major === true ? 'upgrade' : 'update'); + backend(major === true ? 'upgrade' : 'update', {'shutdown': doShutdown}); } },{ label: "{{ lang._('Cancel') }}", action: function(dialogRef){ + if (countdownTimer) clearInterval(countdownTimer); dialogRef.close(); } }] @@ -314,6 +339,14 @@ setTimeout(rebootWait, 45000); }, }); + } else if (data['status'] == 'shutdown') { + BootstrapDialog.show({ + type:BootstrapDialog.TYPE_INFO, + title: "{{ lang._('Shutting down after update') }}", + closable: false, + message: "{{ lang._('The system is shutting down. You chose to power off instead of reboot. The system will need to be started manually.') }}" + + ' ', + }); } else { // schedule next poll setTimeout(trackStatus, 500); @@ -848,7 +881,7 @@ - +
diff --git a/src/opnsense/scripts/firmware/config.sh b/src/opnsense/scripts/firmware/config.sh index ab0d4af93d4..87e7c6e6520 100755 --- a/src/opnsense/scripts/firmware/config.sh +++ b/src/opnsense/scripts/firmware/config.sh @@ -149,6 +149,18 @@ output_done() exit 0 } +output_restart_action() +{ + KEEP_LOG=${1} + PREFER_SHUTDOWN=${2:-0} + + if [ "${PREFER_SHUTDOWN}" = "1" ]; then + output_shutdown "${KEEP_LOG}" + else + output_reboot "${KEEP_LOG}" + fi +} + output_reboot() { KEEP_LOG=${1} @@ -163,6 +175,20 @@ output_reboot() /usr/local/etc/rc.reboot } +output_shutdown() +{ + KEEP_LOG=${1} + + echo '***POWER OFF***' >> ${LOCKFILE} + + if [ -n "${KEEP_LOG}" ]; then + cp ${LOCKFILE} ${LOGFILE} + fi + + sleep 5 + /usr/local/etc/rc.halt +} + # if output is requested clear file and set new request right away if [ -n "${REQUEST}" ]; then output_request "${REQUEST}" diff --git a/src/opnsense/scripts/firmware/update.sh b/src/opnsense/scripts/firmware/update.sh index 74acde9a5be..f7e466ee007 100755 --- a/src/opnsense/scripts/firmware/update.sh +++ b/src/opnsense/scripts/firmware/update.sh @@ -29,6 +29,14 @@ REQUEST="UPDATE" . /usr/local/opnsense/scripts/firmware/config.sh +PREFER_SHUTDOWN=0 +for arg in "$@"; do + if [ "$arg" = "shutdown" ]; then + PREFER_SHUTDOWN=1 + break + fi +done + CMD=${1} FORCE= @@ -71,13 +79,13 @@ fi # if we can update base, we'll do that as well if opnsense-update ${FORCE} -bk -c; then if output_cmd opnsense-update ${FORCE} -bk; then - output_reboot keep-log + output_restart_action keep-log ${PREFER_SHUTDOWN} fi fi if [ "${ALWAYS_REBOOT}" = "1" ]; then if [ "${PKGS_HASH}" != "$(${PKG} query %n-%v 2> /dev/null | sha256)" ]; then - output_reboot keep-log + output_restart_action keep-log ${PREFER_SHUTDOWN} fi fi diff --git a/src/opnsense/scripts/firmware/upgrade.sh b/src/opnsense/scripts/firmware/upgrade.sh index aea4e5733fe..d0dcf30311b 100755 --- a/src/opnsense/scripts/firmware/upgrade.sh +++ b/src/opnsense/scripts/firmware/upgrade.sh @@ -29,13 +29,21 @@ REQUEST="UPGRADE" . /usr/local/opnsense/scripts/firmware/config.sh +PREFER_SHUTDOWN=0 +for arg in "$@"; do + if [ "$arg" = "shutdown" ]; then + PREFER_SHUTDOWN=1 + break + fi +done + if output_cmd opnsense-update -u; then if output_cmd /usr/local/etc/rc.syshook upgrade; then # pending kernel applies before reboot if output_cmd opnsense-update -K -c; then output_cmd opnsense-update -K fi - output_reboot keep-log + output_restart_action keep-log ${PREFER_SHUTDOWN} fi output_txt "The upgrade was aborted due to an error." diff --git a/src/opnsense/scripts/shell/firmware.sh b/src/opnsense/scripts/shell/firmware.sh index eb2a472d7dd..4ad4d7a47dd 100755 --- a/src/opnsense/scripts/shell/firmware.sh +++ b/src/opnsense/scripts/shell/firmware.sh @@ -75,7 +75,7 @@ if [ -n "${RELEASE}" ]; then PROMPT="${RELEASE}/${PROMPT}" elif CHANGELOG=$(${LAUNCHER} -u reboot); then - echo "This update requires a reboot." + echo "This update requires a reboot/ power off." echo fi