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:
Stan Grishin
2025-08-12 08:31:10 +00:00
parent af2ca3d33f
commit a353d15b55
5 changed files with 384 additions and 189 deletions

View File

@@ -1,19 +1,19 @@
# Copyright 2017-2024 MOSSDeF, Stan Grishin (stangri@melmac.ca). # SPDX-License-Identifier: AGPL-3.0-or-later
# This is free software, licensed under AGPL-3.0-or-later. # Copyright 2017-2025 MOSSDeF, Stan Grishin (stangri@melmac.ca).
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-advanced-reboot PKG_NAME:=luci-app-advanced-reboot
PKG_LICENSE:=AGPL-3.0-or-later PKG_LICENSE:=AGPL-3.0-or-later
PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca> PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
PKG_VERSION:=1.0.1 PKG_VERSION:=1.1.0
PKG_RELEASE:=21 PKG_RELEASE:=1
LUCI_TITLE:=Advanced Linksys Reboot Web UI LUCI_TITLE:=Advanced Linksys Reboot Web UI
LUCI_URL:=https://github.com/stangri/luci-app-advanced-reboot/ 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\ 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\ 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 LUCI_DEPENDS:=+luci-base +jshn
define Package/$(PKG_NAME)/config define Package/$(PKG_NAME)/config

View File

@@ -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).

View File

