luci-app-advanced-reboot: update to 1.1.2-6
authorStan Grishin <redacted>
Mon, 15 Jun 2026 19:41:25 +0000 (19:41 +0000)
committerStan Grishin <redacted>
Tue, 16 Jun 2026 22:14:51 +0000 (15:14 -0700)
Update to 1.1.2-6. The headline change is a security fix
for GHSA-vj96-f37g-37f6.

- Security (GHSA-vj96-f37g-37f6, high): the previous read ACL granted
  file.exec on /bin/sh (and dd, mount, sed, fw_setenv, etc.), so a user
  delegated read-only access to this app could execute arbitrary
  commands as root. Device-info gathering is now handled server-side in
  the ucode rpcd backend, the broad file-exec grants are removed, and
  the privileged actions(boot_partition, system reboot, poweroff) are
  moved into a dedicated write block.
- New device support: Linksys EA7500v3, Linksys MX6200 (with a device
  helper script), and Zyxel WSM20.
- Tests: added a ucode test suite (error handling, obtain_device_info,
  and boot_partition) with a mock fs layer and a runner.
- Build: dropped the jq/host build dependency. Build/Prepare now merges
  the per-device JSON files into devices.json with printf/cat, so the
  merged file is no longer committed and the separate install hook is
  gone.
- Maintenance: project moved to the mossdef-org org; melmac.ca URLs
  updated to mossdef.org across the Makefile, README, and LuCI view.

