-'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;
handleSaveApply: null,
handleSave: null,
- handleReset: null
+ handleReset: null,
});
#!/bin/sh
# 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() {
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))
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%%-*}")"
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
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
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}"
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
fi
fi
json_init
- json_dump; json_cleanup;
+ json_dump >&4; json_cleanup;
fi
}