@@ -1,177 +1,279 @@
'use strict'; "use strict";
'require view'; "require view";
'require rpc'; "require rpc";
'require ui'; "require ui";
'require uci'; "require uci";
'require fs'; "require fs";
var pkg = { var pkg = {
get Name() { return 'luci-app-advanced-reboot'; }, get Name() {
get URL() { return 'https://docs.openwrt.melmac.net/' + pkg.Name + '/'; } return "luci-app-advanced-reboot";
},
get URL() {
return "https://docs.openwrt.melmac.ca/" + pkg.Name + "/";
},
}; };
return view.extend({ return view.extend({
translateTable: { translateTable: {
NO_BOARD_NAME : function(args) { return _('Unable to find Device Board Name.')}, NO_BOARD_NAME: function (args) {
NO_DUAL_FLAG: function (args) { return _('Unable to find Dual Boot Flag Partition.') }, return _("Unable to find Device Board Name.");
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_DUAL_FLAG: function (args) {
NO_FIRM_ENV : function(args) { return _('Unable to obtain firmware environment variable: %s.').format(args[0])}, return _("Unable to find Dual Boot Flag Partition.");
ERR_SET_ENV : function(args) { return _('Unable to set firmware environment variable: %s to %s.').format(args[0],args[1])} },
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({ callReboot: rpc.declare({
object: 'system', object: "system",
method: 'reboot', method: "reboot",
expect: { result: 0 } expect: { result: 0 },
}), }),
callObtainDeviceInfo: rpc.declare({ callObtainDeviceInfo: rpc.declare({
object: 'luci.advanced_reboot', object: "luci.advanced_reboot",
method: 'obtain_device_info', method: "obtain_device_info",
expect: { } expect: {},
}), }),
callTogglePartition: rpc.declare({ callTogglePartition: rpc.declare({
object: 'luci.advanced_reboot', object: "luci.advanced_reboot",
method: 'toggle_boot_partition', method: "toggle_boot_partition",
expect: { } expect: {},
}), }),
callPowerOff: function () { callPowerOff: function () {
return fs.exec('/sbin/poweroff').then(function() { return fs.exec("/sbin/poweroff").then(function () {
ui.showModal(_('Shutting down...'), [ 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.')) 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 () { handlePowerOff: function () {
ui.showModal(_("Power Off Device"), [
ui.showModal(_('Power Off Device'), [ E(
E('p', _("WARNING: Power off might result in a reboot on a device which doesn't support power off.<br /><br />\ "p",
Click \"Proceed\" below to power off your device.")), _(
E('div', { 'class': 'right' }, [ 'WARNING: Power off might result in a reboot on a device which doesn\'t support power off.<br /><br />\
E('button', { Click "Proceed" below to power off your device.'
'class': 'btn', )
'click': ui.hideModal ),
}, _('Cancel')), ' ', E("div", { class: "right" }, [
E('button', { E(
'class': 'btn cbi-button cbi-button-positive important', "button",
'click': L.bind(this.callPowerOff, this) {
}, _('Proceed')) 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) { handleReboot: function (ev) {
return this.callReboot().then(function(res) { return this.callReboot()
.then(function (res) {
if (res != 0) { if (res != 0) {
ui.addNotification(null, E('p', _('The reboot command failed with code %d').format(res))); ui.addNotification(
L.raise('Error', 'Reboot failed'); null,
E("p", _("The reboot command failed with code %d").format(res))
);
L.raise("Error", "Reboot failed");
} }
ui.showModal(_('Rebooting…'), [ ui.showModal(_("Rebooting…"), [
E('p', { 'class': 'spinning' }, _('Waiting for device...')) E("p", { class: "spinning" }, _("Waiting for device...")),
]); ]);
window.setTimeout(function () { window.setTimeout(function () {
ui.showModal(_('Rebooting…'), [ ui.showModal(_("Rebooting…"), [
E('p', { 'class': 'spinning alert-message warning' }, E(
_('Device unreachable! Still waiting for device...')) "p",
{ class: "spinning alert-message warning" },
_("Device unreachable! Still waiting for device...")
),
]); ]);
}, 150000); }, 150000);
ui.awaitReconnect(); 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) { handleTogglePartition: function (ev) {
return this.callTogglePartition().then(L.bind(function(res) { return this.callTogglePartition().then(
L.bind(function (res) {
if (res.error) { if (res.error) {
ui.hideModal() ui.hideModal();
return ui.addNotification(null, E('p', this.translateTable[res.error](res.args))); 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) { if (res != 0) {
ui.addNotification(null, E('p', _('The reboot command failed with code %d').format(res))); ui.addNotification(
L.raise('Error', 'Reboot failed'); null,
E("p", _("The reboot command failed with code %d").format(res))
);
L.raise("Error", "Reboot failed");
} }
ui.showModal(_('Rebooting…'), [ 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.')) 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 () { window.setTimeout(function () {
ui.showModal(_('Rebooting…'), [ ui.showModal(_("Rebooting…"), [
E('p', { 'class': 'spinning alert-message warning' }, E(
_('Device unreachable! Still waiting for device...')) "p",
{ class: "spinning alert-message warning" },
_("Device unreachable! Still waiting for device...")
),
]); ]);
}, 150000); }, 150000);
ui.awaitReconnect(); ui.awaitReconnect();
}) })
.catch(function(e) { ui.addNotification(null, E('p', e.message)) }); .catch(function (e) {
}, this)); ui.addNotification(null, E("p", e.message));
});
}, this)
);
}, },
handleAlternativeReboot: function (ev) { handleAlternativeReboot: function (ev) {
return Promise.all([ return Promise.all([
L.resolveDefault(fs.stat('/usr/sbin/fw_printenv'), null), L.resolveDefault(fs.stat("/usr/sbin/fw_printenv"), null),
L.resolveDefault(fs.stat('/usr/sbin/fw_setenv'), null), L.resolveDefault(fs.stat("/usr/sbin/fw_setenv"), null),
]).then(L.bind(function (data) { ]).then(
L.bind(function (data) {
if (!data[0] || !data[1]) { 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"), [ ui.showModal(
E('p', _("WARNING: An alternative partition might have its own settings and completely different firmware.<br /><br />\ _("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,\ 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 />\ 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\ 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 />\ and boot back to the currently active partition.<br /><br />\
Click \"Proceed\" below to reboot device to an alternative partition.")), Click "Proceed" below to reboot device to an alternative partition.'
E('div', { 'class': 'right' }, [ )
E('button', { ),
'class': 'btn', E("div", { class: "right" }, [
'click': ui.hideModal E(
}, _('Cancel')), ' ', "button",
E('button', { {
'class': 'btn cbi-button cbi-button-positive important', class: "btn",
'click': L.bind(this.handleTogglePartition, this) click: ui.hideModal,
}, _('Proceed')) },
]) _("Cancel")
]); ),
}, this)) " ",
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 = []; var res = [];
partitions.forEach(L.bind(function(partition) { partitions.forEach(
L.bind(function (partition) {
var func, text; var func, text;
if (partition.state == 'Current') { if (partition.state == "Current") {
func = 'handleReboot'; func = "handleReboot";
text = _('Reboot to current partition'); text = _("Reboot to current partition");
} else { } else {
func = 'handleAlternativeReboot'; func = "handleAlternativeReboot";
text = _('Reboot to alternative partition...'); text = _("Reboot to alternative partition...");
} }
res.push([ res.push([
(partition.number + 0x100).toString(16).substr(-2).toUpperCase(), (partition.number + 0x100).toString(16).substr(-2).toUpperCase(),
_(partition.state), _(partition.state),
partition.os.replace("Unknown", _("Unknown")).replace("Compressed", _("Compressed")), partition.os
E('button', { .replace("Unknown", _("Unknown"))
'class': 'btn cbi-button cbi-button-apply important', .replace("Compressed", _("Compressed")),
'click': ui.createHandlerFn(this, func) E(
}, text) "button",
]) {
}, this)); class: "btn cbi-button cbi-button-apply important",
click: ui.createHandlerFn(this, func),
},
text
),
]);
}, this)
);
return res; return res;
}, },
@@ -179,8 +281,8 @@ return view.extend({
load: function () { load: function () {
return Promise.all([ return Promise.all([
uci.changes(), uci.changes(),
L.resolveDefault(fs.stat('/sbin/poweroff'), null), L.resolveDefault(fs.stat("/sbin/poweroff"), null),
this.callObtainDeviceInfo() this.callObtainDeviceInfo(),
]); ]);
}, },
@@ -189,53 +291,90 @@ return view.extend({
poweroff_supported = data[1] != null ? true : false, poweroff_supported = data[1] != null ? true : false,
device_info = data[2]; device_info = data[2];
var body = E([ var body = E([E("h2", _("Advanced Reboot"))]);
E('h2', _('Advanced Reboot'))
]);
for (var config in (changes || {})) { for (var config in changes || {}) {
body.appendChild(E('p', { 'class': 'alert-message warning' }, body.appendChild(
_('Warning: There are unsaved changes that will get lost on reboot!'))); E(
"p",
{ class: "alert-message warning" },
_("Warning: There are unsaved changes that will get lost on reboot!")
)
);
break; break;
} }
if (device_info.error) 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) { if (device_info.device_name) {
var partitions_table = E('table', { 'class': 'table' }, [ var partitions_table = E("table", { class: "table" }, [
E('tr', { 'class': 'tr table-titles' }, [ E("tr", { class: "tr table-titles" }, [
E('th', { 'class': 'th' }, [ _('Partition') ]), E("th", { class: "th" }, [_("Partition")]),
E('th', { 'class': 'th' }, [ _('Status') ]), E("th", { class: "th" }, [_("Status")]),
E('th', { 'class': 'th' }, [ _('Firmware') ]), E("th", { class: "th" }, [_("Firmware")]),
E('th', { 'class': 'th' }, [ _('Reboot') ]) 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); body.appendChild(partitions_table);
} else { } else {
body.appendChild(E('p', { 'class' : 'alert-message warning'}, body.appendChild(
device_info.rom_board_name ? _("Warning: Device (%s) is unknown or isn't a dual-firmware device!" + "%s" + E(
"If you are seeing this on an OpenWrt dual-firmware supported device," + "%s" + "please refer to " + "p",
"%sHow to add a new device section of the README%s.").format(device_info.rom_board_name, "<br /><br />", "<br />", { class: "alert-message warning" },
"<a href=\"" + pkg.URL + "#how-to-add-a-new-device\" target=\"_blank\">", "</a>" ) device_info.rom_board_name
: _('Warning: Unable to obtain device information!') ? _(
)); "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( body.appendChild(
poweroff_supported ? E('button', { poweroff_supported
'class': 'btn cbi-button cbi-button-apply important', ? E(
'click': ui.createHandlerFn(this, 'handlePowerOff') "button",
}, _('Perform power off...')) {
class: "btn cbi-button cbi-button-apply important",
: E('p', { 'class' : 'alert-message warning'}, click: ui.createHandlerFn(this, "handlePowerOff"),
_('Warning: This system does not support powering off!')) },
_("Perform power off...")
)
: E(
"p",
{ class: "alert-message warning" },
_("Warning: This system does not support powering off!")
)
); );
return body; return body;
@@ -243,5 +382,5 @@ return view.extend({
handleSaveApply: null, handleSaveApply: null,
handleSave: null, handleSave: null,
handleReset: null handleReset: null,
}); });

View File

@@ -1,13 +1,23 @@
#!/bin/sh #!/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 # 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/" readonly devices_dir="/usr/share/advanced-reboot/devices/"
. /lib/functions.sh . /lib/functions.sh
. /usr/share/libubox/jshn.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_present() { command -v "$1" >/dev/null 2>&1; }
is_alt_mountable() { is_alt_mountable() {
@@ -25,29 +35,29 @@ is_alt_mountable() {
alt_partition_mount() { alt_partition_mount() {
local ubi_dev op_ubi="$1" ubi_vol="${2:-0}" local ubi_dev op_ubi="$1" ubi_vol="${2:-0}"
mkdir -p /var/alt_rom 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")" ubi_dev="$(echo "$ubi_dev" | sed -n "s/^UBI device number\s*\(\d*\),.*$/\1/p")"
if [ -z "$ubi_dev" ]; then if [ -z "$ubi_dev" ]; then
ubidetach -m "$op_ubi" ubidetach -m "$op_ubi" >/dev/null 2>&1
return 1 return 1
fi fi
ubiblock --create "/dev/ubi${ubi_dev}_${ubi_vol}" && \ 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 mount -t squashfs -r "/dev/ubiblock${ubi_dev}_${ubi_vol}" /var/alt_rom >/dev/null 2>&1
} }
alt_partition_unmount() { alt_partition_unmount() {
local mtdCount i=0 op_ubi="$1" ubi_vol="${2:-0}" local mtdCount i=0 op_ubi="$1" ubi_vol="${2:-0}"
mtdCount="$(ubinfo | grep 'Present UBI devices' | tr ',' '\n' | grep -c 'ubi')" mtdCount="$(ubinfo | grep 'Present UBI devices' | tr ',' '\n' | grep -c 'ubi')"
[ -z "$mtdCount" ] && mtdCount=10 [ -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 while [ "$i" -le "$mtdCount" ]; do
if [ ! -e "/sys/devices/virtual/ubi/ubi${i}/mtd_num" ]; then if [ ! -e "/sys/devices/virtual/ubi/ubi${i}/mtd_num" ]; then
break break
fi fi
ubi_mtd="$(cat /sys/devices/virtual/ubi/ubi${i}/mtd_num)" ubi_mtd="$(cat /sys/devices/virtual/ubi/ubi${i}/mtd_num)"
if [ -n "$ubi_mtd" ] && [ "$ubi_mtd" = "$op_ubi" ]; then if [ -n "$ubi_mtd" ] && [ "$ubi_mtd" = "$op_ubi" ]; then
ubiblock --remove /dev/ubi${i}_${ubi_vol} ubiblock --remove "/dev/ubi${i}_${ubi_vol}" >/dev/null 2>&1
ubidetach -m "$op_ubi" ubidetach -m "$op_ubi" >/dev/null 2>&1
rm -rf /var/alt_rom rm -rf /var/alt_rom
fi fi
i=$((i + 1)) i=$((i + 1))
@@ -71,6 +81,7 @@ get_alt_partition_os_info(){
alt_partition_unmount "$op_ubi" "$ubi_vol" alt_partition_unmount "$op_ubi" "$ubi_vol"
alt_partition_mount "$op_ubi" "$ubi_vol" alt_partition_mount "$op_ubi" "$ubi_vol"
if [ -s "/var/alt_rom/etc/os-release" ]; then if [ -s "/var/alt_rom/etc/os-release" ]; then
# shellcheck disable=SC2031
op_info="$(. /var/alt_rom/etc/os-release && echo "$PRETTY_NAME")" op_info="$(. /var/alt_rom/etc/os-release && echo "$PRETTY_NAME")"
if [ "${op_info//SNAPSHOT}" != "$op_info" ]; then if [ "${op_info//SNAPSHOT}" != "$op_info" ]; then
op_info="$(. /var/alt_rom/etc/os-release && echo "${OPENWRT_RELEASE%%-*}")" op_info="$(. /var/alt_rom/etc/os-release && echo "${OPENWRT_RELEASE%%-*}")"
@@ -134,7 +145,7 @@ obtain_device_info(){
if [ -n "$labelOffset" ]; then if [ -n "$labelOffset" ]; then
if [ -n "$partition1MTD" ]; 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 if [ -n "$p1_label" ]; then
p1_version="$(echo "$p1_label" | sed -n "s/\(.*\)Linux-\([0-9.]\+\).*$/\2/p")" p1_version="$(echo "$p1_label" | sed -n "s/\(.*\)Linux-\([0-9.]\+\).*$/\2/p")"
if [ "${p1_label//LEDE}" != "$p1_label" ]; then p1_os="LEDE"; fi if [ "${p1_label//LEDE}" != "$p1_label" ]; then p1_os="LEDE"; fi
@@ -149,7 +160,7 @@ obtain_device_info(){
fi fi
if [ -n "$partition2MTD" ]; then 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 if [ -n "$p2_label" ]; then
p2_version="$(echo "$p2_label" | sed -n "s/\(.*\)Linux-\([0-9.]\+\).*$/\2/p")" p2_version="$(echo "$p2_label" | sed -n "s/\(.*\)Linux-\([0-9.]\+\).*$/\2/p")"
if [ "${p2_label//LEDE}" != "$p2_label" ]; then p2_os="LEDE"; fi 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 if is_alt_mountable "$partition1MTD" "$partition2MTD"; then
opOffset="${opOffset:-1}" opOffset="${opOffset:-1}"
ubiVolume="${ubiVolume:-0}" 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 if [ "$current_partition" = "$bootEnv1Partition1Value" ]; then
op_ubi=$(( ${partition2MTD:3:3} + $opOffset )) op_ubi=$(( p2num + opOffset ))
else else
op_ubi=$(( ${partition1MTD:3:3} + $opOffset )) op_ubi=$(( p1num + opOffset ))
fi fi
cp_info="$(get_main_partition_os_info $op_ubi)" cp_info="$(get_main_partition_os_info "$op_ubi")"
op_info="$(get_alt_partition_os_info $op_ubi $vendorName $ubiVolume)" op_info="$(get_alt_partition_os_info "$op_ubi" "$vendorName" "$ubiVolume")"
if [ "$current_partition" = "$bootEnv1Partition1Value" ]; then if [ "$current_partition" = "$bootEnv1Partition1Value" ]; then
p1_os="${cp_info:-$p1_os}" p1_os="${cp_info:-$p1_os}"
p2_os="${op_info:-$p2_os}" p2_os="${op_info:-$p2_os}"
@@ -336,7 +353,7 @@ toggle_boot_partition(){
json_add_string "$newEnvSetting" json_add_string "$newEnvSetting"
json_close_array json_close_array
json_add_string 'rom_board_name' "$romBoardName" json_add_string 'rom_board_name' "$romBoardName"
json_dump; json_cleanup; json_dump >&4; json_cleanup;
return return
fi fi
fi fi
@@ -378,7 +395,7 @@ toggle_boot_partition(){
fi fi
fi fi
json_init json_init
json_dump; json_cleanup; json_dump >&4; json_cleanup;
fi fi
} }

View File

@@ -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
}