From 208df8fd3208fd7db90861c8fddc13d53cdd4840 Mon Sep 17 00:00:00 2001 From: Michael Pfeifroth Date: Thu, 6 Aug 2026 11:33:35 +0200 Subject: [PATCH] luci-mod-system: derive factory-reset LAN IP from board.json The three flash.js call sites that invoke ui.awaitReconnect() after a factory reset, backup restore or sysupgrade currently hardcode '192.168.1.1' as the post-reset LAN IP. That is the built-in config_generate default, but vendors and target images routinely ship a different value via CONFIG_TARGET_PREINIT_IP together with CONFIG_TARGET_DEFAULT_LAN_IP_FROM_PREINIT, which writes the target's factory ipaddr into board.json's network.lan section. flash.js has no way to see that value today, so users of such images are told to wait for a device at an IP the device will never come up on. Prefetch board.json's network.lan.ipaddr in load() via the luci-rpc getBoardJSON method and cache it in a module-scoped factoryLanIP string, defaulting to '192.168.1.1' both for stock builds (where board.json has no network.lan.ipaddr key -- ucidef_set_interface_lan only writes device and protocol; the '192.168.1.1' default is applied at runtime by config_generate) and as a safety net if the rpc call fails. All three ui.awaitReconnect() call sites then receive the resolved IP synchronously. The prefetch is important for firstboot and sysupgrade: those non- returning fs.exec() calls fire immediately, so any ubus round-trip issued afterwards would race against rpcd shutting down and typically resolve only after the rpc timeout (~20s), with the wrong address. By resolving in load() we guarantee the value is a plain string at click time. At the sysupgrade site the user-supplied image is not necessarily the same build as the running one (e.g. flashing a stock image onto a vendor build or vice versa), so the running system's board.json is not authoritative for the post-upgrade factory IP. Keep '192.168.1.1' as an additional awaitReconnect() candidate there to preserve the pre- patch coverage; ui.awaitReconnect() probes every address it is handed, so a duplicate when factoryLanIP already equals '192.168.1.1' is harmless. Also extend the luci-mod-system-flash ACL group's read.ubus section with 'luci-rpc': [ 'getBoardJSON' ]; without it, restricted users with only the flash ACL would silently fail the rpc call ('root' works only because its rpcd session is granted '*'). Signed-off-by: Michael Pfeifroth --- .../resources/view/system/flash.js | 23 +++++++++++++++---- .../usr/share/rpcd/acl.d/luci-mod-system.json | 3 ++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/flash.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/flash.js index 1fbe513f1dac..2a79f011faf9 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/flash.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/flash.js @@ -15,6 +15,17 @@ const callSystemValidateFirmwareImage = rpc.declare({ expect: { '': { valid: false, forceable: true } } }); +const callGetBoardJSON = rpc.declare({ + object: 'luci-rpc', + method: 'getBoardJSON', + expect: { '': {} } +}); + +/* Cached factory-reset LAN IP, prefetched in load() so it can be passed + * synchronously to ui.awaitReconnect() before firstboot/sysupgrade wipe + * the device and rpcd disappears. */ +let factoryLanIP = '192.168.1.1'; + function findStorageSize(procmtd, procpart) { let kernsize = 0, rootsize = 0, wholesize = 0; @@ -71,7 +82,11 @@ return view.extend({ fs.trimmed('/proc/sys/kernel/hostname'), fs.trimmed('/proc/mtd'), fs.trimmed('/proc/partitions'), - fs.trimmed('/proc/mounts') + fs.trimmed('/proc/mounts'), + L.resolveDefault(callGetBoardJSON(), {}).then(bj => { + factoryLanIP = bj?.network?.lan?.ipaddr ?? '192.168.1.1'; + return factoryLanIP; + }) ]; return Promise.all(tasks); @@ -101,7 +116,7 @@ return view.extend({ /* Currently the sysupgrade rpc call will not return, hence no promise handling */ fs.exec('/sbin/firstboot', [ '-r', '-y' ]); - ui.awaitReconnect('192.168.1.1', 'openwrt.lan'); + ui.awaitReconnect(factoryLanIP, 'openwrt.lan'); }, handleRestore(ev) { @@ -163,7 +178,7 @@ return view.extend({ E('p', { 'class': 'spinning' }, _('The system is rebooting now. If the restored configuration changed the current LAN IP address, you might need to reconnect manually.')) ]); - ui.awaitReconnect(window.location.host, '192.168.1.1', 'openwrt.lan'); + ui.awaitReconnect(window.location.host, factoryLanIP, 'openwrt.lan'); }, this)) .catch(function(e) { ui.addNotification(null, E('p', e.message)) }) .finally(function() { btn.firstChild.data = _('Upload archive...') }); @@ -337,7 +352,7 @@ return view.extend({ if (opts['keep'][0].checked) ui.awaitReconnect(window.location.host); else - ui.awaitReconnect(window.location.host, '192.168.1.1', 'openwrt.lan'); + ui.awaitReconnect(window.location.host, factoryLanIP, '192.168.1.1', 'openwrt.lan'); }, handleBackupList(ev) { diff --git a/modules/luci-mod-system/root/usr/share/rpcd/acl.d/luci-mod-system.json b/modules/luci-mod-system/root/usr/share/rpcd/acl.d/luci-mod-system.json index ce8437fbbde9..25a35ed86cfa 100644 --- a/modules/luci-mod-system/root/usr/share/rpcd/acl.d/luci-mod-system.json +++ b/modules/luci-mod-system/root/usr/share/rpcd/acl.d/luci-mod-system.json @@ -179,7 +179,8 @@ "/sbin/sysupgrade --list-backup": [ "exec" ] }, "ubus": { - "file": [ "exec", "read", "stat" ] + "file": [ "exec", "read", "stat" ], + "luci-rpc": [ "getBoardJSON" ] } }, "write": {