Signed-off-by: Stan Grishin <redacted>
70 files changed:
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/share/advanced-reboot/devices.disabled/linksys-ea9500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices.disabled/netgear-wac510.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices.disabled/xiaomi-ax3600.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices.disabled/xiaomi-ax9000.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices.json [deleted file]
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/_device_json_transform.jq [deleted file]
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/dlink-fgs1210-28.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-e4200v2-ea4500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-e4200v2.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-e7350.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea3500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea4500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea6350v3.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea6350v4.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea7300v1.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea7300v2.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea7500v1.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea7500v2.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea7500v3.json [new file with mode: 0644]
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea8100v1.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea8100v2.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea8300.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea8500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr5500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr7350.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr7500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr8300.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mr9000.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx2000.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx4200v1.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx4200v2.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx4300.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx5300.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx5500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx6200.json [new file with mode: 0644]
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx8500.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-spnmx56.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-whw01v1.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-whw03.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-whw03v2.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-wrt1200ac.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-wrt1900ac.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-wrt1900acs.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-wrt1900acv2.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-wrt3200acm.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-wrt32x.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/mercusys-mr90xv1.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/netgear-gs308t-v1.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/zyxel-nbg6817.json
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/zyxel-wsm20.json [new file with mode: 0644]
applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/helpers/linksys-mx6200.sh [new file with mode: 0755]
applications/luci-app-advanced-reboot/root/usr/share/rpcd/acl.d/luci-app-advanced-reboot.json
applications/luci-app-advanced-reboot/root/usr/share/rpcd/ucode/luci.advanced-reboot
applications/luci-app-advanced-reboot/tests/01_error_handling/01_no_board_name [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/01_error_handling/02_no_device_match [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/01_error_handling/03_invalid_boot_arg [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/01_error_handling/04_partition_not_found [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/02_obtain_device_info/01_linksys_e7350 [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/02_obtain_device_info/02_zyxel_nbg6817 [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/02_obtain_device_info/03_zyxel_wsm20 [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/03_boot_partition/01_linksys_e7350_switch [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/03_boot_partition/02_set_env_failure [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/03_boot_partition/03_zyxel_wsm20_switch [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/lib/mocklib.uc [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/lib/mocklib/fs.uc [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/mocks/fs/open~_usr_share_advanced-reboot_devices_json.txt [new file with mode: 0644]
applications/luci-app-advanced-reboot/tests/run_tests.sh [new file with mode: 0644]

index 606e2d969b28c6f766d747f5bf9bf5692410758d..e0e581d329b88517609ec887c9d412c27e5b2d57 100644 (file)
@@ -6,16 +6,14 @@ include $(TOPDIR)/rules.mk
 PKG_NAME:=luci-app-advanced-reboot
 PKG_LICENSE:=AGPL-3.0-or-later
 PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
-PKG_VERSION:=1.1.1
-PKG_RELEASE:=15
-
-PKG_BUILD_DEPENDS:=jq/host
+PKG_VERSION:=1.1.2
+PKG_RELEASE:=6
 
 LUCI_TITLE:=Advanced Linksys Reboot Web UI
-LUCI_URL:=https://github.com/stangri/luci-app-advanced-reboot/
+LUCI_URL:=https://github.com/mossdef-org/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.ca/luci-app-advanced-reboot/
+       routers are listed at https://docs.mossdef.org/luci-app-advanced-reboot/
 LUCI_DEPENDS:=+luci-base +jshn
 
 define Package/$(PKG_NAME)/config
@@ -26,23 +24,28 @@ help
        Version: $(PKG_VERSION)-$(PKG_RELEASE)
 endef
 
-include ../../luci.mk
-
-# Prune individual device JSON directories from the package image.
-# The default LuCI install logic from luci.mk copies everything under
-# htdocs/, root/, etc. We let it run, then remove folders we keep only
-# in source control.
-define Package/$(PKG_NAME)/install
-       $(call Package/$(PKG_NAME)/install/default,$(1))
-       @mkdir -p $(1)/usr/share/advanced-reboot
-       @if [ -d $(1)/usr/share/advanced-reboot/devices ] \
-               && ls $(1)/usr/share/advanced-reboot/devices/*.json >/dev/null 2>&1; then \
-                       $(STAGING_DIR_HOST)/bin/jq -s '.' $(1)/usr/share/advanced-reboot/devices/*.json \
-                               > $(1)/usr/share/advanced-reboot/devices.json; \
+# Consolidate individual device JSON files into devices.json in the build tree.
+# luci.mk's Build/Prepare calls Build/Prepare/$(LUCI_NAME) as a per-package hook.
+# The merged file lands in root/ so luci.mk's install step copies it automatically.
+define Build/Prepare/luci-app-advanced-reboot
+       @if [ -d $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices ] \
+               && ls $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices/*.json >/dev/null 2>&1; then \
+                       sep=''; printf '[' > $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices.json; \
+                       for f in $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices/*.json; do \
+                               printf '%s' "$$$$sep" >> $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices.json; \
+                               cat "$$$$f" >> $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices.json; \
+                               sep=','; \
+                       done; \
+                       printf ']' >> $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices.json; \
        fi
-       @if [ -s $(1)/usr/share/advanced-reboot/devices.json ]; then \
-               $(RM) -r $(1)/usr/share/advanced-reboot/devices $(1)/usr/share/advanced-reboot/devices.disabled || true; \
+       @rm -rf $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices.disabled
+       @if grep -q '"device"' $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices.json 2>/dev/null; then \
+               rm -rf $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices; \
+       else \
+               rm -f $(PKG_BUILD_DIR)/root/usr/share/advanced-reboot/devices.json; \
        fi
 endef
 
+include ../../luci.mk
+
 # call BuildPackage - OpenWrt buildroot signature
index c647595665e98c3069c142f554f04c0348b9b776..644df678f716c3df4b1fa27281eaefc7546874c4 100644 (file)
@@ -21,7 +21,7 @@ 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/)
+[https://docs.mossdef.org/luci-app-advanced-reboot/](https://docs.mossdef.org/luci-app-advanced-reboot/)
 
 ## License
 
index 2351bf15d750896ea89e7cb50551cb46f9657f39..92b6ba6f72531087dfdee04ae8e0aeed118c0c0b 100644 (file)
@@ -10,7 +10,7 @@ var pkg = {
                return "luci-app-advanced-reboot";
        },
        get URL() {
-               return "https://docs.openwrt.melmac.ca/" + pkg.Name + "/";
+               return "https://docs.mossdef.org/" + pkg.Name + "/";
        },
 };
 
@@ -65,6 +65,11 @@ return view.extend({
                ERR_SAVE_ENV: function (args) {
                        return _("Unable to save environment changes.");
                },
+               ERR_SET_HELPER: function (args) {
+                       return _(
+                               "Unable to run device helper: %s for partition: %s."
+                       ).format(args[0], args[1]);
+               },
                NO_TARGET_FLAG: function (args) {
                        return _("Target partition flag is not defined for this device.");
                },
index f952abecf256153850e8bc5c88c154e3410e24dd..99033f01f22f263a2435d93b4e39ed106d3392d3 100644 (file)
        "partitions": [
                {
                        "number": 1,
-                       "param_values": [1],
-                       "mtd": "mtd3",
-                       "labelOffsetBytes": 28
+                       "param_values": [
+                               1
+                       ],
+                       "firmware": {
+                               "kernel": "mtd3",
+                               "kernel_label_offset": 28,
+                               "rootfs": "mtd4"
+                       }
                },
                {
                        "number": 2,
-                       "param_values": [2],
-                       "mtd": "mtd6",
-                       "labelOffsetBytes": 28
+                       "param_values": [
+                               2
+                       ],
+                       "firmware": {
+                               "kernel": "mtd6",
+                               "kernel_label_offset": 28,
+                               "rootfs": "mtd7"
+                       }
                }
        ]
-}
\ No newline at end of file
+}
index e8792aaafe8c0a293c945e8c9ddeb725085f2ad2..06463f2013c5e511926e8730d31d87237625cdeb 100644 (file)
        "partitions": [
                {
                        "number": 1,
-                       "param_values": [0, 3800000],
-                       "mtd": "mtd9",
-                       "labelOffsetBytes": null
+                       "param_values": [
+                               0,
+                               3800000
+                       ],
+                       "firmware": {
+                               "rootfs": "mtd10"
+                       }
                },
                {
                        "number": 2,
-                       "param_values": [3800000, 0],
-                       "mtd": "mtd10",
-                       "labelOffsetBytes": null
+                       "param_values": [
+                               3800000,
+                               0
+                       ],
+                       "firmware": {
+                               "rootfs": "mtd11"
+                       }
                }
        ]
 }
index 70bbbaf885c08f1d66494054d240a01224c684c5..5d54bb65b4d540e8235ecb6c7fae8420a85ba4b7 100644 (file)
                                0,
                                0
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 266432
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 266432,
+                               "rootfs": "mtd13"
+                       }
                },
                {
                        "number": 2,
                                1,
                                1
                        ],
-                       "mtd": "mtd13",
-                       "labelOffsetBytes": 266432
+                       "firmware": {
+                               "kernel": "mtd13",
+                               "kernel_label_offset": 266432,
+                               "rootfs": "mtd14"
+                       }
                }
        ]
 }
index 285bf13aa2f1360252d94d505b1a0f410b6cba27..ee9f55a524e6d3eb05580286079f4b7eb214c1e1 100644 (file)
                                0,
                                0
                        ],
-                       "mtd": "mtd20",
-                       "labelOffsetBytes": 266432
+                       "firmware": {
+                               "kernel": "mtd20",
+                               "kernel_label_offset": 266432,
+                               "rootfs": "mtd21"
+                       }
                },
                {
                        "number": 2,
                                1,
                                1
                        ],
-                       "mtd": "mtd21",
-                       "labelOffsetBytes": 266432
+                       "firmware": {
+                               "kernel": "mtd21",
+                               "kernel_label_offset": 266432,
+                               "rootfs": "mtd22"
+                       }
                }
        ]
 }
diff --git a/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices.json b/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices.json
deleted file mode 100644 (file)
index 4a73edd..0000000
+++ /dev/null
@@ -1,1506 +0,0 @@
-[
-  {
-    "device": {
-      "vendor": "D-Link",
-      "model": "DGS-1210-28",
-      "board": [
-        "d-link,dgs-1210-28"
-      ]
-    },
-    "commands": {
-      "params": [
-        "bootcmd",
-        "image"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [],
-        "mtd": "mtd5",
-        "labelOffsetBytes": null
-      },
-      {
-        "number": 2,
-        "param_values": [
-          "run addargs\\; bootm 0xb4e80000",
-          "/dev/mtdblock7"
-        ],
-        "mtd": "mtd9",
-        "labelOffsetBytes": null
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "E4200v2/EA4500",
-      "board": [
-        "linksys-viper",
-        "linksys,viper"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd3",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "E4200v2",
-      "board": [
-        "linksys-e4200v2",
-        "linksys,e4200-v2"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd3",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "E7350",
-      "board": [
-        "linksys,e7350"
-      ]
-    },
-    "commands": {
-      "params": [
-        "bootimage"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd3",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd6",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA3500",
-      "board": [
-        "linksys-audi",
-        "linksys,audi",
-        "linksys-ea3500",
-        "linksys,ea3500"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd3",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA4500",
-      "board": [
-        "linksys-ea4500",
-        "linksys,ea4500"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd3",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA6350v3",
-      "board": [
-        "linksys-ea6350v3",
-        "linksys,ea6350v3"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd10",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA6350v4",
-      "board": [
-        "linksys,ea6350-v4"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA7300v1",
-      "board": [
-        "linksys,ea7300-v1"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA7300v2",
-      "board": [
-        "linksys,ea7300-v2"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA7500v1",
-      "board": [
-        "linksys,ea7500-v1"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd13",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd15",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA7500v2",
-      "board": [
-        "linksys,ea7500-v2"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA8100v1",
-      "board": [
-        "linksys,ea8100-v1"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA8100v2",
-      "board": [
-        "linksys,ea8100-v2"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA8300",
-      "board": [
-        "linksys-ea8300",
-        "linksys,ea8300"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd10",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "EA8500",
-      "board": [
-        "linksys-ea8500",
-        "linksys,ea8500"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd13",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd15",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MR5500",
-      "board": [
-        "linksys,mr5500"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd14",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MR7350",
-      "board": [
-        "linksys,mr7350"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd14",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd16",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MR7500",
-      "board": [
-        "linksys,mr7500"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd13",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd15",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MR8300",
-      "board": [
-        "linksys-mr8300",
-        "linksys,mr8300"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd10",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MR9000 (Dallas)",
-      "board": [
-        "linksys,mr9000"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd10",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MX2000",
-      "board": [
-        "linksys,mx2000"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd14",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MX4200v1",
-      "board": [
-        "linksys,mx4200v1"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd21",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd23",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MX4200v2",
-      "board": [
-        "linksys,mx4200v2"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd21",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd23",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MX4300",
-      "board": [
-        "linksys,mx4300"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd21",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd23",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MX5300",
-      "board": [
-        "linksys,mx5300"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd21",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd23",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MX5500",
-      "board": [
-        "linksys,mx5500"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd14",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "MX8500",
-      "board": [
-        "linksys,mx8500"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd21",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd23",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "SPNMX56",
-      "board": [
-        "linksys,spnmx56"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd12",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd14",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WHW01 V1 (Velop)",
-      "board": [
-        "linksys,whw01"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd9",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd11",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WHW03 (Velop)",
-      "board": [
-        "linksys,whw03"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mmcblk0p14",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mmcblk0p16",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WHW03 V2 (Velop)",
-      "board": [
-        "linksys-whw03v2",
-        "linksys,whw03v2"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd9",
-        "labelOffsetBytes": 192
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2
-        ],
-        "mtd": "mtd11",
-        "labelOffsetBytes": 192
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WRT1200AC",
-      "board": [
-        "linksys-caiman",
-        "linksys,caiman",
-        "linksys,wrt1200ac"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd4",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd6",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WRT1900ACv1",
-      "board": [
-        "linksys-mamba",
-        "linksys,mamba",
-        "linksys,wrt1900ac-v1"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd4",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd6",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WRT1900ACS",
-      "board": [
-        "linksys-shelby",
-        "linksys,shelby",
-        "linksys,wrt1900acs"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd4",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd6",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WRT1900ACv2",
-      "board": [
-        "linksys-cobra",
-        "linksys,cobra",
-        "linksys,wrt1900ac-v2"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd4",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd6",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WRT3200ACM",
-      "board": [
-        "linksys-rango",
-        "linksys,rango",
-        "linksys,wrt3200acm"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Linksys",
-      "model": "WRT32X",
-      "board": [
-        "linksys-venom",
-        "linksys,venom",
-        "linksys,wrt32x"
-      ]
-    },
-    "commands": {
-      "params": [
-        "boot_part",
-        "bootcmd"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          1,
-          "run nandboot"
-        ],
-        "mtd": "mtd5",
-        "labelOffsetBytes": null
-      },
-      {
-        "number": 2,
-        "param_values": [
-          2,
-          "run altnandboot"
-        ],
-        "mtd": "mtd7",
-        "labelOffsetBytes": null
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "MERCUSYS",
-      "model": "MR90X v1",
-      "board": [
-        "mercusys,mr90x-v1"
-      ]
-    },
-    "commands": {
-      "params": [
-        "tp_boot_idx"
-      ],
-      "get": "fw_printenv",
-      "set": "fw_setenv",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          0
-        ],
-        "mtd": "mtd2",
-        "labelOffsetBytes": null,
-        "altMountOptions": {
-          "mtdOffset": 0,
-          "ubiVolume": 2
-        }
-      },
-      {
-        "number": 2,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd3",
-        "labelOffsetBytes": null,
-        "altMountOptions": {
-          "mtdOffset": 0,
-          "ubiVolume": 2
-        }
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "Netgear",
-      "model": "GS308T v1",
-      "board": [
-        "netgear,gs308t-v1"
-      ]
-    },
-    "commands": {
-      "params": [
-        "bootpartition"
-      ],
-      "get": "fw_printsys",
-      "set": "fw_setsys",
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          0
-        ],
-        "mtd": "mtd6",
-        "labelOffsetBytes": null
-      },
-      {
-        "number": 2,
-        "param_values": [
-          1
-        ],
-        "mtd": "mtd9",
-        "labelOffsetBytes": null
-      }
-    ]
-  },
-  {
-    "device": {
-      "vendor": "ZyXEL",
-      "model": "NBG6817",
-      "board": [
-        "nbg6817",
-        "zyxel,nbg6817"
-      ]
-    },
-    "commands": {
-      "params": [],
-      "get": null,
-      "set": null,
-      "save": null
-    },
-    "partitions": [
-      {
-        "number": 1,
-        "param_values": [
-          "ff"
-        ],
-        "mtd": "mmcblk0p4",
-        "labelOffsetBytes": 32
-      },
-      {
-        "number": 2,
-        "param_values": [
-          "01"
-        ],
-        "mtd": "mmcblk0p7",
-        "labelOffsetBytes": 32
-      }
-    ]
-  }
-]
diff --git a/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/_device_json_transform.jq b/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/_device_json_transform.jq
deleted file mode 100644 (file)
index 23eb440..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-# Tabs only for indentation, spaces ok in comments.
-
-# jq -f _device_json_transform.jq old.json
-# Transform from the old schema:
-# {
-#      "vendorName": "...",
-#      "deviceName": "...",
-#      "boardNames": ["..."],
-#      "partition1MTD": "mtdX",
-#      "partition2MTD": "mtdY",
-#      "labelOffset": 32,
-#      "bootEnv1": "boot_part",
-#      "bootEnv1Partition1Value": 1,
-#      "bootEnv1Partition2Value": 2,
-#      "bootEnv2": "bootcmd",
-#      "bootEnv2Partition1Value": "run nandboot",
-#      "bootEnv2Partition2Value": "run altnandboot",
-#      "opOffset": 0,
-#      "ubiVolume": 2
-# }
-#
-# …to the new schema:
-# {
-#      "device": { "vendor": "...", "model": "...", "board": ["..."] },
-#      "commands": { "params": [...], "get": "fw_printenv", "set": "fw_setenv", "save": null },
-#      "partitions": [
-#              { "number": 1, "param_values": [...], "mtd": "mtdX", "labelOffsetBytes": <int|null>, "altMountOptions": {...|null} },
-#              { "number": 2, "param_values": [...], "mtd": "mtdY", "labelOffsetBytes": <int|null>, "altMountOptions": {...|null} }
-#      ]
-# }
-
-. as $in
-| ([$in.bootEnv1, $in.bootEnv2] | map(select(. != null))) as $params
-| {
-       device: {
-               vendor: $in.vendorName,
-               model: $in.deviceName,
-               board: (if ($in.boardNames | type) == "array" then $in.boardNames else [$in.boardNames] end)
-       },
-       commands: {
-               params: $params,
-               get: "fw_printenv",
-               set: "fw_setenv",
-               save: null
-       },
-       partitions: [
-               (if $in.partition1MTD != null then
-                       {
-                               number: 1,
-                               param_values: (
-                                       # Align to commands.params: value for param[0], param[1], ...
-                                       # Only include entries that exist (skip nulls)
-                                       [
-                                               (if ($params | length) > 0 then $in.bootEnv1Partition1Value else empty end),
-                                               (if ($params | length) > 1 then $in.bootEnv2Partition1Value else empty end)
-                                       ] | map(select(. != null))
-                               ),
-                               mtd: $in.partition1MTD,
-                               labelOffsetBytes: ($in.labelOffset // null)
-                       } + (
-                               if ($in.opOffset != null or $in.ubiVolume != null) then
-                                       { altMountOptions: { mtdOffset: ($in.opOffset // null), ubiVolume: ($in.ubiVolume // null) } }
-                               else
-                                       {}
-                               end
-                       )
-               else empty end),
-               (if $in.partition2MTD != null then
-                       {
-                               number: 2,
-                               param_values: (
-                                       [
-                                               (if ($params | length) > 0 then $in.bootEnv1Partition2Value else empty end),
-                                               (if ($params | length) > 1 then $in.bootEnv2Partition2Value else empty end)
-                                       ] | map(select(. != null))
-                               ),
-                               mtd: $in.partition2MTD,
-                               labelOffsetBytes: ($in.labelOffset // null)
-                       } + (
-                               if ($in.opOffset != null or $in.ubiVolume != null) then
-                                       { altMountOptions: { mtdOffset: ($in.opOffset // null), ubiVolume: ($in.ubiVolume // null) } }
-                               else
-                                       {}
-                               end
-                       )
-               else empty end)
-       ]
-}
index 8584916c202a0f4620be57e233c362fcc1e2448a..77547d94d2bc74d5d2a274378c368c2e91fb9cbb 100644 (file)
@@ -19,8 +19,9 @@
                {
                        "number": 1,
                        "param_values": [],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": null
+                       "firmware": {
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
@@ -28,8 +29,9 @@
                                "run addargs\\; bootm 0xb4e80000",
                                "/dev/mtdblock7"
                        ],
-                       "mtd": "mtd9",
-                       "labelOffsetBytes": null
+                       "firmware": {
+                               "rootfs": "mtd10"
+                       }
                }
        ]
 }
index 361e18338974febba5c26ab3ca83e0911b9bc3ad..29c0d4702febfeef17e31bfe064bd3e145385cf9 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd3",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd3",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd4"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                }
        ]
 }
index dc11ac3b6b16f4307b7b431088bf8066f7e4890f..b691cbeeddb8bbbc67bf965692ecbd364d3ca70c 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd3",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd3",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd4"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                }
        ]
 }
index bd072c21ed215eab9dae65d1370f77e790eac45b..5a53dc6ce2d03d7dfcd94efdfe22d6b9416f1d09 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd3",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd3",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd4"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd6",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd6",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd7"
+                       }
                }
        ]
 }
index 2db56154524dd2e01a714db7423613478cf0fc56..a7332d45e7e2d28abaee201a3854233a65ddf477 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd3",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd3",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd4"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                }
        ]
 }
index f5b8d0318ab58269aedb89acf9d7070289ff9510..6aeb3ccee5d012c9abe9bddf1570ac08ab50833d 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd3",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd3",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd4"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                }
        ]
 }
index 32f6cd108a91980ecfe9f255672423c01ae403ec..f7e0f1106abb9ae78c6a6ec94b0cedbde5983b8c 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd10",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd10",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd11"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                }
        ]
 }
index 74694d5e952ecc288821f54e23dca862ca974b60..ee475eb0f5c7056c4dfb1688075d68ee86c99f6f 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd7",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
index 580a3f55b858e2980540fd04a8452ab163def9d8..31d8fee763d7e654563d135ea086b2ba0ac3043c 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd7",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
index 5513a7a6c8b1658c7bb9774c1d0865f460c498e0..b79d552b8e95c27155ae28c8acc408e737f99cb1 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd7",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
index 9012333d832e004b0113768f22480e2d83c9e70f..7bfcbde6f6f4af03effd26ee50656f0809305ad4 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd13",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd13",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd14"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd15",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd15",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd16"
+                       }
                }
        ]
 }
index 8a7dcae18019235e45e58a8fd2176808f58503fc..2ed0b3dc69059268ff1a8936df2574e5368cb676 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd7",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
diff --git a/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea7500v3.json b/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-ea7500v3.json
new file mode 100644 (file)
index 0000000..ca19a31
--- /dev/null
@@ -0,0 +1,44 @@
+{
+       "device": {
+               "vendor": "Linksys",
+               "model": "EA7500v3",
+               "board": [
+                       "linksys,ea7500-v3"
+               ]
+       },
+       "commands": {
+               "params": [
+                       "boot_part",
+                       "bootimage"
+               ],
+               "get": "fw_printenv",
+               "set": "fw_setenv",
+               "save": null
+       },
+       "partitions": [
+               {
+                       "number": 1,
+                       "param_values": [
+                               1,
+                               1
+                       ],
+                       "firmware": {
+                               "kernel": "mtd3",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd4"
+                       }
+               },
+               {
+                       "number": 2,
+                       "param_values": [
+                               2,
+                               2
+                       ],
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
+               }
+       ]
+}
index f2f0489e41570b43f54c58a6b1f557a659526158..5cfe6c63437249fea4bc9a4036505e8415125f71 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd7",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
index cbed6e1cb510f9ba365d8d9e5967973088c00f74..9e655ecbb224cf5b6a72bffe7c69715ffee0cc90 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd7",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
index 80518db673a5b007a205e7fe4b7aa547eb0faec8..d7f61d3985e036476db9fa0a02e86151021c2b98 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd10",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd10",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd11"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                }
        ]
 }
index e2f7ccc5088064e20d258f0eaeccd00b20e57832..a70e40195885a4df14dd5822b3c7beafcece7684 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd13",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd13",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd14"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd15",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd15",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd16"
+                       }
                }
        ]
 }
index 13ff2d2f799323d6ce7b87cfbf5b5f57526ff44f..b0754c474edc10f1394460e834715de35926bdca 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd14",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd14",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd15"
+                       }
                }
        ]
 }
index 68bff35efdc8085e132fc0cdd18b1e86a468e7b6..9d500c364ba13baded6f0019f9da9df2e1479c61 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd14",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd14",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd15"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd16",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd16",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd17"
+                       }
                }
        ]
 }
index 4dc7309d73157bbcabf9ab084c3670f0c0d65e0a..2353f64ec1647c25c0285162f6a3d43ae690c0e4 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd13",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd13",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd14"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd15",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd15",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd16"
+                       }
                }
        ]
 }
index 625ab0fc755edd04132088b5a8d4f2cec9a48ffc..3a66e32a12841eb5364d33940ead7391ff1e41a8 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd10",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd10",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd11"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                }
        ]
 }
index 763692bf100a1b36fffc25cfa35bf1ba02e920b3..ae89fd86aaa5b8c64fd2591d690bfaa5e4279c3f 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd10",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd10",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd11"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                }
        ]
 }
index c10f8a989124ced8da90064d6672682d6f0c6494..f0bf3e5b5abf5297e5a8afdfd8db4a3bd753f0f8 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd14",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd14",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd15"
+                       }
                }
        ]
 }
index 735798e6c31de078ef03e6aa1b9a73e0f4ffa967..0be21758f429f5871911bf1872127d2cc68bdb4e 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd21",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd21",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd22"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd23",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd23",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd24"
+                       }
                }
        ]
 }
index 906aa8414cc2d25fdf44467b2902eb8d4b1f24c0..d1eaa653acb2dfac5377c8c1dae0efc0efa3fa9b 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd21",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd21",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd22"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd23",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd23",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd24"
+                       }
                }
        ]
 }
index 0c494969d3de6cb2e2a58e85c7e017cb502fe76f..84fc45e56c40a4b667b666604cd40554288114ee 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd21",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd21",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd22"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd23",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd23",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd24"
+                       }
                }
        ]
 }
index d904eeea89652bd05332dbfc978fe26c7d733df6..6d7d78c6c870e6abf07a0ded6a3e5bb049ba5b4c 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd21",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd21",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd22"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd23",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd23",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd24"
+                       }
                }
        ]
 }
index 35407fc774424416bd379e54130c030544f91eb6..e79c1f583955b66d50640e434ac64e64e4daf282 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd14",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd14",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd15"
+                       }
                }
        ]
 }
diff --git a/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx6200.json b/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/linksys-mx6200.json
new file mode 100644 (file)
index 0000000..56b7a4f
--- /dev/null
@@ -0,0 +1,32 @@
+{
+       "device": {
+               "vendor": "Linksys",
+               "model": "MX6200",
+               "board": ["linksys,mx6200"]
+       },
+       "commands": {
+               "params": ["boot_part"],
+               "get": "fw_printenv",
+               "set": "fw_setenv",
+               "save": null,
+               "set_helper": "/usr/share/advanced-reboot/helpers/linksys-mx6200.sh"
+       },
+       "partitions": [
+               {
+                       "number": 1,
+                       "param_values": [1],
+                       "firmware": {
+                               "rootfs": "mtd19",
+                               "rootfs_ubi_volume": 1
+                       }
+               },
+               {
+                       "number": 2,
+                       "param_values": [2],
+                       "firmware": {
+                               "rootfs": "mtd19",
+                               "rootfs_ubi_volume": 1
+                       }
+               }
+       ]
+}
index e9ab606179938eab893ce695eed3d4e7a26cfdfd..5b976d8cd754474d077f7d53709a45832057c3b2 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd21",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd21",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd22"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd23",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd23",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd24"
+                       }
                }
        ]
 }
index 09bc2a2f782fb2e2a187d6c6de3e74abba5acc44..110cddef380ef136e8c5de33c86e44753b3e65da 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd12",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd12",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd13"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd14",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd14",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd15"
+                       }
                }
        ]
 }
index f1b73d38e065b9bd1ca8b8707111b3dac17a4e41..962b7b3993038eed85adc46a239d4b4c7684c209 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd9",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd9",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd10"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd11",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd11",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd12"
+                       }
                }
        ]
 }
index 26b16e8a6133b489143e2310e030dd9750f9fb7e..cc7fd86cc13022ed824f34ba53162411fc1b995a 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mmcblk0p14",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mmcblk0p14",
+                               "kernel_label_offset": 192
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mmcblk0p16",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mmcblk0p16",
+                               "kernel_label_offset": 192
+                       }
                }
        ]
 }
index 3bd967476c17c2f42f825bb40ff627b4f6c9cf58..748f2e25acdde0ee89d57fa3f8789ef920720d7d 100644 (file)
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd9",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd9",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd10"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                2
                        ],
-                       "mtd": "mtd11",
-                       "labelOffsetBytes": 192
+                       "firmware": {
+                               "kernel": "mtd11",
+                               "kernel_label_offset": 192,
+                               "rootfs": "mtd12"
+                       }
                }
        ]
 }
index 346891ae67b8eb48079ebf3e1932e0d9cac714bf..438422904eea64f25f85ff37fb6197ec866a8b9c 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd4",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd4",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd5"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd6",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd6",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd7"
+                       }
                }
        ]
 }
index 8ccb6c7500e512952d7bb122c86f0dfc628d262c..c8289f105a3a720c3b492cd354e5b2f84dcb332e 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd4",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd4",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd5"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd6",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd6",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd7"
+                       }
                }
        ]
 }
index 42bb14b7630f80ae6d2e7ac8a0faeeab821c9776..8c5b0575b9b854c7c5eb3ecff38e951e9928a400 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd4",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd4",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd5"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd6",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd6",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd7"
+                       }
                }
        ]
 }
index f585a542a431067bed2fdac7d6d51c545252b921..6749cc4c190e9615e71ca84f6544f4c2531150df 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd4",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd4",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd5"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd6",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd6",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd7"
+                       }
                }
        ]
 }
index f7dc5e59cc15ae92c8918cbec989cf8d2854b73f..9c69fffdd70dc5f72603dc821f7d258213ec26a6 100644 (file)
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd5",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mtd7",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
index 5ef1ccf7f69d6e90e11d8e8f6af721c352a1dbc3..84028d09f10409965aeeed89f5042dd7c8283920 100644 (file)
@@ -24,8 +24,9 @@
                                1,
                                "run nandboot"
                        ],
-                       "mtd": "mtd5",
-                       "labelOffsetBytes": null
+                       "firmware": {
+                               "rootfs": "mtd6"
+                       }
                },
                {
                        "number": 2,
@@ -33,8 +34,9 @@
                                2,
                                "run altnandboot"
                        ],
-                       "mtd": "mtd7",
-                       "labelOffsetBytes": null
+                       "firmware": {
+                               "rootfs": "mtd8"
+                       }
                }
        ]
 }
index 96a4b3d250b73c23bc2ae194017ad4243f069401..3c020a5eccab7c4d4202bf0aeb0c94b4e21eeaf6 100644 (file)
                        "param_values": [
                                0
                        ],
-                       "mtd": "mtd2",
-                       "labelOffsetBytes": null,
-                       "altMountOptions": {
-                               "mtdOffset": 0,
-                               "ubiVolume": 2
+                       "firmware": {
+                               "rootfs": "mtd2",
+                               "rootfs_ubi_volume": 2
                        }
                },
                {
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd3",
-                       "labelOffsetBytes": null,
-                       "altMountOptions": {
-                               "mtdOffset": 0,
-                               "ubiVolume": 2
+                       "firmware": {
+                               "rootfs": "mtd3",
+                               "rootfs_ubi_volume": 2
                        }
                }
        ]
index b1143d3842e89463c9e0682fdf65b3a40d967fd8..10b53126c8d6c96e60023222ae0bcaf7bcbdc7c6 100644 (file)
                        "param_values": [
                                0
                        ],
-                       "mtd": "mtd6",
-                       "labelOffsetBytes": null
+                       "firmware": {
+                               "rootfs": "mtd7"
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                1
                        ],
-                       "mtd": "mtd9",
-                       "labelOffsetBytes": null
+                       "firmware": {
+                               "rootfs": "mtd10"
+                       }
                }
        ]
 }
index 966945d26521065842c88dfefdf8860925bc34d2..fedf46fdb353b0aa1a2b88cd6480314aac6d0dc4 100644 (file)
                        "param_values": [
                                "ff"
                        ],
-                       "mtd": "mmcblk0p4",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mmcblk0p4",
+                               "kernel_label_offset": 32
+                       }
                },
                {
                        "number": 2,
                        "param_values": [
                                "01"
                        ],
-                       "mtd": "mmcblk0p7",
-                       "labelOffsetBytes": 32
+                       "firmware": {
+                               "kernel": "mmcblk0p7",
+                               "kernel_label_offset": 32
+                       }
                }
        ]
 }
diff --git a/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/zyxel-wsm20.json b/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/devices/zyxel-wsm20.json
new file mode 100644 (file)
index 0000000..5e7bd5e
--- /dev/null
@@ -0,0 +1,42 @@
+{
+       "device": {
+               "vendor": "ZyXEL",
+               "model": "WSM20",
+               "board": [
+                       "zyxel,wsm20"
+               ]
+       },
+       "commands": {
+               "params": [],
+               "get": null,
+               "set": null,
+               "save": null,
+               "dual_flag": {
+                       "mtd": "persist",
+                       "offset": 4
+               }
+       },
+       "partitions": [
+               {
+                       "number": 1,
+                       "param_values": [
+                               "01"
+                       ],
+                       "firmware": {
+                               "kernel": "mtd4",
+                               "kernel_label_offset": 32,
+                               "rootfs": "mtd5"
+                       }
+               },
+               {
+                       "number": 2,
+                       "param_values": [
+                               "02"
+                       ],
+                       "firmware": {
+                               "kernel": "mtd6",
+                               "kernel_label_offset": 32
+                       }
+               }
+       ]
+}
diff --git a/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/helpers/linksys-mx6200.sh b/applications/luci-app-advanced-reboot/root/usr/share/advanced-reboot/helpers/linksys-mx6200.sh
new file mode 100755 (executable)
index 0000000..0c0ceb2
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+# advanced-reboot helper for Linksys MX6200
+# Sets bootconfig partitions after fw_setenv boot_part has been called.
+# Usage: linksys-mx6200.sh <partition_number>
+
+. /lib/upgrade/platform.sh
+
+case "$1" in
+       1)
+               linksys_bootconfig_set_primaryboot "0:bootconfig" 0
+               linksys_bootconfig_set_primaryboot "0:bootconfig1" 0
+               ;;
+       2)
+               linksys_bootconfig_set_primaryboot "0:bootconfig" 1
+               linksys_bootconfig_set_primaryboot "0:bootconfig1" 1
+               ;;
+       *)
+               exit 1
+               ;;
+esac
index ed0b5d1bff110538d7e197ec36809ad81ee973ac..281d59c8d24996c4c880c4baf99ce2c3a0f8c089 100644 (file)
@@ -1,36 +1,18 @@
 {
        "luci-app-advanced-reboot": {
-               "description": "Grant file access for luci-app-advanced-reboot",
+               "description": "Grant access for luci-app-advanced-reboot",
                "read": {
                        "ubus": {
-                               "luci.advanced-reboot": [ "obtain_device_info", "boot_partition" ],
+                               "luci.advanced-reboot": [ "obtain_device_info" ]
+                       }
+               },
+               "write": {
+                       "ubus": {
+                               "luci.advanced-reboot": [ "boot_partition" ],
                                "system": [ "reboot" ]
                        },
                        "file": {
-                               "/bin/dd": [ "list", "exec" ],
-                               "/bin/grep": [ "list", "exec" ],
-                               "/bin/mount": [ "list", "exec" ],
-                               "/bin/sed": [ "list", "exec" ],
-                               "/bin/sh": [ "list", "exec" ],
-                               "/bin/umount": [ "list", "exec" ],
-                               "/bin/uname": [ "list", "exec" ],
-                               "/etc/os-release": [ "read" ],
-                               "/lib/functions.sh": [ "read" ],
-                               "/proc/mounts": [ "read" ],
-                               "/proc/version": [ "read" ],
-                               "/sbin/poweroff": [ "list", "exec" ],
-                               "/tmp/sysinfo/board_name": [ "read" ],
-                               "/usr/bin/hexdump": [ "list", "exec" ],
-                               "/usr/bin/logger": [ "list", "exec" ],
-                               "/usr/bin/printf": [ "list", "exec" ],
-                               "/usr/bin/tr": [ "list", "exec" ],
-                               "/usr/sbin/fw_printenv": [ "list", "exec" ],
-                               "/usr/sbin/fw_printsys": [ "list", "exec" ],
-                               "/usr/sbin/fw_setenv": [ "list", "exec" ],
-                               "/usr/sbin/fw_setsys": [ "list", "exec" ],
-                               "/usr/sbin/ubiblock": [ "list", "exec" ],
-                               "/usr/sbin/ubiattach": [ "list", "exec" ],
-                               "/usr/sbin/ubidetach": [ "list", "exec" ]
+                               "/sbin/poweroff": [ "list", "exec" ]
                        }
                }
        }
index 5c316ec69b513a1e896fe18b968f3428048db369..f903b6297ea68b43398f5e091e2409f5e162edf7 100644 (file)
  *             "params": ["<env1>", "<env2>"],
  *             "get": "fw_printenv",
  *             "set": "fw_setenv",
- *             "save": null
+ *             "save": null,
+ *             "set_helper": "/usr/share/advanced-reboot/helpers/<device>.sh",
+ *             "dual_flag": { "mtd": "<partition-name>", "offset": <int> }
  *     },
  *     "partitions": [
- *             { "number": 1, "param_values": [v1, w1], "mtd": "mtdX", "labelOffsetBytes": <int|null>, "altMountOptions": { "mtdOffset": <int>, "ubiVolume": <int> } },
- *             { "number": 2, "param_values": [v2, w2], "mtd": "mtdY", "labelOffsetBytes": <int|null> }
+ *             { "number": 1, "param_values": [v1, w1], "firmware": { "kernel": "mtdX", "rootfs": "mtdY", "kernel_label_offset": <int>, "rootfs_ubi_volume": <int> } },
+ *             { "number": 2, "param_values": [v2, w2], "firmware": { ... } }
  *     ]
  * }
  *
  * Notes:
- * - `altMountOptions` is optional; when absent, defaults are mtdOffset=1, ubiVolume=0.
  * - `param_values[i]` aligns with `commands.params[i]`.
+ * - `set_helper` is optional; when present, the script is called with the partition
+ *   number as its sole argument after the standard params have been set.
+ * - When `commands.params` is empty the dual-flag method is used: a single byte in
+ *   an MTD partition selects the boot slot. `commands.dual_flag` is optional and
+ *   customizes this: `mtd` is the partition name to locate via find_mtd_part
+ *   (default: "0:dual_flag"/"0:DUAL_FLAG"), `offset` is the byte offset of the flag
+ *   (default: 0). `param_values[0]` for each partition is the matching flag byte as
+ *   two lowercase hex digits (e.g. "01"). Example: ZyXEL WSM20 stores the boot
+ *   number at offset 4 of the "persist" partition (1 => first OS, 2 => second OS).
+ * - `firmware.kernel` is optional; the device to read raw label from at kernel_label_offset.
+ * - `firmware.rootfs` is optional; the device to UBI-mount for alt partition info.
+ * - `firmware.kernel_label_offset` is optional; byte offset for label probe (requires kernel).
+ * - `firmware.rootfs_ubi_volume` is optional; UBI volume index within rootfs (default 0).
  */
 
 "use strict";
 import * as fs from "fs";
 let DEVICES_DIR = "/usr/share/advanced-reboot/devices/";
 let DEVICES_JSON = "/usr/share/advanced-reboot/devices.json";
+/* Reset in alt_partition_mount() on each call; tracks whether the UBI device
+ * was already attached (e.g. by the kernel on IPQ5018) so alt_partition_unmount() knows
+ * not to detach it. */
+let alt_ubi_pre_attached = false;
 
 function file_exists(p) {
        try {
@@ -90,28 +108,28 @@ function find_device_info(romBoardName) {
 
        // Read one big JSON file instead of many small ones
        let txt = fs.readfile(DEVICES_JSON);
-       if (!txt) return null;
-
-       // devices.json : [ { device: {...} }, { device: {...} }, ... ]
-       let arr = json(txt);
-       if (!arr || type(arr) != "array") return null;
-
-       // Loop over each object in the array
-       for (let obj in arr) {
-               if (!obj || !obj.device) continue;
-
-               let boards = obj.device.board;
-               if (!boards) continue;
-
-               // normalise to array
-               if (type(boards) != "array") boards = ["" + boards];
-
-               for (let i = 0; i < length(boards); i++) {
-                       let s = "" + boards[i];
-                       if (s == rb) return obj;
+       if (txt) {
+               // devices.json : [ { device: {...} }, { device: {...} }, ... ]
+               let arr = json(txt);
+               if (arr && type(arr) == "array") {
+                       // Loop over each object in the array
+                       for (let obj in arr) {
+                               if (!obj || !obj.device) continue;
+
+                               let boards = obj.device.board;
+                               if (!boards) continue;
+
+                               // normalise to array
+                               if (type(boards) != "array") boards = ["" + boards];
+
+                               for (let i = 0; i < length(boards); i++) {
+                                       let s = "" + boards[i];
+                                       if (s == rb) return obj;
+                               }
+                       }
                }
        }
-       // Fallback: old schema with many small files
+       // Fallback: individual device JSON files
        let list = fs.glob(DEVICES_DIR + "*.json");
        for (let f in list) {
                let ftxt = fs.readfile(f);
@@ -188,6 +206,22 @@ function get_partition_info_current() {
        return get_volume_info("/");
 }
 
+/* Find the UBI device number for a given MTD index via sysfs.
+ *
+ * Checks /sys/class/ubi/ubiN/mtd_num entries to find a match.
+ *
+ * @param {Number|String} mtd_idx  MTD index to look up.
+ * @returns {Number|null} UBI device number, or null if not found.
+ */
+function find_ubi_dev_by_mtd(mtd_idx) {
+       let out = command(
+               "for d in /sys/class/ubi/ubi[0-9]*; do " +
+               "[ \"$(cat \"$d/mtd_num\" 2>/dev/null)\" = \"" + mtd_idx + "\" ] && " +
+               "echo \"${d##*/ubi}\" && break; done"
+       );
+       return out && length(out) ? int(out) : null;
+}
+
 /* Determine if an alternate partition can be mounted.
  *
  * Requires: ubiattach, ubiblock, and mount commands available.
@@ -229,8 +263,18 @@ function alt_partition_mount(op_ubi, ubi_vol) {
                op_ubi +
                " 2>/dev/null | sed -n 's/^UBI device number\\s*\\([0-9]*\\),.*$/\\1/p'"
        );
-       log("mounted alternative partition UBI device number: " + out);
        let dev = out && length(out) ? int(out) : null;
+       alt_ubi_pre_attached = false;
+       if (dev == null) {
+               /* ubiattach failed — MTD may already be attached (e.g. by kernel).
+                * Look up the existing UBI device number via sysfs. */
+               dev = find_ubi_dev_by_mtd(op_ubi);
+               if (dev != null) {
+                       alt_ubi_pre_attached = true;
+                       log("found pre-attached UBI device " + dev + " for mtd" + op_ubi);
+               }
+       }
+       log("alternative partition UBI device number: " + dev);
        if (dev == null) {
                command("ubidetach -m " + op_ubi + " >/dev/null 2>&1");
                return false;
@@ -247,6 +291,7 @@ function alt_partition_mount(op_ubi, ubi_vol) {
 /* Unmount and detach any UBI devices associated with a given MTD index.
  *
  * Also removes possible ubiblock nodes for the device.
+ * Skips ubidetach when the UBI was pre-attached (not attached by us).
  *
  * @param {Number|String} op_ubi   UBI mtd index previously attached.
  * @param {Number|String} ubi_vol  UBI volume index (ignored during cleanup).
@@ -254,25 +299,18 @@ function alt_partition_mount(op_ubi, ubi_vol) {
  * @sideeffects  umounts /var/alt_rom, ubidetach, removes ubiblock nodes.
  */
 function alt_partition_unmount(op_ubi, ubi_vol) {
-       let mtdCount = command(
-               'ubinfo | grep "Present UBI devices" | tr "," "\\n" | grep -c "ubi"'
-       );
-       mtdCount = int(mtdCount) || 10;
        command("umount /var/alt_rom");
-       for (let i = 0; i <= mtdCount; i++) {
-               let mtd = fs.readfile("/sys/devices/virtual/ubi/ubi" + i + "/mtd_num");
-               if (!mtd) break;
-               mtd = trim(mtd);
-               if (mtd == "" + op_ubi) {
-                       /* remove any ubiblock nodes on this ubi dev */
-                       for (let vid = 0; vid < 16; vid++)
-                               command(
-                                       "ubiblock --remove /dev/ubi" + i + "_" + vid + " >/dev/null 2>&1"
-                               );
+       let dev = find_ubi_dev_by_mtd(op_ubi);
+       if (dev != null) {
+               /* remove any ubiblock nodes on this ubi dev */
+               for (let vid = 0; vid < 16; vid++)
+                       command(
+                               "ubiblock --remove /dev/ubi" + dev + "_" + vid + " >/dev/null 2>&1"
+                       );
+               if (!alt_ubi_pre_attached)
                        command("ubidetach -m " + op_ubi + " >/dev/null 2>&1");
-                       command("rm -rf /var/alt_rom");
-               }
        }
+       command("rm -rf /var/alt_rom");
 }
 
 /* Get label/OS info from an alternative partition, using UBI mount flow.
@@ -331,8 +369,16 @@ function get_partition_info_fallback(mtd, offset, vendor) {
        return { label: label, os: os };
 }
 
-function read_dual_flag_mtd() {
-       for (let name in ["0:dual_flag", "0:DUAL_FLAG"]) {
+/* Locate the MTD block device holding the dual-boot flag.
+ *
+ * @param {String|null} mtd_name  Partition name to look up via find_mtd_part.
+ *                                When null, falls back to the conventional
+ *                                "0:dual_flag"/"0:DUAL_FLAG" names.
+ * @returns {String|null} Device path (e.g., /dev/mtdblockN) or null if not found.
+ */
+function read_dual_flag_mtd(mtd_name) {
+       let names = mtd_name ? ["" + mtd_name] : ["0:dual_flag", "0:DUAL_FLAG"];
+       for (let name in names) {
                let dev = command(". /lib/functions.sh; find_mtd_part " + shellquote(name));
                if (dev) return dev;
        }
@@ -341,15 +387,18 @@ function read_dual_flag_mtd() {
 
 /* Read the single-byte dual-boot flag value from a block device.
  *
- * @param {String} dev  Path to the block (e.g., /dev/mtdX) returned by find_mtd_part.
+ * @param {String} dev     Path to the block (e.g., /dev/mtdX) returned by find_mtd_part.
+ * @param {Number} offset  Byte offset of the flag (default 0).
  * @returns {String|null} Lowercase hex byte (e.g., "00", "01") or null on error.
  */
-function read_dual_flag_value(dev) {
+function read_dual_flag_value(dev, offset) {
        if (!dev || dev == "" || !file_exists(dev)) return null;
+       let off = offset != null ? int(offset) : 0;
        let r = command(
                "dd if=" +
                shellquote(dev) +
-               " bs=1 count=1 2>/dev/null | hexdump -n 1 -e '" +
+               " bs=1 count=1 skip=" + off +
+               " 2>/dev/null | hexdump -n 1 -e '" +
                '1/1 "%02x"' +
                "'"
        );
@@ -358,20 +407,36 @@ function read_dual_flag_value(dev) {
 
 /* Write a single-byte dual-boot flag value to a block device.
  *
- * @param {String} dev  Device path (e.g., /dev/mtdX).
- * @param {String} cur  Hex byte without prefix (e.g., "01"); will be written as \\x01.
+ * @param {String} dev     Device path (e.g., /dev/mtdX).
+ * @param {String} cur     Hex byte without prefix (e.g., "01"); written as \\x01.
+ * @param {Number} offset  Byte offset of the flag (default 0).
  * @returns {Boolean} true if write succeeded, false otherwise.
  * @sideeffects  Overwrites one byte on the target device.
  */
-function write_dual_flag_value(dev, cur) {
+function write_dual_flag_value(dev, cur, offset) {
+       let off = offset != null ? int(offset) : 0;
        cur = "\\x" + cur;
-       let rc = command(
-               "printf %b " +
-               shellquote(cur) +
-               " > " +
-               shellquote(dev) +
-               " 2>/dev/null; echo $?"
-       );
+       let rc;
+       if (off == 0) {
+               rc = command(
+                       "printf %b " +
+                       shellquote(cur) +
+                       " > " +
+                       shellquote(dev) +
+                       " 2>/dev/null; echo $?"
+               );
+       } else {
+               /* Patch a single byte in place without truncating the rest of the
+                * partition (matches upstream zyxel_mstc_upgrade_prepare). */
+               rc = command(
+                       "printf %b " +
+                       shellquote(cur) +
+                       " | dd of=" +
+                       shellquote(dev) +
+                       " bs=1 seek=" + off +
+                       " count=1 conv=notrunc 2>/dev/null; echo $?"
+               );
+       }
        return rc == "0";
 }
 
@@ -411,12 +476,13 @@ function obtain_device_info() {
                        push(curvals, v);
                }
        } else {
-               let df = read_dual_flag_mtd();
+               let dual = cmds.dual_flag ?? {};
+               let df = read_dual_flag_mtd(dual.mtd);
                let v = null;
                if (!df) return { error: "NO_DUAL_FLAG", rom_board_name: board };
                if (!file_exists(df))
                        return { error: "NO_DUAL_FLAG_BLOCK", rom_board_name: board };
-               v = read_dual_flag_value(df);
+               v = read_dual_flag_value(df, dual.offset);
                push(curvals, v);
        }
 
@@ -424,32 +490,31 @@ function obtain_device_info() {
        for (let i = 0; i < length(parts); i++) {
                let p = parts[i] ?? {};
                let num = p.number != null ? p.number : i;
-               let mtd = p.mtd ?? null;
+               let fw = p.firmware ?? {};
+               let kernel = fw.kernel ?? null;
+               let rootfs = fw.rootfs ?? null;
+               let label_offset = fw.kernel_label_offset ?? null;
+               let ubi_vol = fw.rootfs_ubi_volume ?? 0;
                let info = {};
-               let v;
 
                if ("" + curvals[0] == "" + p.param_values[0]) {
                        /* current partition */
                        active_num = num;
                        info = get_partition_info_current();
-               } else if (mtd && is_alt_mountable([mtd])) {
-                       /* attempt alt mount if we have mount options (or defaults) and tools */
-                       let amo = p.altMountOptions ?? {};
-                       let mtdOff = amo.mtdOffset == null ? 1 : int(amo.mtdOffset);
-                       let ubiVol = amo.ubiVolume == null ? 0 : int(amo.ubiVolume);
-                       let idx = mtd_index(mtd);
+               } else if (rootfs && is_alt_mountable([rootfs])) {
+                       /* attempt alt mount for rootfs */
+                       let idx = mtd_index(rootfs);
                        if (idx != null) {
-                               let op_ubi = idx + mtdOff;
-                               info = get_partition_info_alt(op_ubi, dev.vendor ?? null, ubiVol);
+                               info = get_partition_info_alt(idx, dev.vendor ?? null, ubi_vol);
                                if (info && info.label == null && info.os == null) info = {};
                        }
                }
-               /* raw-label fallback if needed */
-               if (mtd && p.labelOffsetBytes != null) {
+               /* raw-label fallback from kernel partition */
+               if (kernel && label_offset != null) {
                        if (!info?.label || !info?.os) {
                                let fb = get_partition_info_fallback(
-                                       mtd,
-                                       p.labelOffsetBytes,
+                                       kernel,
+                                       label_offset,
                                        dev.vendor
                                );
                                if (!info) {
@@ -468,7 +533,7 @@ function obtain_device_info() {
                        number: "" + num,
                        label: info ? info.label : null,
                        os: info ? info.os : null,
-                       mtd: mtd,
+                       mtd: kernel ?? rootfs,
                });
        }
 
@@ -573,15 +638,33 @@ function boot_partition(req) {
                        }
                        if (rc != "0") return { error: "ERR_SAVE_ENV", rom_board_name: board };
                }
+
+               /* Run device-specific helper if defined */
+               let helper = cmds.set_helper;
+               if (helper && helper != "" &&
+                       match(helper, /^\/usr\/share\/advanced-reboot\/helpers\/[^\/]+$/)) {
+                       let cmd = shellquote(helper) +
+                               " " + shellquote("" + number) +
+                               " 2>/dev/null; echo $?";
+                       log("boot_partition running helper: " + cmd);
+                       let rc = command(cmd);
+                       if (rc != "0")
+                               return {
+                                       error: "ERR_SET_HELPER",
+                                       args: [helper, "" + number],
+                                       rom_board_name: board,
+                               };
+               }
        } else {
-               let df = read_dual_flag_mtd();
+               let dual = cmds.dual_flag ?? {};
+               let df = read_dual_flag_mtd(dual.mtd);
                if (!df) return { error: "NO_DUAL_FLAG", rom_board_name: board };
                if (!file_exists(df))
                        return { error: "NO_DUAL_FLAG_BLOCK", rom_board_name: board };
 
                log("boot_partition setting dual flag: " + df + " to " + target.param_values[0]);
 
-               if (!write_dual_flag_value(df, target.param_values[0]))
+               if (!write_dual_flag_value(df, target.param_values[0], dual.offset))
                        return { error: "ERR_SET_DUAL_FLAG", args: [df], rom_board_name: board };
        }
        return {};
diff --git a/applications/luci-app-advanced-reboot/tests/01_error_handling/01_no_board_name b/applications/luci-app-advanced-reboot/tests/01_error_handling/01_no_board_name
new file mode 100644 (file)
index 0000000..12f00d0
--- /dev/null
@@ -0,0 +1,12 @@
+Test obtain_device_info when board_name file is missing.
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.obtain_device_info.call();
+printf("%J\n", result);
+-- End --
+
+-- Expect stdout --
+{ "error": "NO_BOARD_NAME" }
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/01_error_handling/02_no_device_match b/applications/luci-app-advanced-reboot/tests/01_error_handling/02_no_device_match
new file mode 100644 (file)
index 0000000..2eb36bf
--- /dev/null
@@ -0,0 +1,16 @@
+Test obtain_device_info when board name has no matching device config.
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+unknown,device
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.obtain_device_info.call();
+printf("%J\n", result);
+-- End --
+
+-- Expect stdout --
+{ "error": "NO_BOARD_NAME_MATCH", "rom_board_name": "unknown,device" }
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/01_error_handling/03_invalid_boot_arg b/applications/luci-app-advanced-reboot/tests/01_error_handling/03_invalid_boot_arg
new file mode 100644 (file)
index 0000000..70ff4c2
--- /dev/null
@@ -0,0 +1,28 @@
+Test boot_partition with missing or non-numeric number argument.
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+linksys,e7350
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+
+// No args at all
+let r1 = api.boot_partition.call();
+printf("no_args: %s\n", r1.error);
+
+// Empty object
+let r2 = api.boot_partition.call({ args: {} });
+printf("empty: %s\n", r2.error);
+
+// Non-numeric
+let r3 = api.boot_partition.call({ args: { number: "abc" } });
+printf("abc: %s\n", r3.error);
+-- End --
+
+-- Expect stdout --
+no_args: INVALID_ARG
+empty: INVALID_ARG
+abc: INVALID_ARG
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/01_error_handling/04_partition_not_found b/applications/luci-app-advanced-reboot/tests/01_error_handling/04_partition_not_found
new file mode 100644 (file)
index 0000000..d7781ff
--- /dev/null
@@ -0,0 +1,20 @@
+Test boot_partition with a valid numeric partition that does not exist on the device.
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+linksys,e7350
+-- End --
+
+-- File fs/popen~fw_printenv_-n_bootimage_2_dev_null.txt --
+1
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.boot_partition.call({ args: { number: "9" } });
+printf("%J\n", result);
+-- End --
+
+-- Expect stdout --
+{ "error": "PARTITION_NOT_FOUND", "args": [ "9" ] }
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/02_obtain_device_info/01_linksys_e7350 b/applications/luci-app-advanced-reboot/tests/02_obtain_device_info/01_linksys_e7350
new file mode 100644 (file)
index 0000000..ba248e3
--- /dev/null
@@ -0,0 +1,57 @@
+Test obtain_device_info for Linksys E7350 with partition 1 active.
+Uses fw_printenv params and kernel label fallback for alt partition.
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+linksys,e7350
+-- End --
+
+-- File fs/popen~fw_printenv_-n_bootimage_2_dev_null.txt --
+1
+-- End --
+
+-- File fs/popen~_etc_os-release_2_dev_null_echo_OPENWRT_RELEASE_2_dev_null.txt --
+OpenWrt SNAPSHOT r28105-abcdef0123
+-- End --
+
+-- File fs/open~_proc_version.txt --
+Linux version 6.6.63 (builder@buildhost) (aarch64-openwrt-linux-musl-gcc) #0
+-- End --
+
+-- File fs/popen~dd_if_dev_mtd3_bs_1_skip_192_count_64.txt --
+Linux version 6.6.63 OpenWrt
+-- End --
+
+-- File fs/popen~dd_if_dev_mtd6_bs_1_skip_192_count_64.txt --
+Linux version 6.6.50 OpenWrt
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.obtain_device_info.call();
+
+// Verify device info
+printf("vendor: %s\n", result.device.vendor);
+printf("model: %s\n", result.device.model);
+printf("board: %s\n", result.device.board);
+printf("active: %s\n", result.device.partition_active);
+printf("partitions: %d\n", length(result.partitions));
+
+// Partition 1 (current)
+let p1 = result.partitions[0];
+printf("p1: number=%s label=%s os=%s\n", p1.number, p1.label, p1.os);
+
+// Partition 2 (alternate — label from dd fallback)
+let p2 = result.partitions[1];
+printf("p2: number=%s label=%s os=%s\n", p2.number, p2.label, p2.os);
+-- End --
+
+-- Expect stdout --
+vendor: Linksys
+model: E7350
+board: linksys,e7350
+active: 1
+partitions: 2
+p1: number=1 label=OpenWrt SNAPSHOT r28105-abcdef0123 os=6.6.63
+p2: number=2 label=OpenWrt os=6.6.50
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/02_obtain_device_info/02_zyxel_nbg6817 b/applications/luci-app-advanced-reboot/tests/02_obtain_device_info/02_zyxel_nbg6817
new file mode 100644 (file)
index 0000000..475b7f8
--- /dev/null
@@ -0,0 +1,58 @@
+Test obtain_device_info for ZyXEL NBG6817 using the dual-flag method.
+Partition 1 active (dual flag reads "ff" which matches partition 1's param_values).
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+zyxel,nbg6817
+-- End --
+
+-- File fs/popen~_lib_functions_sh_find_mtd_part_0_dual_flag_.txt --
+/dev/mtdblock12
+-- End --
+
+-- File fs/popen~dd_if_dev_mtdblock12_bs_1_count_1_skip_0_2_dev_null_hexdump_-n_1_-e_1_1_02x_.txt --
+ff
+-- End --
+
+-- File fs/popen~_etc_os-release_2_dev_null_echo_OPENWRT_RELEASE_2_dev_null.txt --
+OpenWrt 24.10.0 r28427-6df0e3d02a
+-- End --
+
+-- File fs/open~_proc_version.txt --
+Linux version 6.6.63 (builder@buildhost) (arm-openwrt-linux-muslgnueabi-gcc) #0
+-- End --
+
+-- File fs/popen~dd_if_dev_mmcblk0p4_bs_1_skip_32_count_64.txt --
+Linux version 6.6.63 OpenWrt
+-- End --
+
+-- File fs/popen~dd_if_dev_mmcblk0p7_bs_1_skip_32_count_64.txt --
+Linux-6.6.50 ZyXEL
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.obtain_device_info.call();
+
+printf("vendor: %s\n", result.device.vendor);
+printf("model: %s\n", result.device.model);
+printf("board: %s\n", result.device.board);
+printf("active: %s\n", result.device.partition_active);
+printf("partitions: %d\n", length(result.partitions));
+
+let p1 = result.partitions[0];
+printf("p1: number=%s label=%s os=%s\n", p1.number, p1.label, p1.os);
+
+let p2 = result.partitions[1];
+printf("p2: number=%s label=%s os=%s\n", p2.number, p2.label, p2.os);
+-- End --
+
+-- Expect stdout --
+vendor: ZyXEL
+model: NBG6817
+board: zyxel,nbg6817
+active: 1
+partitions: 2
+p1: number=1 label=OpenWrt 24.10.0 r28427-6df0e3d02a os=6.6.63
+p2: number=2 label=ZyXEL os=6.6.50
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/02_obtain_device_info/03_zyxel_wsm20 b/applications/luci-app-advanced-reboot/tests/02_obtain_device_info/03_zyxel_wsm20
new file mode 100644 (file)
index 0000000..9df51b2
--- /dev/null
@@ -0,0 +1,55 @@
+Test obtain_device_info for ZyXEL WSM20 using the dual-flag method with a
+custom MTD partition ("persist") and byte offset (4).
+Partition 1 active (the flag byte at offset 4 reads "01", matching partition 1).
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+zyxel,wsm20
+-- End --
+
+-- File fs/popen~_lib_functions_sh_find_mtd_part_persist_.txt --
+/dev/mtdblock7
+-- End --
+
+-- File fs/popen~dd_if_dev_mtdblock7_bs_1_count_1_skip_4_2_dev_null_hexdump_-n_1_-e_1_1_02x_.txt --
+01
+-- End --
+
+-- File fs/popen~_etc_os-release_2_dev_null_echo_OPENWRT_RELEASE_2_dev_null.txt --
+OpenWrt 25.12.2 r32802-f505120278
+-- End --
+
+-- File fs/open~_proc_version.txt --
+Linux version 6.12.74 (builder@buildhost) (mipsel-openwrt-linux-musl-gcc) #0
+-- End --
+
+-- File fs/popen~dd_if_dev_mtd6_bs_1_skip_32_count_64.txt --
+Linux-6.6.50 ZyXEL
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.obtain_device_info.call();
+
+printf("vendor: %s\n", result.device.vendor);
+printf("model: %s\n", result.device.model);
+printf("board: %s\n", result.device.board);
+printf("active: %s\n", result.device.partition_active);
+printf("partitions: %d\n", length(result.partitions));
+
+let p1 = result.partitions[0];
+printf("p1: number=%s label=%s os=%s\n", p1.number, p1.label, p1.os);
+
+let p2 = result.partitions[1];
+printf("p2: number=%s label=%s os=%s\n", p2.number, p2.label, p2.os);
+-- End --
+
+-- Expect stdout --
+vendor: ZyXEL
+model: WSM20
+board: zyxel,wsm20
+active: 1
+partitions: 2
+p1: number=1 label=OpenWrt 25.12.2 r32802-f505120278 os=6.12.74
+p2: number=2 label=ZyXEL os=6.6.50
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/03_boot_partition/01_linksys_e7350_switch b/applications/luci-app-advanced-reboot/tests/03_boot_partition/01_linksys_e7350_switch
new file mode 100644 (file)
index 0000000..acb8809
--- /dev/null
@@ -0,0 +1,24 @@
+Test boot_partition for Linksys E7350: switch to partition 2 (success).
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+linksys,e7350
+-- End --
+
+-- File fs/popen~fw_printenv_-n_bootimage_2_dev_null.txt --
+1
+-- End --
+
+-- File fs/popen~fw_setenv_bootimage_2_2_dev_null_echo_.txt --
+0
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.boot_partition.call({ args: { number: "2" } });
+printf("%J\n", result);
+-- End --
+
+-- Expect stdout --
+{ }
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/03_boot_partition/02_set_env_failure b/applications/luci-app-advanced-reboot/tests/03_boot_partition/02_set_env_failure
new file mode 100644 (file)
index 0000000..6cc906d
--- /dev/null
@@ -0,0 +1,26 @@
+Test boot_partition when fw_setenv fails (non-zero exit code).
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+linksys,e7350
+-- End --
+
+-- File fs/popen~fw_printenv_-n_bootimage_2_dev_null.txt --
+1
+-- End --
+
+-- File fs/popen~fw_setenv_bootimage_2_2_dev_null_echo_.txt --
+1
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.boot_partition.call({ args: { number: "2" } });
+printf("error: %s\n", result.error);
+printf("args: %J\n", result.args);
+-- End --
+
+-- Expect stdout --
+error: ERR_SET_ENV
+args: [ "bootimage", "2" ]
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/03_boot_partition/03_zyxel_wsm20_switch b/applications/luci-app-advanced-reboot/tests/03_boot_partition/03_zyxel_wsm20_switch
new file mode 100644 (file)
index 0000000..1e96c23
--- /dev/null
@@ -0,0 +1,25 @@
+Test boot_partition for ZyXEL WSM20: switch to partition 2 (success).
+Writes the boot-number byte "02" at offset 4 of the "persist" partition.
+
+-- File fs/open~_tmp_sysinfo_board_name.txt --
+zyxel,wsm20
+-- End --
+
+-- File fs/popen~_lib_functions_sh_find_mtd_part_persist_.txt --
+/dev/mtdblock7
+-- End --
+
+-- File fs/popen~printf_b_x02_dd_of_dev_mtdblock7_bs_1_seek_4_count_1_conv_notrunc_2_dev_null_echo_.txt --
+0
+-- End --
+
+-- Testcase --
+let ar = require('advanced_reboot');
+let api = ar["luci.advanced-reboot"];
+let result = api.boot_partition.call({ args: { number: "2" } });
+printf("%J\n", result);
+-- End --
+
+-- Expect stdout --
+{ }
+-- End --
diff --git a/applications/luci-app-advanced-reboot/tests/lib/mocklib.uc b/applications/luci-app-advanced-reboot/tests/lib/mocklib.uc
new file mode 100644 (file)
index 0000000..152160b
--- /dev/null
@@ -0,0 +1,248 @@
+/* strict mode compliance: ensure that global variables are defined */
+if (!exists(global, 'REQUIRE_SEARCH_PATH'))
+       global.REQUIRE_SEARCH_PATH = [];
+
+if (!exists(global, 'MOCK_SEARCH_PATH'))
+       global.MOCK_SEARCH_PATH = [];
+
+if (!exists(global, 'TRACE_CALLS'))
+       global.TRACE_CALLS = null;
+
+let _fs = require("fs");
+
+/* Force reloading fs module on next require */
+delete global.modules.fs;
+
+let _log = (level, fmt, ...args) => {
+       let color, prefix;
+
+       switch (level) {
+       case 'info':
+               color = 34;
+               prefix = '!';
+               break;
+
+       case 'warn':
+               color = 33;
+               prefix = 'W';
+               break;
+
+       case 'error':
+               color = 31;
+               prefix = 'E';
+               break;
+
+       default:
+               color = 0;
+               prefix = 'I';
+       }
+
+       let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt);
+       warn(replace(sprintf(f, ...args), "\n", "\n    "), "\n");
+};
+
+let read_data_file = (path) => {
+       for (let dir in MOCK_SEARCH_PATH) {
+               let fd = _fs.open(dir + '/' + path, "r");
+
+               if (fd) {
+                       let data = fd.read("all");
+                       fd.close();
+
+                       return data;
+               }
+       }
+
+       return null;
+};
+
+let read_json_file = (path) => {
+       let data = read_data_file(path);
+
+       if (data != null)  {
+               try {
+                       return json(data);
+               }
+               catch (e) {
+                       _log('error', "Unable to parse JSON data in %s: %s", path, e);
+
+                       return NaN;
+               }
+       }
+
+       return null;
+};
+
+let format_json = (data) => {
+       let rv;
+
+       let format_value = (value) => {
+               switch (type(value)) {
+               case "object":
+                       return sprintf("{ /* %d keys */ }", length(value));
+
+               case "array":
+                       return sprintf("[ /* %d items */ ]", length(value));
+
+               case "string":
+                       if (length(value) > 64)
+                               value = substr(value, 0, 64) + "...";
+
+                       /* fall through */
+                       return sprintf("%J", value);
+
+               default:
+                       return sprintf("%J", value);
+               }
+       };
+
+       switch (type(data)) {
+       case "object":
+               rv = "{";
+
+               let k = sort(keys(data));
+
+               for (let i, n in k)
+                       rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n]));
+
+               rv += " }";
+               break;
+
+       case "array":
+               rv = "[";
+
+               for (let i, v in data)
+                       rv += (i ? "," : "") + " " + format_value(v);
+
+               rv += " ]";
+               break;
+
+       default:
+               rv = format_value(data);
+       }
+
+       return rv;
+};
+
+let trace_call = (ns, func, args) => {
+       let msg = "[call] " +
+               (ns ? ns + "." : "") +
+               func;
+
+       for (let k, v in args) {
+               msg += ' ' + k + ' <';
+
+               switch (type(v)) {
+               case "array":
+               case "object":
+                       msg += format_json(v);
+                       break;
+
+               default:
+                       msg += v;
+               }
+
+               msg += '>';
+       }
+
+       switch (TRACE_CALLS) {
+       case '1':
+       case 'stdout':
+               _fs.stdout.write(msg + "\n");
+               break;
+
+       case 'stderr':
+               _fs.stderr.write(msg + "\n");
+               break;
+       }
+};
+
+/* Captured file contents from mock writefile */
+let _captured = {};
+
+/* Prepend mocklib to REQUIRE_SEARCH_PATH */
+for (let pattern in REQUIRE_SEARCH_PATH) {
+       if (!match(pattern, /\*\.uc$/))
+               continue;
+
+       let path = replace(pattern, /\*/, 'mocklib'),
+           stat = _fs.stat(path);
+
+       if (!stat || stat.type != 'file')
+               continue;
+
+       if (!length(global.MOCK_SEARCH_PATH))
+               global.MOCK_SEARCH_PATH = [ replace(path, /mocklib\.uc$/, '../mocks') ];
+
+       unshift(REQUIRE_SEARCH_PATH, replace(path, /mocklib\.uc$/, 'mocklib/*.uc'));
+       break;
+}
+
+if (!length(global.MOCK_SEARCH_PATH))
+       global.MOCK_SEARCH_PATH = [ './mocks' ];
+
+/* Register global mocklib namespace */
+global.mocklib = {
+       require: function(module) {
+               let path, res, ex;
+
+               if (type(REQUIRE_SEARCH_PATH) == "array" && index(REQUIRE_SEARCH_PATH[0], 'mocklib/*.uc') != -1)
+                       path = shift(REQUIRE_SEARCH_PATH);
+
+               try {
+                       res = require(module);
+               }
+               catch (e) {
+                       ex = e;
+               }
+
+               if (path)
+                       unshift(REQUIRE_SEARCH_PATH, path);
+
+               if (ex)
+                       die(ex);
+
+               return res;
+       },
+
+       I: (...args) => _log('info', ...args),
+       N: (...args) => _log('notice', ...args),
+       W: (...args) => _log('warn', ...args),
+       E: (...args) => _log('error', ...args),
+
+       format_json,
+       read_data_file,
+       read_json_file,
+       trace_call,
+
+       capture: (path, data) => { _captured[path] = data; },
+       read_captured: (path) => _captured[path],
+       has_captured: (path) => exists(_captured, path),
+       delete_captured: (path) => { delete _captured[path]; },
+};
+
+/* Override stdlib functions */
+global.system = function(argv, timeout) {
+       trace_call(null, "system", { command: argv, timeout });
+
+       return 0;
+};
+
+global.time = function() {
+       trace_call(null, "time");
+
+       return 1615382640;
+};
+
+global.print = ((_orig) => function(...args) {
+       if (length(args) == 1 && type(args[0]) in ["array", "object"])
+               printf("%s\n", format_json(args[0]));
+       else
+               _orig(...args);
+})(global.print);
+
+global.getenv = function(key) {
+       return null;
+};
+
+return global.mocklib;
diff --git a/applications/luci-app-advanced-reboot/tests/lib/mocklib/fs.uc b/applications/luci-app-advanced-reboot/tests/lib/mocklib/fs.uc
new file mode 100644 (file)
index 0000000..3c531b9
--- /dev/null
@@ -0,0 +1,124 @@
+let mocklib = global.mocklib; // ucode-lsp disable
+
+return {
+       readfile: function(path, limit) {
+               /* Check captured content first (from mock writefile) */
+               if (mocklib.has_captured(path)) {
+                       mocklib.trace_call("fs", "readfile", { path, limit, source: "captured" });
+                       let data = mocklib.read_captured(path);
+                       return limit ? substr(data, 0, limit) : data;
+               }
+
+               let file = sprintf("fs/open~%s.txt", replace(path, /[^A-Za-z0-9_-]+/g, '_')),
+                   mock = mocklib.read_data_file(file);
+
+               if (mock == null) {
+                       /* Silently return null for missing fixtures */
+                       return null;
+               }
+
+               mocklib.trace_call("fs", "readfile", { path, limit });
+
+               return limit ? substr(mock, 0, limit) : mock;
+       },
+
+       writefile: function(path, data) {
+               mocklib.capture(path, data);
+               mocklib.trace_call("fs", "writefile", { path, length: length(data) });
+
+               return length(data);
+       },
+
+       popen: (cmdline, mode) => {
+               let read = (!mode || index(mode, "r") != -1),
+                   path = sprintf("fs/popen~%s.txt", replace(cmdline, /[^A-Za-z0-9_-]+/g, '_')),
+                   mock = mocklib.read_data_file(path);
+
+               if (read && mock == null) {
+                       /* Silently return empty-read handle for missing fixtures */
+                       return {
+                               read: function(amount) { return ''; },
+                               write: function() {},
+                               close: function() {},
+                               error: function() { return null; }
+                       };
+               }
+
+               mocklib.trace_call("fs", "popen", { cmdline, mode });
+
+               return {
+                       read: function(amount) {
+                               let rv;
+
+                               switch (amount) {
+                               case "all":
+                                       rv = mock;
+                                       mock = "";
+                                       break;
+
+                               case "line":
+                                       let i = index(mock, "\n");
+                                       i = (i > -1) ? i + 1 : length(mock);
+                                       rv = substr(mock, 0, i);
+                                       mock = substr(mock, i);
+                                       break;
+
+                               default:
+                                       let n = +amount;
+                                       n = (n > 0) ? n : 0;
+                                       rv = substr(mock, 0, n);
+                                       mock = substr(mock, n);
+                                       break;
+                               }
+
+                               return rv;
+                       },
+
+                       write: function() {},
+                       close: function() {},
+
+                       error: function() {
+                               return null;
+                       }
+               };
+       },
+
+       stat: function(path) {
+               /* Check captured content first */
+               if (mocklib.has_captured(path)) {
+                       mocklib.trace_call("fs", "stat", { path, source: "captured" });
+                       return { type: "file" };
+               }
+
+               let file = sprintf("fs/stat~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')),
+                   mock = mocklib.read_json_file(file);
+
+               if (!mock || mock != mock) {
+                       /* No fixture: fall back to reasonable defaults */
+                       if (match(path, /\/$/))
+                               mock = { type: "directory" };
+                       else
+                               mock = { type: "file" };
+               }
+
+               mocklib.trace_call("fs", "stat", { path });
+
+               return mock;
+       },
+
+       glob: function(pattern) {
+               mocklib.trace_call("fs", "glob", { pattern });
+
+               /* Return empty array — tests use devices.json fixture directly */
+               return [];
+       },
+
+       unlink: function(path) {
+               mocklib.delete_captured(path);
+               mocklib.trace_call("fs", "unlink", { path });
+
+               return true;
+       },
+
+       error: () => "Unspecified error"
+};
diff --git a/applications/luci-app-advanced-reboot/tests/mocks/fs/open~_usr_share_advanced-reboot_devices_json.txt b/applications/luci-app-advanced-reboot/tests/mocks/fs/open~_usr_share_advanced-reboot_devices_json.txt
new file mode 100644 (file)
index 0000000..f2d4d8f
--- /dev/null
@@ -0,0 +1,102 @@
+[
+       {
+               "device": {
+                       "vendor": "Linksys",
+                       "model": "E7350",
+                       "board": ["linksys,e7350"]
+               },
+               "commands": {
+                       "params": ["bootimage"],
+                       "get": "fw_printenv",
+                       "set": "fw_setenv",
+                       "save": null
+               },
+               "partitions": [
+                       {
+                               "number": 1,
+                               "param_values": [1],
+                               "firmware": {
+                                       "kernel": "mtd3",
+                                       "kernel_label_offset": 192,
+                                       "rootfs": "mtd4"
+                               }
+                       },
+                       {
+                               "number": 2,
+                               "param_values": [2],
+                               "firmware": {
+                                       "kernel": "mtd6",
+                                       "kernel_label_offset": 192,
+                                       "rootfs": "mtd7"
+                               }
+                       }
+               ]
+       },
+       {
+               "device": {
+                       "vendor": "ZyXEL",
+                       "model": "NBG6817",
+                       "board": ["zyxel,nbg6817"]
+               },
+               "commands": {
+                       "params": [],
+                       "get": null,
+                       "set": null,
+                       "save": null
+               },
+               "partitions": [
+                       {
+                               "number": 1,
+                               "param_values": ["ff"],
+                               "firmware": {
+                                       "kernel": "mmcblk0p4",
+                                       "kernel_label_offset": 32
+                               }
+                       },
+                       {
+                               "number": 2,
+                               "param_values": ["01"],
+                               "firmware": {
+                                       "kernel": "mmcblk0p7",
+                                       "kernel_label_offset": 32
+                               }
+                       }
+               ]
+       },
+       {
+               "device": {
+                       "vendor": "ZyXEL",
+                       "model": "WSM20",
+                       "board": ["zyxel,wsm20"]
+               },
+               "commands": {
+                       "params": [],
+                       "get": null,
+                       "set": null,
+                       "save": null,
+                       "dual_flag": {
+                               "mtd": "persist",
+                               "offset": 4
+                       }
+               },
+               "partitions": [
+                       {
+                               "number": 1,
+                               "param_values": ["01"],
+                               "firmware": {
+                                       "kernel": "mtd4",
+                                       "kernel_label_offset": 32,
+                                       "rootfs": "mtd5"
+                               }
+                       },
+                       {
+                               "number": 2,
+                               "param_values": ["02"],
+                               "firmware": {
+                                       "kernel": "mtd6",
+                                       "kernel_label_offset": 32
+                               }
+                       }
+               ]
+       }
+]
diff --git a/applications/luci-app-advanced-reboot/tests/run_tests.sh b/applications/luci-app-advanced-reboot/tests/run_tests.sh
new file mode 100644 (file)
index 0000000..b13ae8a
--- /dev/null
@@ -0,0 +1,344 @@
+#!/usr/bin/env bash
+# Functional test runner for luci-app-advanced-reboot.
+#
+# Tests:
+#   00: Device JSON validation (structure, required fields)
+#   01: Error handling (missing board, unknown device, bad args)
+#   02: obtain_device_info (fw_printenv, dual-flag)
+#   03: boot_partition (switch, failure)
+#
+# Usage: cd source.mossdef.org/luci-app-advanced-reboot && bash tests/run_tests.sh
+
+set -o pipefail
+
+line='........................................'
+
+# ── Patch phase ─────────────────────────────────────────────────────
+# Convert ES namespace import to require() so the mock framework can
+# intercept the fs module through the module cache.
+
+patch_dir="/tmp/ar_test_modules.$$"
+mkdir -p "$patch_dir"
+
+sed \
+       -e 's|import \* as fs from "fs";|let fs = require("fs");|' \
+       ./root/usr/share/rpcd/ucode/luci.advanced-reboot > "$patch_dir/advanced_reboot.uc"
+
+trap "rm -rf '$patch_dir'" EXIT
+
+# Search paths: patched module first, then tests/lib (for mocklib)
+ucode="ucode -S -L$patch_dir -L./tests/lib"
+
+# ── Device JSON validation ──────────────────────────────────────────
+
+DEVICES_DIR="./root/usr/share/advanced-reboot/devices"
+
+n_tests=0
+n_fails=0
+
+pass() {
+       printf "  PASS: %s\n" "$1"
+}
+fail() {
+       printf "  FAIL: %s\n" "$1"
+       [ -n "$2" ] && printf "        %s\n" "$2"
+       n_fails=$((n_fails + 1))
+}
+
+if [ -d "$DEVICES_DIR" ]; then
+       printf "\n##\n## 00: Device JSON validation\n##\n\n"
+
+       device_count=0
+       for f in "$DEVICES_DIR"/*.json; do
+               [ -f "$f" ] || continue
+               device_count=$((device_count + 1))
+               base="$(basename "$f")"
+
+               # Valid JSON
+               n_tests=$((n_tests + 1))
+               if ! jq empty "$f" 2>/dev/null; then
+                       fail "$base: invalid JSON"
+                       continue
+               fi
+               pass "$base: valid JSON"
+
+               # Required: device.vendor
+               n_tests=$((n_tests + 1))
+               vendor="$(jq -r '.device.vendor // empty' "$f")"
+               if [ -n "$vendor" ]; then
+                       pass "$base: has device.vendor '$vendor'"
+               else
+                       fail "$base: missing device.vendor"
+               fi
+
+               # Required: device.model
+               n_tests=$((n_tests + 1))
+               model="$(jq -r '.device.model // empty' "$f")"
+               if [ -n "$model" ]; then
+                       pass "$base: has device.model '$model'"
+               else
+                       fail "$base: missing device.model"
+               fi
+
+               # Required: device.board (must be array)
+               n_tests=$((n_tests + 1))
+               board_type="$(jq -r '.device.board | type' "$f")"
+               if [ "$board_type" = "array" ]; then
+                       board_len="$(jq '.device.board | length' "$f")"
+                       if [ "$board_len" -gt 0 ]; then
+                               pass "$base: device.board is array with $board_len entries"
+                       else
+                               fail "$base: device.board is empty array"
+                       fi
+               else
+                       fail "$base: device.board is not an array (got $board_type)"
+               fi
+
+               # Required: partitions (must be array with >= 2 entries)
+               n_tests=$((n_tests + 1))
+               part_type="$(jq -r '.partitions | type' "$f")"
+               if [ "$part_type" = "array" ]; then
+                       part_len="$(jq '.partitions | length' "$f")"
+                       if [ "$part_len" -ge 2 ]; then
+                               pass "$base: has $part_len partitions"
+                       else
+                               fail "$base: need >= 2 partitions (got $part_len)"
+                       fi
+               else
+                       fail "$base: partitions is not an array"
+               fi
+
+               # Each partition must have number and param_values
+               for i in $(seq 0 $((part_len - 1))); do
+                       n_tests=$((n_tests + 1))
+                       has_num="$(jq ".partitions[$i] | has(\"number\")" "$f")"
+                       has_pv="$(jq ".partitions[$i] | has(\"param_values\")" "$f")"
+                       if [ "$has_num" = "true" ] && [ "$has_pv" = "true" ]; then
+                               pass "$base: partition $i has number and param_values"
+                       else
+                               fail "$base: partition $i missing number ($has_num) or param_values ($has_pv)"
+                       fi
+               done
+       done
+
+       printf "\n  Validated %d device files\n" "$device_count"
+fi
+
+# ── Mock-based functional tests ────────────────────────────────────
+
+extract_sections() {
+       local file=$1
+       local dir=$2
+       local count=0
+       local tag line outfile
+
+       while IFS= read -r line; do
+               case "$line" in
+                       "-- Testcase --")
+                               tag="test"
+                               count=$((count + 1))
+                               outfile=$(printf "%s/%03d.in" "$dir" $count)
+                               printf "" > "$outfile"
+                       ;;
+                       "-- Environment --")
+                               tag="env"
+                               count=$((count + 1))
+                               outfile=$(printf "%s/%03d.env" "$dir" $count)
+                               printf "" > "$outfile"
+                       ;;
+                       "-- Expect stdout --"|"-- Expect stderr --"|"-- Expect exitcode --")
+                               tag="${line#-- Expect }"
+                               tag="${tag% --}"
+                               count=$((count + 1))
+                               outfile=$(printf "%s/%03d.%s" "$dir" $count "$tag")
+                               printf "" > "$outfile"
+                       ;;
+                       "-- File "*" --")
+                               tag="file"
+                               outfile="${line#-- File }"
+                               outfile="$(echo "${outfile% --}" | xargs)"
+                               outfile="$dir/files$(readlink -m "/${outfile:-file}")"
+                               mkdir -p "$(dirname "$outfile")"
+                               printf "" > "$outfile"
+                       ;;
+                       "-- End --")
+                               tag=""
+                               outfile=""
+                       ;;
+                       *)
+                               if [ -n "$tag" ]; then
+                                       printf "%s\\n" "$line" >> "$outfile"
+                               fi
+                       ;;
+               esac
+       done < "$file"
+
+       return $(ls -l "$dir/"*.in 2>/dev/null | wc -l)
+}
+
+run_testcase() {
+       local num=$1
+       local dir=$2
+       local in=$3
+       local env=$4
+       local out=$5
+       local err=$6
+       local code=$7
+       local fail=0
+
+       $ucode \
+               -D MOCK_SEARCH_PATH='["'"$dir"'/files", "./tests/mocks"]' \
+               ${env:+-F "$env"} \
+               -l mocklib \
+               - <"$in" >"$dir/res.out" 2>"$dir/res.err"
+
+       printf "%d\n" $? > "$dir/res.code"
+
+       touch "$dir/empty"
+
+       if ! cmp -s "$dir/res.err" "${err:-$dir/empty}"; then
+               [ $fail = 0 ] && printf "!\n"
+               printf "Testcase #%d: Expected stderr did not match:\n" $num
+               diff -u --color=always --label="Expected stderr" --label="Resulting stderr" "${err:-$dir/empty}" "$dir/res.err"
+               printf -- "---\n"
+               fail=1
+       fi
+
+       if ! cmp -s "$dir/res.out" "${out:-$dir/empty}"; then
+               [ $fail = 0 ] && printf "!\n"
+               printf "Testcase #%d: Expected stdout did not match:\n" $num
+               diff -u --color=always --label="Expected stdout" --label="Resulting stdout" "${out:-$dir/empty}" "$dir/res.out"
+               printf -- "---\n"
+               fail=1
+       fi
+
+       if [ -n "$code" ] && ! cmp -s "$dir/res.code" "$code"; then
+               [ $fail = 0 ] && printf "!\n"
+               printf "Testcase #%d: Expected exit code did not match:\n" $num
+               diff -u --color=always --label="Expected code" --label="Resulting code" "$code" "$dir/res.code"
+               printf -- "---\n"
+               fail=1
+       fi
+
+       return $fail
+}
+
+run_test() {
+       local file=$1
+       local name=${file##*/}
+       local res ecode eout eerr ein eenv tests
+       local testcase_first=0 failed=0 count=0
+
+       printf "%s %s " "$name" "${line:${#name}}"
+
+       mkdir "/tmp/test.$$"
+
+       extract_sections "$file" "/tmp/test.$$"
+       tests=$?
+
+       [ -f "/tmp/test.$$/001.in" ] && testcase_first=1
+
+       for res in "/tmp/test.$$/"[0-9]*; do
+               case "$res" in
+                       *.in)
+                               count=$((count + 1))
+
+                               if [ $testcase_first = 1 ]; then
+                                       # Flush previous test
+                                       if [ -n "$ein" ]; then
+                                               run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
+
+                                               eout=""
+                                               eerr=""
+                                               ecode=""
+                                               eenv=""
+                                       fi
+
+                                       ein=$res
+                               else
+                                       run_testcase $count "/tmp/test.$$" "$res" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
+
+                                       eout=""
+                                       eerr=""
+                                       ecode=""
+                                       eenv=""
+                               fi
+
+                       ;;
+                       *.env) eenv=$res ;;
+                       *.stdout) eout=$res ;;
+                       *.stderr) eerr=$res ;;
+                       *.exitcode) ecode=$res ;;
+               esac
+       done
+
+       # Flush last test
+       if [ $testcase_first = 1 ] && [ -n "$ein" ]; then
+               run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
+       fi
+
+       rm -r "/tmp/test.$$"
+
+       if [ $failed = 0 ]; then
+               printf "OK\n"
+       else
+               printf "%s %s FAILED (%d/%d)\n" "$name" "${line:${#name}}" $failed $tests
+       fi
+
+       return $failed
+}
+
+
+select_tests="$@"
+
+use_test() {
+       local input="$(readlink -f "$1")"
+       local test
+
+       [ -f "$input" ] || return 1
+       [ -n "$select_tests" ] || return 0
+
+       for test in $select_tests; do
+               test="$(readlink -f "$test")"
+
+               [ "$test" != "$input" ] || return 0
+       done
+
+       return 1
+}
+
+for catdir in tests/[0-9][0-9]_*; do
+       [ -d "$catdir" ] || continue
+
+       printf "\n##\n## Running %s tests\n##\n\n" "${catdir##*/[0-9][0-9]_}"
+
+       for testfile in "$catdir/"[0-9][0-9]_*; do
+               use_test "$testfile" || continue
+
+               n_tests=$((n_tests + 1))
+               run_test "$testfile" || n_fails=$((n_fails + 1))
+       done
+done
+
+# ── Shell script syntax checks ──────────────────────────────────────
+
+printf "\n##\n## Checking shell script syntax\n##\n\n"
+for shellscript in \
+       root/etc/uci-defaults/* \
+       root/usr/share/advanced-reboot/helpers/*.sh; do
+       [ -f "$shellscript" ] || continue
+       head -1 "$shellscript" | grep -q '^#!/bin/sh' || continue
+       name="${shellscript#root/}"
+       n_tests=$((n_tests + 1))
+       printf "%s %s " "$name" "${line:${#name}}"
+       if sh -n "$shellscript" 2>/dev/null; then
+               printf "OK\n"
+       else
+               printf "FAIL\n"
+               sh -n "$shellscript"
+               n_fails=$((n_fails + 1))
+       fi
+done
+
+printf "\nRan %d tests, %d okay, %d failures\n" $n_tests $((n_tests - n_fails)) $n_fails
+exit $n_fails
git clone https://git.99rst.org/PROJECT