luci-app-advanced-reboot: update to 1.1.0-1
authorStan Grishin <[email protected]>
Tue, 12 Aug 2025 08:31:10 +0000 (08:31 +0000)
committerStan Grishin <[email protected]>
Tue, 19 Aug 2025 03:35:10 +0000 (03:35 +0000)
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 <[email protected]>
(cherry picked from commit a353d15b5592455e9efb95af290c036b780de31c)

applications/luci-app-advanced-reboot/Makefile
applications/luci-app-advanced-reboot/README.md
applications/luci-app-advanced-reboot/htdocs/luci-static/resources/view/system/advanced_reboot.js
applications/luci-app-advanced-reboot/root/usr/libexec/rpcd/luci.advanced_reboot
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr7350.json [new file with mode: 0644]

index 5166242a8d0d5fb215909b7b517b6c9cf2328f13..dec193f77d9e6d4b69343d74c9bea1388f640c71 100644 (file)
@@ -1,19 +1,19 @@
-# Copyright 2017-2024 MOSSDeF, Stan Grishin ([email protected]).
-# 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 ([email protected]).
 
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=luci-app-advanced-reboot
 PKG_LICENSE:=AGPL-3.0-or-later
 PKG_MAINTAINER:=Stan Grishin <[email protected]>
-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
index 11df6a73805c004208c08ee2e9acc809fa297f25..c647595665e98c3069c142f554f04c0348b9b776 100644 (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).
index 3fdf6c18ec901b54d923f88ebc9becd6651183f0..20c6fccac8fa13530e31ae84a8fe05ece5368cf2 100644 (file)
-'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) {
-                       if (res != 0) {
-                               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...'))
-                       ]);
-
-                       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)) });
-       },
-
-       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)));
-                       }
-
-                       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' }, _('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" }, _("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)) });
-               }, this));
+                       .catch(function (e) {
+                               ui.addNotification(null, E("p", e.message));
+                       });
+       },
+
+       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))
+                                       );
+                               }
+
+                               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.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...")
+                                                               ),
+                                                       ]);
+                                               }, 150000);
+
+                                               ui.awaitReconnect();
+                                       })
+                                       .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) {
-                       if (!data[0] || !data[1]) {
-                               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 />\
+                       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!"))
+                                       );
+                               }
+
+                               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) {
-                       var func, text;
-
-                       if (partition.state == 'Current') {
-                               func = 'handleReboot';
-                               text = _('Reboot to current partition');
-                       } else {
-                               func = 'handleAlternativeReboot';
-                               text = _('Reboot to alternative partition...');
-                       }
-
-                       res.push([
-                               (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));
+               partitions.forEach(
+                       L.bind(function (partition) {
+                               var func, text;
+
+                               if (partition.state == "Current") {
+                                       func = "handleReboot";
+                                       text = _("Reboot to current partition");
+                               } else {
+                                       func = "handleAlternativeReboot";
+                                       text = _("Reboot to alternative partition...");
+                               }
+
+                               res.push([
+                                       (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)
+               );
 
                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,
 });
index c79ed7747401443cfaaed9518de3768e50bd7749..af9dd817e332ec8cecd67a27c82010e5c1ba1483 100755 (executable)
@@ -1,13 +1,23 @@
 #!/bin/sh
-# Copyright 2017-2020 Stan Grishin ([email protected])
+# Copyright 2017-2025 Stan Grishin ([email protected])
 # 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
 }
 
diff --git a/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr7350.json b/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr7350.json
new file mode 100644 (file)
index 0000000..1d1fc86
--- /dev/null
@@ -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
+}
\ No newline at end of file