mirror of
https://github.com/openwrt/luci.git
synced 2025-12-21 19:14:34 +04:00
luci-app-advanced-reboot: update to 1.1.0-1
Makefile: - update SPDX license identified and copyright - update documentation link README: - add short README with the link to full documentation System View javascript: - switch from single to double quotes - update documentation link - more readable translateTable - better code readability for - callPowerOff - handlePowerOff - handleReboot - handleTogglePartition - handleAlternativeReboot - parsePartitions - some visual elements RPCD Script: - more resilient to unexpected CLI output - add debugger function to ease development/debugging - better check if `/var/alt_rom` is mounted before calling umount - better handling of MTD indices Device Support: - add Linksys MR7350 which slipped thru the cracks Signed-off-by: Stan Grishin <stangri@melmac.ca>
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
# Copyright 2017-2024 MOSSDeF, Stan Grishin (stangri@melmac.ca).
|
||||
# This is free software, licensed under AGPL-3.0-or-later.
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright 2017-2025 MOSSDeF, Stan Grishin (stangri@melmac.ca).
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-advanced-reboot
|
||||
PKG_LICENSE:=AGPL-3.0-or-later
|
||||
PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
|
||||
PKG_VERSION:=1.0.1
|
||||
PKG_RELEASE:=21
|
||||
PKG_VERSION:=1.1.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=Advanced Linksys Reboot Web UI
|
||||
LUCI_URL:=https://github.com/stangri/luci-app-advanced-reboot/
|
||||
LUCI_DESCRIPTION:=Provides Web UI (found under System/Advanced Reboot) to reboot supported Linksys and ZyXEL routers to\
|
||||
an alternative partition. Also provides Web UI to shut down (power off) your device. Supported dual-partition\
|
||||
routers are listed at https://docs.openwrt.melmac.net/luci-app-advanced-reboot/
|
||||
routers are listed at https://docs.openwrt.melmac.ca/luci-app-advanced-reboot/
|
||||
LUCI_DEPENDS:=+luci-base +jshn
|
||||
|
||||
define Package/$(PKG_NAME)/config
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
# README
|
||||
# luci-app-advanced-reboot
|
||||
|
||||
Documentation for this project is available at [https://docs.openwrt.melmac.net/luci-app-advanced-reboot/](https://docs.openwrt.melmac.net/luci-app-advanced-reboot/).
|
||||
`luci-app-advanced-reboot` is a LuCI (web interface) application for OpenWrt that provides an easy way to reboot your router into an alternative firmware partition (for dual-partition devices) or perform other advanced reboot operations directly from the web UI.
|
||||
|
||||
## Features
|
||||
|
||||
- Detects supported dual-partition devices.
|
||||
- Displays current and alternative firmware details.
|
||||
- Allows rebooting into the alternative partition without using SSH.
|
||||
- Supports switching between OpenWrt and vendor firmware (if present).
|
||||
|
||||
## Installation
|
||||
|
||||
You can install this package from the official OpenWrt package repositories or from the Melmac OpenWrt repository:
|
||||
|
||||
```sh
|
||||
opkg update
|
||||
opkg install luci-app-advanced-reboot
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation is available at:
|
||||
[https://docs.openwrt.melmac.ca/luci-app-advanced-reboot/](https://docs.openwrt.melmac.ca/luci-app-advanced-reboot/)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the terms of the GNU General Public License v3.0 (GPL-3.0).
|
||||
|
||||
@@ -1,241 +1,380 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require fs';
|
||||
"use strict";
|
||||
"require view";
|
||||
"require rpc";
|
||||
"require ui";
|
||||
"require uci";
|
||||
"require fs";
|
||||
|
||||
var pkg = {
|
||||
get Name() { return 'luci-app-advanced-reboot'; },
|
||||
get URL() { return 'https://docs.openwrt.melmac.net/' + pkg.Name + '/'; }
|
||||
get Name() {
|
||||
return "luci-app-advanced-reboot";
|
||||
},
|
||||
get URL() {
|
||||
return "https://docs.openwrt.melmac.ca/" + pkg.Name + "/";
|
||||
},
|
||||
};
|
||||
|
||||
return view.extend({
|
||||
translateTable: {
|
||||
NO_BOARD_NAME : function(args) { return _('Unable to find Device Board Name.')},
|
||||
NO_DUAL_FLAG: function (args) { return _('Unable to find Dual Boot Flag Partition.') },
|
||||
NO_DUAL_FLAG_BLOCK: function (args) { return _('The Dual Boot Flag Partition: %s is not a block device.').format(args[0])},
|
||||
ERR_SET_DUAL_FLAG : function(args) { return _('Unable to set Dual Boot Flag Partition entry for partition: %s.').format(args[0])},
|
||||
NO_FIRM_ENV : function(args) { return _('Unable to obtain firmware environment variable: %s.').format(args[0])},
|
||||
ERR_SET_ENV : function(args) { return _('Unable to set firmware environment variable: %s to %s.').format(args[0],args[1])}
|
||||
NO_BOARD_NAME: function (args) {
|
||||
return _("Unable to find Device Board Name.");
|
||||
},
|
||||
NO_DUAL_FLAG: function (args) {
|
||||
return _("Unable to find Dual Boot Flag Partition.");
|
||||
},
|
||||
NO_DUAL_FLAG_BLOCK: function (args) {
|
||||
return _(
|
||||
"The Dual Boot Flag Partition: %s is not a block device."
|
||||
).format(args[0]);
|
||||
},
|
||||
ERR_SET_DUAL_FLAG: function (args) {
|
||||
return _(
|
||||
"Unable to set Dual Boot Flag Partition entry for partition: %s."
|
||||
).format(args[0]);
|
||||
},
|
||||
NO_FIRM_ENV: function (args) {
|
||||
return _("Unable to obtain firmware environment variable: %s.").format(
|
||||
args[0]
|
||||
);
|
||||
},
|
||||
ERR_SET_ENV: function (args) {
|
||||
return _("Unable to set firmware environment variable: %s to %s.").format(
|
||||
args[0],
|
||||
args[1]
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
callReboot: rpc.declare({
|
||||
object: 'system',
|
||||
method: 'reboot',
|
||||
expect: { result: 0 }
|
||||
object: "system",
|
||||
method: "reboot",
|
||||
expect: { result: 0 },
|
||||
}),
|
||||
|
||||
callObtainDeviceInfo: rpc.declare({
|
||||
object: 'luci.advanced_reboot',
|
||||
method: 'obtain_device_info',
|
||||
expect: { }
|
||||
object: "luci.advanced_reboot",
|
||||
method: "obtain_device_info",
|
||||
expect: {},
|
||||
}),
|
||||
|
||||
callTogglePartition: rpc.declare({
|
||||
object: 'luci.advanced_reboot',
|
||||
method: 'toggle_boot_partition',
|
||||
expect: { }
|
||||
object: "luci.advanced_reboot",
|
||||
method: "toggle_boot_partition",
|
||||
expect: {},
|
||||
}),
|
||||
|
||||
callPowerOff: function() {
|
||||
return fs.exec('/sbin/poweroff').then(function() {
|
||||
ui.showModal(_('Shutting down...'), [
|
||||
E('p', { 'class': 'spinning' }, _('The system is shutting down now.<br /> DO NOT POWER OFF THE DEVICE!<br /> It might be necessary to renew the address of your computer to reach the device again, depending on your settings.'))
|
||||
callPowerOff: function () {
|
||||
return fs.exec("/sbin/poweroff").then(function () {
|
||||
ui.showModal(_("Shutting down..."), [
|
||||
E(
|
||||
"p",
|
||||
{ class: "spinning" },
|
||||
_(
|
||||
"The system is shutting down now.<br /> DO NOT POWER OFF THE DEVICE!<br /> It might be necessary to renew the address of your computer to reach the device again, depending on your settings."
|
||||
)
|
||||
),
|
||||
]);
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
handlePowerOff: function() {
|
||||
|
||||
ui.showModal(_('Power Off Device'), [
|
||||
E('p', _("WARNING: Power off might result in a reboot on a device which doesn't support power off.<br /><br />\
|
||||
Click \"Proceed\" below to power off your device.")),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')), ' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive important',
|
||||
'click': L.bind(this.callPowerOff, this)
|
||||
}, _('Proceed'))
|
||||
])
|
||||
handlePowerOff: function () {
|
||||
ui.showModal(_("Power Off Device"), [
|
||||
E(
|
||||
"p",
|
||||
_(
|
||||
'WARNING: Power off might result in a reboot on a device which doesn\'t support power off.<br /><br />\
|
||||
Click "Proceed" below to power off your device.'
|
||||
)
|
||||
),
|
||||
E("div", { class: "right" }, [
|
||||
E(
|
||||
"button",
|
||||
{
|
||||
class: "btn",
|
||||
click: ui.hideModal,
|
||||
},
|
||||
_("Cancel")
|
||||
),
|
||||
" ",
|
||||
E(
|
||||
"button",
|
||||
{
|
||||
class: "btn cbi-button cbi-button-positive important",
|
||||
click: L.bind(this.callPowerOff, this),
|
||||
},
|
||||
_("Proceed")
|
||||
),
|
||||
]),
|
||||
]);
|
||||
|
||||
},
|
||||
|
||||
handleReboot: function(ev) {
|
||||
return this.callReboot().then(function(res) {
|
||||
handleReboot: function (ev) {
|
||||
return this.callReboot()
|
||||
.then(function (res) {
|
||||
if (res != 0) {
|
||||
ui.addNotification(null, E('p', _('The reboot command failed with code %d').format(res)));
|
||||
L.raise('Error', 'Reboot failed');
|
||||
ui.addNotification(
|
||||
null,
|
||||
E("p", _("The reboot command failed with code %d").format(res))
|
||||
);
|
||||
L.raise("Error", "Reboot failed");
|
||||
}
|
||||
|
||||
ui.showModal(_('Rebooting…'), [
|
||||
E('p', { 'class': 'spinning' }, _('Waiting for device...'))
|
||||
ui.showModal(_("Rebooting…"), [
|
||||
E("p", { class: "spinning" }, _("Waiting for device...")),
|
||||
]);
|
||||
|
||||
window.setTimeout(function() {
|
||||
ui.showModal(_('Rebooting…'), [
|
||||
E('p', { 'class': 'spinning alert-message warning' },
|
||||
_('Device unreachable! Still waiting for device...'))
|
||||
window.setTimeout(function () {
|
||||
ui.showModal(_("Rebooting…"), [
|
||||
E(
|
||||
"p",
|
||||
{ class: "spinning alert-message warning" },
|
||||
_("Device unreachable! Still waiting for device...")
|
||||
),
|
||||
]);
|
||||
}, 150000);
|
||||
|
||||
ui.awaitReconnect();
|
||||
})
|
||||
.catch(function(e) { ui.addNotification(null, E('p', e.message)) });
|
||||
.catch(function (e) {
|
||||
ui.addNotification(null, E("p", e.message));
|
||||
});
|
||||
},
|
||||
|
||||
handleTogglePartition: function(ev) {
|
||||
return this.callTogglePartition().then(L.bind(function(res) {
|
||||
handleTogglePartition: function (ev) {
|
||||
return this.callTogglePartition().then(
|
||||
L.bind(function (res) {
|
||||
if (res.error) {
|
||||
ui.hideModal()
|
||||
return ui.addNotification(null, E('p', this.translateTable[res.error](res.args)));
|
||||
ui.hideModal();
|
||||
return ui.addNotification(
|
||||
null,
|
||||
E("p", this.translateTable[res.error](res.args))
|
||||
);
|
||||
}
|
||||
|
||||
return this.callReboot().then(function(res) {
|
||||
return this.callReboot()
|
||||
.then(function (res) {
|
||||
if (res != 0) {
|
||||
ui.addNotification(null, E('p', _('The reboot command failed with code %d').format(res)));
|
||||
L.raise('Error', 'Reboot failed');
|
||||
ui.addNotification(
|
||||
null,
|
||||
E("p", _("The reboot command failed with code %d").format(res))
|
||||
);
|
||||
L.raise("Error", "Reboot failed");
|
||||
}
|
||||
|
||||
ui.showModal(_('Rebooting…'), [
|
||||
E('p', { 'class': 'spinning' }, _('The system is rebooting to an alternative partition now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings.'))
|
||||
ui.showModal(_("Rebooting…"), [
|
||||
E(
|
||||
"p",
|
||||
{ class: "spinning" },
|
||||
_(
|
||||
"The system is rebooting to an alternative partition now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings."
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
window.setTimeout(function() {
|
||||
ui.showModal(_('Rebooting…'), [
|
||||
E('p', { 'class': 'spinning alert-message warning' },
|
||||
_('Device unreachable! Still waiting for device...'))
|
||||
window.setTimeout(function () {
|
||||
ui.showModal(_("Rebooting…"), [
|
||||
E(
|
||||
"p",
|
||||
{ class: "spinning alert-message warning" },
|
||||
_("Device unreachable! Still waiting for device...")
|
||||
),
|
||||
]);
|
||||
}, 150000);
|
||||
|
||||
ui.awaitReconnect();
|
||||
})
|
||||
.catch(function(e) { ui.addNotification(null, E('p', e.message)) });
|
||||
}, this));
|
||||
.catch(function (e) {
|
||||
ui.addNotification(null, E("p", e.message));
|
||||
});
|
||||
}, this)
|
||||
);
|
||||
},
|
||||
|
||||
handleAlternativeReboot: function(ev) {
|
||||
handleAlternativeReboot: function (ev) {
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.stat('/usr/sbin/fw_printenv'), null),
|
||||
L.resolveDefault(fs.stat('/usr/sbin/fw_setenv'), null),
|
||||
]).then(L.bind(function (data) {
|
||||
L.resolveDefault(fs.stat("/usr/sbin/fw_printenv"), null),
|
||||
L.resolveDefault(fs.stat("/usr/sbin/fw_setenv"), null),
|
||||
]).then(
|
||||
L.bind(function (data) {
|
||||
if (!data[0] || !data[1]) {
|
||||
return ui.addNotification(null, E('p', _('No access to fw_printenv or fw_printenv!')));
|
||||
return ui.addNotification(
|
||||
null,
|
||||
E("p", _("No access to fw_printenv or fw_printenv!"))
|
||||
);
|
||||
}
|
||||
|
||||
ui.showModal(_('Reboot Device to an Alternative Partition') + " - " + _("Confirm"), [
|
||||
E('p', _("WARNING: An alternative partition might have its own settings and completely different firmware.<br /><br />\
|
||||
ui.showModal(
|
||||
_("Reboot Device to an Alternative Partition") + " - " + _("Confirm"),
|
||||
[
|
||||
E(
|
||||
"p",
|
||||
_(
|
||||
'WARNING: An alternative partition might have its own settings and completely different firmware.<br /><br />\
|
||||
As your network configuration and WiFi SSID/password on alternative partition might be different,\
|
||||
you might have to adjust your computer settings to be able to access your device once it reboots.<br /><br />\
|
||||
Please also be aware that alternative partition firmware might not provide an easy way to switch active partition\
|
||||
and boot back to the currently active partition.<br /><br />\
|
||||
Click \"Proceed\" below to reboot device to an alternative partition.")),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')), ' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive important',
|
||||
'click': L.bind(this.handleTogglePartition, this)
|
||||
}, _('Proceed'))
|
||||
])
|
||||
]);
|
||||
}, this))
|
||||
Click "Proceed" below to reboot device to an alternative partition.'
|
||||
)
|
||||
),
|
||||
E("div", { class: "right" }, [
|
||||
E(
|
||||
"button",
|
||||
{
|
||||
class: "btn",
|
||||
click: ui.hideModal,
|
||||
},
|
||||
_("Cancel")
|
||||
),
|
||||
" ",
|
||||
E(
|
||||
"button",
|
||||
{
|
||||
class: "btn cbi-button cbi-button-positive important",
|
||||
click: L.bind(this.handleTogglePartition, this),
|
||||
},
|
||||
_("Proceed")
|
||||
),
|
||||
]),
|
||||
]
|
||||
);
|
||||
}, this)
|
||||
);
|
||||
},
|
||||
|
||||
parsePartitions: function(partitions) {
|
||||
parsePartitions: function (partitions) {
|
||||
var res = [];
|
||||
|
||||
partitions.forEach(L.bind(function(partition) {
|
||||
partitions.forEach(
|
||||
L.bind(function (partition) {
|
||||
var func, text;
|
||||
|
||||
if (partition.state == 'Current') {
|
||||
func = 'handleReboot';
|
||||
text = _('Reboot to current partition');
|
||||
if (partition.state == "Current") {
|
||||
func = "handleReboot";
|
||||
text = _("Reboot to current partition");
|
||||
} else {
|
||||
func = 'handleAlternativeReboot';
|
||||
text = _('Reboot to alternative partition...');
|
||||
func = "handleAlternativeReboot";
|
||||
text = _("Reboot to alternative partition...");
|
||||
}
|
||||
|
||||
res.push([
|
||||
(partition.number+0x100).toString(16).substr(-2).toUpperCase(),
|
||||
(partition.number + 0x100).toString(16).substr(-2).toUpperCase(),
|
||||
_(partition.state),
|
||||
partition.os.replace("Unknown", _("Unknown")).replace("Compressed", _("Compressed")),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-apply important',
|
||||
'click': ui.createHandlerFn(this, func)
|
||||
}, text)
|
||||
])
|
||||
}, this));
|
||||
partition.os
|
||||
.replace("Unknown", _("Unknown"))
|
||||
.replace("Compressed", _("Compressed")),
|
||||
E(
|
||||
"button",
|
||||
{
|
||||
class: "btn cbi-button cbi-button-apply important",
|
||||
click: ui.createHandlerFn(this, func),
|
||||
},
|
||||
text
|
||||
),
|
||||
]);
|
||||
}, this)
|
||||
);
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
load: function() {
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.changes(),
|
||||
L.resolveDefault(fs.stat('/sbin/poweroff'), null),
|
||||
this.callObtainDeviceInfo()
|
||||
L.resolveDefault(fs.stat("/sbin/poweroff"), null),
|
||||
this.callObtainDeviceInfo(),
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
render: function (data) {
|
||||
var changes = data[0],
|
||||
poweroff_supported = data[1] != null ? true : false,
|
||||
device_info = data[2];
|
||||
|
||||
var body = E([
|
||||
E('h2', _('Advanced Reboot'))
|
||||
]);
|
||||
var body = E([E("h2", _("Advanced Reboot"))]);
|
||||
|
||||
for (var config in (changes || {})) {
|
||||
body.appendChild(E('p', { 'class': 'alert-message warning' },
|
||||
_('Warning: There are unsaved changes that will get lost on reboot!')));
|
||||
for (var config in changes || {}) {
|
||||
body.appendChild(
|
||||
E(
|
||||
"p",
|
||||
{ class: "alert-message warning" },
|
||||
_("Warning: There are unsaved changes that will get lost on reboot!")
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (device_info.error)
|
||||
body.appendChild(E('p', { 'class' : 'alert-message warning'}, _("ERROR: ") + this.translateTable[device_info.error]()));
|
||||
body.appendChild(
|
||||
E(
|
||||
"p",
|
||||
{ class: "alert-message warning" },
|
||||
_("ERROR: ") + this.translateTable[device_info.error]()
|
||||
)
|
||||
);
|
||||
|
||||
body.appendChild(E('h3', (device_info.device_name || '') + _(' Partitions')));
|
||||
body.appendChild(
|
||||
E("h3", (device_info.device_name || "") + _(" Partitions"))
|
||||
);
|
||||
if (device_info.device_name) {
|
||||
var partitions_table = E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, [ _('Partition') ]),
|
||||
E('th', { 'class': 'th' }, [ _('Status') ]),
|
||||
E('th', { 'class': 'th' }, [ _('Firmware') ]),
|
||||
E('th', { 'class': 'th' }, [ _('Reboot') ])
|
||||
])
|
||||
var partitions_table = E("table", { class: "table" }, [
|
||||
E("tr", { class: "tr table-titles" }, [
|
||||
E("th", { class: "th" }, [_("Partition")]),
|
||||
E("th", { class: "th" }, [_("Status")]),
|
||||
E("th", { class: "th" }, [_("Firmware")]),
|
||||
E("th", { class: "th" }, [_("Reboot")]),
|
||||
]),
|
||||
]);
|
||||
|
||||
cbi_update_table(partitions_table, this.parsePartitions(device_info.partitions));
|
||||
cbi_update_table(
|
||||
partitions_table,
|
||||
this.parsePartitions(device_info.partitions)
|
||||
);
|
||||
|
||||
body.appendChild(partitions_table);
|
||||
} else {
|
||||
body.appendChild(E('p', { 'class' : 'alert-message warning'},
|
||||
device_info.rom_board_name ? _("Warning: Device (%s) is unknown or isn't a dual-firmware device!" + "%s" +
|
||||
"If you are seeing this on an OpenWrt dual-firmware supported device," + "%s" + "please refer to " +
|
||||
"%sHow to add a new device section of the README%s.").format(device_info.rom_board_name, "<br /><br />", "<br />",
|
||||
"<a href=\"" + pkg.URL + "#how-to-add-a-new-device\" target=\"_blank\">", "</a>" )
|
||||
: _('Warning: Unable to obtain device information!')
|
||||
));
|
||||
|
||||
body.appendChild(
|
||||
E(
|
||||
"p",
|
||||
{ class: "alert-message warning" },
|
||||
device_info.rom_board_name
|
||||
? _(
|
||||
"Warning: Device (%s) is unknown or isn't a dual-firmware device!" +
|
||||
"%s" +
|
||||
"If you are seeing this on an OpenWrt dual-firmware supported device," +
|
||||
"%s" +
|
||||
"please refer to " +
|
||||
"%sHow to add a new device section of the README%s."
|
||||
).format(
|
||||
device_info.rom_board_name,
|
||||
"<br /><br />",
|
||||
"<br />",
|
||||
'<a href="' +
|
||||
pkg.URL +
|
||||
'#how-to-add-a-new-device" target="_blank">',
|
||||
"</a>"
|
||||
)
|
||||
: _("Warning: Unable to obtain device information!")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
body.appendChild(E('hr'));
|
||||
body.appendChild(E("hr"));
|
||||
body.appendChild(
|
||||
poweroff_supported ? E('button', {
|
||||
'class': 'btn cbi-button cbi-button-apply important',
|
||||
'click': ui.createHandlerFn(this, 'handlePowerOff')
|
||||
}, _('Perform power off...'))
|
||||
|
||||
: E('p', { 'class' : 'alert-message warning'},
|
||||
_('Warning: This system does not support powering off!'))
|
||||
poweroff_supported
|
||||
? E(
|
||||
"button",
|
||||
{
|
||||
class: "btn cbi-button cbi-button-apply important",
|
||||
click: ui.createHandlerFn(this, "handlePowerOff"),
|
||||
},
|
||||
_("Perform power off...")
|
||||
)
|
||||
: E(
|
||||
"p",
|
||||
{ class: "alert-message warning" },
|
||||
_("Warning: This system does not support powering off!")
|
||||
)
|
||||
);
|
||||
|
||||
return body;
|
||||
@@ -243,5 +382,5 @@ return view.extend({
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
handleReset: null,
|
||||
});
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2017-2020 Stan Grishin (stangri@melmac.ca)
|
||||
# Copyright 2017-2025 Stan Grishin (stangri@melmac.ca)
|
||||
# shellcheck disable=SC2039,SC1091,SC3043,SC3057,SC3060
|
||||
|
||||
|
||||
# TechRef: https://openwrt.org/docs/techref/rpcd
|
||||
# TESTS
|
||||
# ubus -v list luci.advanced_reboot
|
||||
# ubus -S call luci.advanced_reboot obtain_device_info '{"name": "advanced-reboot" }'
|
||||
# ubus -S call luci.advanced_reboot toggle_boot_partition '{"name": "advanced-reboot" }'
|
||||
|
||||
readonly devices_dir="/usr/share/advanced-reboot/devices/"
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
logger() { /usr/bin/logger -t advanced-reboot "$1"; }
|
||||
packageName='advanced-reboot'
|
||||
debug() { local i j; for i in "$@"; do eval "j=\$$i"; logger "${packageName:+-t $packageName}" "${i}: ${j} "; done; }
|
||||
|
||||
logger() { /usr/bin/logger -t advanced-reboot "$*"; }
|
||||
is_present() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
is_alt_mountable() {
|
||||
@@ -25,29 +35,29 @@ is_alt_mountable() {
|
||||
alt_partition_mount() {
|
||||
local ubi_dev op_ubi="$1" ubi_vol="${2:-0}"
|
||||
mkdir -p /var/alt_rom
|
||||
ubi_dev="$(ubiattach -m "$op_ubi")"
|
||||
ubi_dev="$(ubiattach -m "$op_ubi" 2>/dev/null)"
|
||||
ubi_dev="$(echo "$ubi_dev" | sed -n "s/^UBI device number\s*\(\d*\),.*$/\1/p")"
|
||||
if [ -z "$ubi_dev" ]; then
|
||||
ubidetach -m "$op_ubi"
|
||||
ubidetach -m "$op_ubi" >/dev/null 2>&1
|
||||
return 1
|
||||
fi
|
||||
ubiblock --create "/dev/ubi${ubi_dev}_${ubi_vol}" && \
|
||||
mount -t squashfs -r "/dev/ubiblock${ubi_dev}_${ubi_vol}" /var/alt_rom
|
||||
ubiblock --create "/dev/ubi${ubi_dev}_${ubi_vol}" >/dev/null 2>&1 && \
|
||||
mount -t squashfs -r "/dev/ubiblock${ubi_dev}_${ubi_vol}" /var/alt_rom >/dev/null 2>&1
|
||||
}
|
||||
|
||||
alt_partition_unmount() {
|
||||
local mtdCount i=0 op_ubi="$1" ubi_vol="${2:-0}"
|
||||
mtdCount="$(ubinfo | grep 'Present UBI devices' | tr ',' '\n' | grep -c 'ubi')"
|
||||
[ -z "$mtdCount" ] && mtdCount=10
|
||||
[ -d /var/alt_rom ] && umount /var/alt_rom
|
||||
grep -qs '/var/alt_rom ' /proc/mounts && umount /var/alt_rom
|
||||
while [ "$i" -le "$mtdCount" ]; do
|
||||
if [ ! -e "/sys/devices/virtual/ubi/ubi${i}/mtd_num" ]; then
|
||||
break
|
||||
fi
|
||||
ubi_mtd="$(cat /sys/devices/virtual/ubi/ubi${i}/mtd_num)"
|
||||
if [ -n "$ubi_mtd" ] && [ "$ubi_mtd" = "$op_ubi" ]; then
|
||||
ubiblock --remove /dev/ubi${i}_${ubi_vol}
|
||||
ubidetach -m "$op_ubi"
|
||||
ubiblock --remove "/dev/ubi${i}_${ubi_vol}" >/dev/null 2>&1
|
||||
ubidetach -m "$op_ubi" >/dev/null 2>&1
|
||||
rm -rf /var/alt_rom
|
||||
fi
|
||||
i=$((i + 1))
|
||||
@@ -71,6 +81,7 @@ get_alt_partition_os_info(){
|
||||
alt_partition_unmount "$op_ubi" "$ubi_vol"
|
||||
alt_partition_mount "$op_ubi" "$ubi_vol"
|
||||
if [ -s "/var/alt_rom/etc/os-release" ]; then
|
||||
# shellcheck disable=SC2031
|
||||
op_info="$(. /var/alt_rom/etc/os-release && echo "$PRETTY_NAME")"
|
||||
if [ "${op_info//SNAPSHOT}" != "$op_info" ]; then
|
||||
op_info="$(. /var/alt_rom/etc/os-release && echo "${OPENWRT_RELEASE%%-*}")"
|
||||
@@ -134,7 +145,7 @@ obtain_device_info(){
|
||||
|
||||
if [ -n "$labelOffset" ]; then
|
||||
if [ -n "$partition1MTD" ]; then
|
||||
p1_label="$(dd if="/dev/${partition1MTD}" bs=1 skip="${labelOffset}" count=64 2>/dev/null)"
|
||||
p1_label="$(dd if="/dev/${partition1MTD}" bs=1 skip="${labelOffset}" count=64 2>/dev/null | tr -d '\0')"
|
||||
if [ -n "$p1_label" ]; then
|
||||
p1_version="$(echo "$p1_label" | sed -n "s/\(.*\)Linux-\([0-9.]\+\).*$/\2/p")"
|
||||
if [ "${p1_label//LEDE}" != "$p1_label" ]; then p1_os="LEDE"; fi
|
||||
@@ -149,7 +160,7 @@ obtain_device_info(){
|
||||
fi
|
||||
|
||||
if [ -n "$partition2MTD" ]; then
|
||||
p2_label="$(dd if="/dev/${partition2MTD}" bs=1 skip="${labelOffset}" count=64 2>/dev/null)"
|
||||
p2_label="$(dd if="/dev/${partition2MTD}" bs=1 skip="${labelOffset}" count=64 2>/dev/null | tr -d '\0')"
|
||||
if [ -n "$p2_label" ]; then
|
||||
p2_version="$(echo "$p2_label" | sed -n "s/\(.*\)Linux-\([0-9.]\+\).*$/\2/p")"
|
||||
if [ "${p2_label//LEDE}" != "$p2_label" ]; then p2_os="LEDE"; fi
|
||||
@@ -192,13 +203,19 @@ obtain_device_info(){
|
||||
if is_alt_mountable "$partition1MTD" "$partition2MTD"; then
|
||||
opOffset="${opOffset:-1}"
|
||||
ubiVolume="${ubiVolume:-0}"
|
||||
# Robustly extract numeric MTD indices (handles mtd2, mtd22, mtd22ro, etc.)
|
||||
local p1num p2num
|
||||
p1num="${partition1MTD#mtd}"; p1num="${p1num%%[^0-9]*}"
|
||||
p2num="${partition2MTD#mtd}"; p2num="${p2num%%[^0-9]*}"
|
||||
[ -n "$p1num" ] || p1num=0
|
||||
[ -n "$p2num" ] || p2num=0
|
||||
if [ "$current_partition" = "$bootEnv1Partition1Value" ]; then
|
||||
op_ubi=$(( ${partition2MTD:3:3} + $opOffset ))
|
||||
op_ubi=$(( p2num + opOffset ))
|
||||
else
|
||||
op_ubi=$(( ${partition1MTD:3:3} + $opOffset ))
|
||||
op_ubi=$(( p1num + opOffset ))
|
||||
fi
|
||||
cp_info="$(get_main_partition_os_info $op_ubi)"
|
||||
op_info="$(get_alt_partition_os_info $op_ubi $vendorName $ubiVolume)"
|
||||
cp_info="$(get_main_partition_os_info "$op_ubi")"
|
||||
op_info="$(get_alt_partition_os_info "$op_ubi" "$vendorName" "$ubiVolume")"
|
||||
if [ "$current_partition" = "$bootEnv1Partition1Value" ]; then
|
||||
p1_os="${cp_info:-$p1_os}"
|
||||
p2_os="${op_info:-$p2_os}"
|
||||
@@ -336,7 +353,7 @@ toggle_boot_partition(){
|
||||
json_add_string "$newEnvSetting"
|
||||
json_close_array
|
||||
json_add_string 'rom_board_name' "$romBoardName"
|
||||
json_dump; json_cleanup;
|
||||
json_dump >&4; json_cleanup;
|
||||
return
|
||||
fi
|
||||
fi
|
||||
@@ -378,7 +395,7 @@ toggle_boot_partition(){
|
||||
fi
|
||||
fi
|
||||
json_init
|
||||
json_dump; json_cleanup;
|
||||
json_dump >&4; json_cleanup;
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"vendorName": "Linksys",
|
||||
"deviceName": "MR7350",
|
||||
"boardNames": [ "linksys,mr7350" ],
|
||||
"partition1MTD": "mtd14",
|
||||
"partition2MTD": "mtd16",
|
||||
"labelOffset": 192,
|
||||
"bootEnv1": "boot_part",
|
||||
"bootEnv1Partition1Value": 1,
|
||||
"bootEnv1Partition2Value": 2,
|
||||
"bootEnv2": null,
|
||||
"bootEnv2Partition1Value": null,
|
||||
"bootEnv2Partition2Value": null
|
||||
}
|
||||
Reference in New Issue
Block a user