update 2026-09-24 16:56:26

This commit is contained in:
action committed 2026-09-24 16:56:26 +08:00
1 parent b31a5c3878
commit e4cee331e9
6885 files changed
+3240523

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
#=====================================================================================================
# This file is licensed under the terms of the GNU General Public
# License version 2. This program is licensed "as is" without any
# warranty of any kind, whether express or implied.
#
# This file is a part of the luci-app-amlogic plugin
# https://github.com/ophub/luci-app-amlogic
#
# Description: Supports install/update/backup/restore/snapshot operations using OpenWrt on many boxes
# Copyright (C) 2021- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021- https://github.com/ophub/luci-app-amlogic
#
# Command: curl -fsSL git.io/luci-app-amlogic | bash
#=====================================================================================================
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-amlogic
PKG_VERSION:=3.1.321
PKG_RELEASE:=2
PKG_LICENSE:=GPL-2.0 License
PKG_MAINTAINER:=ophub <https://github.com/ophub/luci-app-amlogic>
LUCI_TITLE:=LuCI support for custom TV boxes
LUCI_PKGARCH:=all
LUCI_DEPENDS:= \
@(aarch64||arm) +luci-base +rpcd +rpcd-mod-ucode \
+bash +blkid +block-mount +curl +dosfstools +e2fsprogs \
+fdisk +jq +losetup +lsblk +parted +perl +pv +uuidgen
define Package/$(PKG_NAME)/conffiles
/etc/config/amlogic
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="175" height="20" role="img" aria-label="OpenWrt: Compiler author"><title>OpenWrt: Compiler author</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="175" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="76" height="20" fill="#555"/><rect x="76" width="99" height="20" fill="#4c1"/><rect width="175" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZXNtb2tlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViPC90aXRsZT48cGF0aCBkPSJNMTIgLjI5N2MtNi42MyAwLTEyIDUuMzczLTEyIDEyIDAgNS4zMDMgMy40MzggOS44IDguMjA1IDExLjM4NS42LjExMy44Mi0uMjU4LjgyLS41NzcgMC0uMjg1LS4wMS0xLjA0LS4wMTUtMi4wNC0zLjMzOC43MjQtNC4wNDItMS42MS00LjA0Mi0xLjYxQzQuNDIyIDE4LjA3IDMuNjMzIDE3LjcgMy42MzMgMTcuN2MtMS4wODctLjc0NC4wODQtLjcyOS4wODQtLjcyOSAxLjIwNS4wODQgMS44MzggMS4yMzYgMS44MzggMS4yMzYgMS4wNyAxLjgzNSAyLjgwOSAxLjMwNSAzLjQ5NS45OTguMTA4LS43NzYuNDE3LTEuMzA1Ljc2LTEuNjA1LTIuNjY1LS4zLTUuNDY2LTEuMzMyLTUuNDY2LTUuOTMgMC0xLjMxLjQ2NS0yLjM4IDEuMjM1LTMuMjItLjEzNS0uMzAzLS41NC0xLjUyMy4xMDUtMy4xNzYgMCAwIDEuMDA1LS4zMjIgMy4zIDEuMjMuOTYtLjI2NyAxLjk4LS4zOTkgMy0uNDA1IDEuMDIuMDA2IDIuMDQuMTM4IDMgLjQwNSAyLjI4LTEuNTUyIDMuMjg1LTEuMjMgMy4yODUtMS4yMy42NDUgMS42NTMuMjQgMi44NzMuMTIgMy4xNzYuNzY1Ljg0IDEuMjMgMS45MSAxLjIzIDMuMjIgMCA0LjYxLTIuODA1IDUuNjI1LTUuNDc1IDUuOTIuNDIuMzYuODEgMS4wOTYuODEgMi4yMiAwIDEuNjA2LS4wMTUgMi44OTYtLjAxNSAzLjI4NiAwIC4zMTUuMjEuNjkuODI1LjU3QzIwLjU2NSAyMi4wOTIgMjQgMTcuNTkyIDI0IDEyLjI5N2MwLTYuNjI3LTUuMzczLTEyLTEyLTEyIi8+PC9zdmc+"/><text aria-hidden="true" x="475" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">OpenWrt</text><text x="475" y="140" transform="scale(.1)" fill="#fff" textLength="490">OpenWrt</text><text aria-hidden="true" x="1245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="890">Compiler author</text><text x="1245" y="140" transform="scale(.1)" fill="#fff" textLength="890">Compiler author</text></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="85" height="20" role="img" aria-label="JavaScript"><title>JavaScript</title><g shape-rendering="crispEdges"><rect width="0" height="20" fill="#555"/><rect x="0" width="85" height="20" fill="#323330"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjZjdkZjFlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+SmF2YVNjcmlwdDwvdGl0bGU+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6bTIyLjAzNCAxOC4yNzZjLS4xNzUtMS4wOTUtLjg4OC0yLjAxNS0zLjAwMy0yLjg3My0uNzM2LS4zNDUtMS41NTQtLjU4NS0xLjc5Ny0xLjE0LS4wOTEtLjMzLS4xMDUtLjUxLS4wNDYtLjcwNS4xNS0uNjQ2LjkxNS0uODQgMS41MTUtLjY2LjM5LjEyLjc1LjQyLjk3Ni45IDEuMDM0LS42NzYgMS4wMzQtLjY3NiAxLjc1NS0xLjEyNS0uMjctLjQyLS40MDQtLjYwMS0uNTg2LS43OC0uNjMtLjcwNS0xLjQ2OS0xLjA2NS0yLjgzNC0xLjAzNGwtLjcwNS4wODljLS42NzYuMTY1LTEuMzIuNTI1LTEuNzEgMS4wMDUtMS4xNCAxLjI5MS0uODExIDMuNTQxLjU2OSA0LjQ3MSAxLjM2NSAxLjAyIDMuMzYxIDEuMjQ0IDMuNjE2IDIuMjA1LjI0IDEuMTctLjg3IDEuNTQ1LTEuOTY2IDEuNDEtLjgxMS0uMTgtMS4yNi0uNTg2LTEuNzU1LTEuMzM2bC0xLjgzIDEuMDUxYy4yMS40OC40NS42ODkuODEgMS4xMDkgMS43NCAxLjc1NiA2LjA5IDEuNjY2IDYuODcxLTEuMDA0LjAyOS0uMDkuMjQtLjcwNS4wNzQtMS42NWwuMDQ2LjA2N3ptLTguOTgzLTcuMjQ1aC0yLjI0OGMwIDEuOTM4LS4wMDkgMy44NjQtLjAwOSA1LjgwNSAwIDEuMjMyLjA2MyAyLjM2My0uMTM4IDIuNzExLS4zMy42ODktMS4xOC42MDEtMS41NjYuNDgtLjM5Ni0uMTk2LS41OTctLjQ2Ni0uODMtLjg1NS0uMDYzLS4xMDUtLjExLS4xOTYtLjEyNy0uMTk2bC0xLjgyNSAxLjEyNWMuMzA1LjYzLjc1IDEuMTcyIDEuMzI0IDEuNTE3Ljg1NS41MSAyLjAwNC42NzUgMy4yMDcuNDA1Ljc4My0uMjI2IDEuNDU4LS42OTEgMS44MTEtMS40MTEuNTEtLjkzLjQwMi0yLjA3LjM5Ny0zLjM0Ni4wMTItMi4wNTQgMC00LjEwOSAwLTYuMTc5bC4wMDQtLjA1NnoiLz48L3N2Zz4="/><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="570">JavaScript</text></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="47" height="20" role="img" aria-label="Lua"><title>Lua</title><g shape-rendering="crispEdges"><rect width="0" height="20" fill="#555"/><rect x="0" width="47" height="20" fill="#2c2d72"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkx1YTwvdGl0bGU+PHBhdGggZD0iTS4zOCAxMC4zNzdsLS4yNzItLjAzN2MtLjA0OC4zNDQtLjA4Mi42OTUtLjEwMSAxLjA0MWwuMjc1LjAxNmMuMDE4LS4zNC4wNTEtLjY4Mi4wOTgtMS4wMnpNNC4xMzYgMy4yODlsLS4xODQtLjIwNWMtLjI1OC4yMzItLjUwOS40OC0uNzQ2LjczNGwuMjAyLjE4OGMuMjMxLS4yNDguNDc2LS40OS43MjgtLjcxN3pNNS43NjkgMi4wNTlsLS4xNDYtLjIzNWMtLjI5Ni4xODYtLjU4Ni4zODUtLjg2My41OTRsLjE2Ni4yMTljLjI3LS4yMDMuNTU0LS4zOTkuODQzLS41Nzh6TTEuODI0IDE4LjM2OWMuMTg1LjI5Ny4zODQuNTg2LjU5My44NjNsLjIyLS4xNjRjLS4yMDUtLjI3MS0uMzk5LS41NTUtLjU4LS44NDRsLS4yMzMuMTQ1ek0xLjEyNyAxNi40MDJsLS4yNTUuMTA0Yy4xMjkuMzE4LjI3NC42MzUuNDMxLjk0M2wuMDA1LjAxLjI0NS0uMTI1LS4wMDUtLjAxYy0uMTUzLS4zMDEtLjI5NS0uNjExLS40MjEtLjkyMnpNLjI5OCA5LjMwOWwuMjY5LjA2M2MuMDc2LS4zMzIuMTY4LS42NjQuMjcyLS45ODZsLS4yNjEtLjA4N2MtLjEwOC4zMzItLjIwMi42NzItLjI4IDEuMDF6TS4yNzQgMTIuNDJsLS4yNzUuMDFjLjAxMi4zNDguMDQuNjk5LjA4MyAxLjA0M2wuMjczLS4wMzNjLS4wNDItLjMzNi0uMDY5LS42OC0uMDgxLTEuMDJ6TS4yNTYgMTQuNTA2Yy4wNzMuMzQuMTYyLjY4Mi4yNjQgMS4wMTRsLjI2My0uMDhjLS4xLS4zMjYtLjE4Ny0uNjU4LS4yNTgtLjk5bC0uMjY5LjA1NnpNMTEuNTczLjI3NUwxMS41NjMgMGMtLjM0OC4wMTItLjY5OS4wMzktMS4wNDQuMDgybC4wMzQuMjczYy4zMzgtLjA0MS42OC0uMDY4IDEuMDItLjA4ek0yMy4yMjEgOC41NjZjLjEuMzI2LjE4Ni42Ni4yNTYuOTkybC4yNy0uMDU5Yy0uMDcyLS4zNC0uMTYtLjY4Mi0uMjYyLTEuMDE0bC0uMjY0LjA4MXpNMTcuNjIxIDEuMzg5Yy0uMzA5LS4xNjQtLjYyNy0uMzE0LS45NDctLjQ0OWwtLjEwNy4yNTJjLjMxNC4xMzMuNjI1LjI4MS45MjYuNDM5bC4xMjgtLjI0MnpNMTUuNjkzLjU3MmMtLjMzMi0uMTA1LS42Ny0uMTk5LTEuMDEtLjI3N2wtLjA2My4yNjhjLjMzMi4wNzYuNjY0LjE2OC45ODguMjczbC4wODUtLjI2NHpNNi42NzQgMS41NDVjLjI5OC0uMTUuNjA2LS4yOTEuOTE2LS40MThMNy40ODYuODczYy0uMzE3LjEyNy0uNjMyLjI3Mi0uOTM3LjQyOGwtLjAxNS4wMDguMTI1LjI0NC4wMTUtLjAwOHpNMjMuNzI3IDExLjU4OGwuMjc1LS4wMWExMS43OTcgMTEuNzk3IDAgMCAwLS4wODItMS4wNDVsLS4yNzMuMDMzYy4wNDEuMzM4LjA2OC42ODIuMDggMS4wMjJ6TTEzLjY1NC4xMDVjLS4zNDYtLjA0Ny0uNjk2LS4wOC0xLjA0My0uMDk4bC0uMDE0LjI3M2MuMzM5LjAxOC42ODMuMDUxIDEuMDE5LjA5OGwuMDM4LS4yNzN6TTkuNTQ0LjUyN2wtLjA1OC0uMjdjLS4zNC4wNzItLjY4MS4xNi0xLjAxNC4yNjRsLjA4MS4yNjJjLjMyNS0uMDk5LjY1OS0uMTg1Ljk5MS0uMjU2ek0xLjkyMSA1LjQ2OWwuMjMxLjE1Yy4xODUtLjI4NS4zODQtLjU2Ni41OTItLjgzNGwtLjIxNy0uMTdjLS4yMTMuMjc2LS40MTcuNTYzLS42MDYuODU0ek0uOTQzIDcuMzE4bC4yNTMuMTA3Yy4xMzItLjMxMy4yOC0uNjI1LjQzOS0uOTI0bC0uMjQzLS4xMjhjLS4xNjMuMzA3LS4zMTQuNjI1LS40NDkuOTQ1ek0xOC4yMjMgMjEuOTQzbC4xNDUuMjM0Yy4yOTUtLjE4Ni41ODYtLjM4NS44NjMtLjU5NGwtLjE2NC0uMjE5Yy0uMjcyLjIwNC0uNTU3LjQtLjg0NC41Nzl6TTIxLjI0OCAxOS4yMTlsLjIxNy4xN2MuMjE1LS4yNzMuNDE4LS41NjEuNjA3LS44NTRsLS4yMy0uMTQ4Yy0uMTg2LjI4NS0uMzg1LjU2NC0uNTk0LjgzMnpNMTkuODU1IDIwLjcxNWwuMTg0LjIwM2MuMjU4LS4yMy41MS0uNDc5Ljc0Ni0uNzMybC0uMjAxLS4xODhjLS4yMy4yNDgtLjQ3Ny40ODgtLjcyOS43MTd6TTIyLjM1OSAxNy41MDRsLjI0NC4xMjljLjE2Mi0uMzA3LjMxNC0uNjI1LjQ0OS0uOTQ1bC0uMjU0LS4xMDdhMTEuMjcgMTEuMjcgMCAwIDEtLjQzOS45MjN6TTIzLjYxNyAxMy42MjlsLjI3My4wMzljLjA0OS0uMzQ2LjA4Mi0uNjk1LjEwMi0xLjA0M2wtLjI3NS0uMDE0Yy0uMDE4LjMzOC0uMDUxLjY4Mi0uMSAxLjAxOHpNMjMuMTU2IDE1LjYyMWwuMjY0LjA4NmMuMTA3LS4zMzIuMjAxLS42Ny4yNzktMS4wMWwtLjI2OC0uMDYzYy0uMDc3LjMzMy0uMTY5LjY2NS0uMjc1Ljk4N3pNMjIuNDUzIDYuNjcyYy4xNTQuMzAzLjI5Ny42MTcuNDI0LjkzMmwuMjU2LS4xMDRjLS4xMzEtLjMyMi0uMjc3LS42NDMtLjQzNi0uOTUzbC0uMjQ0LjEyNXpNOC4yOTYgMjMuNDE4Yy4zMzEuMTA3LjY3LjIwMSAxLjAwOS4yNzlsLjA2Mi0uMjY4Yy0uMzMxLS4wNzYtLjY2My0uMTY4LS45ODYtLjI3M2wtLjA4NS4yNjJ6TTEwLjMzNSAyMy44ODljLjM0NS4wNDkuNjk2LjA4MiAxLjA0My4xMDJsLjAxNC0uMjc1Yy0uMzM5LS4wMTgtLjY4Mi0uMDUxLTEuMDE5LS4wOThsLS4wMzguMjcxek0xNy4zMjYgMjIuNDQ5Yy0uMzAzLjE1NC0uNjEzLjI5Ny0uOTI2LjQyNGwuMTA0LjI1NmMuMzE4LS4xMzEuNjM5LS4yNzUuOTQ3LS40MzRsLjAwNC0uMDAyLS4xMjMtLjI0Ni0uMDA2LjAwMnpNNC42MTMgMjEuNDY3Yy4yNzQuMjEzLjU2Mi40MTguODU0LjYwNWwuMTQ5LS4yM2MtLjI4NS0uMTg0LS41NjUtLjM4NS0uODMzLS41OTJsLS4xNy4yMTd6TTEyLjQxNyAyMy43MjVsLjAwOS4yNzVjLjM0OC0uMDE0LjY5OS0uMDQxIDEuMDQ1LS4wODRsLS4wMzUtLjI3MWMtLjMzNi4wNDEtLjY4LjA2OC0xLjAxOS4wOHpNNi4zNyAyMi42MDRjLjMwNy4xNjIuNjI1LjMxNC45NDYuNDQ5bC4xMDctLjI1NGMtLjMxMy0uMTMzLS42MjQtLjI3OS0uOTI0LS40MzlsLS4xMjkuMjQ0ek0zLjA4MyAyMC4wNDFjLjIzMy4yNTguNDguNTEuNzM0Ljc0NmwuMTg4LS4yMDFjLS4yNDktLjIzLS40OS0uNDc3LS43MTctLjcyOWwtLjIwNS4xODR6TTE0LjQ0NSAyMy40NzVsLjA1OS4yN2MuMzQtLjA3NC42OC0uMTYyIDEuMDE0LS4yNjZsLS4wODItLjI2MmMtLjMyNS4wOTktLjY1OS4xODUtLjk5MS4yNTh6TTIxLjE4LjEyOUEyLjY4OSAyLjY4OSAwIDEgMCAyMS4xOCA1LjUwNyAyLjY4OSAyLjY4OSAwIDEgMCAyMS4xOC4xMjl6TTE1LjMyNCAxNS40NDdjMCAuNDcxLjMxNC42Ni44NTIuNjYuNjcgMCAxLjI5Ny0uMzk2IDEuMjk3LTEuMDE2di0uNjQ1Yy0uMjMuMTA3LS4zNzkuMTQxLTEuMTA3LjI0LS43MzUuMTA5LTEuMDQyLjMwNi0xLjA0Mi43NjF6TTEyIDIuODE4Yy01LjA3IDAtOS4xOCA0LjEwOS05LjE4IDkuMTggMCA1LjA2OCA0LjExIDkuMTggOS4xOCA5LjE4IDUuMDcgMCA5LjE4LTQuMTExIDkuMTgtOS4xOCAwLTUuMDctNC4xMS05LjE4LTkuMTgtOS4xOHptLTIuLine truncated

After

Width:  |  Height:  |  Size: 5.9 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="187" height="20" role="img" aria-label="OpenWrt: flippy&apos;s homepage"><title>OpenWrt: flippy&apos;s homepage</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="187" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="76" height="20" fill="#555"/><rect x="76" width="111" height="20" fill="blueviolet"/><rect width="187" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZXNtb2tlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViPC90aXRsZT48cGF0aCBkPSJNMTIgLjI5N2MtNi42MyAwLTEyIDUuMzczLTEyIDEyIDAgNS4zMDMgMy40MzggOS44IDguMjA1IDExLjM4NS42LjExMy44Mi0uMjU4LjgyLS41NzcgMC0uMjg1LS4wMS0xLjA0LS4wMTUtMi4wNC0zLjMzOC43MjQtNC4wNDItMS42MS00LjA0Mi0xLjYxQzQuNDIyIDE4LjA3IDMuNjMzIDE3LjcgMy42MzMgMTcuN2MtMS4wODctLjc0NC4wODQtLjcyOS4wODQtLjcyOSAxLjIwNS4wODQgMS44MzggMS4yMzYgMS44MzggMS4yMzYgMS4wNyAxLjgzNSAyLjgwOSAxLjMwNSAzLjQ5NS45OTguMTA4LS43NzYuNDE3LTEuMzA1Ljc2LTEuNjA1LTIuNjY1LS4zLTUuNDY2LTEuMzMyLTUuNDY2LTUuOTMgMC0xLjMxLjQ2NS0yLjM4IDEuMjM1LTMuMjItLjEzNS0uMzAzLS41NC0xLjUyMy4xMDUtMy4xNzYgMCAwIDEuMDA1LS4zMjIgMy4zIDEuMjMuOTYtLjI2NyAxLjk4LS4zOTkgMy0uNDA1IDEuMDIuMDA2IDIuMDQuMTM4IDMgLjQwNSAyLjI4LTEuNTUyIDMuMjg1LTEuMjMgMy4yODUtMS4yMy42NDUgMS42NTMuMjQgMi44NzMuMTIgMy4xNzYuNzY1Ljg0IDEuMjMgMS45MSAxLjIzIDMuMjIgMCA0LjYxLTIuODA1IDUuNjI1LTUuNDc1IDUuOTIuNDIuMzYuODEgMS4wOTYuODEgMi4yMiAwIDEuNjA2LS4wMTUgMi44OTYtLjAxNSAzLjI4NiAwIC4zMTUuMjEuNjkuODI1LjU3QzIwLjU2NSAyMi4wOTIgMjQgMTcuNTkyIDI0IDEyLjI5N2MwLTYuNjI3LTUuMzczLTEyLTEyLTEyIi8+PC9zdmc+"/><text aria-hidden="true" x="475" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">OpenWrt</text><text x="475" y="140" transform="scale(.1)" fill="#fff" textLength="490">OpenWrt</text><text aria-hidden="true" x="1305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="1010">flippy&apos;s homepage</text><text x="1305" y="140" transform="scale(.1)" fill="#fff" textLength="1010">flippy&apos;s homepage</text></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="161" height="20" role="img" aria-label="Plugin: luci-app-amlogic"><title>Plugin: luci-app-amlogic</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="161" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="60" height="20" fill="#555"/><rect x="60" width="101" height="20" fill="#007ec6"/><rect width="161" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZXNtb2tlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViPC90aXRsZT48cGF0aCBkPSJNMTIgLjI5N2MtNi42MyAwLTEyIDUuMzczLTEyIDEyIDAgNS4zMDMgMy40MzggOS44IDguMjA1IDExLjM4NS42LjExMy44Mi0uMjU4LjgyLS41NzcgMC0uMjg1LS4wMS0xLjA0LS4wMTUtMi4wNC0zLjMzOC43MjQtNC4wNDItMS42MS00LjA0Mi0xLjYxQzQuNDIyIDE4LjA3IDMuNjMzIDE3LjcgMy42MzMgMTcuN2MtMS4wODctLjc0NC4wODQtLjcyOS4wODQtLjcyOSAxLjIwNS4wODQgMS44MzggMS4yMzYgMS44MzggMS4yMzYgMS4wNyAxLjgzNSAyLjgwOSAxLjMwNSAzLjQ5NS45OTguMTA4LS43NzYuNDE3LTEuMzA1Ljc2LTEuNjA1LTIuNjY1LS4zLTUuNDY2LTEuMzMyLTUuNDY2LTUuOTMgMC0xLjMxLjQ2NS0yLjM4IDEuMjM1LTMuMjItLjEzNS0uMzAzLS41NC0xLjUyMy4xMDUtMy4xNzYgMCAwIDEuMDA1LS4zMjIgMy4zIDEuMjMuOTYtLjI2NyAxLjk4LS4zOTkgMy0uNDA1IDEuMDIuMDA2IDIuMDQuMTM4IDMgLjQwNSAyLjI4LTEuNTUyIDMuMjg1LTEuMjMgMy4yODUtMS4yMy42NDUgMS42NTMuMjQgMi44NzMuMTIgMy4xNzYuNzY1Ljg0IDEuMjMgMS45MSAxLjIzIDMuMjIgMCA0LjYxLTIuODA1IDUuNjI1LTUuNDc1IDUuOTIuNDIuMzYuODEgMS4wOTYuODEgMi4yMiAwIDEuNjA2LS4wMTUgMi44OTYtLjAxNSAzLjI4NiAwIC4zMTUuMjEuNjkuODI1LjU3QzIwLjU2NSAyMi4wOTIgMjQgMTcuNTkyIDI0IDEyLjI5N2MwLTYuNjI3LTUuMzczLTEyLTEyLTEyIi8+PC9zdmc+"/><text aria-hidden="true" x="395" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">Plugin</text><text x="395" y="140" transform="scale(.1)" fill="#fff" textLength="330">Plugin</text><text aria-hidden="true" x="1095" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="910">luci-app-amlogic</text><text x="1095" y="140" transform="scale(.1)" fill="#fff" textLength="910">luci-app-amlogic</text></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Theme-aware styles for luci-app-amlogic JS views.
*/
/* --- Status text ------------------------------------ */
.amlogic-status-ok { color: #1a7f37; }
.amlogic-status-err { color: #c0392b; }
.amlogic-status-info { color: #1f6feb; }
/* --- Spacious row table -----------------------------------------------
* Used by views that lay out a single button + status text per row
* (check / backup / upload). border-collapse:collapse avoids the visible
* blank band that border-spacing would otherwise add around the table
* edges; row breathing room comes purely from cell padding.
* ---------------------------------------------------------------------- */
.amlogic-row-table {
width: 100%;
border-collapse: collapse;
}
.amlogic-row-table > tbody > tr > td,
.amlogic-row-table > tr > td {
padding: 10px 12px;
vertical-align: middle;
line-height: 1.6;
}
/* --- Snapshot grid (cards) --------------------------------------------- */
.amlogic-snap-list {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
width: 100%;
background: transparent;
}
.amlogic-snap-list:after,
.amlogic-snap-list .amlogic-snap-item:not(:nth-child(3n)) {
content: "";
}
.amlogic-snap-item {
box-sizing: border-box;
width: 150px;
height: 130px;
margin: 8px;
padding: 4px;
text-align: center;
font-size: 14px;
color: inherit;
background: transparent;
border: 1px solid rgba(127, 127, 127, 0.35);
border-radius: 4px;
transition: background-color 0.15s ease-in-out;
}
.amlogic-snap-item:hover {
background: rgba(127, 127, 127, 0.15);
}
.amlogic-snap-line {
line-height: 40px;
}
/* --- Info-page logo / author tiles ------------------------------------- */
.amlogic-info-logo {
max-width: 100%;
height: auto;
}
/* --- Dark mode overrides (system / browser preference) -----------------
* Reads prefers-color-scheme from the system / browser. When the user has
* picked a dark theme, switch to brighter colors to keep readable contrast.
* LuCI itself only adjusts the background, so this stylesheet fills in the
* status text and snapshot border colors that the upstream theme leaves
* untouched.
* ---------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
.amlogic-status-ok { color: #4ade80; }
.amlogic-status-err { color: #f87171; }
.amlogic-status-info { color: #60a5fa; }
.amlogic-snap-item {
border-color: rgba(200, 200, 200, 0.25);
}
.amlogic-snap-item:hover {
background: rgba(255, 255, 255, 0.08);
}
}
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0
// CPU Freq Settings page
//
// Purpose: configure scaling governor and min/max frequency per CPU policy
// (cluster). After save, the backend script fixcpufreq.pl is invoked so the
// new values take effect immediately. The backing RPC is exposed by
// /usr/share/rpcd/ucode/luci.amlogic.
'use strict';
'require view';
'require form';
'require rpc';
'require uci';
// Query CPU cluster info (each policy: core type, available governors, freqs).
const callPolicies = rpc.declare({ object: 'luci.amlogic', method: 'cpu_policies',
expect: { policies: [] } });
// Tell backend to apply the new governor / frequency limits right after save.
const callReloadCpu = rpc.declare({ object: 'luci.amlogic', method: 'reload_cpu' });
return view.extend({
// Load in parallel: CPU policy info + UCI config (we will read/write it).
load: function () {
return Promise.all([
callPolicies(),
uci.load('amlogic')
]);
},
// Render a tabbed form section per CPU policy (cluster), with options to
// set the governor and min/max frequency. The form is bound to the amlogic.armcpu UCI section.
render: function (data) {
const policies = data[0] || [];
// Auto-create the armcpu section when missing, so that on a fresh
// install/upgrade the user can save without first creating the section
// manually.
if (!uci.get('amlogic', 'armcpu')) {
uci.add('amlogic', 'settings', 'armcpu');
}
// Build CBI Map bound to the amlogic.armcpu NamedSection.
const m = new form.Map('amlogic', _('CPU Freq Settings'),
_('Set CPU Scaling Governor to Max Performance or Balance Mode'));
const s = m.section(form.NamedSection, 'armcpu', 'settings');
s.anonymous = true;
// Render an independent tab + form options per CPU cluster (big/little).
policies.forEach(function (p) {
const tabId = 'tab' + p.id;
s.tab(tabId, p.name);
// Microarchitecture (read-only display, inline so it aligns with the label).
const ct = s.taboption(tabId, form.DummyValue, 'core_type' + p.id,
_('Microarchitectures:'));
ct.cfgvalue = function () { return p.core_type; };
ct.renderWidget = function (section_id) {
return E('span', { style: 'line-height:2em; display:inline-block; vertical-align:middle' },
this.cfgvalue(section_id) || '');
};
// CPU scaling governor: list of governors exposed by the kernel
// (performance / schedutil / etc).
const gov = s.taboption(tabId, form.ListValue, 'governor' + p.id,
_('CPU Scaling Governor:'));
(p.governors || []).forEach(function (e) { gov.value(e, e.toUpperCase()); });
gov.default = 'schedutil';
gov.rmempty = false;
// Minimum running frequency (kHz).
const minF = s.taboption(tabId, form.ListValue, 'minfreq' + p.id, _('Min Freq:'));
(p.freqs || []).forEach(function (e) { minF.value(e); });
minF.default = '500000';
minF.rmempty = false;
// Maximum running frequency (kHz).
const maxF = s.taboption(tabId, form.ListValue, 'maxfreq' + p.id, _('Max Freq:'));
(p.freqs || []).forEach(function (e) { maxF.value(e); });
maxF.default = '1512000';
maxF.rmempty = false;
});
// Reload CPU settings after the LuCI apply mechanism commits UCI to disk.
// The standard apply flow (apply_rollback → ucitrack → init.d restart)
// reads UCI before the new values are fully written, so we re-apply here.
document.addEventListener('uci-applied', function () {
callReloadCpu();
});
return m.render();
}
});
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0
// Backup Firmware Config + Snapshot management + (optional) KVM switch
//
// Purpose: pack current /etc config into openwrt_config.tar.gz and download it;
// manage system snapshots (list / create / restore / delete); optionally show
// KVM dual-partition switch when the platform reports has_kvm.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (backup_create, snapshot_*, platform_info, kvm_switch).
'use strict';
'require view';
'require rpc';
'require ui';
'require fs';
'require view.amlogic.shared as amlogicShared';
// Generate one openwrt_config.tar.gz on the server and return its path so the
// frontend can stream it back via fs.read_direct.
const callBackup = rpc.declare({ object: 'luci.amlogic', method: 'backup_create' });
// List existing snapshot names (e.g. etc-000 / etc-001 / etc-202401...).
const callSnapList = rpc.declare({ object: 'luci.amlogic', method: 'snapshot_list',
expect: { names: [] } });
// Create a new snapshot (the backend chooses its name).
const callSnapCreate = rpc.declare({ object: 'luci.amlogic', method: 'snapshot_create' });
// Delete a snapshot by short name (without the etc- prefix).
const callSnapDel = rpc.declare({ object: 'luci.amlogic', method: 'snapshot_delete',
params: ['name'] });
// Restore the given snapshot and reboot.
const callSnapRest = rpc.declare({ object: 'luci.amlogic', method: 'snapshot_restore',
params: ['name'] });
// Read platform info (this page only consumes has_kvm to decide whether to
// show the KVM switch row).
const callPlatform = rpc.declare({ object: 'luci.amlogic', method: 'platform_info' });
// Switch active KVM partition.
const callKvmSwitch = rpc.declare({ object: 'luci.amlogic', method: 'kvm_switch' });
// The view object for the Backup & Snapshot page.
return view.extend({
handleSave: null,
handleSaveApply: null,
handleReset: null,
// Load in parallel: platform info + current snapshot list.
load: function () {
amlogicShared.ensureCss();
return Promise.all([callPlatform(), callSnapList()]);
},
// Render the page: backup config section + snapshot management section + (optional) KVM switch section.
render: function (data) {
const platform = data[0] || {};
const snapNames = data[1] || [];
const view = this;
const editListBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-save',
value: _('Open List'),
// Navigate to the backuplist page to edit the backup file list.
click: function () {
location.href = L.url('admin/system/amlogic/backuplist');
}
});
// Download status hint; updated to success color after generation completes.
const downloadStatus = E('span', { class: 'amlogic-status-err' });
const downloadBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-save',
value: _('Download Backup'),
click: ui.createHandlerFn(view, function (ev) {
const btn = ev.currentTarget;
btn.disabled = true;
btn.value = _('Generating...');
// Step 1: ask the server to pack /etc into openwrt_config.tar.gz.
return callBackup().then(function (r) {
if (!r || !r.ok) {
downloadStatus.textContent = _("Couldn't open file:") + ' ' + (r ? r.path : '');
return;
}
// Switch to success color before streaming the blob
downloadStatus.className = 'amlogic-status-ok';
downloadStatus.textContent = _('The file Will download automatically.') + ' ' + r.path;
// Fetch the file blob via fs.read_direct and trigger browser download.
return fs.read_direct(r.path, 'blob').then(function (blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'openwrt_config.tar.gz';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function () { URL.revokeObjectURL(url); }, 10000);
});
}).catch(function (e) {
downloadStatus.textContent = _('Create upload file error.') +
' ' + (e && e.message ? e.message : '');
}).finally(function () {
btn.disabled = false;
btn.value = _('Download Backup');
});
})
});
// Navigate to the upload page where the user can upload a backup file to restore.
const restoreBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-save',
value: _('Upload Backup'),
click: function () {
location.href = L.url('admin/system/amlogic/upload');
}
});
// Snapshot grid; styles adapt to the current theme via amlogic-snap-* classes.
const snapDiv = E('div', { class: 'amlogic-snap-list' });
// Render snapshot cards; etc-000/001 are read-only (no Delete button).
// Backend expects the short name without the "etc-" prefix.
function renderSnapshots(names) {
snapDiv.innerHTML = '';
if (!names || !names.length) {
snapDiv.appendChild(E('span', { class: 'amlogic-status-err' },
_('Currently OpenWrt does not support the snapshot function.') +
_('Please use this plugin to reinstall or upgrade OpenWrt to enable the snapshot function.')));
return;
}
names.forEach(function (n) {
let title;
if (n === 'etc-000') title = _('Initialize Snapshot');
else if (n === 'etc-001') title = _('Update Snapshot');
else title = n;
const restoreSnapBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-apply',
value: _('Restore Snap'),
click: ui.createHandlerFn(view, function (ev) {
const short = n.replace(/^etc-/, '');
if (!confirm(_('You selected a snapshot:') + ' [ ' + n + ' ] , ' +
_('Confirm recovery and restart OpenWrt?')))
return;
// Capture btn before entering async chain; ev.currentTarget becomes null after await.
const btn = ev.currentTarget;
btn.disabled = true;
btn.value = _('Restoring...');
return callSnapRest(short).then(function (r) {
if (r && r.code == 0) {
btn.value = _('Successfully Restored');
ui.addNotification(null,
E('p', { class: 'amlogic-status-err' },
_('Snapshot restored. The system will reboot now.')));
} else {
btn.value = _('Restore Failed');
}
}).catch(function () { btn.value = _('Restore Failed'); });
})
});
const item = E('div', {
class: 'amlogic-snap-item',
id: 'snapshots_div_' + n
}, [
E('div', { class: 'amlogic-snap-line' }, title),
E('div', { class: 'amlogic-snap-line' }, restoreSnapBtn)
]);
if (n !== 'etc-000' && n !== 'etc-001') {
const delBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-remove',
value: _('Delete Snap'),
click: ui.createHandlerFn(view, function (ev) {
const short = n.replace(/^etc-/, '');
if (!confirm(_('You selected a snapshot:') + ' [ ' + n + ' ] , ' +
_('Confirm delete?')))
return;
// Capture btn before entering async chain; ev.currentTarget becomes null after await.
const btn = ev.currentTarget;
btn.disabled = true;
btn.value = _('Deleting...');
return callSnapDel(short).then(function (r) {
if (r && r.code == 0) {
btn.value = _('Successfully Deleted');
item.style.display = 'none';
} else {
btn.value = _('Delete Failed');
}
}).catch(function () { btn.value = _('Delete Failed'); });
})
});
item.appendChild(E('div', { class: 'amlogic-snap-line' }, delBtn));
}
snapDiv.appendChild(item);
});
}
renderSnapshots(snapNames);
// Create snapshot button above the grid.
const createSnapBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-save',
value: _('Create Snapshot'),
click: ui.createHandlerFn(view, function (ev) {
const btn = ev.currentTarget;
btn.disabled = true;
return callSnapCreate().then(function () {
return callSnapList();
}).then(function (r) {
renderSnapshots(Array.isArray(r) ? r : (r && r.names) || []);
}).finally(function () { btn.disabled = false; });
})
});
// If the platform supports KVM dual partitions, show a button to trigger partition switch.
const sections = [
E('h2', _('Backup Firmware Config')),
E('p', _('Backup OpenWrt config (openwrt_config.tar.gz). Use this file to restore the config in [Manually Upload Update].')),
E('div', { class: 'cbi-section' }, [
E('table', { class: 'amlogic-row-table' }, [
E('tr', [
E('td', { width: '20%', align: 'right' }, _('Edit List:')),
E('td', { width: '80%', align: 'left' }, editListBtn)
]),
E('tr', [
E('td', { width: '20%', align: 'right' }, _('Backup Config:')),
E('td', { width: '80%', align: 'left' }, [downloadBtn, ' ', downloadStatus])
]),
E('tr', [
E('td', { width: '20%', align: 'right' }, _('Restore Backup:')),
E('td', { width: '80%', align: 'left' }, restoreBtn)
])
])
]),
E('h2', _('Snapshot Management')),
E('p', _('Create a snapshot of the current system configuration, or restore to a snapshot.')),
E('div', { class: 'cbi-section' }, [
E('div', { style: 'margin-bottom:10px' }, createSnapBtn),
snapDiv
])
];
// If the platform supports KVM dual partitions, show a button to trigger partition switch.
if (platform.has_kvm) {
const kvmBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-save',
value: _('Switch System'),
click: ui.createHandlerFn(view, function (ev) {
if (!confirm(_('Are you sure you want to switch systems?'))) return;
ev.currentTarget.disabled = true;
ui.showModal(_('System is switching...'), [
E('p', { class: 'spinning' }, _('Waiting for system switching...'))
]);
return callKvmSwitch().then(function () {
if (ui.awaitReconnect) {
ui.awaitReconnect();
} else {
// Fallback for older LuCI builds without awaitReconnect
setTimeout(function () { location.reload(); }, 5000);
}
});
})
});
sections.push(E('h2', _('KVM dual system switching')));
sections.push(E('p', _('You can freely switch between KVM dual partitions, using OpenWrt systems in different partitions.')));
sections.push(E('div', { class: 'cbi-section' }, [kvmBtn]));
}
return E('div', {}, sections);
}
});
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-2.0
// Backup file list editor
//
// Purpose: edit /etc/amlogic_backup_list.conf which controls which files/dirs
// the openwrt-backup script archives. If the file is missing or empty, the
// backend seeds it from the default BACKUP_LIST in /usr/sbin/openwrt-backup.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (save_backup_list, prime_backup_list).
'use strict';
'require view';
'require rpc';
'require ui';
'require fs';
// Persist the user-edited content back to /etc/amlogic_backup_list.conf.
const callSave = rpc.declare({ object: 'luci.amlogic', method: 'save_backup_list',
params: ['content'] });
// Seed the list file from defaults embedded in openwrt-backup if missing or empty.
const callPrime = rpc.declare({ object: 'luci.amlogic', method: 'prime_backup_list' });
// This page uses its own Save button; hide LuCI's default Save/Apply/Reset.
return view.extend({
// This page uses its own Save button; hide LuCI's default Save/Apply/Reset.
handleSave: null,
handleSaveApply: null,
handleReset: null,
// Load the existing backup list content to pre-fill the editor. If the file is
load: function () {
// Run prime first (idempotent), then read the file content.
return callPrime().then(function () {
return fs.read('/etc/amlogic_backup_list.conf').catch(function () { return ''; });
});
},
// Render a full-width textarea pre-filled with the existing content, and a Save button
// that persists the changes via RPC. Also include a Back button to return to the main Backup page.
render: function (text) {
// Full-width multi-line editor pre-filled with the existing list.
const ta = E('textarea', {
rows: '30',
style: 'width:100%; font-family:monospace'
}, text || '');
// Status hint area (saved successfully / failed).
const status = E('span', { style: 'margin-left:1em' });
const saveBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-save',
value: _('Save'),
click: ui.createHandlerFn(this, function (ev) {
const btn = ev.currentTarget;
btn.disabled = true;
return callSave(ta.value).then(function (r) {
if (r && (r.ok || r.code == 0)) {
status.textContent = _('Successfully saved.');
// Navigate back after a short delay so the success message is visible.
setTimeout(function () {
location.href = L.url('admin/system/amlogic/backup');
}, 700);
} else {
status.textContent = _('Save Failed');
}
}).catch(function (e) {
status.textContent = _('Save Failed') + ': ' + (e && e.message ? e.message : String(e));
}).finally(function () { btn.disabled = false; });
})
});
// Return to the Backup main page (without saving).
const backBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-reset',
value: _('Back'),
click: function () { location.href = L.url('admin/system/amlogic/backup'); }
});
return E('div', {}, [
E('h2', _('Edit list of backup files')),
E('p', _('In this list, you can manually add the file or directory list of files that need to be backed up. Please confirm that the format and content of the file are correct.')),
E('div', { class: 'cbi-section' }, [ta]),
E('div', { class: 'cbi-page-actions' }, [saveBtn, ' ', backBtn, status])
]);
}
});
@@ -0,0 +1,471 @@
// SPDX-License-Identifier: GPL-2.0
// Online Download Update + Rescue Kernel
//
// Purpose: check / download / install plugin, kernel, and firmware updates online.
// Log tail is polled at 1 Hz; the last log line drives a state machine
// (idle → checking → button → installing → done). Terminal log lines contain
// raw HTML <input> button strings which are parsed and rendered as DOM buttons.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (state, check_*, start_*, read_log_tail).
'use strict';
'require view';
'require rpc';
'require ui';
'require dom';
'require poll';
'require view.amlogic.shared as amlogicShared';
// ── RPCs ─────────────────────────────────────────────────────────────────────
// Query system state (firmware / plugin / kernel versions).
const callState = rpc.declare({ object: 'luci.amlogic', method: 'state' });
// Run plugin / kernel / firmware version check and write progress to the log.
const callCheckPlugin = rpc.declare({ object: 'luci.amlogic', method: 'check_plugin', params: ['options'] });
const callCheckKernel = rpc.declare({ object: 'luci.amlogic', method: 'check_kernel', params: ['options'] });
const callCheckFirmware = rpc.declare({ object: 'luci.amlogic', method: 'check_firmware', params: ['options'] });
const callStartPlugin = rpc.declare({ object: 'luci.amlogic', method: 'start_plugin' });
const callStartKernel = rpc.declare({ object: 'luci.amlogic', method: 'start_kernel' });
const callStartUpdate = rpc.declare({ object: 'luci.amlogic', method: 'start_update', params: ['amlogic_update_sel'] });
const callStartRescue = rpc.declare({ object: 'luci.amlogic', method: 'start_rescue' });
// Read the last line (up to 4096 bytes) of the named log file.
const callLogTail = rpc.declare({ object: 'luci.amlogic', method: 'read_log_tail', params: ['name'], expect: { line: '' } });
// ── Log-line HTML button parser ──────────────────────────────────────────────
// Parse an <input> button string from the log; return null for plain-text lines.
// Returned object: { type, param, label } where type identifies the action.
function parseButtonLine(line) {
if (!line || line.indexOf('<input') === -1) return null;
// Extract value="..."
var valueMatch = line.match(/value="([^"]*)"/);
var onclickMatch = line.match(/onclick="([^"]*)"/);
if (!valueMatch || !onclickMatch) return null;
var value = valueMatch[1];
var onclick = onclickMatch[1];
// Text after the self-closing tag
var labelMatch = line.match(/\/>(.*)$/);
var label = labelMatch ? labelMatch[1].trim() : '';
// Plugin download: b_check_plugin(this, 'download_VERSION')
if (value === 'Download' && onclick.indexOf('b_check_plugin') !== -1) {
var m = onclick.match(/b_check_plugin\s*\(\s*this\s*,\s*'([^']*)'\s*\)/);
return { type: 'plugin-download', param: m ? m[1] : null, label: label };
}
// Firmware download: b_check_firmware(this, 'PARAM')
if (value === 'Download' && onclick.indexOf('b_check_firmware') !== -1) {
var m = onclick.match(/b_check_firmware\s*\(\s*this\s*,\s*'([^']*)'\s*\)/);
return { type: 'firmware-download', param: m ? m[1] : null, label: label };
}
// Kernel download: b_check_kernel(this, 'PARAM')
if (value === 'Download' && onclick.indexOf('b_check_kernel') !== -1) {
var m = onclick.match(/b_check_kernel\s*\(\s*this\s*,\s*'([^']*)'\s*\)/);
return { type: 'kernel-download', param: m ? m[1] : null, label: label };
}
// Firmware install: amlogic_update(this, 'PARAM')
if (value === 'Update' && onclick.indexOf('amlogic_update') !== -1) {
var m = onclick.match(/amlogic_update\s*\(\s*this\s*,\s*'([^']*)'\s*\)/);
return { type: 'firmware-update', param: m ? m[1] : null, label: label };
}
// Kernel install: amlogic_kernel(this)
if (value === 'Update' && onclick.indexOf('amlogic_kernel') !== -1) {
return { type: 'kernel-update', param: null, label: label };
}
// Plugin install: amlogic_plugin(this)
if (value === 'Update' && onclick.indexOf('amlogic_plugin') !== -1) {
return { type: 'plugin-update', param: null, label: label };
}
return null;
}
// ── Per-action state machine ──────────────────────────────────────────────────
// States: 'idle' | 'checking' | 'button' | 'installing' | 'done'
// Action object factory: { name, statusEl, state, subBtn }
function makeAction(name, statusEl) {
return { name: name, statusEl: statusEl, state: 'idle', subBtn: null };
}
// Update the status span with a text string (blue info style).
function showInfo(el, text) {
dom.content(el, E('span', { class: 'amlogic-status-info' }, text));
}
// Update the status span with a success string (green).
function showOk(el, text) {
dom.content(el, E('span', { class: 'amlogic-status-ok' }, text));
}
// Update the status span with an error string (red).
function showErr(el, text) {
dom.content(el, E('span', { class: 'amlogic-status-err' }, text));
}
// Replace statusEl contents with a real DOM button + optional label text.
// onClick is called when the user presses it.
function showSubButton(el, btnLabel, labelText, onClick) {
var btn = E('input', {
type: 'button',
class: 'cbi-button cbi-button-reload',
value: btnLabel,
click: onClick
});
var nodes = [btn];
if (labelText) nodes.push(E('span', { class: 'amlogic-status-info' }, ' ' + labelText));
dom.content(el, nodes);
return btn;
}
// ── Poll handler (called at 1 Hz) ────────────────────────────────────────────
// Inspect the last non-empty log line and advance the action state machine.
function handlePoll(action) {
if (action.state === 'done') return;
return callLogTail(action.name).then(function (raw) {
// Only inspect the last non-empty line to avoid false-positive
// 'Failed'/'error' matches from intermediate opkg/apk output.
var lines = (raw || '').split('\n');
var line = '';
for (var i = lines.length - 1; i >= 0; i--) {
var l = lines[i].trim();
if (l) { line = l; break; }
}
if (action.state === 'idle') {
return;
}
if (action.state === 'button') {
// Waiting for user to click sub-button; don't re-render on every poll.
return;
}
if (action.state === 'installing') {
// Show progress lines; detect terminal keywords.
if (!line) return;
// Skip if line is the same HTML button (stale log from check phase).
if (line.indexOf('<input') !== -1) return;
if (line.indexOf('Successfully updated') !== -1 ||
line.indexOf('Successful Update') !== -1 ||
line.indexOf('Successful') !== -1) {
showOk(action.statusEl, line);
action.state = 'done';
} else if (line.indexOf('Failed') !== -1 || line.indexOf('failed') !== -1 ||
line.indexOf('Error') !== -1 || line.indexOf('error') !== -1) {
showErr(action.statusEl, line);
action.state = 'done';
} else {
// Show current progress line (may change rapidly).
showInfo(action.statusEl, line);
}
return;
}
// state === 'checking': look for HTML button line or plain progress text.
var parsed = parseButtonLine(line);
if (parsed) {
action.state = 'button';
action._parsed = parsed;
renderParsedButton(action);
} else if (line) {
showInfo(action.statusEl, line);
}
});
}
// Render the sub-button parsed from the log line and wire up the second-phase RPC.
function renderParsedButton(action) {
var parsed = action._parsed;
var el = action.statusEl;
switch (parsed.type) {
case 'plugin-download':
showSubButton(el, _('Download'), parsed.label, function (ev) {
ev.currentTarget.disabled = true;
ev.currentTarget.value = _('Downloading...');
action.state = 'checking';
callCheckPlugin(parsed.param).then(function () {});
});
break;
case 'firmware-download':
showSubButton(el, _('Download'), parsed.label, function (ev) {
ev.currentTarget.disabled = true;
ev.currentTarget.value = _('Downloading...');
action.state = 'checking';
callCheckFirmware(parsed.param).then(function () {});
});
break;
case 'kernel-download':
showSubButton(el, _('Download'), parsed.label, function (ev) {
ev.currentTarget.disabled = true;
ev.currentTarget.value = _('Downloading...');
action.state = 'checking';
callCheckKernel(parsed.param).then(function () {});
});
break;
case 'firmware-update':
showSubButton(el, _('Update'), parsed.label, function (ev) {
ev.currentTarget.disabled = true;
ev.currentTarget.value = _('Updating...');
// Transition to 'installing'; terminal result comes via log polling.
action.state = 'installing';
dom.content(el, E('span', { class: 'amlogic-status-info' }, _('Starting update...')));
callStartUpdate(parsed.param).catch(function () {
// Only show error if the trigger call itself fails (rare).
showErr(el, _('Failed to start update'));
action.state = 'done';
});
});
break;
case 'kernel-update':
showSubButton(el, _('Update'), parsed.label, function (ev) {
ev.currentTarget.disabled = true;
ev.currentTarget.value = _('Updating...');
action.state = 'installing';
dom.content(el, E('span', { class: 'amlogic-status-info' }, _('Starting kernel update...')));
callStartKernel().catch(function () {
showErr(el, _('Failed to start kernel update'));
action.state = 'done';
});
});
break;
case 'plugin-update':
showSubButton(el, _('Update'), parsed.label, function (ev) {
ev.currentTarget.disabled = true;
ev.currentTarget.value = _('Updating...');
action.state = 'installing';
dom.content(el, E('span', { class: 'amlogic-status-info' }, _('Starting plugin update...')));
callStartPlugin().catch(function () {
showErr(el, _('Failed to start plugin update'));
action.state = 'done';
});
});
break;
default:
showInfo(el, parsed.label || '');
}
}
// ── Main view ────────────────────────────────────────────────────────────────
return view.extend({
handleSave: null,
handleSaveApply: null,
handleReset: null,
render: function () {
amlogicShared.ensureCss();
// Version display spans (left persistent, filled by state RPC)
var verFirmware = E('span', _('Collecting data...'));
var verPlugin = E('span', _('Collecting data...'));
var verKernel = E('span', _('Collecting data...'));
var verRescue = E('span', _('Collecting data...'));
// Per-action status spans (right side, updated by poll)
var statusFw = E('span');
var statusPl = E('span');
var statusKn = E('span');
var statusRs = E('span');
// State machines
var actPlugin = makeAction('plugin', statusPl);
var actKernel = makeAction('kernel', statusKn);
var actFirmware = makeAction('firmware', statusFw);
var actRescue = makeAction('rescue', statusRs);
// ── Action buttons (left column) ──────────────────────────────────
// Helper: reset an action back to idle so it can be re-run after 'done'.
function resetAction(action, btn, idleLabel) {
action.state = 'idle';
action.subBtn = null;
dom.content(action.statusEl, '');
btn.disabled = false;
btn.value = idleLabel;
}
// Each button triggers the first-phase RPC to start the check/download/install process,
// then the poll handler picks up log lines to advance the state machine and render sub-buttons as needed.
var btnPlugin = E('input', {
type: 'button', class: 'cbi-button cbi-button-reload',
value: _('Only update Amlogic Service'),
click: ui.createHandlerFn(this, function (ev) {
var btn = ev.currentTarget;
// Allow re-run after completion by resetting 'done' → 'idle'
if (actPlugin.state === 'done')
resetAction(actPlugin, btn, _('Only update Amlogic Service'));
if (actPlugin.state !== 'idle') return;
btn.disabled = true;
btn.value = _('Checking...');
actPlugin.state = 'checking';
dom.content(actPlugin.statusEl, '');
return callCheckPlugin('check').then(function () {
btn.disabled = false;
btn.value = _('Only update Amlogic Service');
}).catch(function () {
btn.disabled = false;
btn.value = _('Only update Amlogic Service');
showErr(actPlugin.statusEl, _('Check Failed'));
actPlugin.state = 'idle';
});
})
});
// The kernel and firmware buttons have similar logic, just different RPCs and labels.
var btnKernel = E('input', {
type: 'button', class: 'cbi-button cbi-button-reload',
value: _('Update system kernel only'),
click: ui.createHandlerFn(this, function (ev) {
var btn = ev.currentTarget;
if (actKernel.state === 'done')
resetAction(actKernel, btn, _('Update system kernel only'));
if (actKernel.state !== 'idle') return;
btn.disabled = true;
btn.value = _('Checking...');
actKernel.state = 'checking';
dom.content(actKernel.statusEl, '');
return callCheckKernel('check').then(function () {
btn.disabled = false;
btn.value = _('Update system kernel only');
}).catch(function () {
btn.disabled = false;
btn.value = _('Update system kernel only');
showErr(actKernel.statusEl, _('Check Failed'));
actKernel.state = 'idle';
});
})
});
// Firmware update may include both kernel and plugin updates, so it has a more generic label.
var btnFirmware = E('input', {
type: 'button', class: 'cbi-button cbi-button-reload',
value: _('Complete system update'),
click: ui.createHandlerFn(this, function (ev) {
var btn = ev.currentTarget;
if (actFirmware.state === 'done')
resetAction(actFirmware, btn, _('Complete system update'));
if (actFirmware.state !== 'idle') return;
btn.disabled = true;
btn.value = _('Checking...');
actFirmware.state = 'checking';
dom.content(actFirmware.statusEl, '');
return callCheckFirmware('check').then(function () {
btn.disabled = false;
btn.value = _('Complete system update');
}).catch(function () {
btn.disabled = false;
btn.value = _('Complete system update');
showErr(actFirmware.statusEl, _('Check Failed'));
actFirmware.state = 'idle';
});
})
});
// Rescue button: triggers the rescue RPC which starts mutual recovery; the log will show progress and terminal status.
var btnRescue = E('input', {
type: 'button', class: 'cbi-button cbi-button-reload',
value: _('Rescue the original system kernel'),
click: ui.createHandlerFn(this, function (ev) {
var btn = ev.currentTarget;
if (actRescue.state === 'done')
resetAction(actRescue, btn, _('Rescue the original system kernel'));
if (actRescue.state !== 'idle') return;
btn.disabled = true;
btn.value = _('Rescuing...');
// Set installing state before the call so the poller picks up log lines.
actRescue.state = 'installing';
showInfo(actRescue.statusEl, _('Starting rescue...'));
return callStartRescue().then(function () {
btn.disabled = false;
btn.value = _('Rescue the original system kernel');
}).catch(function () {
btn.disabled = false;
btn.value = _('Rescue the original system kernel');
showErr(actRescue.statusEl, _('Failed to start rescue'));
actRescue.state = 'idle';
});
})
});
// ── Layout ────────────────────────────────────────────────────────
var body = E('div', {}, [
E('h2', _('Check Update')),
E('p', _('Provide OpenWrt Firmware, Kernel and Plugin online check, download and update service.')),
E('div', { class: 'cbi-section' }, [
E('p', { style: 'text-align:center' }, [
_('Update plugins first, then update the kernel or firmware. More options can be configured in [Plugin Settings].')
]),
E('table', { class: 'amlogic-row-table' }, [
E('tr', [
E('td', { width: '35%', align: 'right' }, btnPlugin),
E('td', { width: '65%', align: 'left' }, [verPlugin, ' ', statusPl])
]),
E('tr', [
E('td', { width: '35%', align: 'right' }, btnKernel),
E('td', { width: '65%', align: 'left' }, [verKernel, ' ', statusKn])
]),
E('tr', [
E('td', { width: '35%', align: 'right' }, btnFirmware),
E('td', { width: '65%', align: 'left' }, [verFirmware, ' ', statusFw])
])
])
]),
E('h2', _('Rescue Kernel')),
E('p', _('When a kernel update fails and causes the OpenWrt system to be unbootable, the kernel can be restored by mutual recovery from eMMC/NVMe/sdX.')),
E('div', { class: 'cbi-section' }, [
E('table', { class: 'amlogic-row-table' }, [
E('tr', [
E('td', { width: '35%', align: 'right' }, btnRescue),
E('td', { width: '65%', align: 'left' }, [verRescue, ' ', statusRs])
])
])
])
]);
// ── Populate version spans ────────────────────────────────────────
callState().then(function (s) {
dom.content(verFirmware, s && s.current_firmware_version
? E('span', { class: 'amlogic-status-ok' }, _('Current Version') + ' [ ' + s.current_firmware_version + ' ] ')
: E('span', { class: 'amlogic-status-err' }, _('Invalid value.')));
dom.content(verPlugin, s && s.current_plugin_version
? E('span', { class: 'amlogic-status-ok' }, _('Current Version') + ' [ ' + s.current_plugin_version + ' ] ')
: E('span', { class: 'amlogic-status-err' }, _('Invalid value.')));
dom.content(verKernel, s && s.current_kernel_version
? E('span', { class: 'amlogic-status-ok' }, _('Current Version') + ' [ ' + s.current_kernel_version + ' ] ')
: E('span', { class: 'amlogic-status-err' }, _('Invalid value.')));
// Rescue row: show same kernel version directly via the verRescue span
dom.content(verRescue, s && s.current_kernel_version
? E('span', { class: 'amlogic-status-ok' }, _('Current Version') + ' [ ' + s.current_kernel_version + ' ] ')
: E('span', { class: 'amlogic-status-err' }, _('Invalid value.')));
});
// ── 1 Hz poll ─────────────────────────────────────────────────────
poll.add(function () {
return Promise.all([
handlePoll(actPlugin),
handlePoll(actKernel),
handlePoll(actFirmware),
handlePoll(actRescue)
]);
}, 1);
return body;
}
});
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0
// Plugin Settings
//
// Purpose: configure the `config` section in /etc/config/amlogic via form.Map,
// covering firmware/kernel GitHub repos, tags, version branch, plugin branch,
// keep-config flag, bootloader write flag, and shared partition fs type.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (platform_info, state).
'use strict';
'require view';
'require form';
'require rpc';
'require uci';
// Get platform info; only used to display the device PLATFORM tag.
const callPlatform = rpc.declare({ object: 'luci.amlogic', method: 'platform_info' });
// Query runtime state; kernel_release is used to derive default kernel tag and branch.
const callState = rpc.declare({ object: 'luci.amlogic', method: 'state' });
// This page uses its own Save button; hide LuCI's default Save/Apply/Reset.
return view.extend({
// Load platform info + state in parallel, along with the UCI config (we will read/write it).
load: function () {
return Promise.all([
callPlatform(),
callState(),
uci.load('amlogic')
]);
},
// Render form options bound to the amlogic.config NamedSection, and pre-fill some defaults based on platform/state.
render: function (data) {
const platform = data[0] || {};
const state = data[1] || {};
// Auto-create the section if missing
if (!uci.get('amlogic', 'config')) {
uci.add('amlogic', 'amlogic', 'config');
}
// Build a form.Map bound to the amlogic.config NamedSection, with options for various plugin settings.
const m = new form.Map('amlogic', _('Plugin Settings'),
_('You can customize the github.com download repository of OpenWrt files and kernels in [Online Download Update].') +
'<br />' + _('Tip: The same files as the current OpenWrt system\'s BOARD (such as rock5b) and kernel (such as 5.10) will be downloaded.'));
const o = m.section(form.NamedSection, 'config', 'amlogic');
o.anonymous = true;
// 1. Display device platform
const dev = o.option(form.DummyValue, 'mydevice', _('Current Device:'),
_('Display the PLATFORM classification of the device.'));
dev.cfgvalue = function () { return 'PLATFORM: ' + (platform.platform || 'Unknown'); };
// 2. OpenWrt download repository
const repo = o.option(form.Value, 'amlogic_firmware_repo',
_('OpenWrt download repository:'),
_('Set the OpenWrt files download repository on github.com in [Online Download Update].'));
repo.default = 'https://github.com/breakingbadboy/OpenWrt';
repo.rmempty = false;
// 3. Tags keyword
const tag = o.option(form.Value, 'amlogic_firmware_tag',
_('OpenWrt download tags keyword:'),
_('Set the OpenWrt files download tags keyword for github.com in [Online Download Update].'));
tag.default = 'ARMv8';
tag.rmempty = false;
// 4. File suffix
const suffix = o.option(form.ListValue, 'amlogic_firmware_suffix',
_('OpenWrt files suffix:'),
_('Set the OpenWrt files download suffix for github.com in [Online Download Update].'));
['.7z', '.zip', '.img.gz', '.img.xz'].forEach(function (s) { suffix.value(s, _(s)); });
suffix.default = '.img.gz';
suffix.rmempty = false;
// 5. Kernel download repository
const kpath = o.option(form.ListValue, 'amlogic_kernel_path',
_('Kernel download repository:'),
_('Set the kernel files download repository on github.com in [Online Download Update].'));
kpath.value('https://github.com/breakingbadboy/OpenWrt');
kpath.value('https://github.com/ophub/kernel');
kpath.default = 'https://github.com/breakingbadboy/OpenWrt';
kpath.rmempty = false;
// 6. Kernel tags; available tags depend on the selected kernel repo.
// Default is auto-derived from kernel_release suffixes (-rk3588/-rk35xx).
const currentKpath = uci.get('amlogic', 'config', 'amlogic_kernel_path') ||
'https://github.com/breakingbadboy/OpenWrt';
const knownTags = {
kernel_rk3588: 'kernel_rk3588 [Rockchip RK3588 Kernel]',
kernel_rk35xx: 'kernel_rk35xx [Rockchip RK35xx Kernel]',
kernel_stable: 'kernel_stable [Mainline Stable Kernel]'
};
if (currentKpath.indexOf('ophub/kernel') >= 0) {
knownTags.kernel_flippy = 'kernel_flippy [Mainline Stable Kernel by Flippy]';
knownTags.kernel_beta = 'kernel_beta [Beta Kernel]';
}
// Determine default tag from saved config or kernel_release uname string.
let kernelTagDefault = uci.get('amlogic', 'config', 'amlogic_kernel_tags') || '';
if (!kernelTagDefault) {
const u = state.kernel_release || '';
if (u.indexOf('-rk3588') >= 0) kernelTagDefault = 'kernel_rk3588';
else if (u.indexOf('-rk35xx') >= 0) kernelTagDefault = 'kernel_rk35xx';
else if (u.indexOf('-beta') >= 0) kernelTagDefault = 'kernel_beta';
else kernelTagDefault = 'kernel_stable';
}
const ktags = o.option(form.ListValue, 'amlogic_kernel_tags',
_('Kernel download tags:'),
_('Set the kernel files download tags on github.com in [Online Download Update].'));
Object.keys(knownTags).forEach(function (k) { ktags.value(k, _(knownTags[k])); });
if (!knownTags[kernelTagDefault]) ktags.value(kernelTagDefault, kernelTagDefault);
ktags.default = kernelTagDefault;
ktags.rmempty = false;
// 7. Kernel branch
const kbranch = o.option(form.ListValue, 'amlogic_kernel_branch',
_('Set version branch:'),
_('Set the version branch of the OpenWrt files and kernel selected in [Online Download Update].'));
['5.10', '5.15', '6.1', '6.6', '6.12', '6.18'].forEach(function (b) {
kbranch.value(b, _(b));
});
const m2 = (state.kernel_release || '').match(/^(\d+\.\d+)/);
kbranch.default = m2 ? m2[1] : '5.10';
kbranch.rmempty = false;
// 8. Plugin branch
const pbranch = o.option(form.ListValue, 'amlogic_plugin_branch',
_('Set plugin branch:'),
state.has_luci_js
? _('Set the branch of the luci-app-amlogic plugin used in [Only update Amlogic Service]. Select main for JavaScript version or lua for Lua version.')
: _('Set the branch of the luci-app-amlogic plugin used in [Only update Amlogic Service]. This system does not have JS LuCI, only the Lua branch is available.'));
if (state.has_luci_js)
pbranch.value('main', _('main [JavaScript version]'));
pbranch.value('lua', _('lua [Lua version]'));
pbranch.default = state.has_luci_js ? 'main' : 'lua';
pbranch.rmempty = false;
// 9. Keep config
const fcfg = o.option(form.Flag, 'amlogic_firmware_config',
_('Keep config update:'),
_('Set whether to keep the current config during [Online Download Update] and [Manually Upload Update].'));
fcfg.default = '1';
fcfg.rmempty = false;
// 9. Write bootloader
const wb = o.option(form.Flag, 'amlogic_write_bootloader',
_('Auto write bootloader:'),
_('[Recommended choice] Set whether to auto write bootloader during install and update OpenWrt.'));
wb.default = '0';
wb.rmempty = false;
// 10. Shared partition fs type
const fst = o.option(form.ListValue, 'amlogic_shared_fstype',
_('Set the file system type:'),
_('[Default ext4] Set the file system type of the shared partition (/mnt/mmcblk*p4) when install OpenWrt.'));
['ext4', 'f2fs', 'btrfs', 'xfs'].forEach(function (s) { fst.value(s, _(s)); });
fst.default = 'ext4';
fst.rmempty = false;
return m.render();
}
});
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0
// Plugin landing / info page
//
// Purpose: render the plugin logo, badge links, feature summary, and supported-box list.
// Calls sync_menu on load to update sidebar menu flags from runtime platform detection.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (author, sync_menu, state).
'use strict';
'require view';
'require rpc';
'require view.amlogic.shared as amlogicShared';
// Extract OPENWRT_AUTHOR from /usr/sbin/openwrt-update-amlogic and return the author's GitHub repo URL.
const callAuthor = rpc.declare({
object: 'luci.amlogic',
method: 'author',
expect: { openwrt_author: 'Invalid value.' }
});
// Sync menu_install / menu_armcpu UCI flags from runtime platform detection.
const callSyncMenu = rpc.declare({ object: 'luci.amlogic', method: 'sync_menu' });
// Get runtime state to determine plugin_branch for the language badge.
const callState = rpc.declare({ object: 'luci.amlogic', method: 'state' });
// Static list of supported boxes (display only, not used in any logic).
const SUPPORTED_BOXES = [
_('Amlogic s922x --- [ Beelink, Beelink-Pro, Ugoos-AM6-Plus, ODROID-N2, Khadas-VIM3, Ali-CT2000 ]'),
_('Amlogic s905x3 -- [ X96-Max+, HK1-Box, H96-Max-X3, Ugoos-X3, TX3, X96-Air, A95XF3-Air ]'),
_('Amlogic s905x2 -- [ X96Max-4G, X96Max-2G, MECOOL-KM3-4G, Tanix-Tx5-Max, A95X-F2 ]'),
_('Amlogic s912 ---- [ H96-Pro-Plus, Octopus-Planet, A1, A2, Z6-Plus, TX92, X92, TX8-MAX, TX9-Pro ]'),
_('Amlogic s905x --- [ HG680P, B860H, TBee, T95, TX9, XiaoMI-3S, X96 ]'),
_('Amlogic s905w --- [ X96-Mini, TX3-Mini, W95, X96W/FunTV, MXQ-Pro-4K ]'),
_('Amlogic s905d --- [ Phicomm-N1, MECOOL-KI-Pro, SML-5442TW ]'),
_('Amlogic s905l --- [ UNT402A, M201-S ]'),
_('Amlogic s905l2 -- [ MGV2000, MGV3000, Wojia-TV-IPBS9505, M301A, E900v21E ]'),
_('Amlogic s905l3 -- [ CM211-1, CM311-1, HG680-LC, M401A, UNT400G1, UNT402A, ZXV10-BV310 ]'),
_('Amlogic s905l3a - [ E900V22C/D, CM311-1a-YST, M401A, M411A, UNT403A, UNT413A, IP112H ]'),
_('Amlogic s905l3b - [ CM211-1, CM311-1, E900V22D, E900V21E, E900V22E, M302A/M304A ]'),
_('Amlogic s905 ---- [ Beelink-Mini-MX-2G, Sunvell-T95M, MXQ-Pro+4K, SumaVision-Q5 ]'),
_('Allwinner H6 ---- [ V-Plus Cloud ]'),
_('Rockchip -------- [ BeikeYun, L1-Pro, FastRhino R66S/R68S, Radxa 5B/E25 ]'),
_('Used in KVM ----- [ Can be used in KVM virtual machine of Armbian system. ]')
];
// Open an external link in a new tab; fall back to the current window if the
// browser blocks the popup.
function openExternal(url) {
const w = window.open(url, '_blank', 'noopener,noreferrer');
if (!w) window.location.href = url;
}
// This is a read-only info page, so we disable the top Save / Apply / Reset buttons by setting the handlers to null.
return view.extend({
// Read-only info page: disable the top Save / Apply / Reset buttons.
handleSave: null,
handleSaveApply: null,
handleReset: null,
// On load, call sync_menu to update sidebar menu flags from runtime platform detection. If the sidebar nav is out of
// sync with the server-side menu flags, reload once to pick up the new menu (guarded by #menu-synced hash).
load: function () {
// Call sync_menu; reload once when the sidebar nav disagrees with the
// server-side menu flags in either direction (guarded by #menu-synced hash).
const alreadyReloaded = window.location.hash === '#menu-synced';
const syncMenuPromise = callSyncMenu().then(function (res) {
if (alreadyReloaded || !res) return;
// Check if the sidebar navigation already contains the install link.
const installLink = document.querySelector('a[href*="amlogic/install"]');
// Menu became visible on the server but is absent in the browser nav.
const needShow = (res.show_install === 'yes' && !installLink);
// Menu became hidden on the server but is still present in the browser nav.
const needHide = (res.show_install === 'no' && installLink);
if (needShow || needHide) {
// Server-side index cache updated; reload to pick up new nav.
window.location.replace(window.location.pathname + '#menu-synced');
window.location.reload();
}
}).catch(function () {});
return Promise.all([callAuthor(), syncMenuPromise]).then(function (r) { return r[0]; });
},
// Render the plugin info: logo, badge links, feature summary, and supported-box list.
render: function (authorUrl) {
// Inject the theme stylesheet on first entry so amlogic-* classes work.
amlogicShared.ensureCss();
const res = L.resource('amlogic');
// Language badge image — default to javascript.svg (JS version),
// will be confirmed/corrected after the state RPC resolves.
var langsImg = E('img', {
id: 'Langs',
src: res + '/javascript.svg', alt: 'javascript',
width: '86', height: '20',
style: 'margin:0 5px; vertical-align:middle; border-radius:3px'
});
// Fetch plugin_branch from state and update the language badge.
callState().then(function (s) {
var isLua = s && s.plugin_branch === 'lua';
langsImg.src = res + (isLua ? '/lua.svg' : '/javascript.svg');
langsImg.alt = isLua ? 'lua' : 'javascript';
langsImg.setAttribute('width', isLua ? '46' : '86');
}).catch(function () {});
// Flatten the supported-boxes list to [text, <br>, text, <br>, ...].
const boxRows = [];
for (let i = 0; i < SUPPORTED_BOXES.length; i++)
boxRows.push(SUPPORTED_BOXES[i], E('br'));
return E('div', { class: 'cbi-section' }, [
E('h2', { style: 'border-bottom:none' }, _('Amlogic Service')),
E('p', { style: 'border:none; margin-bottom:0' }, _('Supports management of Amlogic s9xxx, Allwinner (V-Plus Cloud), and Rockchip (BeikeYun, Chainedbox L1 Pro) boxes.')),
E('table', { class: 'amlogic-row-table' }, [
E('tr', [
E('td', { colspan: '2', style: 'text-align:center' }, [
E('img', { src: res + '/logo.png', alt: 'Logo', width: '135' })
])
]),
E('tr', [
E('td', { colspan: '2', style: 'text-align:center' }, [
E('img', {
src: res + '/packit.svg', alt: 'Packit',
width: '168', height: '20',
style: 'cursor:pointer; margin:0 5px; vertical-align:middle; border-radius:3px',
click: function () { openExternal('https://github.com/unifreq/openwrt_packit'); }
}),
E('img', {
src: res + '/author.svg', alt: 'Author',
width: '168', height: '20',
style: 'cursor:pointer; margin:0 5px; vertical-align:middle; border-radius:3px',
click: function () {
callAuthor().then(function (repo) {
let url = String(repo || '').trim();
if (!url) return;
if (url.indexOf('https') === -1)
url = 'https://github.com/' + url;
openExternal(url);
});
}
}),
E('img', {
src: res + '/plugin.svg', alt: 'luci-app-amlogic',
width: '160', height: '20',
style: 'cursor:pointer; margin:0 5px; vertical-align:middle; border-radius:3px',
click: function () { openExternal('https://github.com/ophub/luci-app-amlogic'); }
}),
langsImg
])
]),
E('tr', [
E('td', { width: '20%', align: 'right' }, _('Supported functions:')),
E('td', { width: '80%', align: 'left' },
_('Provide services such as install to EMMC, Update Firmware and Kernel, Backup and Recovery Config, Snapshot management, etc.'))
]),
E('tr', [
E('td', { width: '20%', align: 'right', style: 'vertical-align:top; padding-top:4px' }, _('Supported Boxes:')),
E('td', { width: '80%', align: 'left', style: 'line-height:1.8' }, boxRows)
])
])
]);
}
});
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0
// Install OpenWrt to EMMC
//
// Purpose: let the user select a device model (or enter dtb/soc/uboot manually),
// then invoke start_install and poll the log tail until success or failure.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (model_database, start_install, read_log_tail).
'use strict';
'require view';
'require rpc';
'require ui';
'require dom';
'require poll';
'require view.amlogic.shared as amlogicShared';
// Read the model database (parsed by the backend from /usr/share/amlogic/model_database.txt).
const callDB = rpc.declare({ object: 'luci.amlogic', method: 'model_database',
expect: { entries: [] } });
// Start install; parameter is the composed string "id@dtb:soc:uboot".
const callStartInst = rpc.declare({ object: 'luci.amlogic', method: 'start_install',
params: ['amlogic_install_sel'] });
// Read the last line of the named log (here we use the 'install' log).
const callLogTail = rpc.declare({ object: 'luci.amlogic', method: 'read_log_tail',
params: ['name'], expect: { line: '' } });
// This page uses its own Install button; hide LuCI's default Save/Apply/Reset.
return view.extend({
handleSave: null,
handleSaveApply: null,
handleReset: null,
load: function () {
return callDB();
},
// Render the plugin info: logo, badge links, feature summary, and supported-box list.
render: function (entries) {
amlogicShared.ensureCss();
// Model dropdown: 0 = placeholder, 99 = manual entry, others from database.
const sel = E('select', { style: 'width:auto', name: 'amlogic_soc', id: 'amlogic_soc' });
sel.appendChild(E('option', { value: '0' }, _('Select List')));
(entries || []).forEach(function (e) {
sel.appendChild(E('option', { value: e.id },
'[ ' + e.id + ' ] ' + e.name));
});
sel.appendChild(E('option', { value: '99' }, _('Enter the dtb file name')));
// The three manual fields are only required when the user picks 99.
const dtbInput = E('input', { type: 'text', class: 'cbi-input-text',
style: 'width:235px', id: 'amlogic_dtb' });
const socInput = E('input', { type: 'text', class: 'cbi-input-text',
style: 'width:235px', id: 'amlogic_socname' });
const ubootInput = E('input', { type: 'text', class: 'cbi-input-text',
style: 'width:235px', id: 'amlogic_uboot' });
const trDtb = E('tr', { style: 'display:none' }, [
E('td', { width: '30%', align: 'right' }, _('Enter the dtb file name:')),
E('td', { width: '70%', align: 'left' }, dtbInput)
]);
const trSoc = E('tr', { style: 'display:none' }, [
E('td', { width: '30%', align: 'right' }, _('Enter the soc name:')),
E('td', { width: '70%', align: 'left' }, socInput)
]);
const trUboot = E('tr', { style: 'display:none' }, [
E('td', { width: '30%', align: 'right' }, _('Enter the uboot_overload name:')),
E('td', { width: '70%', align: 'left' }, ubootInput)
]);
sel.addEventListener('change', function () {
// On selection change, dynamically show/hide the three manual rows.
const show = (sel.value === '99') ? '' : 'none';
trDtb.style.display = show;
trSoc.style.display = show;
trUboot.style.display = show;
});
// Status / log line shown next to the install button while it runs.
// 'installing' tracks whether the background install is in progress.
const logSpan = E('span');
var installing = false;
var installDone = false;
const btn = E('input', {
type: 'button', class: 'cbi-button cbi-button-reload',
value: _('Install'),
click: ui.createHandlerFn(this, function (ev) {
if (installing) return;
const text = sel.options[sel.selectedIndex].text;
// Confirm dialog; install proceeds only after user confirms.
if (!confirm(_('You have chosen:') + ' ' + text + ', ' + _('Start install?')))
return;
// dtb falls back to auto_dtb when the manual field is blank.
const dtbVal = dtbInput.value || 'auto_dtb';
const socVal = socInput.value || '';
const ubVal = ubootInput.value || '';
// Compose the "id@dtb:soc:uboot" string the backend expects.
const composed = sel.value + '@' + dtbVal + ':' + socVal + ':' + ubVal;
const target = ev.currentTarget;
target.disabled = true;
target.value = _('Installing...');
installing = true;
installDone = false;
// Fire the background install — result comes via log polling.
return callStartInst(composed).catch(function () {
target.value = _('Install Failed');
target.disabled = false;
installing = false;
});
})
});
// Poll the install log once per second; detect success/failure keywords.
poll.add(function () {
return callLogTail('install').then(function (line) {
if (!line || line === '\n') {
dom.content(logSpan, '');
return;
}
dom.content(logSpan, E('span', { class: 'amlogic-status-info' }, ' ' + line));
if (installing && !installDone) {
if (line.indexOf('Successfully') !== -1 || line.indexOf('successful') !== -1) {
installDone = true;
installing = false;
btn.value = _('Successful Install');
btn.disabled = false;
dom.content(logSpan, E('span', { class: 'amlogic-status-ok' }, ' ' + line));
} else if (line.indexOf('Failed') !== -1 || line.indexOf('failed') !== -1 ||
line.indexOf('Error') !== -1 || line.indexOf('error') !== -1) {
installDone = true;
installing = false;
btn.value = _('Install Failed');
btn.disabled = false;
dom.content(logSpan, E('span', { class: 'amlogic-status-err' }, ' ' + line));
}
}
});
}, 1);
return E('div', {}, [
E('h2', _('Install OpenWrt')),
E('p', _('Install OpenWrt to EMMC, Please select the device model, Or enter the dtb file name.')),
E('div', { class: 'cbi-section' }, [
E('table', { class: 'amlogic-row-table' }, [
E('tr', [
E('td', { width: '35%', align: 'right' }, _('Select the device model:')),
E('td', { width: '65%', align: 'left' }, sel)
]),
trDtb, trSoc, trUboot,
E('tr', [
E('td', { width: '30%', align: 'right' }, _('Install OpenWrt:')),
E('td', { width: '70%', align: 'left' }, [btn, ' ', logSpan])
])
])
])
]);
}
});
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-2.0
// Service log viewer
//
// Purpose: read the full amlogic service log into a read-only textarea and
// refresh it every 2 seconds; provide controls to pause/resume polling, clear
// the log, and download the current contents as a plain-text file.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (read_log_full, del_log).
'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
'require view.amlogic.shared as amlogicShared';
// Read the full log content (entire text).
const callLogFull = rpc.declare({ object: 'luci.amlogic', method: 'read_log_full',
params: ['name'], expect: { content: '' } });
// Clear the service main log.
const callDelLog = rpc.declare({ object: 'luci.amlogic', method: 'del_log' });
// This page uses its own Start/Stop Refresh buttons and does not need the default Save/Apply/Reset buttons,
// so we disable them by setting the handlers to null.
return view.extend({
handleSave: null,
handleSaveApply: null,
handleReset: null,
render: function () {
amlogicShared.ensureCss();
// Read-only, no-wrap, monospace log display area.
const ta = E('textarea', {
rows: '20', readonly: 'readonly', wrap: 'off',
style: 'width:100%; font-family:monospace; white-space:pre'
});
// Status hint: Running / Stopped.
const status = E('span', { class: 'amlogic-status-ok', style: 'margin-left:1em' });
// Polling is enabled by default when the page opens.
let polling = true;
// Refresh the full log and scroll to the bottom.
function refresh() {
return callLogFull('main').then(function (txt) {
ta.value = txt || '';
ta.scrollTop = ta.scrollHeight;
});
}
const view = this;
const stopBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-reset',
value: _('Stop Refresh'),
click: function () { polling = false; status.textContent = _('Stopped'); }
});
const startBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-apply',
value: _('Start Refresh'),
click: function () { polling = true; status.textContent = _('Running'); refresh(); }
});
const cleanBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-remove',
value: _('Clean Log'),
click: ui.createHandlerFn(view, function () {
return callDelLog().then(refresh);
})
});
const dlBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-save',
value: _('Download Log'),
click: function () {
// Wrap textarea content in a Blob and trigger a browser download via a temporary <a>.
const blob = new Blob([ta.value || ''], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'amlogic_service.log';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
});
// Polling: only refresh while polling is true; runs once every 2s.
poll.add(function () { if (polling) return refresh(); }, 2);
refresh();
status.textContent = _('Running');
return E('div', {}, [
E('h2', _('Service Log')),
E('p', _('Display the log of plug-in service operation.')),
E('div', { class: 'cbi-section' }, [ta]),
E('div', { class: 'cbi-page-actions' },
[stopBtn, ' ', startBtn, ' ', cleanBtn, ' ', dlBtn, status])
]);
}
});
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0
// Safe Power Off page
//
// Purpose: call the backend poweroff RPC then run a 5-second on-page countdown;
// switch status to "Powered off" and show a power-off icon when done.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (poweroff).
'use strict';
'require view';
'require rpc';
'require ui';
'require view.amlogic.shared as amlogicShared';
// Trigger the backend poweroff RPC.
const callPoweroff = rpc.declare({ object: 'luci.amlogic', method: 'poweroff' });
// This page uses its own PowerOff button and does not need the default Save/Apply/Reset buttons,
// so we disable them by setting the handlers to null.
return view.extend({
handleSave: null,
handleSaveApply: null,
handleReset: null,
render: function () {
amlogicShared.ensureCss();
// Status text; amlogic-status-err color adapts to dark mode automatically.
const status = E('span', { class: 'amlogic-status-err', style: 'margin-left:1em' });
// Power-off icon, hidden by default; shown only after the countdown ends.
const icon = E('img', {
src: L.resource('amlogic/poweroff.png'),
width: '32', height: '32',
style: 'width:32px; height:32px; max-width:32px; display:none; margin-left:1em; vertical-align:middle'
});
// PowerOff button click handler: confirm twice, then call RPC and start countdown on success.
const btn = E('input', {
type: 'button', class: 'cbi-button cbi-button-remove',
value: _('Perform PowerOff'),
click: ui.createHandlerFn(this, function (ev) {
// Confirm twice to avoid accidental clicks.
if (!confirm(_('Are you sure you want to power off the device?')))
return;
ev.currentTarget.disabled = true;
return callPoweroff().then(function () {
// Show a 5-second countdown; replace with "Powered off" and icon when done.
status.textContent = _('Powering off, please wait...');
let n = 5;
const t = setInterval(function () {
if (--n <= 0) {
clearInterval(t);
status.textContent = _('Powered off.');
icon.style.display = 'inline-block';
} else {
status.textContent = _('Powering off, please wait...') + ' ' + n;
}
}, 1000);
});
})
});
return E('div', {}, [
E('h2', _('Power Off')),
E('p', _('Click the button below to safely power off the device.')),
E('div', { class: 'cbi-section' }, [btn, status, icon])
]);
}
});
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0
// Shared helper module for all luci-app-amlogic JS views.
//
// Purpose: inject the theme-aware stylesheet amlogic.css into <head> on first
// page load so every view can use .amlogic-status-* / .amlogic-snap-* classes
// and prefers-color-scheme dark mode overrides without duplicating inline styles.
'use strict';
'require baseclass';
return baseclass.extend({
// Inject amlogic.css once; guarded by element id to prevent duplicates.
ensureCss: function () {
if (document.getElementById('amlogic-shared-css')) return;
const link = document.createElement('link');
link.id = 'amlogic-shared-css';
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = L.resource('view/amlogic/amlogic.css');
document.head.appendChild(link);
}
});
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0
// Manually Upload Update
//
// Purpose: upload local files (firmware / kernel / ipk / config backup) to the server,
// list them, and trigger install / restore / update actions. Chunked upload uses
// a custom ubus RPC instead of cgi-upload to avoid session ACL issues.
// Backend RPC: /usr/share/rpcd/ucode/luci.amlogic (upload_path, list_uploads, upload_chunk,
// delete_upload, install_upload, start_update, start_kernel, read_log_tail, state).
'use strict';
'require view';
'require rpc';
'require ui';
'require dom';
'require poll';
'require fs';
'require view.amlogic.shared as amlogicShared';
// Query the upload directory the backend allows (default /tmp/upload/, the backend ensures it exists).
const callUploadPath = rpc.declare({ object: 'luci.amlogic', method: 'upload_path', expect: { path: '/tmp/upload/' } });
// List files in the upload dir plus flags about installable firmware/kernel/cfg.
const callList = rpc.declare({ object: 'luci.amlogic', method: 'list_uploads' });
// Delete a named file in the upload dir.
const callDelete = rpc.declare({ object: 'luci.amlogic', method: 'delete_upload', params: ['name'] });
// Install one uploaded file (ipk) or restore a config backup; the backend dispatches by suffix.
const callInstall = rpc.declare({ object: 'luci.amlogic', method: 'install_upload', params: ['name'] });
// Chunked upload via our own RPC (sidesteps cgi-upload's session ACL flow).
const callUploadChunk = rpc.declare({ object: 'luci.amlogic', method: 'upload_chunk', params: ['name', 'data', 'append'] });
// Run the full firmware update flow with parameters auto@updated@/tmp.
const callStartUpd = rpc.declare({ object: 'luci.amlogic', method: 'start_update', params: ['amlogic_update_sel'] });
// Run the kernel-only replace flow.
const callStartKnl = rpc.declare({ object: 'luci.amlogic', method: 'start_kernel' });
// Tail a named log to surface progress during install/update.
const callLogTail = rpc.declare({ object: 'luci.amlogic', method: 'read_log_tail', params: ['name'], expect: { line: '' } });
// Read system state (this page only uses current_firmware_version).
const callState = rpc.declare({ object: 'luci.amlogic', method: 'state' });
// This page uses its own Upload/Install buttons and does not need the default Save/Apply/Reset buttons,
// so we disable them by setting the handlers to null.
return view.extend({
handleSave: null,
handleSaveApply: null,
handleReset: null,
// On load, ensure the CSS is injected and load the upload path from the backend.
load: function () {
amlogicShared.ensureCss();
return callUploadPath();
},
// Render the upload page: file input + upload button, table of existing uploads with Remove + Install/Restore buttons,
// and firmware/kernel update buttons if applicable. Also start polling the install/update log tails.
render: function (path) {
const view = this;
const tableContainer = E('div');
const descSpan = E('span', { class: 'amlogic-status-ok', style: 'font-weight:bold' });
const fwBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-reload',
value: _('Update OpenWrt firmware'), style: 'display:none',
click: ui.createHandlerFn(this, function (ev) {
return doButton(ev.currentTarget,
_('Updating...'), _('Update Failed'),
function () { return callStartUpd('auto@updated@/tmp'); });
})
});
const knBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-reload',
value: _('Replace OpenWrt Kernel'), style: 'display:none; margin-left:5px',
click: ui.createHandlerFn(this, function (ev) {
return doButton(ev.currentTarget,
_('Updating...'), _('Update Failed'),
function () { return callStartKnl(); });
})
});
const fwLog = E('span');
const knLog = E('span');
const fwVer = E('span', _('Collecting data...'));
function doButton(btn, busyText, fail, fn) {
// Shared button state machine: disabled → RPC → busyText or fail label.
// On success (code==0) the button stays in busyText state (device rebooting);
// on failure, re-enable so the user can retry.
btn.disabled = true; btn.value = busyText;
return Promise.resolve(fn()).then(function (r) {
if (!r || r.code !== 0) {
btn.value = fail;
btn.disabled = false;
}
// code == 0 → keep busyText + disabled (update in progress)
}).catch(function () {
btn.value = fail;
btn.disabled = false;
});
}
function refreshList() {
// Refresh the file table; show/hide firmware/kernel update buttons
// based on has_firmware / has_kernel flags from the backend.
return callList().then(function (info) {
dom.content(tableContainer, buildTable(info.items || [], info.path));
let parts = [];
if (info.has_config)
parts.push(_('There are config file in the upload directory, and you can restore the config. '));
if (info.has_kernel)
parts.push(_('There are kernel files in the upload directory, and you can replace the kernel.'));
if (info.has_firmware)
parts.push(_('There are openwrt firmware file in the upload directory, and you can update the openwrt.'));
descSpan.textContent = parts.length ? ' Tip: ' + parts.join('') : '';
fwBtn.style.display = info.has_firmware ? '' : 'none';
knBtn.style.display = info.has_kernel ? '' : 'none';
});
}
// Helper to build the file table with Remove + Install/Restore buttons per row.
function buildTable(items, dir) {
// File table: expose Remove + Install (ipk) / Restore (config backup) per row.
const tbl = E('table', { class: 'table cbi-section-table' }, [
E('tr', { class: 'tr cbi-section-table-titles' }, [
E('th', { class: 'th' }, _('File name')),
E('th', { class: 'th' }, _('Modify time')),
E('th', { class: 'th' }, _('Attributes')),
E('th', { class: 'th' }, _('Size')),
E('th', { class: 'th' }, _('Remove')),
E('th', { class: 'th' }, _('Install'))
])
]);
items.forEach(function (it) {
const removeBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-remove',
value: _('Remove'),
click: ui.createHandlerFn(view, function (ev) {
return callDelete(it.name).then(refreshList);
})
});
let installCell;
if (it.ipk) {
// ipk: "Install" calls install_upload to install one package.
installCell = E('input', {
type: 'button', class: 'cbi-button cbi-button-apply',
value: _('Install'),
click: ui.createHandlerFn(view, function (ev) {
ev.currentTarget.disabled = true;
return callInstall(it.name).then(function (r) {
if (r && r.code !== 0 && r.output)
ui.addNotification(null,
E('p', { class: 'amlogic-status-err' }, [
r.output, E('br'),
E('b', _('Please refresh the page to see the changes.'))
]));
else if (r && r.code === 0)
ui.addNotification(null,
E('p', { class: 'amlogic-status-ok' },
_('Package installed successfully. Please refresh the page to see the changes.')));
return refreshList();
});
})
});
} else if (it.cfg) {
// Config backup (openwrt-config.tar.gz): "Restore" makes the
// backend roll the config back and reboot.
installCell = E('input', {
type: 'button', class: 'cbi-button cbi-button-apply',
value: _('Restore'),
click: ui.createHandlerFn(view, function (ev) {
ev.currentTarget.disabled = true;
ui.addNotification(null,
E('p', { class: 'amlogic-status-ok' },
_('Tip: The config is being restored, and it will automatically restart after completion.')));
return callInstall(it.name);
})
});
} else {
installCell = E('span', '-');
}
tbl.appendChild(E('tr', { class: 'tr cbi-rowstyle-1' }, [
E('td', { class: 'td' }, it.name),
E('td', { class: 'td' }, it.mtime),
E('td', { class: 'td' }, it.modestr),
E('td', { class: 'td' }, it.size),
E('td', { class: 'td' }, removeBtn),
E('td', { class: 'td' }, installCell)
]));
});
if (!items.length)
tbl.appendChild(E('tr', [E('td', { class: 'td', colspan: '6' }, _('No specify upload file.'))]));
return tbl;
}
// Chunked file upload via luci.amlogic.upload_chunk RPC (avoids cgi-upload session ACL).
// 32 KB raw per chunk → ~44 KB base64, safely under the ubus per-request size limit.
const CHUNK_SIZE = 32 * 1024;
const uploadStatus = E('span');
const uploadFileName = E('span', { style: 'margin-right:1em' });
// Encode a Uint8Array as base64 in 32 KB slices to avoid call-stack limits.
function bytesToB64(buf) {
let s = '';
for (let i = 0; i < buf.length; i += 0x8000)
s += String.fromCharCode.apply(null, buf.subarray(i, i + 0x8000));
return btoa(s);
}
// Upload a file in chunks; on success, refresh the file list to show the new upload.
function uploadFile(file, target, basename) {
const total = file.size;
let offset = 0;
let first = true;
function step() {
if (offset >= total) {
uploadStatus.textContent = _('File saved to') + ' ' + target;
return refreshList();
}
const slice = file.slice(offset, offset + CHUNK_SIZE);
return slice.arrayBuffer().then(function (buf) {
const b64 = bytesToB64(new Uint8Array(buf));
const append = !first;
first = false;
return callUploadChunk(basename, b64, append).then(function (r) {
if (!r || !r.ok)
throw new Error((r && r.error) || _('upload chunk failed'));
offset += CHUNK_SIZE;
const pct = Math.min(100, (offset / total) * 100);
uploadStatus.textContent = _('Uploading file…') +
' ' + pct.toFixed(1) + '%';
return step();
});
});
}
return step();
}
// Hidden file input to trigger the file picker dialog; on file selection, start the chunked upload.
const hiddenFileInput = E('input', {
type: 'file', style: 'display:none',
change: ui.createHandlerFn(this, function (ev) {
const file = ev.target.files && ev.target.files[0];
if (!file) return;
const basename = file.name.replace(/^.*[\\\/]/, '');
const target = path.replace(/\/+$/, '') + '/' + basename;
uploadFileName.textContent = basename;
uploadStatus.textContent = _('Uploading file…') + ' 0%';
return uploadFile(file, target, basename).catch(function (e) {
uploadStatus.textContent = _('Create upload file error.') +
' ' + (e && e.message ? e.message : e);
}).finally(function () {
hiddenFileInput.value = '';
});
})
});
const uploadBtn = E('input', {
type: 'button', class: 'cbi-button cbi-button-apply',
value: _('Upload file...'),
click: function () { hiddenFileInput.click(); }
});
// Polling: pull firmware and kernel log tails once per second.
poll.add(function () {
return Promise.all([
callLogTail('firmware').then(function (l) {
dom.content(fwLog, l && l !== '\n'
? E('span', { class: 'amlogic-status-info' }, ' ' + l) : '');
}),
callLogTail('kernel').then(function (l) {
dom.content(knLog, l && l !== '\n'
? E('span', { class: 'amlogic-status-info' }, ' ' + l) : '');
})
]);
}, 1);
callState().then(function (s) {
dom.content(fwVer, s.current_firmware_version
? E('span', { class: 'amlogic-status-ok' }, _('Current Version') + ' [ ' + s.current_firmware_version + ' ] ')
: E('span', { class: 'amlogic-status-err' }, _('Invalid value.')));
});
refreshList();
return E('div', {}, [
E('h2', _('Upload')),
E('p', _('Update plugins first, then update the kernel or firmware.')),
E('p', _('After uploading [Firmware], [Kernel], [IPK] or [Backup Config], the operation buttons will be displayed.')),
E('div', { class: 'cbi-section' }, [
E('label', { style: 'display:inline-block;width:150px' },
_('Choose local file:')),
uploadBtn, ' ', uploadFileName, hiddenFileInput,
E('br'), uploadStatus
]),
E('h3', _('Upload file list')),
E('div', { class: 'cbi-section' }, [descSpan, tableContainer]),
E('div', { class: 'cbi-section' }, [
E('p', { style: 'text-align:center' },
_('After uploading firmware (.img/.img.gz/.img.xz/.7z suffix) or kernel files (3 kernel files), the update button will be displayed.')),
// Single centered cell: when fwBtn / knBtn are hidden the row would otherwise show an empty left column.
E('div', { style: 'text-align:center' },
[fwBtn, knBtn, ' ', fwVer, ' ', fwLog, knLog])
])
]);
}
});
+654
View File
@@ -0,0 +1,654 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: https://github.com/ophub/luci-app-amlogic\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru_RU\n"
"X-Generator: Poedit 2.3.1\n"
msgid "Choose local file:"
msgstr "Выберите локальный файл:"
msgid "Couldn't open file:"
msgstr "Не удалось открыть файл:"
msgid "The file Will download automatically."
msgstr "Файл будет загружен автоматически."
msgid "Create upload file error."
msgstr "Ошибка создания загружаемого файла."
msgid "Download"
msgstr "Скачать"
msgid "Download file"
msgstr "Скачать файл"
msgid "File name"
msgstr "Имя файла"
msgid "File saved to"
msgstr "Файл сохранён в"
msgid "FileTransfer"
msgstr "Передача файлов"
msgid "Install"
msgstr "Установить"
msgid "Attributes"
msgstr "Атрибуты"
msgid "Modify time"
msgstr "Время изменения"
msgid "No specify upload file."
msgstr "Не указан файл для загрузки."
msgid "Path on Route:"
msgstr "Путь на маршрутизаторе:"
msgid "Remove"
msgstr "Удалить"
msgid "Size"
msgstr "Размер"
msgid "Upload"
msgstr "Загрузить"
msgid "Upload file list"
msgstr "Список загруженных файлов"
msgid "There are config file in the upload directory, and you can restore the config. "
msgstr "В каталоге загрузки есть файл конфигурации — вы можете восстановить настройки."
msgid "There are kernel files in the upload directory, and you can replace the kernel."
msgstr "В каталоге загрузки находятся файлы ядра — вы можете заменить ядро."
msgid "There are openwrt firmware file in the upload directory, and you can update the openwrt."
msgstr "В каталоге загрузки есть файл прошивки OpenWrt — вы можете обновить OpenWrt."
msgid "Update plugins first, then update the kernel or firmware."
msgstr "Сначала обновите плагины, затем ядро или прошивку."
msgid "After uploading [Firmware], [Kernel], [IPK] or [Backup Config], the operation buttons will be displayed."
msgstr "После загрузки [прошивки], [ядра], [IPK-пакета] или [резервной конфигурации] появятся соответствующие кнопки управления."
msgid "After uploading firmware (.img/.img.gz/.img.xz/.7z suffix) or kernel files (3 kernel files), the update button will be displayed."
msgstr "После загрузки файлов прошивки (с расширениями .img/.img.gz/.img.xz/.7z) или файлов ядра (3 файла), появится кнопка обновления."
msgid "Amlogic Service"
msgstr "Сервис Amlogic"
msgid "Restore Config / Replace OpenWrt Kernel"
msgstr "Восстановить конфигурацию / Заменить ядро OpenWrt"
msgid "Manually Upload Update"
msgstr "Обновление вручную"
msgid "Install OpenWrt"
msgstr "Установить OpenWrt"
msgid "Select the device model:"
msgstr "Выберите модель устройства:"
msgid "Select List"
msgstr "Выбрать из списка"
msgid "Enter the dtb file name"
msgstr "Введите имя файла DTB"
msgid "Enter the soc name:"
msgstr "Введите имя SoC:"
msgid "Enter the uboot_overload name:"
msgstr "Введите имя uboot_overload:"
msgid "Invalid value."
msgstr "Недопустимое значение."
msgid "Install OpenWrt:"
msgstr "Установка OpenWrt:"
msgid "Install OpenWrt to EMMC, Please select the device model, Or enter the dtb file name."
msgstr "Установка OpenWrt на eMMC. Пожалуйста, выберите модель устройства или введите имя файла DTB."
msgid "Tip: Writing is in progress, and it will automatically restart after completion."
msgstr "Подсказка: идёт запись — после завершения устройство автоматически перезагрузится."
msgid "You have chosen:"
msgstr "Вы выбрали:"
msgid "Start install?"
msgstr "Начать установку?"
msgid "Installing..."
msgstr "Установка..."
msgid "Install Failed"
msgstr "Установка не удалась"
msgid "Successful Install"
msgstr "Установка завершена успешно"
msgid "Update"
msgstr "Обновить"
msgid "Updating..."
msgstr "Обновление..."
msgid "Update Failed"
msgstr "Обновление не удалось"
msgid "Successful Update"
msgstr "Обновление завершено успешно"
msgid "Update OpenWrt firmware"
msgstr "Обновить прошивку OpenWrt"
msgid "kernel"
msgstr "ядро"
msgid "Replace OpenWrt Kernel"
msgstr "Заменить ядро OpenWrt"
msgid "Tip: The kernel is being replaced, and it will automatically restart after completion."
msgstr "Подсказка: идёт замена ядра — после завершения устройство автоматически перезагрузится."
msgid "Supports management of Amlogic s9xxx, Allwinner (V-Plus Cloud), and Rockchip (BeikeYun, Chainedbox L1 Pro) boxes."
msgstr "Поддерживает управление устройствами на базе Amlogic s9xxx, Allwinner (V-Plus Cloud) и Rockchip (BeikeYun, Chainedbox L1 Pro)."
msgid "Supported functions:"
msgstr "Поддерживаемые функции:"
msgid "Provide services such as install to EMMC, Update Firmware and Kernel, Backup and Recovery Config, Snapshot management, etc."
msgstr "Позволяет устанавливать OpenWrt на eMMC, обновлять прошивку и ядро, выполнять резервное копирование и восстановление конфигурации, а также управлять снапшотами (мгновенными снимками системы)."
msgid "Supported Boxes:"
msgstr "Поддерживаемые устройства:"
msgid "Amlogic s922x --- [ Beelink, Beelink-Pro, Ugoos-AM6-Plus, ODROID-N2, Khadas-VIM3, Ali-CT2000 ]"
msgstr "Amlogic s922x — [Beelink, Beelink-Pro, Ugoos-AM6-Plus, ODROID-N2, Khadas-VIM3, Ali-CT2000]"
msgid "Amlogic s905x3 -- [ X96-Max+, HK1-Box, H96-Max-X3, Ugoos-X3, TX3, X96-Air, A95XF3-Air ]"
msgstr "Amlogic s905x3 — [X96-Max+, HK1-Box, H96-Max-X3, Ugoos-X3, TX3, X96-Air, A95XF3-Air]"
msgid "Amlogic s905x2 -- [ X96Max-4G, X96Max-2G, MECOOL-KM3-4G, Tanix-Tx5-Max, A95X-F2 ]"
msgstr "Amlogic s905x2 — [X96Max-4G, X96Max-2G, MECOOL-KM3-4G, Tanix-Tx5-Max, A95X-F2]"
msgid "Amlogic s912 ---- [ H96-Pro-Plus, Octopus-Planet, A1, A2, Z6-Plus, TX92, X92, TX8-MAX, TX9-Pro ]"
msgstr "Amlogic s912 — [H96-Pro-Plus, Octopus-Planet, A1, A2, Z6-Plus, TX92, X92, TX8-MAX, TX9-Pro]"
msgid "Amlogic s905x --- [ HG680P, B860H, TBee, T95, TX9, XiaoMI-3S, X96 ]"
msgstr "Amlogic s905x — [HG680P, B860H, TBee, T95, TX9, XiaoMI-3S, X96]"
msgid "Amlogic s905w --- [ X96-Mini, TX3-Mini, W95, X96W/FunTV, MXQ-Pro-4K ]"
msgstr "Amlogic s905w — [X96-Mini, TX3-Mini, W95, X96W/FunTV, MXQ-Pro-4K]"
msgid "Amlogic s905d --- [ Phicomm-N1, MECOOL-KI-Pro, SML-5442TW ]"
msgstr "Amlogic s905d — [Phicomm-N1, MECOOL-KI-Pro, SML-5442TW]"
msgid "Amlogic s905l --- [ UNT402A, M201-S ]"
msgstr "Amlogic s905l — [UNT402A, M201-S]"
msgid "Amlogic s905l2 -- [ MGV2000, MGV3000, Wojia-TV-IPBS9505, M301A, E900v21E ]"
msgstr "Amlogic s905l2 — [MGV2000, MGV3000, Wojia-TV-IPBS9505, M301A, E900v21E]"
msgid "Amlogic s905l3 -- [ CM211-1, CM311-1, HG680-LC, M401A, UNT400G1, UNT402A, ZXV10-BV310 ]"
msgstr "Amlogic s905l3 — [CM211-1, CM311-1, HG680-LC, M401A, UNT400G1, UNT402A, ZXV10-BV310]"
msgid "Amlogic s905l3a - [ E900V22C/D, CM311-1a-YST, M401A, M411A, UNT403A, UNT413A, IP112H ]"
msgstr "Amlogic s905l3a — [E900V22C/D, CM311-1a-YST, M401A, M411A, UNT403A, UNT413A, IP112H]"
msgid "Amlogic s905l3b - [ CM211-1, CM311-1, E900V22D, E900V21E, E900V22E, M302A/M304A ]"
msgstr "Amlogic s905l3b — [CM211-1, CM311-1, E900V22D, E900V21E, E900V22E, M302A/M304A]"
msgid "Amlogic s905 ---- [ Beelink-Mini-MX-2G, Sunvell-T95M, MXQ-Pro+4K, SumaVision-Q5 ]"
msgstr "Amlogic s905 — [Beelink-Mini-MX-2G, Sunvell-T95M, MXQ-Pro+4K, SumaVision-Q5]"
msgid "Allwinner H6 ---- [ V-Plus Cloud ]"
msgstr "Allwinner H6 — [V-Plus Cloud]"
msgid "Rockchip -------- [ BeikeYun, L1-Pro, FastRhino R66S/R68S, Radxa 5B/E25 ]"
msgstr "Rockchip — [BeikeYun, L1-Pro, FastRhino R66S/R68S, Radxa 5B/E25]"
msgid "Used in KVM ----- [ Can be used in KVM virtual machine of Armbian system. ]"
msgstr "Используется в KVM — [можно использовать в виртуальной машине KVM на базе Armbian]."
msgid "KVM dual system switching"
msgstr "Переключение между двумя системами в KVM"
msgid "You can freely switch between KVM dual partitions, using OpenWrt systems in different partitions."
msgstr "Вы можете свободно переключаться между двумя разделами KVM, используя OpenWrt из разных разделов."
msgid "Switch System"
msgstr "Переключить систему"
msgid "System is switching..."
msgstr "Переключение системы..."
msgid "Waiting for system switching..."
msgstr "Ожидание завершения переключения..."
msgid "System switchover succeeded, restarting..."
msgstr "Переключение успешно, перезагрузка..."
msgid "Are you sure you want to switch systems?"
msgstr "Вы уверены, что хотите переключить систему?"
msgid "Install Ipk"
msgstr "Установить IPK"
msgid "Plugin Settings"
msgstr "Настройки плагина"
msgid "Backup Firmware Config"
msgstr "Резервное копирование конфигурации"
msgid "Backup Config:"
msgstr "Резервная копия конфигурации:"
msgid "Download Backup"
msgstr "Скачать резервную копию"
msgid "Backup OpenWrt config (openwrt_config.tar.gz). Use this file to restore the config in [Manually Upload Update]."
msgstr "Создать резервную копию конфигурации OpenWrt (openwrt_config.tar.gz). Используйте этот файл для восстановления в разделе [Обновление вручную]."
msgid "Edit List:"
msgstr "Редактировать список:"
msgid "Open List"
msgstr "Открыть список"
msgid "Backup Configuration - Custom List"
msgstr "Резервная копия — пользовательский список"
msgid "Write one configuration item per line, and directories should end with a /."
msgstr "По одному элементу на строку. Имена каталогов должны оканчиваться на «/»."
msgid "Restore Backup:"
msgstr "Восстановить из резервной копии:"
msgid "Upload Backup"
msgstr "Загрузить резервную копию"
msgid "Restore"
msgstr "Восстановить"
msgid "Restore Config"
msgstr "Восстановить конфигурацию"
msgid "Restore configuration"
msgstr "Восстановление конфигурации"
msgid "Config File"
msgstr "Файл конфигурации"
msgid "Tip: The config is being restored, and it will automatically restart after completion."
msgstr "Подсказка: идёт восстановление конфигурации — после завершения устройство автоматически перезагрузится."
msgid "Snapshot Management"
msgstr "Управление снапшотами"
msgid "Create Snapshot"
msgstr "Создать снапшот"
msgid "Creating..."
msgstr "Создание..."
msgid "Created successfully"
msgstr "Создано успешно"
msgid "Creation failed"
msgstr "Ошибка создания"
msgid "Initialize Snapshot"
msgstr "Инициализировать снапшот"
msgid "Update Snapshot"
msgstr "Обновить снапшот"
msgid "Restore Snap"
msgstr "Восстановить снапшот"
msgid "Restoring..."
msgstr "Восстановление..."
msgid "Restore Failed"
msgstr "Восстановление не удалось"
msgid "Successfully Restored"
msgstr "Восстановлено успешно"
msgid "Delete Snap"
msgstr "Удалить снапшот"
msgid "You selected a snapshot:"
msgstr "Вы выбрали снапшот:"
msgid "Confirm delete?"
msgstr "Подтвердить удаление?"
msgid "Confirm recovery and restart OpenWrt?"
msgstr "Подтвердить восстановление и перезагрузку OpenWrt?"
msgid "Delete Failed"
msgstr "Удаление не удалось"
msgid "Successfully Deleted"
msgstr "Удалено успешно"
msgid "Create a snapshot of the current system configuration, or restore to a snapshot."
msgstr "Создать снапшот текущей конфигурации системы или восстановиться из снапшота."
msgid "Currently OpenWrt does not support the snapshot function."
msgstr "В текущей версии OpenWrt функция снапшотов не поддерживается."
msgid "Please use this plugin to reinstall or upgrade OpenWrt to enable the snapshot function."
msgstr "Пожалуйста, используйте этот плагин для переустановки или обновления OpenWrt, чтобы включить поддержку снапшотов."
msgid "Deleting..."
msgstr "Удаление..."
msgid "Online Download Update"
msgstr "Обновление через интернет"
msgid "Config Source"
msgstr "Источник конфигурации"
msgid "You can customize the github.com download repository of OpenWrt files and kernels in [Online Download Update]."
msgstr "Вы можете настроить репозиторий на github.com для загрузки файлов OpenWrt и ядер в разделе [Обновление через интернет]."
msgid "Tip: The same files as the current OpenWrt system's BOARD (such as rock5b) and kernel (such as 5.10) will be downloaded."
msgstr "Подсказка: будут загружены файлы, соответствующие текущей платформе (например, rock5b) и версии ядра (например, 5.10)."
msgid "OpenWrt download repository:"
msgstr "Репозиторий OpenWrt:"
msgid "Set the OpenWrt files download repository on github.com in [Online Download Update]."
msgstr "Укажите репозиторий на github.com для загрузки файлов OpenWrt в разделе [Обновление через интернет]."
msgid "OpenWrt download tags keyword:"
msgstr "Ключевое слово тега OpenWrt:"
msgid "Set the OpenWrt files download tags keyword for github.com in [Online Download Update]."
msgstr "Укажите ключевое слово тега для загрузки файлов OpenWrt на github.com в разделе [Обновление через интернет]."
msgid "OpenWrt files suffix:"
msgstr "Расширение файлов OpenWrt:"
msgid "Set the OpenWrt files download suffix for github.com in [Online Download Update]."
msgstr "Укажите расширение файлов OpenWrt, загружаемых с github.com, в разделе [Обновление через интернет]."
msgid "Kernel download repository:"
msgstr "Репозиторий ядра:"
msgid "Set the kernel files download repository on github.com in [Online Download Update]."
msgstr "Укажите репозиторий на github.com для загрузки файлов ядра в разделе [Обновление через интернет]."
msgid "Kernel download tags:"
msgstr "Теги ядра:"
msgid "Set the kernel files download tags on github.com in [Online Download Update]."
msgstr "Укажите теги для загрузки файлов ядра на github.com в разделе [Обновление через интернет]."
msgid "kernel_rk3588 [Rockchip RK3588 Kernel]"
msgstr "kernel_rk3588 [Ядро для Rockchip RK3588]"
msgid "kernel_rk35xx [Rockchip RK35xx Kernel]"
msgstr "kernel_rk35xx [Ядро для Rockchip RK35xx]"
msgid "kernel_stable [Mainline Stable Kernel]"
msgstr "kernel_stable [Стабильное ядро из mainline]"
msgid "kernel_flippy [Mainline Stable Kernel by Flippy]"
msgstr "kernel_flippy [Стабильное ядро от Flippy]"
msgid "kernel_beta [Beta Kernel]"
msgstr "kernel_beta [Бета-версия ядра]"
msgid "Set version branch:"
msgstr "Ветка версии:"
msgid "Set the version branch of the OpenWrt files and kernel selected in [Online Download Update]."
msgstr "Укажите ветку версии для файлов OpenWrt и ядра в разделе [Обновление через интернет]."
msgid "Set plugin branch:"
msgstr "Ветка плагина:"
msgid "Set the branch of the luci-app-amlogic plugin used in [Only update Amlogic Service]. Select main for JavaScript version or lua for Lua version."
msgstr "Укажите ветку плагина luci-app-amlogic для раздела [Только обновить сервис Amlogic]. Выберите main для JavaScript версии или lua для Lua версии."
msgid "main [JavaScript version]"
msgstr "main [JavaScript версия]"
msgid "lua [Lua version]"
msgstr "lua [Lua версия]"
msgid "Keep config update:"
msgstr "Сохранять конфигурацию при обновлении:"
msgid "Set whether to keep the current config during [Online Download Update] and [Manually Upload Update]."
msgstr "Выберите, сохранять ли текущую конфигурацию при обновлении через интернет или вручную."
msgid "Auto write bootloader:"
msgstr "Автоматически записывать bootloader:"
msgid "[Recommended choice] Set whether to auto write bootloader during install and update OpenWrt."
msgstr "[Рекомендуется] Указывает, записывать ли автоматически bootloader при установке или обновлении OpenWrt."
msgid "Set the file system type:"
msgstr "Тип файловой системы:"
msgid "[Default ext4] Set the file system type of the shared partition (/mnt/mmcblk*p4) when install OpenWrt."
msgstr "[По умолчанию ext4] Укажите тип файловой системы для общего раздела (/mnt/mmcblk*p4) при установке OpenWrt."
msgid "Save Config:"
msgstr "Сохранить конфигурацию:"
msgid "Save"
msgstr "Сохранить"
msgid "Check All Components Update"
msgstr "Проверка обновлений"
msgid "Provide OpenWrt Firmware, Kernel and Plugin online check, download and update service."
msgstr "Предоставляет онлайн-проверку, загрузку и обновление прошивки OpenWrt, ядра и плагинов."
msgid "Only update Amlogic Service"
msgstr "Обновить только сервис Amlogic"
msgid "Update system kernel only"
msgstr "Обновить только ядро системы"
msgid "Complete system update"
msgstr "Полное обновление системы"
msgid "Check Update"
msgstr "Проверить обновления"
msgid "Checking..."
msgstr "Проверка..."
msgid "Current Version"
msgstr "Текущая версия"
msgid "Latest Version"
msgstr "Последняя версия"
msgid "Rescue Kernel"
msgstr "Аварийное восстановление ядра"
msgid "When a kernel update fails and causes the OpenWrt system to be unbootable, the kernel can be restored by mutual recovery from eMMC/NVMe/sdX."
msgstr "Если обновление ядра привело к невозможности загрузки OpenWrt, его можно восстановить с другого носителя (eMMC/NVMe/sdX)."
msgid "Rescue the original system kernel"
msgstr "Восстановить исходное ядро системы"
msgid "Rescuing..."
msgstr "Восстановление..."
msgid "Current Device:"
msgstr "Текущее устройство:"
msgid "Display the PLATFORM classification of the device."
msgstr "Отображает класс PLATFORM устройства."
msgid "Update plugins first, then update the kernel or firmware. More options can be configured in [Plugin Settings]."
msgstr "Сначала обновите плагины, затем ядро или прошивку. Дополнительные настройки доступны в [Настройках плагина]."
msgid "Collecting data..."
msgstr "Сбор данных..."
msgid "Server Logs"
msgstr "Журнал операций"
msgid "Display the execution log of the current operation."
msgstr "Отображает журнал выполнения текущей операции."
msgid "Stop Refresh Log"
msgstr "Остановить обновление журнала"
msgid "Start Refresh Log"
msgstr "Начать обновление журнала"
msgid "Clean Log"
msgstr "Очистить журнал"
msgid "Download Log"
msgstr "Скачать журнал"
msgid "CPU Settings"
msgstr "Настройки CPU"
msgid "CPU Freq"
msgstr "Частота CPU"
msgid "CPU Freq Settings"
msgstr "Настройки частоты CPU"
msgid "Set CPU Scaling Governor to Max Performance or Balance Mode"
msgstr "Выберите режим управления частотой CPU: максимальная производительность или баланс."
msgid "CPU Scaling Governor:"
msgstr "Режим управления частотой:"
msgid "ondemand"
msgstr "Ondemand — автоматический баланс"
msgid "performance"
msgstr "Performance — максимальная производительность"
msgid "schedutil"
msgstr "Schedutil — умный планировщик"
msgid "CPU Freq from 48000 to 716000 (Khz)"
msgstr "Диапазон частоты CPU: от 48000 до 716000 КГц"
msgid "Min Freq:"
msgstr "Мин. частота:"
msgid "Max Freq:"
msgstr "Макс. частота:"
msgid "CPU Switching Threshold:"
msgstr "Порог переключения частоты CPU:"
msgid "Kernel make a decision on whether it should increase the frequency (%)"
msgstr "При загрузке CPU выше указанного процента (%), ядро увеличит частоту."
msgid "CPU Switching Sampling rate:"
msgstr "Период проверки частоты CPU:"
msgid "The sampling rate determines how frequently the governor checks to tune the CPU (ms)"
msgstr "Частота проверки загрузки CPU (мс). Примечание: слишком частое переключение может вызвать дрожание задержек в сети."
msgid "Microarchitectures:"
msgstr "Микроархитектура:"
msgid "Loading"
msgstr "Загрузка"
msgid "PowerOff"
msgstr "Выключить"
msgid "Shut down your router device."
msgstr "Выключить устройство маршрутизатора."
msgid "Perform PowerOff"
msgstr "Выполнить выключение"
msgid "Are you sure you want to shut down?"
msgstr "Вы уверены, что хотите выключить устройство?"
msgid "Device is shutting down..."
msgstr "Устройство выключается..."
msgid "Waiting for the device to shut down..."
msgstr "Ожидание завершения выключения..."
msgid "The device has been turned off"
msgstr "Устройство выключено"
msgid "Are you sure you want to power off the device?"
msgstr "Вы уверены, что хотите выключить устройство?"
msgid "Back"
msgstr "Назад"
msgid "Click the button below to safely power off the device."
msgstr "Нажмите кнопку ниже, чтобы безопасно выключить устройство."
msgid "Display the log of plug-in service operation."
msgstr "Показать журнал работы службы плагина."
msgid "Edit list of backup files"
msgstr "Изменить список файлов резервной копии"
msgid "Enter the dtb file name:"
msgstr "Введите имя файла dtb:"
msgid "In this list, you can manually add the file or directory list of files that need to be backed up. Please confirm that the format and content of the file are correct."
msgstr "В этом списке можно вручную добавить файлы или каталоги, которые нужно резервировать. Пожалуйста, проверьте корректность формата и содержимого."
msgid "Please refresh the page to see the changes."
msgstr "Пожалуйста, обновите страницу, чтобы увидеть изменения."
msgid "Power Off"
msgstr "Выключить"
msgid "Powered off."
msgstr "Выключено."
msgid "Powering off, please wait..."
msgstr "Выключение, пожалуйста, подождите..."
msgid "Running"
msgstr "Работает"
msgid "Save Failed"
msgstr "Ошибка сохранения"
msgid "Service Log"
msgstr "Журнал службы"
msgid "Start Refresh"
msgstr "Начать обновление"
msgid "Stop Refresh"
msgstr "Остановить обновление"
msgid "Stopped"
msgstr "Остановлено"
msgid "Successfully saved."
msgstr "Сохранено успешно."
msgid "Upload file..."
msgstr "Загрузить файл..."
+654
View File
@@ -0,0 +1,654 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: https://github.com/ophub/luci-app-amlogic \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_CN\n"
"X-Generator: Poedit 2.3.1\n"
msgid "Choose local file:"
msgstr "选择本地文件:"
msgid "Couldn't open file:"
msgstr "无法打开文件:"
msgid "The file Will download automatically."
msgstr "文件将自动下载"
msgid "Create upload file error."
msgstr "创建上传文件失败。"
msgid "Download"
msgstr "下载"
msgid "Download file"
msgstr "下载文件"
msgid "File name"
msgstr "文件名"
msgid "File saved to"
msgstr "文件保存到"
msgid "FileTransfer"
msgstr "文件传输"
msgid "Install"
msgstr "安装"
msgid "Attributes"
msgstr "属性"
msgid "Modify time"
msgstr "修改时间"
msgid "No specify upload file."
msgstr "未指定上传文件。"
msgid "Path on Route:"
msgstr "路由根目录:"
msgid "Remove"
msgstr "移除"
msgid "Size"
msgstr "大小"
msgid "Upload"
msgstr "上传"
msgid "Upload file list"
msgstr "上传文件列表"
msgid "There are config file in the upload directory, and you can restore the config. "
msgstr "上传文件列表中有配置文件,你可以恢复配置。"
msgid "There are kernel files in the upload directory, and you can replace the kernel."
msgstr "上传文件列表中有内核文件,你可以升级内核。"
msgid "There are openwrt firmware file in the upload directory, and you can update the openwrt."
msgstr "上传文件列表中有 OpenWrt 固件文件,你可以升级 OpenWrt。"
msgid "Update plugins first, then update the kernel or firmware."
msgstr "首先更新插件,再更新内核或固件。"
msgid "After uploading [Firmware], [Kernel], [IPK] or [Backup Config], the operation buttons will be displayed."
msgstr "当上传 [固件文件],[内核文件],[IPK文件],[配置文件] 后,将自动显示相关操作按钮。"
msgid "After uploading firmware (.img/.img.gz/.img.xz/.7z suffix) or kernel files (3 kernel files), the update button will be displayed."
msgstr "当上传文件列表中有 OpenWrt 固件(.img/.img.gz/.img.xz/.7z 后缀)或内核文件(3个内核文件)时,将自动显示相关操作按钮。"
msgid "Amlogic Service"
msgstr "晶晨宝盒"
msgid "Restore Config / Replace OpenWrt Kernel"
msgstr "恢复配置 / 升级 OpenWrt 内核"
msgid "Manually Upload Update"
msgstr "手动上传更新"
msgid "Install OpenWrt"
msgstr "安装 OpenWrt"
msgid "Select the device model:"
msgstr "选择设备型号:"
msgid "Select List"
msgstr "选择列表"
msgid "Enter the dtb file name"
msgstr "输入自定义 dtb 文件名"
msgid "Enter the soc name:"
msgstr "输入自定义 soc 名称:"
msgid "Enter the uboot_overload name:"
msgstr "输入自定义 uboot_overload 名称:"
msgid "Invalid value."
msgstr "无效值"
msgid "Install OpenWrt:"
msgstr "安装 OpenWrt:"
msgid "Install OpenWrt to EMMC, Please select the device model, Or enter the dtb file name."
msgstr "将 OpenWrt 安装到 EMMC,请选择设备型号,或输入 dtb 文件名。"
msgid "Tip: Writing is in progress, and it will automatically restart after completion."
msgstr "提示:正在写入,完成后将自动重启。"
msgid "You have chosen:"
msgstr "你选择了:"
msgid "Start install?"
msgstr "开始安装吗?"
msgid "Installing..."
msgstr "正在安装..."
msgid "Install Failed"
msgstr "安装失败"
msgid "Successful Install"
msgstr "安装成功"
msgid "Update"
msgstr "升级"
msgid "Updating..."
msgstr "正在更新..."
msgid "Update Failed"
msgstr "更新失败"
msgid "Successful Update"
msgstr "更新成功"
msgid "Update OpenWrt firmware"
msgstr "升级 OpenWrt 固件"
msgid "kernel"
msgstr "内核"
msgid "Replace OpenWrt Kernel"
msgstr "更换 OpenWrt 内核"
msgid "Tip: The kernel is being replaced, and it will automatically restart after completion."
msgstr "提示:正在替换内核,完成后将自动重启。"
msgid "Supports management of Amlogic s9xxx, Allwinner (V-Plus Cloud), and Rockchip (BeikeYun, Chainedbox L1 Pro) boxes."
msgstr "支持对晶晨 s9xxx 系列(斐讯N1、HK1等),全志(微加云),以及瑞芯微(贝壳云、我家云)的盒子进行在线管理。"
msgid "Supported functions:"
msgstr "支持的功能:"
msgid "Provide services such as install to EMMC, Update Firmware and Kernel, Backup and Recovery Config, Snapshot management, etc."
msgstr "支持将 OpenWrt 系统写入 EMMC、升级固件与内核,备份与恢复自定义配置,快照管理等功能。"
msgid "Supported Boxes:"
msgstr "支持的盒子:"
msgid "Amlogic s922x --- [ Beelink, Beelink-Pro, Ugoos-AM6-Plus, ODROID-N2, Khadas-VIM3, Ali-CT2000 ]"
msgstr "晶晨 s922x --- [ Beelink, Beelink-Pro, Ugoos-AM6-Plus, ODROID-N2, Khadas-VIM3, Ali-CT2000 ]"
msgid "Amlogic s905x3 -- [ X96-Max+, HK1-Box, H96-Max-X3, Ugoos-X3, TX3, X96-Air, A95XF3-Air ]"
msgstr "晶晨 s905x3 -- [ X96-Max+, HK1-Box, H96-Max-X3, Ugoos-X3, TX3, X96-Air, A95XF3-Air ]"
msgid "Amlogic s905x2 -- [ X96Max-4G, X96Max-2G, MECOOL-KM3-4G, Tanix-Tx5-Max, A95X-F2 ]"
msgstr "晶晨 s905x2 -- [ X96Max-4G, X96Max-2G, MECOOL-KM3-4G, Tanix-Tx5-Max, A95X-F2 ]"
msgid "Amlogic s912 ---- [ H96-Pro-Plus, Octopus-Planet, A1, A2, Z6-Plus, TX92, X92, TX8-MAX, TX9-Pro ]"
msgstr "晶晨 s912 ---- [ H96-Pro-Plus, 章鱼星球, A1, A2, Z6-Plus, TX92, X92, TX8-MAX, TX9-Pro ]"
msgid "Amlogic s905x --- [ HG680P, B860H, TBee, T95, TX9, XiaoMI-3S, X96 ]"
msgstr "晶晨 s905x --- [ 烽火HG680, 中兴B860H, TBee, T95, TX9, XiaoMI-3S, X96 ]"
msgid "Amlogic s905w --- [ X96-Mini, TX3-Mini, W95, X96W/FunTV, MXQ-Pro-4K ]"
msgstr "晶晨 s905w --- [ X96-Mini, TX3-Mini, W95, X96W/FunTV, MXQ-Pro-4K ]"
msgid "Amlogic s905d --- [ Phicomm-N1, MECOOL-KI-Pro, SML-5442TW ]"
msgstr "晶晨 s905d --- [ 斐讯-N1, MECOOL-KI-Pro, SML-5442TW ]"
msgid "Amlogic s905l --- [ UNT402A, M201-S ]"
msgstr "晶晨 s905l --- [ UNT402A, M201-S ]"
msgid "Amlogic s905l2 -- [ MGV2000, MGV3000, Wojia-TV-IPBS9505, M301A, E900v21E ]"
msgstr "晶晨 s905l2 -- [ MGV2000, MGV3000, Wojia-TV-IPBS9505, M301A, E900v21E ]"
msgid "Amlogic s905l3 -- [ CM211-1, CM311-1, HG680-LC, M401A, UNT400G1, UNT402A, ZXV10-BV310 ]"
msgstr "晶晨 s905l3 -- [ CM211-1, CM311-1, HG680-LC, M401A, UNT400G1, UNT402A, ZXV10-BV310 ]"
msgid "Amlogic s905l3a - [ E900V22C/D, CM311-1a-YST, M401A, M411A, UNT403A, UNT413A, IP112H ]"
msgstr "晶晨 s905l3a - [ E900V22C/D, CM311-1a-YST, M401A, M411A, UNT403A, UNT413A, IP112H ]"
msgid "Amlogic s905l3b - [ CM211-1, CM311-1, E900V22D, E900V21E, E900V22E, M302A/M304A ]"
msgstr "晶晨 s905l3b - [ CM211-1, CM311-1, E900V22D, E900V21E, E900V22E, M302A/M304A ]"
msgid "Amlogic s905 ---- [ Beelink-Mini-MX-2G, Sunvell-T95M, MXQ-Pro+4K, SumaVision-Q5 ]"
msgstr "晶晨 s905 ---- [ Beelink-Mini-MX-2G, Sunvell-T95M, MXQ-Pro+4K, SumaVision-Q5 ]"
msgid "Allwinner H6 ---- [ V-Plus Cloud ]"
msgstr "全志 H6 ------ [ 微加云 ]"
msgid "Rockchip -------- [ BeikeYun, L1-Pro, FastRhino R66S/R68S, Radxa 5B/E25 ]"
msgstr "瑞芯微 ------- [ 贝壳云, 我家云, 电犀牛R66S/R68S, 瑞莎5B/E25 ]"
msgid "Used in KVM ----- [ Can be used in KVM virtual machine of Armbian system. ]"
msgstr "KVM 中使用 --- [ 可以在 Armbian 系统的 KVM 虚拟机中使用。 ]"
msgid "KVM dual system switching"
msgstr "KVM 双系统切换"
msgid "You can freely switch between KVM dual partitions, using OpenWrt systems in different partitions."
msgstr "您可以在 KVM 双分区之间自由切换,使用不同分区中的 OpenWrt 系统。"
msgid "Switch System"
msgstr "切换系统"
msgid "System is switching..."
msgstr "系统切换中..."
msgid "Waiting for system switching..."
msgstr "等待系统完成切换..."
msgid "System switchover succeeded, restarting..."
msgstr "系统切换成功,正在重启..."
msgid "Are you sure you want to switch systems?"
msgstr "你确定要切换系统吗?"
msgid "Install Ipk"
msgstr "安装Ipk"
msgid "Plugin Settings"
msgstr "插件设置"
msgid "Backup Firmware Config"
msgstr "备份固件配置"
msgid "Backup Config:"
msgstr "备份配置:"
msgid "Download Backup"
msgstr "下载备份"
msgid "Backup OpenWrt config (openwrt_config.tar.gz). Use this file to restore the config in [Manually Upload Update]."
msgstr "备份当前 OpenWrt 的相关配置信息(openwrt_config.tar.gz)。使用此文件可在 [手动上传更新] 中恢复配置。"
msgid "Edit List:"
msgstr "编辑列表:"
msgid "Open List"
msgstr "打开列表"
msgid "Backup Configuration - Custom List"
msgstr "备份配置 - 自定义列表"
msgid "Write one configuration item per line, and directories should end with a /."
msgstr "每行写一个配置项,目录需要以/结尾。"
msgid "Restore Backup:"
msgstr "恢复备份:"
msgid "Upload Backup"
msgstr "上传备份"
msgid "Restore"
msgstr "恢复"
msgid "Restore Config"
msgstr "恢复配置"
msgid "Restore configuration"
msgstr "还原配置"
msgid "Config File"
msgstr "配置文件"
msgid "Tip: The config is being restored, and it will automatically restart after completion."
msgstr "提示:正在还原配置,完成后将自动重启。"
msgid "Snapshot Management"
msgstr "快照管理"
msgid "Create Snapshot"
msgstr "创建快照"
msgid "Creating..."
msgstr "正在创建"
msgid "Created successfully"
msgstr "创建成功"
msgid "Creation failed"
msgstr "创建失败"
msgid "Initialize Snapshot"
msgstr "初始化快照"
msgid "Update Snapshot"
msgstr "更新点快照"
msgid "Restore Snap"
msgstr "还原快照"
msgid "Restoring..."
msgstr "正在还原"
msgid "Restore Failed"
msgstr "还原失败"
msgid "Successfully Restored"
msgstr "还原成功"
msgid "Delete Snap"
msgstr "删除快照"
msgid "You selected a snapshot:"
msgstr "你选择了快照:"
msgid "Confirm delete?"
msgstr "确定删除吗?"
msgid "Confirm recovery and restart OpenWrt?"
msgstr "确定恢复并重启 OpenWrt 吗?"
msgid "Delete Failed"
msgstr "删除失败"
msgid "Successfully Deleted"
msgstr "删除成功"
msgid "Create a snapshot of the current system configuration, or restore to a snapshot."
msgstr "创建当前系统配置的快照,或还原到某个快照。"
msgid "Currently OpenWrt does not support the snapshot function."
msgstr "当前 OpenWrt 不支持快照功能。"
msgid "Please use this plugin to reinstall or upgrade OpenWrt to enable the snapshot function."
msgstr "请使用本插件重新安装或升级 OpenWrt 开启快照功能。"
msgid "Deleting..."
msgstr "正在删除..."
msgid "Online Download Update"
msgstr "在线下载更新"
msgid "Config Source"
msgstr "配置来源"
msgid "You can customize the github.com download repository of OpenWrt files and kernels in [Online Download Update]."
msgstr "您可以自定义 [在线下载和更新] 中 OpenWrt 固件和内核的 github.com 下载仓库。"
msgid "Tip: The same files as the current OpenWrt system's BOARD (such as rock5b) and kernel (such as 5.10) will be downloaded."
msgstr "提示:将下载与当前 OpenWrt 系统的 BOARD(如:rock5b)和内核(如:5.10)相同的文件。"
msgid "OpenWrt download repository:"
msgstr "OpenWrt 文件的下载仓库:"
msgid "Set the OpenWrt files download repository on github.com in [Online Download Update]."
msgstr "设置 [在线下载更新] 中 github.com 的 OpenWrt 文件的下载仓库。"
msgid "OpenWrt download tags keyword:"
msgstr "OpenWrt 下载标签关键字:"
msgid "Set the OpenWrt files download tags keyword for github.com in [Online Download Update]."
msgstr "设置 [在线下载更新] 中 github.com 的 OpenWrt 文件下载标签关键字。"
msgid "OpenWrt files suffix:"
msgstr "OpenWrt 文件的后缀:"
msgid "Set the OpenWrt files download suffix for github.com in [Online Download Update]."
msgstr "设置 [在线下载更新] 中 github.com 的 OpenWrt 文件的后缀。"
msgid "Kernel download repository:"
msgstr "内核的下载仓库:"
msgid "Set the kernel files download repository on github.com in [Online Download Update]."
msgstr "设置 [在线下载更新] 中 github.com 的内核文件下载仓库。"
msgid "Kernel download tags:"
msgstr "内核的下载标签:"
msgid "Set the kernel files download tags on github.com in [Online Download Update]."
msgstr "设置 [在线下载更新] 中 github.com 的内核文件下载标签。"
msgid "kernel_rk3588 [Rockchip RK3588 Kernel]"
msgstr "kernel_rk3588 [Rockchip RK3588 内核]"
msgid "kernel_rk35xx [Rockchip RK35xx Kernel]"
msgstr "kernel_rk35xx [Rockchip RK35xx 内核]"
msgid "kernel_stable [Mainline Stable Kernel]"
msgstr "kernel_stable [主线稳定内核]"
msgid "kernel_flippy [Mainline Stable Kernel by Flippy]"
msgstr "kernel_flippy [Flippy 的主线稳定内核]"
msgid "kernel_beta [Beta Kernel]"
msgstr "kernel_beta [测试版内核]"
msgid "Set version branch:"
msgstr "设置版本分支:"
msgid "Set the version branch of the OpenWrt files and kernel selected in [Online Download Update]."
msgstr "设置 [在线下载更新] 时 OpenWrt 文件与内核所选用的版本分支。"
msgid "Set plugin branch:"
msgstr "设置插件分支:"
msgid "Set the branch of the luci-app-amlogic plugin used in [Only update Amlogic Service]. Select main for JavaScript version or lua for Lua version."
msgstr "设置 [仅更新晶晨宝盒] 时使用的插件分支。选择 main 使用 JavaScript 版本,选择 lua 使用 Lua 版本。"
msgid "main [JavaScript version]"
msgstr "main [JavaScript 版本]"
msgid "lua [Lua version]"
msgstr "lua [Lua 版本]"
msgid "Keep config update:"
msgstr "保留配置更新:"
msgid "Set whether to keep the current config during [Online Download Update] and [Manually Upload Update]."
msgstr "设置是否在 [在线下载更新] 和 [手动上传更新] 时保留当前的配置。"
msgid "Auto write bootloader:"
msgstr "自动写入 bootloader:"
msgid "[Recommended choice] Set whether to auto write bootloader during install and update OpenWrt."
msgstr "[推荐选择] 设置在安装和更新 OpenWrt 固件时是否自动写入 bootloader 文件。"
msgid "Set the file system type:"
msgstr "设置文件系统类型:"
msgid "[Default ext4] Set the file system type of the shared partition (/mnt/mmcblk*p4) when install OpenWrt."
msgstr "[默认 ext4] 设置安装 OpenWrt 时共享分区 (/mnt/mmcblk*p4) 的文件系统类型。"
msgid "Save Config:"
msgstr "保存配置:"
msgid "Save"
msgstr "保存"
msgid "Check All Components Update"
msgstr "插件和内核检查更新"
msgid "Provide OpenWrt Firmware, Kernel and Plugin online check, download and update service."
msgstr "提供 OpenWrt 固件,内核和插件在线检查,下载和更新服务。"
msgid "Only update Amlogic Service"
msgstr "仅更新晶晨宝盒"
msgid "Update system kernel only"
msgstr "仅更新系统内核"
msgid "Complete system update"
msgstr "完整更新全系统"
msgid "Check Update"
msgstr "检查更新"
msgid "Checking..."
msgstr "正在检查更新..."
msgid "Current Version"
msgstr "当前版本"
msgid "Latest Version"
msgstr "最新版本"
msgid "Rescue Kernel"
msgstr "救援内核"
msgid "When a kernel update fails and causes the OpenWrt system to be unbootable, the kernel can be restored by mutual recovery from eMMC/NVMe/sdX."
msgstr "当内核更新失败造成 OpenWrt 系统无法启动时,可以从 eMMC/NVME/sdX 相互恢复内核。"
msgid "Rescue the original system kernel"
msgstr "救援原系统内核"
msgid "Rescuing..."
msgstr "正在救援..."
msgid "Current Device:"
msgstr "当前设备:"
msgid "Display the PLATFORM classification of the device."
msgstr "显示设备的 PLATFORM 分类。"
msgid "Update plugins first, then update the kernel or firmware. More options can be configured in [Plugin Settings]."
msgstr "首先更新插件,再更新内核或固件。更多选项可以在插件设置中配置。"
msgid "Collecting data..."
msgstr "正在收集数据…"
msgid "Server Logs"
msgstr "操作日志"
msgid "Display the execution log of the current operation."
msgstr "显示当前操作的执行日志。"
msgid "Stop Refresh Log"
msgstr "停止刷新"
msgid "Start Refresh Log"
msgstr "开始刷新"
msgid "Clean Log"
msgstr "清理日志"
msgid "Download Log"
msgstr "下载日志"
msgid "CPU Settings"
msgstr "CPU 设置"
msgid "CPU Freq"
msgstr "CPU 性能优化调节"
msgid "CPU Freq Settings"
msgstr "CPU 性能优化调节设置"
msgid "Set CPU Scaling Governor to Max Performance or Balance Mode"
msgstr "设置路由器的 CPU 性能模式(高性能/均衡省电)"
msgid "CPU Scaling Governor:"
msgstr "CPU 调速器:"
msgid "ondemand"
msgstr "Ondemand 自动平衡模式"
msgid "performance"
msgstr "Performance 高性能模式"
msgid "schedutil"
msgstr "Schedutil 敏捷调度器模式"
msgid "CPU Freq from 48000 to 716000 (Khz)"
msgstr "CPU 频率范围为 48000 到 716000 (Khz)"
msgid "Min Freq:"
msgstr "最小频率:"
msgid "Max Freq:"
msgstr "最大频率:"
msgid "CPU Switching Threshold:"
msgstr "CPU 切换频率触发阈值:"
msgid "Kernel make a decision on whether it should increase the frequency (%)"
msgstr "当 CPU 占用率超过 (%) 的情况下触发内核切换频率"
msgid "CPU Switching Sampling rate:"
msgstr "CPU 切换周期:"
msgid "The sampling rate determines how frequently the governor checks to tune the CPU (ms)"
msgstr "CPU 检查切换的周期 (ms) 。注意:过于频繁的切换频率会引起网络延迟抖动"
msgid "Microarchitectures:"
msgstr "微架构:"
msgid "Loading"
msgstr "载入中"
msgid "PowerOff"
msgstr "关机"
msgid "Shut down your router device."
msgstr "关闭你的路由器设备。"
msgid "Perform PowerOff"
msgstr "执行关机"
msgid "Are you sure you want to shut down?"
msgstr "你确定要关机吗?"
msgid "Device is shutting down..."
msgstr "设备正在关机..."
msgid "Waiting for the device to shut down..."
msgstr "等待设备关机..."
msgid "The device has been turned off"
msgstr "设备已经关机"
msgid "Are you sure you want to power off the device?"
msgstr "确定要关闭设备吗?"
msgid "Back"
msgstr "返回"
msgid "Click the button below to safely power off the device."
msgstr "点击下方的按钮可以安全地关闭设备。"
msgid "Display the log of plug-in service operation."
msgstr "显示插件服务运行日志。"
msgid "Edit list of backup files"
msgstr "编辑备份文件列表"
msgid "Enter the dtb file name:"
msgstr "输入 dtb 文件名:"
msgid "In this list, you can manually add the file or directory list of files that need to be backed up. Please confirm that the format and content of the file are correct."
msgstr "在此列表中,您可以手动添加需要备份的文件或目录。请确认文件的格式和内容正确。"
msgid "Please refresh the page to see the changes."
msgstr "请刷新页面以查看更改。"
msgid "Power Off"
msgstr "关闭设备"
msgid "Powered off."
msgstr "已关机。"
msgid "Powering off, please wait..."
msgstr "正在关机,请稍候……"
msgid "Running"
msgstr "运行中"
msgid "Save Failed"
msgstr "保存失败"
msgid "Service Log"
msgstr "服务日志"
msgid "Start Refresh"
msgstr "开始刷新"
msgid "Stop Refresh"
msgstr "停止刷新"
msgid "Stopped"
msgstr "已停止"
msgid "Successfully saved."
msgstr "保存成功。"
msgid "Upload file..."
msgstr "上传文件……"
+18
View File
@@ -0,0 +1,18 @@
config amlogic 'config'
option enable '1'
option amlogic_firmware_repo 'https://github.com/breakingbadboy/OpenWrt'
option amlogic_firmware_tag 'ARMv8'
option amlogic_firmware_suffix '.img.gz'
option amlogic_kernel_path 'https://github.com/ophub/kernel'
option amlogic_kernel_tags ''
option amlogic_kernel_branch ''
option amlogic_plugin_branch 'main'
option amlogic_firmware_config '1'
option amlogic_write_bootloader '0'
option amlogic_shared_fstype 'ext4'
config settings 'armcpu'
option governor0 'schedutil'
option minfreq0 '500000'
option maxfreq0 '1512000'
+62
View File
@@ -0,0 +1,62 @@
#!/bin/sh /etc/rc.common
START=60
NAME=amlogic
uci_get_by_type() {
local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
echo ${ret:=$3}
}
uci_set_by_type() {
uci set $NAME.@$1[0].$2=$3 2>/dev/null
uci commit $NAME
}
start() {
[ -x "/usr/sbin/fixcpufreq.pl" ] && /usr/sbin/fixcpufreq.pl && sync
local cpu_policys=$(ls /sys/devices/system/cpu/cpufreq 2>/dev/null | grep -E 'policy[0-9]{1,3}' | xargs)
if [ "${cpu_policys}" = "" ]; then
cpu_policys="policy0"
fi
config_load $NAME
for policy_name in ${cpu_policys}; do
local policy_id="${policy_name//policy/}"
# Get an optional value list for the current device
local governor_list="$(cat /sys/devices/system/cpu/cpufreq/${policy_name}/scaling_available_frequencies 2>/dev/null | xargs)"
local second_place_order="$(echo ${governor_list} | awk '{print $1}')"
local second_place_reverse="$(echo ${governor_list} | awk '{print $NF}')"
# Get the default value in the Config file
local governor=$(uci_get_by_type settings governor${policy_id} schedutil)
local minfreq=$(uci_get_by_type settings minfreq${policy_id} ${second_place_order})
local maxfreq=$(uci_get_by_type settings maxfreq${policy_id} ${second_place_reverse})
# Update result to the corresponding file
echo $governor >/sys/devices/system/cpu/cpufreq/${policy_name}/scaling_governor
echo $minfreq >/sys/devices/system/cpu/cpufreq/${policy_name}/scaling_min_freq
echo $maxfreq >/sys/devices/system/cpu/cpufreq/${policy_name}/scaling_max_freq
# If the governor is ondemand, configure its specific parameters.
if [ "$governor" = "ondemand" ]; then
local ondemand_dir="/sys/devices/system/cpu/cpufreq/${policy_name}/ondemand"
# Check if the per-policy ondemand directory exists.
if [ -d "$ondemand_dir" ]; then
# Read ondemand parameters from UCI, or use default values (e.g., 80 and 20) if not set.
local up_threshold=$(uci_get_by_type settings up_threshold${policy_id} 80)
local sampling_down_factor=$(uci_get_by_type settings sampling_down_factor${policy_id} 20)
# Write the values to the system files.
echo $up_threshold > "${ondemand_dir}/up_threshold"
echo $sampling_down_factor > "${ondemand_dir}/sampling_down_factor"
fi
fi
done
}
reload() {
start
return 0
}
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
# luci-app-amlogic-js: post-install configuration tweaks.
# Detect running platform from /etc/flippy-openwrt-release. The original Lua
# controller hid the "Install OpenWrt" menu unless platform=="amlogic" or
# SHOW_INSTALL_MENU=="yes", and hid "CPU Settings" on the qemu platform.
# Since menu.d JSON cannot run shell logic, we mirror those checks once here
# and store the result as uci flags that the menu entries depend on.
PLATFORM=$(grep -E '^PLATFORM=' /etc/flippy-openwrt-release 2>/dev/null | \
awk -F= '{print $2}' | tr -d '"' | xargs)
SHOW_INSTALL=$(grep -E '^SHOW_INSTALL_MENU=' /etc/flippy-openwrt-release 2>/dev/null | \
awk -F= '{print $2}' | tr -d '"' | xargs)
# Detect whether the root fs is already on eMMC (mmcblk*p* with device/type == "MMC").
# Mirrors root_on_internal_storage() in luci.amlogic: all other boot media
# (TF/SD, NVMe, SATA, USB) keep the install menu visible.
ROOT_PT=$(df / | tail -n1 | awk '{print $1}' | awk -F '/' '{print $3}')
IS_INSTALLED=0
case "$ROOT_PT" in
mmcblk*p*)
BASE_DEV="${ROOT_PT%p*}"
[ "$(cat /sys/block/${BASE_DEV}/device/type 2>/dev/null | xargs)" = "MMC" ] && IS_INSTALLED=1
;;
esac
case "$PLATFORM" in
*amlogic*|*allwinner*) CAN_INSTALL=yes ;;
*) [ "$SHOW_INSTALL" = "yes" ] && CAN_INSTALL=yes || CAN_INSTALL=no ;;
esac
if [ "$CAN_INSTALL" = "yes" ] && [ "$IS_INSTALLED" = "0" ]; then
MENU_INSTALL=yes
else
MENU_INSTALL=no
fi
case "$PLATFORM" in
*qemu*) MENU_ARMCPU=no ;;
*) MENU_ARMCPU=yes ;;
esac
uci -q batch <<-EOF >/dev/null
delete ucitrack.@amlogic[-1]
add ucitrack amlogic
set ucitrack.@amlogic[-1].init=amlogic
commit ucitrack
set amlogic.config.menu_install=${MENU_INSTALL}
set amlogic.config.menu_armcpu=${MENU_ARMCPU}
commit amlogic
EOF
rm -rf /tmp/luci-*
exit 0
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/perl
use strict;
use File::Basename;
my $uci_config_name;
if(-f "/etc/config/amlogic") {
$uci_config_name="amlogic";
} elsif(-f "/etc/config/cpufreq") {
$uci_config_name="cpufreq";
} else {
print "Can not found amlogic or cpufreq config file!\n";
exit(0);
}
my @policy_ids;
my @policy_homes = </sys/devices/system/cpu/cpufreq/policy?>;
if(@policy_homes) {
foreach my $policy_home (@policy_homes) {
push @policy_ids, substr($policy_home, -1);
}
} else {
print "Can not found any policy!\n";
exit 0;
}
our $need_commit = 0;
for(my $i=0; $i <= $#policy_ids; $i++) {
&fix_invalid_value($uci_config_name, $policy_ids[$i], $policy_homes[$i]);
}
if($need_commit > 0) {
&uci_commit($uci_config_name);
}
exit 0;
################################# function ####################################
sub fix_invalid_value {
my($uci_config, $policy_id, $policy_home) = @_;
my %gove_hash = &get_gove_hash($policy_home);
my @freqs = &get_freq_list($policy_home);
my %freq_hash = &get_freq_hash(@freqs);
my $min_freq = &get_min_freq(@freqs);
my $max_freq = &get_max_freq(@freqs);
my $uci_section = "settings";
my $uci_option;
if($uci_config eq "cpufreq" ) {
$uci_option = "governor";
} else {
$uci_option = "governor" . $policy_id;
}
# 如果未设置 governor, 或该 goveernor 不存在, 则修败默认值为 schedutil
my $config_gove = &uci_get_by_type($uci_config, $uci_section, $uci_option, "NA");
if( ($config_gove eq "NA") ||
($gove_hash{$config_gove} != 1)) {
&uci_set_by_type($uci_config, $uci_section, $uci_option, "schedutil");
$need_commit++;
}
# 如果出现不存在的 minfreq, 则修改为实际的 min_freq
if($uci_config eq "cpufreq" ) {
# "minifreq" is a spelling error that has always existed in the upstream source code
$uci_option = "minifreq";
} else {
$uci_option = "minfreq" . $policy_id;
}
my $config_min_freq = &uci_get_by_type($uci_config, $uci_section, $uci_option, "0");
if($freq_hash{$config_min_freq} != 1) {
&uci_set_by_type($uci_config, $uci_section, $uci_option, $min_freq);
$need_commit++;
}
# 如果出现不存在的 maxfreq
# 或 maxfreq < minfreq, 则修改为实际的 max_freq
if($uci_config eq "cpufreq" ) {
$uci_option = "maxfreq";
} else {
$uci_option = "maxfreq" . $policy_id;
}
my $config_max_freq = &uci_get_by_type($uci_config, $uci_section, $uci_option, "0");
if( ( $freq_hash{$config_max_freq} != 1) ||
( $config_max_freq < $config_min_freq)) {
&uci_set_by_type($uci_config, $uci_section, $uci_option, $max_freq);
$need_commit++;
}
}
sub get_freq_list {
my $policy_home = shift;
my @ret_ary;
open my $fh, "<", "${policy_home}/scaling_available_frequencies" or die;
$_ = <$fh>;
chomp;
@ret_ary = split /\s+/;
close($fh);
return @ret_ary;
}
sub get_freq_hash {
my @freq_ary = @_;
my %ret_hash;
foreach my $freq (@freq_ary) {
if($freq =~ m/\d+/) {
$ret_hash{$freq} = 1;
}
}
return %ret_hash;
}
sub get_min_freq {
my @freq_ary = @_;
return (sort {$a<=>$b} @freq_ary)[0];
}
sub get_max_freq {
my @freq_ary = @_;
return (sort {$a<=>$b} @freq_ary)[-1];
}
sub get_gove_hash {
my $policy_home = shift;
my %ret_hash;
open my $fh, "<", "${policy_home}/scaling_available_governors" or die;
$_ = <$fh>;
chomp;
my @gov_ary = split /\s+/;
foreach my $gov (@gov_ary) {
#print "gov: $gov\n";
if($gov =~ m/\w+/) {
$ret_hash{$gov} = 1;
}
}
close($fh);
return %ret_hash;
}
sub uci_get_by_type {
my($config,$section,$option,$default) = @_;
my $ret;
$ret=`uci get ${config}.\@${section}\[0\].${option} 2>/dev/null`;
# 消除回车换行
$ret =~ s/[\n\r]//g;
if($ret eq '') {
return $default;
} else {
return $ret;
}
}
sub uci_set_by_type {
my($config,$section,$option,$value) = @_;
my $ret;
system("uci set ${config}.\@${section}\[0\].${option}=${value}");
return;
}
sub uci_commit {
my $config = shift;
system("uci commit ${config}");
return;
}
+598
View File
@@ -0,0 +1,598 @@
#!/bin/bash
#======================================================================
# Function: Backup and restore config files in the /etc directory
# Copyright (C) 2020-- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021-- https://github.com/ophub/luci-app-amlogic
#======================================================================
VERSION="v1.3"
ZSTD_LEVEL=6
SNAPSHOT_PRESTR=".snapshots/"
BACKUP_DIR="/.reserved"
BACKUP_NAME="openwrt_config.tar.gz"
BACKUP_FILE="${BACKUP_DIR}/${BACKUP_NAME}"
# Customize backup list
backup_list_conf="/etc/amlogic_backup_list.conf"
if [[ -s "${backup_list_conf}" ]]; then
while IFS= read -r line; do
BACKUP_LIST+="${line} "
done <"${backup_list_conf}"
else
BACKUP_LIST='./etc/AdGuardHome.yaml \
./etc/amlogic_backup_list.conf \
./etc/adblocklist/ \
./etc/amule/ \
./etc/balance_irq \
./etc/bluetooth/ \
./etc/china_ssr.txt \
./etc/cifs/cifsdpwd.db \
./etc/smbd/smbdpwd.db \
./etc/ksmbd/ksmbdpwd.db \
./etc/config/ \
./etc/crontabs/ \
./etc/dae/ \
./etc/daed/ \
./usr/share/openclash/core/ \
./etc/openclash/backup/ \
./etc/openclash/config/ \
./etc/openclash/custom/ \
./etc/openclash/game_rules/ \
./etc/openclash/rule_provider/ \
./etc/openclash/proxy_provider/ \
./etc/dnsforwarder/ \
./etc/dnsmasq.conf \
./etc/dnsmasq.d/ \
./etc/dnsmasq.oversea/ \
./etc/dnsmasq.ssr/ \
./etc/docker/daemon.json \
./etc/docker/key.json \
./etc/dropbear/ \
./etc/easy-rsa/ \
./etc/environment \
./etc/exports \
./etc/firewall.user \
./etc/gfwlist/ \
./etc/haproxy.cfg \
./etc/hosts \
./etc/ipsec.conf \
./etc/ipsec.d/ \
./etc/ipsec.secrets \
./etc/ipsec.user \
./etc/ipset/ \
./etc/mosdns/config.yaml \
./etc/mwan3.user \
./etc/nginx/nginx.conf \
./etc/ocserv/ \
./etc/openvpn/ \
./etc/pptpd.conf \
./etc/qBittorrent/ \
./etc/rc.local \
./etc/samba/smbpasswd \
./etc/shadow \
./etc/smartdns/ \
./etc/sqm/ \
./etc/ssh/*key* \
./etc/ssl/private/ \
./etc/ssrplus/ \
./etc/sysupgrade.conf \
./etc/tailscale/ \
./etc/transmission/ \
./etc/uhttpd.crt \
./etc/uhttpd.key \
./etc/urandom.seed \
./etc/v2raya/ \
./etc/verysync/ \
./root/.ssh/'
fi
error_msg() {
echo -e " [ERROR] ${1}"
exit 1
}
if dmesg | grep 'meson' >/dev/null 2>&1; then
PLATFORM="amlogic"
elif dmesg | grep 'rockchip' >/dev/null 2>&1; then
PLATFORM="rockchip"
elif dmesg | grep 'sun50i-h6' >/dev/null 2>&1; then
PLATFORM="allwinner"
else
source /etc/flippy-openwrt-release
case ${PLATFORM} in
amlogic | rockchip | allwinner | qemu-aarch64) : ;;
*) error_msg "Unknown platform, only support amlogic or rockchip or allwinner h6 or qemu-aarch64!" ;;
esac
fi
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
[[ -z "${partition_name}" ]] && error_msg "Cannot find the root partition!"
echo "${partition_name}"
}
# Get the partition message of the root file system
get_root_partition_msg() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_msg=$(lsblk -l -o NAME,PATH,MOUNTPOINT,UUID,FSTYPE,LABEL | awk '$3 ~ "^" "'"${path}"'" "$" {print $0}')
[[ -n "${partition_msg}" ]] && break
done
[[ -z "${partition_msg}" ]] && error_msg "Cannot find the root partition message!"
echo "${partition_msg}"
}
backup() {
cd /
echo -n "Backup config files ... "
[ -d "${BACKUP_DIR}" ] || mkdir -p "${BACKUP_DIR}"
eval tar czf "${BACKUP_FILE}" "${BACKUP_LIST}" 2>/dev/null
sync
if [ -f "${BACKUP_FILE}" ]; then
echo "Has been backed up to [ ${BACKUP_FILE} ], please download and save."
exit 0
else
error_msg "Backup failed!"
fi
}
restore() {
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case ${ROOT_PTNAME} in
mmcblk?p[1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')
PARTITION_NAME="p"
LB_PRE="EMMC_"
;;
[hsv]d[a-z][1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')
PARTITION_NAME=""
LB_PRE=""
;;
*)
error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!"
;;
esac
[ -d "${BACKUP_DIR}" ] || mkdir -p "${BACKUP_DIR}"
[ -f "/tmp/upload/${BACKUP_NAME}" ] && mv -f "/tmp/upload/${BACKUP_NAME}" ${BACKUP_FILE}
[ -f "/mnt/${EMMC_NAME}${PARTITION_NAME}4/${BACKUP_NAME}" ] && mv -f "/mnt/${EMMC_NAME}${PARTITION_NAME}4/${BACKUP_NAME}" ${BACKUP_FILE}
sync
if [ -f "${BACKUP_FILE}" ]; then
echo -n "restore config files ... "
cd /
tar xzf "${BACKUP_FILE}" 2>/dev/null && sync
echo "Successful recovery. Will start automatically, please refresh later!"
sleep 3
reboot
exit 0
else
error_msg "The backup file [ ${BACKUP_FILE} ] not found!"
fi
}
gen_fstab() {
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case ${ROOT_PTNAME} in
mmcblk?p[1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')
PARTITION_NAME="p"
;;
[hsv]d[a-z][1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')
PARTITION_NAME=""
;;
*)
error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!"
;;
esac
ROOT_MSG="$(get_root_partition_msg)"
ROOT_NAME=$(echo $ROOT_MSG | awk '{print $1}')
ROOT_DEV=$(echo $ROOT_MSG | awk '{print $2}')
ROOT_UUID=$(echo $ROOT_MSG | awk '{print $4}')
ROOT_FSTYPE=$(echo $ROOT_MSG | awk '{print $5}')
ROOT_LABEL=$(echo $ROOT_MSG | awk '{print $6}')
BOOT_NAME="${EMMC_NAME}${PARTITION_NAME}1"
BOOT_MSG=$(lsblk -l -o NAME,UUID,FSTYPE,LABEL | grep "${BOOT_NAME}")
BOOT_DEV="/dev/${BOOT_NAME}"
BOOT_UUID=$(echo $BOOT_MSG | awk '{print $2}')
BOOT_FSTYPE=$(echo $BOOT_MSG | awk '{print $3}')
BOOT_LABEL=$(echo $BOOT_MSG | awk '{print $4}')
cat >/etc/config/fstab <<EOF
config global
option anon_swap '0'
option anon_mount '1'
option auto_swap '0'
option auto_mount '1'
option delay_root '5'
option check_fs '0'
config mount
option target '/rom'
option uuid '${ROOT_UUID}'
option enabled '1'
option enabled_fsck '1'
option fstype '${ROOT_FSTYPE}'
EOF
if [ "${ROOT_FSTYPE}" == "btrfs" ]; then
echo " option options 'compress=zstd:${ZSTD_LEVEL}'" >>/etc/config/fstab
fi
cat >>/etc/config/fstab <<EOF
config mount
option target '/boot'
EOF
if [ "${BOOT_FSTYPE}" == "vfat" ]; then
echo " option label '${BOOT_LABEL}'" >>/etc/config/fstab
else
echo " option uuid '${BOOT_UUID}'" >>/etc/config/fstab
fi
cat >>/etc/config/fstab <<EOF
option enabled '1'
option enabled_fsck '1'
option fstype '${BOOT_FSTYPE}'
EOF
echo "/etc/config/fstab generated."
echo "Please reboot before continuing."
exit 0
}
print_list() {
echo "${BACKUP_LIST}"
exit 0
}
list_snapshot() {
echo "----------------------------------------------------------------"
btrfs subvolume list -rt /
echo "----------------------------------------------------------------"
read -p "Press [ enter ] to return." q
}
create_snapshot() {
default_snap_name="etc-$(date +"%m.%d.%H%M%S")"
echo "The default snapshot name is: ${default_snap_name}"
echo "If you want to modify the snapshot name, please enter it below. Cannot contain spaces."
echo "If you do not want to modify it, just press [ Enter ]. Or press the [ q ] key to go back directly."
while :; do
read -p "[${default_snap_name}] : " nname
if [ "${nname}" == "" ]; then
snap_name="${default_snap_name}"
break
elif echo "${nname}" | grep -E "\s+" >/dev/null; then
echo "The name [${nname}] contains spaces, please re-enter!"
continue
elif [ "${nname}" == "q" ] || [ "${nname}" == "Q" ]; then
return
else
if btrfs subvolume list -rt / | awk '{print $4}' | grep "^\\${SNAPSHOT_PRESTR}${nname}$" >/dev/null; then
echo "Name: [ ${nname} ] has been used, please re-enter!"
continue
else
snap_name="${nname}"
break
fi
fi
done
(
cd /
chattr -ia etc/config/fstab
btrfs subvolume snapshot -r /etc "${SNAPSHOT_PRESTR}${snap_name}"
if [[ "$?" -eq "0" ]]; then
echo "The snapshot is created successfully: ${snap_name}"
else
echo "Snapshot creation failed!"
fi
)
read -p "Press [ enter ] to return." q
}
restore_snapshot() {
echo "Below are the existing etc snapshots, please enter the name of one of them."
echo "Tip: [ etc-000 ] This is the factory initial configuration."
echo " [ etc-001 ] if it exists, it is the initial configuration after upgrading from the previous version."
echo "----------------------------------------------------------------"
btrfs subvolume list -rt /
echo "----------------------------------------------------------------"
read -p "Please enter the name of the snapshot to be restored (only the part after ${SNAPSHOT_PRESTR} needs to be entered): " snap_name
if btrfs subvolume list -rt / | grep "${SNAPSHOT_PRESTR}${snap_name}" >/dev/null; then
while :; do
echo "Once the snapshot is restored, the current [ /etc ] will be overwritten!"
read -p "Are you sure you want to restore the snapshot: [$snap_name]? y/n [n] " yn
case $yn in
y | Y)
(
cd /
chattr -ia etc/config/fstab
mv etc etc.backup
btrfs subvolume snapshot "${SNAPSHOT_PRESTR}${snap_name}" etc
if [[ "$?" -eq "0" ]]; then
btrfs subvolume delete -c etc.backup
echo "Successfully restored, please enter [ reboot ] to restart the openwrt."
else
rm -rf etc
mv etc.backup etc
echo "Recovery failed, [ etc ] has not changed!"
fi
)
read -p "Press [ enter ] to return." q
break
;;
*)
break
;;
esac
done
else
read -p "The snapshot name is incorrect, please run the program again! Press [ Enter ] to go back." q
fi
}
delete_snapshot() {
echo "Below are the existing [ etc ] snapshots, please enter the name of one of them."
echo "Tip: [ etc-000 ] This is the factory initial configuration (cannot be deleted)"
echo " [ etc-001 ] if it exists, it is the initial configuration after upgrading from the previous version (cannot be deleted)"
echo "----------------------------------------------------------------"
btrfs subvolume list -rt /
echo "----------------------------------------------------------------"
read -p "Please enter the name of the snapshot to be deleted (only the part after ${SNAPSHOT_PRESTR} needs to be entered): " snap_name
if [ "${snap_name}" == "etc-000" ] || [ "${snap_name}" == "etc-001" ]; then
read -p "The key snapshot cannot be deleted! Press [ enter ] to return." q
elif [ "${snap_name}" == "" ]; then
read -p "Name is empty! Press [ enter ] to return." q
else
if btrfs subvolume list -rt / | grep "${SNAPSHOT_PRESTR}${snap_name}" >/dev/null; then
read -p "Are you sure you want to delete ${snap_name}? y/n [n] " yn
case $yn in
y | Y)
(
cd /
btrfs subvolume delete -c "${SNAPSHOT_PRESTR}${snap_name}"
if [[ "$?" -eq "0" ]]; then
echo "Snapshot [ ${snap_name} ] has been deleted."
else
echo "Snapshot [ ${snap_name} ] failed to delete!"
fi
)
read -p "Press [ Enter ] to return." q
;;
*)
break
;;
esac
else
read -p "The name of the snapshot is incorrect, press [ Enter ] to return." q
fi
fi
}
migrate_snapshot() {
cur_rootdev="$(get_root_partition_name)"
dev_pre=$(echo "${cur_rootdev}" | awk '{print substr($1, 1, length($1)-1);}')
rootdev_idx=$(echo "${cur_rootdev}" | awk '{print substr($1, length($1),1);}')
case $rootdev_idx in
2)
old_rootpath="/mnt/${dev_pre}3"
;;
3)
old_rootpath="/mnt/${dev_pre}2"
;;
*)
echo "Judge the old version of rootfs path failed!"
read -p "Press [ enter ] to return." q
return
;;
esac
echo "The following are snapshots of etc found from the old version of rootfs, please enter the name of one of them."
echo "Tip: Automatically exclude etc-000 and etc-001"
echo "-----------------------------------------------------------------------------------"
btrfs subvolume list -rt "${old_rootpath}" | grep -v "${SNAPSHOT_PRESTR}etc-000" | grep -v "${SNAPSHOT_PRESTR}etc-001"
echo "-----------------------------------------------------------------------------------"
read -p "Please enter the name of the snapshot to be migrated (only the part after $(SNAPSHOT_PRESTR) needs to be entered): " old_snap_name
if [ "${old_snap_name}" == "" ]; then
read -p "The name is empty, Press [ enter ] to return." q
return
elif ! btrfs subvolume list -rt "${old_rootpath}" | awk '{print $4}' | grep "^${SNAPSHOT_PRESTR}${old_snap_name}$" >/dev/null; then
echo "The name was entered incorrectly, and the corresponding snapshot was not found!"
read -p "Press [ enter ] to return." q
return
elif [ "${old_snap_name}" == "etc-000" ] || [ "${old_snap_name}" == "etc-001" ]; then
echo "Critical snapshots are not allowed to migrate!"
read -p "Press [ enter ] to return." q
return
fi
# Find out if there is a snapshot with the same name under the current rootfs
if btrfs subvolume list -rt / | awk '{print $4}' | grep "^\\${SNAPSHOT_PRESTR}${old_snap_name}$" >/dev/null; then
echo "A snapshot with the name [ ${old_snap_name} ] already exists and cannot be migrated! (But you can delete the existing snapshot with the same name and then migrate)"
read -p "Press [ enter ] to return." q
return
fi
need_size=$(du -h -d0 ${old_rootpath}/${SNAPSHOT_PRESTR}${old_snap_name} | tail -n1 | awk '{print $1}')
echo "----------------------------------------------------------------------------------------------"
df -h
echo "----------------------------------------------------------------------------------------------"
echo -e "Note: To migrate the snapshot [ ${old_snap_name} ] of [ ${old_rootpath} ] to the current rootfs, it takes about [ ${need_size} ] space,"
echo -e " Please confirm whether the partition [/dev/${cur_rootdev}] where [/] is located has enough free space (Available)?"
read -p "Are you sure to migrate? y/n [n] " yn
if [ "$yn" == "y" ] || [ "$yn" == "Y" ]; then
(
cd /
btrfs send ${old_rootpath}/${SNAPSHOT_PRESTR}${old_snap_name} | btrfs receive ${SNAPSHOT_PRESTR}
if [ $? -eq 0 ]; then
btrfs property set -ts ${SNAPSHOT_PRESTR}${old_snap_name} ro false
cp ${SNAPSHOT_PRESTR}etc-000/config/fstab ${SNAPSHOT_PRESTR}${old_snap_name}/config/
cp ${SNAPSHOT_PRESTR}etc-000/fstab ${SNAPSHOT_PRESTR}${old_snap_name}/
cp ${SNAPSHOT_PRESTR}etc-000/openwrt_release ${SNAPSHOT_PRESTR}${old_snap_name}/
cp ${SNAPSHOT_PRESTR}etc-000/openwrt_version ${SNAPSHOT_PRESTR}${old_snap_name}/
cp ${SNAPSHOT_PRESTR}etc-000/flippy-openwrt-release ${SNAPSHOT_PRESTR}${old_snap_name}/
cp ${SNAPSHOT_PRESTR}etc-000/banner ${SNAPSHOT_PRESTR}${old_snap_name}/banner
btrfs property set -ts ${SNAPSHOT_PRESTR}${old_snap_name} ro true
echo "The migration is complete, if you want to apply the snapshot [ ${old_snap_name} ], please use the restore snapshot function."
else
echo "The migration failed!"
fi
read -p "Press [ enter ] to return." q
return
)
fi
}
snapshot_help() {
clear
cat <<EOF
============================================================================================================================
1. What is a snapshot?
A snapshot is the state record of a certain subvolume at a certain point in time, and a snapshot is a special subvolume;
When the local snapshot is first generated, it shares the disk space with the original subvolume,
so it does not occupy additional space, and only the files that have changed subsequently occupy space;
Snapshots migrated from other places are not snapshots in essence, but just ordinary subvolumes, so they take up space.
2. How to display existing snapshots?
input the command: btrfs subvolume list -rt /
---------------------------------------------------------------------------------------------
EOF
btrfs subvolume list -rt /
cat <<EOF
---------------------------------------------------------------------------------------------
2. How to create a snapshot?
btrfs subvolume snapshot -r /etc /.snapshots/snapshot_1 # -r It means to generate a read-only snapshot
3. How to delete a snapshot?
btrfs subvolume delete -c /.snapshots/snapshot_1 # -c 是 commit 的意思
4. How to rename a snapshot?
Just use the [ mv ] command of the operating system, for example:
mv /.snapshots/snapshot_1 /.snapshots/snapshot_2
5. How to restore the snapshot?
mv /etc /etc.backup # Back up /etc to /etc.backup, that is, rename the subvolume
btrfs subvolume snapshot /.snapshots/etc-001 /etc # Use snapshot etc-001 to generate snapshot etc, no -r parameter
# (Yes, snapshots can also be re-generated snapshots)
btrfs delete -c /etc.backup # After the previous step is successful, you can delete the etc backup just now
6. How to restore a certain file from the snapshot?
Example A: Restore the mount point configuration file from snapshot etc-000: /etc/config/fstab
cp /.snapshots/etc-000/config/fstab /etc/config/
Example B: Restore network configuration from snapshot etc-001:
cp /.snapshots/etc-001/config/network /etc/config/
Example C: Restore the ssr configuration file from snapshot etc-001:
cp /.snapshots/etc-001/config/shadowsocksr /etc/config/
# (Yes, it is the [ cp ] command of the operating system)
7. How to migrate snapshots from remote?
btrfs supports ssh remote migration snapshots, for example:
ssh 192.168.1.1 btrfs send /.snapshots/snapshot_x | btrfs receive /.snapshots
After the command is completed, the remote snapshot_x is copied to the local /.snapshots/snapshot_x
(as mentioned above, the migration is a subvolume, which takes up space)
============================================================================================================================
EOF
exit 0
}
print_help() {
echo "Usage: $0 -b [ backup ]"
echo " $0 -r [ restore ]"
echo " $0 -g [ generate fstab ]"
echo " $0 -p [ print backup list ]"
echo " $0 -l [ list snapshots ]"
echo " $0 -c [ create snapshot ]"
echo " $0 -s [ restore snapshot ]"
echo " $0 -d [ delete snapshot ]"
echo " $0 -h [ help ]"
echo " $0 -q [ quit ]"
exit 0
}
menu() {
while :; do
clear
cat <<EOF
┌────────[ backup config ]────────┐
│ │
│ b. backup config │
│ r. restore config │
│ g. generate fstab │
│ p. print backup list │
│ │
├─────[ Snapshot management ]─────┤
│ │
│ l. list snapshots │
│ c. create snapshot │
│ d. delete snapshot │
│ R. restore snapshot │
│ m. migrate snapshot │
│ s. snapshot help │
│ │
╞═════════════════════════════════╡
│ │
│ h. help │
│ q. quit │
│ │
└─────────────────────────────────┘
EOF
echo -ne "please select: [ ]\b\b"
read select
case $select in
b | backup) backup ;;
r | restore)
restore
gen_fstab
;;
g | gen_fstab) gen_fstab ;;
p | print_list) print_list ;;
l | list_snapshot) list_snapshot ;;
c | create_snapshot) create_snapshot ;;
d | delete_snapshot) delete_snapshot ;;
R | restore_snapshot) restore_snapshot ;;
m | migrate_snapshot) migrate_snapshot ;;
s | snapshot_help) snapshot_help ;;
h | help) print_help ;;
q | quit) exit 0 ;;
esac
done
}
getopts 'brgplcRmsdhq' opts
case $opts in
b | backup) backup ;;
r | restore)
restore
gen_fstab
;;
g | gen_fstab) gen_fstab ;;
p | print_list) print_list ;;
l | list_snapshot) list_snapshot ;;
c | create_snapshot) create_snapshot ;;
d | delete_snapshot) delete_snapshot ;;
R | restore_snapshot) restore_snapshot ;;
m | migrate_snapshot) migrate_snapshot ;;
s | snapshot_help) snapshot_help ;;
h | help) print_help ;;
q | quit) exit 0 ;;
*) menu ;;
esac
+155
View File
@@ -0,0 +1,155 @@
#!/bin/bash
#===========================================================================
#
# This file is licensed under the terms of the GNU General Public
# License version 2. This program is licensed "as is" without any
# warranty of any kind, whether express or implied.
#
# This file is a part of the make OpenWrt for Amlogic s9xxx tv box
# https://github.com/ophub/luci-app-amlogic
#
# Description: Backup and restore the system in emmc
# Copyright (C) 2017- The function borrowed from armbian/ddbr, Author: xXx
# Copyright (C) 2021- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021- https://github.com/ophub/luci-app-amlogic
#
# Command: openwrt-ddbr
#
#========================= Set default parameters ===========================
#
# Set font color
blue_font_prefix="\033[34m"
purple_font_prefix="\033[35m"
green_font_prefix="\033[32m"
yellow_font_prefix="\033[33m"
red_font_prefix="\033[31m"
font_color_suffix="\033[0m"
INFO="[${blue_font_prefix}INFO${font_color_suffix}]"
STEPS="[${purple_font_prefix}STEPS${font_color_suffix}]"
SUCCESS="[${green_font_prefix}SUCCESS${font_color_suffix}]"
OPT="[${yellow_font_prefix}OPT${font_color_suffix}]"
ERROR="[${red_font_prefix}ERROR${font_color_suffix}]"
#
# Check the output path
out_path="/ddbr"
# File name for backup/restore
ddbr_image="BACKUP-arm-64-emmc.img.gz"
# Need remaining space, unit: GB
need_space="2"
#
#===========================================================================
# Encountered a serious error, abort the script execution
error_msg() {
echo -e "${ERROR} ${1}"
exit 1
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
[[ -z "${partition_name}" ]] && error_msg "Cannot find the root partition!"
echo "${partition_name}"
}
# Check emmc
do_checkemmc() {
# Get device name
mydevice_name="$(cat /proc/device-tree/model | tr -d '\000')"
echo -e "${INFO} The device name: [ ${mydevice_name} ]"
# Find the partition where root is located
root_ptname="$(get_root_partition_name)"
# Find the EMMC drive
emmc="$(lsblk -l -o NAME | grep -oE "mmcblk[0-9]boot0" | sort | uniq | sed "s/boot0//g")"
# Find emmc disk, find emmc that does not contain the boot0 partition
[[ -z "${emmc}" ]] && emmc="$(lsblk -l -o NAME | grep -oE '(mmcblk[0-9]?)' | grep -vE ^${root_ptname:0:-2} | uniq)"
# Check if emmc exists
[[ -z "${emmc}" ]] && error_msg "The eMMC storage not found in this device!"
# Show the emmc name
echo -e "${INFO} The device eMMC name: [ /dev/${emmc} ]"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case "${root_ptname}" in
mmcblk?p[1-4])
disk_name="$(echo ${root_ptname} | awk '{print substr($1, 1, length($1)-2)}')"
if lsblk -l -o NAME | grep "${disk_name}boot0" >/dev/null; then
error_msg "The current system is running on emmc. Please perform backup/restore operation in [ SD/TF/USB ]!"
fi
link_ptname="p"
;;
[hsv]d[a-z][1-4])
disk_name="$(echo ${root_ptname} | awk '{print substr($1, 1, length($1)-1)}')"
link_ptname=""
;;
nvme?n?p[1-4])
disk_name="$(echo ${root_ptname} | awk '{print substr($1, 1, length($1)-2)}')"
link_ptname="p"
;;
*)
error_msg "Unable to recognize the disk type of ${root_ptname}!"
;;
esac
# Set check parameters
out_path="/mnt/${disk_name}${link_ptname}4"
dev_intsize="$(fdisk -s /dev/${emmc})"
[[ -z "$(echo "${dev_intsize}" | sed -n "/^[0-9]\+$/p")" ]] && error_msg "Unable to get EMMC size."
echo -e "${INFO} The device EMMC size: [ $(($dev_intsize / 1024 / 1024))GB ]"
# check directory
[[ -d "${out_path}" ]] || mkdir -p ${out_path}
echo -e "${INFO} The ddbr file path: [ ${out_path}/${ddbr_image} ]\n"
}
# Check the remaining space
do_checkspace() {
remaining_space="$(df -Tk ${out_path} | grep '/dev/' | awk '{print $5}' | echo $(($(xargs) / 1024 / 1024)))"
if [[ -z "$(echo "${remaining_space}" | sed -n "/^[0-9]\+$/p")" ]]; then
error_msg "The path is not available, the remaining space cannot be obtained."
fi
if [[ "${remaining_space}" -lt "${need_space}" ]]; then
error_msg "The remaining space is [ ${remaining_space} ] Gb, and more than [ ${need_space} ] Gb space is required."
fi
}
# Backup the emmc system
do_backup() {
echo -e "${STEPS} Start to backup the system in emmc."
do_checkspace
echo -e "Saving and Compressing [ /dev/${emmc} ] to [ ${out_path}/${ddbr_image} ], Please wait..."
rm -f ${out_path}/${ddbr_image} 2>/dev/null && sync
dd if=/dev/${emmc} | pv -s ${dev_intsize}"K" | gzip >${out_path}/${ddbr_image}
[[ "${?}" -eq "0" ]] && sync && echo -e "${SUCCESS} Backup is complete."
}
# Restore the emmc system
do_restore() {
echo -e "${STEPS} Start to restore the system in emmc."
[[ ! -f ${out_path}/${ddbr_image} ]] && error_msg "The [ ${out_path}/${ddbr_image} ] File not found."
echo -e "Restoring [ ${out_path}/${ddbr_image} ] to [ /dev/${emmc} ], Please wait..."
gunzip -c ${out_path}/${ddbr_image} | pv -s ${dev_intsize}"K" | dd of=/dev/${emmc}
[[ "${?}" -eq "0" ]] && sync && echo -e "${SUCCESS} Restore is complete."
}
echo -e "${STEPS} Welcome to use the EMMC system backup/restore service."
# Check script permission
[[ -x "/usr/sbin/openwrt-ddbr" ]] || error_msg "Please grant execution permission: [ chmod +x /usr/sbin/openwrt-ddbr ]"
# Check emmc
do_checkemmc
# Prompt the user to select backup/restore
echo -ne "${OPT} Do you want to backup or restore? Backup=(b) Restore=(r): "
read br
case "${br}" in
b | B | backup) do_backup ;;
r | R | restore) do_restore ;;
*) exit 0 ;;
esac
+721
View File
@@ -0,0 +1,721 @@
#!/bin/bash
#======================================================================================
# Function: Install openwrt to emmc for Amlogic S9xxx STB
# Copyright (C) 2020-- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021-- https://github.com/ophub/luci-app-amlogic
#======================================================================================
#
# The script supports directly setting parameters for installation, skipping interactive selection
# openwrt-install-amlogic ${AUTO_MAINLINE_UBOOT} ${ID} ${FDTFILE}:${SOC}:${UBOOT_OVERLOAD} ${SHARED_FSTYPE}
# E.g: openwrt-install-amlogic yes 11 auto ext4
# E.g: openwrt-install-amlogic no 99 meson-gxl-s905d-phicomm-n1.dtb:s905d:u-boot-n1.bin ext4
# Tip: When custom dtb file, set ${SOC_ID} to 99, and parameter ${FDTFILE}:${SOC}:${UBOOT_OVERLOAD} must be set
# Tip: ${SHARED_FSTYPE}: Shared partition can be ext4, xfs, btrfs, f2fs
# You can also execute the script directly, and interactively select related functions
# E.g: openwrt-install-amlogic
#
#======================================================================================
# Encountered a serious error, abort the script execution
error_msg() {
echo -e "[ERROR] ${1}"
exit 1
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
[[ -z "${partition_name}" ]] && error_msg "Cannot find the root partition!"
echo "${partition_name}"
}
# Receive one-key command related parameters
AUTO_MAINLINE_UBOOT="${1}"
ZSTD_LEVEL="6"
op_release="/etc/flippy-openwrt-release"
# For [luci-app-amlogic] input parameter: DTB, SOC & UBOOT_OVERLOAD
# When there is no input parameter, select manually
SPECIFY_ID=""
SPECIFY_SOC=""
SPECIFY_DTB=""
SPECIFY_UBOOT=""
[[ -n "${2}" ]] && {
SPECIFY_ID="${2}"
if [[ "${2}" -eq "99" ]]; then
if [[ -n "${3}" ]]; then
# E.g: meson-gxl-s905d-phicomm-n1.dtb:s905d:u-boot-n1.bin
SPECIFY_DTB="$(echo "${3}" | awk -F ':' '{print $1}')"
SPECIFY_SOC="$(echo "${3}" | awk -F ':' '{print $2}')"
SPECIFY_UBOOT="$(echo "${3}" | awk -F ':' '{print $3}')"
else
error_msg "Please enter the DTB file name!"
fi
fi
}
# shared partition can be ext4, xfs, btrfs, f2fs
SHARED_FSTYPE="${4}"
echo "AUTO_MAINLINE_UBOOT: ${AUTO_MAINLINE_UBOOT}"
echo "SPECIFY_DTB: ${SPECIFY_DTB}"
echo "SPECIFY_SOC: ${SPECIFY_SOC}"
echo "SPECIFY_UBOOT: ${SPECIFY_UBOOT}"
echo "SHARED_FSTYPE: ${SHARED_FSTYPE}"
# Current device model
MYDEVICE_NAME=$(cat /proc/device-tree/model | tr -d '\000')
if [[ -z "${MYDEVICE_NAME}" ]]; then
error_msg "The device name is empty and cannot be recognized."
elif [[ ! -f "${op_release}" ]]; then
error_msg "The [ ${op_release} ] file is missing."
else
echo -e "Current device: ${MYDEVICE_NAME} [ amlogic ]"
sleep 3
fi
# Find the device name of /
root_devname="$(get_root_partition_name)"
if lsblk -l | grep -E "^${root_devname}boot0" >/dev/null; then
error_msg "you are running in emmc mode, please boot system with usb or tf card!"
fi
install_emmc="$(lsblk -l -o NAME | grep -oE '(mmcblk[0-9]?boot0)' | sed "s/boot0//g")"
if [[ "${install_emmc}" == "" ]]; then
error_msg "No emmc can be found to install the openwrt system!"
fi
# EMMC DEVICE NAME
EMMC_NAME="${install_emmc}"
EMMC_DEVPATH="/dev/${EMMC_NAME}"
echo ${EMMC_DEVPATH}
EMMC_SIZE=$(lsblk -l -b -o NAME,SIZE | grep ${EMMC_NAME} | sort | uniq | head -n1 | awk '{print $2}')
echo "${EMMC_NAME} : ${EMMC_SIZE} bytes"
ROOT_NAME=$(lsblk -l -o NAME,MAJ:MIN,MOUNTPOINT | grep -e '/$' | awk '{print $1}')
echo "ROOTFS: ${ROOT_NAME}"
BOOT_NAME=$(lsblk -l -o NAME,MAJ:MIN,MOUNTPOINT | grep -e '/boot$' | awk '{print $1}')
echo "BOOT: ${BOOT_NAME}"
# box model database
# The field separator is :
# " " or "" or NA or NULL means this field is null
# The fields list:
# 1. id
# 2. model name
# 3. SOC
# 4. FDTFILE
# 5. UBOOT_OVERLOAD
# 6. MAINLINE_UBOOT
# 7. ANDROID_UBOOT
# 8. brief description
#
# allow use external modal database
if [[ -f "/etc/model_database.txt" ]]; then
model_database="$(cat /etc/model_database.txt)"
else
error_msg "[ /etc/model_database.txt ] file is missing."
fi
function display_database() {
while read -r line; do
if [[ "$line" =~ ^# ]]; then
# Process comment lines, starting with #
line="${line/#+\s+/}"
echo "$line >>>"
else
# Process data lines, starting with id
IFS=':' read -r -a fields <<<"$line"
printf "%5s %-48s%-10s%-s\n" "${fields[0]}" "${fields[1]}" "${fields[2]}" "${fields[7]}"
fi
done < <(echo "${model_database}")
}
function search_model() {
local id="${1}"
local ret_count="$(echo "${model_database}" | awk -F ':' "\$1~/^$id\$/ {print \$0}" | wc -l)"
if [[ "${ret_count}" -eq "1" ]]; then
echo "${model_database}" | awk -F ':' "\$1~/^$id\$/ {print \$0}" | sed -e 's/NA//g' -e 's/NULL//g' -e 's/[ ][ ]*//g'
fi
}
echo "Please select s9xxx box model:"
echo "----------------------------------------------------------------------------------------------------"
display_database
echo "----------------------------------------------------------------------------------------------------"
# For [luci-app-amlogic] input parameter: SOC & DTB
# When there is no input parameter, select manually
if [[ -n "${SPECIFY_ID}" ]]; then
boxtype="${SPECIFY_ID}"
else
echo -n "Please choose: "
read boxtype
fi
if [[ "${boxtype}" -eq "99" ]]; then
FDTFILE="${SPECIFY_DTB}"
AMLOGIC_SOC="${SPECIFY_SOC}"
UBOOT_OVERLOAD="${SPECIFY_UBOOT}"
MAINLINE_UBOOT=""
ANDROID_UBOOT=""
elif [[ "${boxtype}" -eq "0" ]]; then
read -p "Please Input SoC Name(such as s9xxx): " AMLOGIC_SOC
AMLOGIC_SOC="${AMLOGIC_SOC}"
read -p "Please Input DTB Name(such as meson-xxx.dtb): " FDTFILE
FDTFILE="${FDTFILE}"
read -p "Please Input UBOOT_OVERLOAD Name(such as u-boot-xxx.bin): " UBOOT_OVERLOAD
UBOOT_OVERLOAD="${UBOOT_OVERLOAD}"
read -p "Please Input MAINLINE_UBOOT Name(such as /lib/u-boot/xxx-u-boot.bin.sd.bin): " MAINLINE_UBOOT
MAINLINE_UBOOT="${MAINLINE_UBOOT}"
read -p "Please Input ANDROID_UBOOT Name(such as /lib/u-boot/xxx-bootloader.img): " ANDROID_UBOOT
ANDROID_UBOOT="${ANDROID_UBOOT}"
else
ret="$(search_model ${boxtype})"
if [[ -z "${ret}" ]]; then
error_msg "Input error, exit!"
fi
# 3. soc
# 4. FDTFILE
# 5. UBOOT_OVERLOAD
# 6. MAINLINE_UBOOT
# 7. ANDROID_UBOOT
AMLOGIC_SOC="$(echo "${ret}" | awk -F ':' '{print $3}')"
FDTFILE="$(echo "${ret}" | awk -F ':' '{print $4}')"
UBOOT_OVERLOAD="$(echo "${ret}" | awk -F ':' '{print $5}')"
MAINLINE_UBOOT="$(echo "${ret}" | awk -F ':' '{print $6}')"
ANDROID_UBOOT="$(echo "${ret}" | awk -F ':' '{print $7}')"
fi
if [[ -z "${FDTFILE}" || ! -f "/boot/dtb/amlogic/${FDTFILE}" ]]; then
error_msg "/boot/dtb/amlogic/${FDTFILE} does not exist!"
fi
echo "AMLOGIC_SOC Value [ ${AMLOGIC_SOC} ]"
echo "FDTFILE Value [ ${FDTFILE} ]"
echo "UBOOT_OVERLOAD Value [ ${UBOOT_OVERLOAD} ]"
echo "MAINLINE_UBOOT Value [ ${MAINLINE_UBOOT} ]"
echo "ANDROID_UBOOT Value [ ${ANDROID_UBOOT} ]"
sed -i "s|^SOC=.*|SOC='${AMLOGIC_SOC}'|g" ${op_release} 2>/dev/null
sed -i "s|^FDTFILE=.*|FDTFILE='${FDTFILE}'|g" ${op_release} 2>/dev/null
sed -i "s|^UBOOT_OVERLOAD=.*|UBOOT_OVERLOAD='${UBOOT_OVERLOAD}'|g" ${op_release} 2>/dev/null
sed -i "s|^MAINLINE_UBOOT=.*|MAINLINE_UBOOT='${MAINLINE_UBOOT}'|g" ${op_release} 2>/dev/null
sed -i "s|^ANDROID_UBOOT=.*|ANDROID_UBOOT='${ANDROID_UBOOT}'|g" ${op_release} 2>/dev/null
K510="1"
[[ "$(hexdump -n 15 -x "/boot/zImage" 2>/dev/null | head -n 1 | awk '{print $7}')" == "0108" ]] && K510="0"
echo -e "K510 [ ${K510} ]"
# backup old bootloader
if [[ ! -f "/root/BackupOldBootloader.img" ]]; then
echo "Backup bootloader -> [ BackupOldBootloader.img ] ... "
dd if=/dev/$EMMC_NAME of=/root/BackupOldBootloader.img bs=1M count=4 conv=fsync
echo "Backup bootloader complete."
echo
fi
swapoff -a
# umount all other mount points
MOUNTS=$(lsblk -l -o MOUNTPOINT)
for mnt in $MOUNTS; do
if [ "$mnt" == "MOUNTPOINT" ]; then
continue
fi
if [ "$mnt" == "" ]; then
continue
fi
if [ "$mnt" == "/" ]; then
continue
fi
if [ "$mnt" == "/boot" ]; then
continue
fi
if [ "$mnt" == "/opt" ]; then
continue
fi
if [ "$mnt" == "[SWAP]" ]; then
echo "swapoff -a"
swapoff -a
continue
fi
if echo $mnt | grep $EMMC_NAME; then
echo "umount -f $mnt"
umount -f $mnt
if [ $? -ne 0 ]; then
error_msg "$mnt Cannot be uninstalled, the installation process is aborted."
fi
fi
done
# Delete old partition if exists
p=$(lsblk -l | grep -e "${EMMC_NAME}p" | wc -l)
echo "A total of [ $p ] old partitions on EMMC will be deleted"
>/tmp/fdisk.script
while [ $p -ge 1 ]; do
echo "d" >>/tmp/fdisk.script
if [ $p -gt 1 ]; then
echo "$p" >>/tmp/fdisk.script
fi
p=$((p - 1))
done
# you can change ROOT size(MB) >= 320
ROOT1="1280"
ROOT2="1280"
if [[ "${AMLOGIC_SOC}" == "s912" ]] && [[ "${boxtype}" == "213" || "${boxtype}" == "2e" ]]; then
BOOT="512"
BLANK1="700"
BLANK2="220"
BLANK3="0"
BLANK4="0"
elif [[ "${AMLOGIC_SOC}" == "s912" || "${AMLOGIC_SOC}" == "s905d" ]]; then
BOOT="512"
BLANK1="68"
BLANK2="220"
BLANK3="0"
BLANK4="0"
elif [[ "${AMLOGIC_SOC}" == "s905x" ]]; then
BOOT="160"
BLANK1="700"
BLANK2="0"
BLANK3="0"
BLANK4="0"
elif [[ "${FDTFILE}" == "meson-sm1-skyworth-lb2004-a4091.dtb" ]]; then
BOOT="512"
BLANK1="108"
BLANK2="562"
BLANK3="0"
BLANK4="0"
elif [[ "${AMLOGIC_SOC}" == "s905l3a" ]] && [[ "${boxtype}" == "304" || "${boxtype}" == "34" ]]; then
# e900v22c/d(s905l3a)
BOOT="256"
BLANK1="570"
BLANK2="0"
BLANK3="0"
BLANK4="0"
elif [[ "${AMLOGIC_SOC}" == "s905l3a" ]] && [[ "${boxtype}" == "305" || "${boxtype}" == "33" ]]; then
# CM311-1a-YST(s905l3a)
BOOT="512"
BLANK1="108"
BLANK2="778"
BLANK3="0"
BLANK4="0"
elif [[ "${AMLOGIC_SOC}" == "s905l3b" ]]; then
# M302A/M304A(s905l3b)
BOOT="512"
BLANK1="128"
BLANK2="720"
BLANK3="0"
BLANK4="0"
elif [[ "${AMLOGIC_SOC}" == "s905x3" ]] && [[ "${boxtype}" == "525" || "${boxtype}" == "5n" ]]; then
# Whale(s905x3)
BOOT="512"
BLANK1="108"
BLANK2="650"
BLANK3="0"
BLANK4="0"
elif [[ "${boxtype}" =~ ^(409|410|49|4a)$ ]]; then
# WXY-OES(A311D):409/49, WXY-OES-PLUS(S922X):410/4a
BOOT="512"
BLANK1="700"
BLANK2="0"
BLANK3="0"
BLANK4="0"
else
BOOT="160"
BLANK1="68"
BLANK2="0"
BLANK3="162"
BLANK4="0"
fi
DST_TOTAL_MB=$((EMMC_SIZE / 1024 / 1024))
start1=$((BLANK1 * 2048))
end1=$((start1 + (BOOT * 2048) - 1))
start2=$(((BLANK2 * 2048) + end1 + 1))
end2=$((start2 + (ROOT1 * 2048) - 1))
start3=$(((BLANK3 * 2048) + end2 + 1))
end3=$((start3 + (ROOT2 * 2048) - 1))
start4=$(((BLANK4 * 2048) + end3 + 1))
end4=$((DST_TOTAL_MB * 2048 - 1))
cat >>/tmp/fdisk.script <<EOF
n
p
1
$start1
$end1
n
p
2
$start2
$end2
n
p
3
$start3
$end3
n
p
$start4
$end4
t
1
c
t
2
83
t
3
83
t
4
83
w
EOF
fdisk /dev/${EMMC_NAME} </tmp/fdisk.script 2>/dev/null
if [ $? -ne 0 ]; then
echo "The fdisk partition fails, Please try again."
dd if=/root/BackupOldBootloader.img of=/dev/${EMMC_NAME} conv=fsync && sync
dd if=/dev/zero of=/dev/${EMMC_NAME} bs=512 count=1 && sync
exit 1
fi
echo "Partition complete."
# write some zero data to part begin
seek=$((start1 / 2048))
dd if=/dev/zero of=/dev/${EMMC_NAME} bs=1M count=1 seek=$seek conv=fsync
seek=$((start2 / 2048))
dd if=/dev/zero of=/dev/${EMMC_NAME} bs=1M count=1 seek=$seek conv=fsync
seek=$((start3 / 2048))
dd if=/dev/zero of=/dev/${EMMC_NAME} bs=1M count=1 seek=$seek conv=fsync
seek=$((start4 / 2048))
dd if=/dev/zero of=/dev/${EMMC_NAME} bs=1M count=1 seek=$seek conv=fsync
#Mainline U-BOOT detection
FLASH_MAINLINE_UBOOT=0
if [[ -n "${MAINLINE_UBOOT}" && -f "${MAINLINE_UBOOT}" ]]; then
cat <<EOF
----------------------------------------------------------------------------------
Found an available mainline bootloader (Mainline u-boot), you can flash into EMMC.
----------------------------------------------------------------------------------
EOF
while :; do
# For [luci-app-amlogic] input parameter: SOC & DTB
# When there is no input parameter, select manually
if [[ "${AUTO_MAINLINE_UBOOT}" == "yes" ]]; then
if [[ "${K510}" -eq "1" ]]; then
yn="y"
else
yn="n"
fi
elif [[ "${AUTO_MAINLINE_UBOOT}" == "no" ]]; then
yn="n"
else
read -p "Please choose whether to write the mainline bootloader to EMMC? y/n " yn
fi
case $yn in
y | Y)
FLASH_MAINLINE_UBOOT=1
break
;;
n | N)
FLASH_MAINLINE_UBOOT=0
break
;;
esac
done
fi
if [[ "${FLASH_MAINLINE_UBOOT}" -eq "1" ]]; then
echo -e "Write Mainline bootloader: [ ${MAINLINE_UBOOT} ]"
dd if=${MAINLINE_UBOOT} of=/dev/${EMMC_NAME} bs=1 count=444 conv=fsync
dd if=${MAINLINE_UBOOT} of=/dev/${EMMC_NAME} bs=512 skip=1 seek=1 conv=fsync
elif [[ -n "${ANDROID_UBOOT}" && -f "${ANDROID_UBOOT}" ]]; then
echo -e "Write Android bootloader: [ ${ANDROID_UBOOT} ]"
dd if=${ANDROID_UBOOT} of=/dev/${EMMC_NAME} bs=1 count=444 conv=fsync
dd if=${ANDROID_UBOOT} of=/dev/${EMMC_NAME} bs=512 skip=1 seek=1 conv=fsync
else
echo "Did not change the original bootloader."
fi
# fix wifi macaddr
if [ -x /usr/bin/fix_wifi_macaddr.sh ]; then
/usr/bin/fix_wifi_macaddr.sh
fi
# mkfs
echo "Start creating file system ... "
echo "Create a boot file system ... "
echo "format boot partiton..."
mkfs.fat -n EMMC_BOOT -F 32 /dev/${EMMC_NAME}p1
mkdir -p /mnt/${EMMC_NAME}p1
sleep 2
umount -f /mnt/${EMMC_NAME}p1 2>/dev/null
echo "format rootfs1 partiton..."
ROOTFS1_UUID=$(/usr/bin/uuidgen)
mkfs.btrfs -f -U ${ROOTFS1_UUID} -L EMMC_ROOTFS1 -m single /dev/${EMMC_NAME}p2
mkdir -p /mnt/${EMMC_NAME}p2
sleep 2
umount -f /mnt/${EMMC_NAME}p2 2>/dev/null
echo "format rootfs2 partiton..."
ROOTFS2_UUID=$(/usr/bin/uuidgen)
mkfs.btrfs -f -U ${ROOTFS2_UUID} -L EMMC_ROOTFS2 -m single /dev/${EMMC_NAME}p3
mkdir -p /mnt/${EMMC_NAME}p3
sleep 2
umount -f /mnt/${EMMC_NAME}p3 2>/dev/null
# mount and copy
echo "Wait for the boot file system to mount ... "
i=1
max_try=10
while [ $i -le $max_try ]; do
mount -t vfat /dev/${EMMC_NAME}p1 /mnt/${EMMC_NAME}p1 2>/dev/null
sleep 2
mnt=$(lsblk -l -o MOUNTPOINT | grep /mnt/${EMMC_NAME}p1)
if [ "$mnt" == "" ]; then
if [ $i -lt $max_try ]; then
echo "Not mounted successfully, try again ..."
i=$((i + 1))
else
error_msg "Cannot mount the boot file system, give up!"
fi
else
echo "Successfully mounted."
echo "copy boot ..."
cd /mnt/${EMMC_NAME}p1
rm -rf /boot/'System Volume Information/'
(cd /boot && tar cf - .) | tar xf -
sync
echo "Edit uEnv.txt ..."
[[ -f "uEnv.txt" ]] && {
sed -i -E \
-e "s|/dtb/amlogic.*|/dtb/amlogic/${FDTFILE}|" \
-e "s|UUID=[^ ]*|UUID=${ROOTFS1_UUID}|" \
-e "s|rootflags=compress=zstd:[^ ]*|rootflags=compress=zstd:${ZSTD_LEVEL}|" \
uEnv.txt
}
# Replace the extlinux/extlinux.conf if it exists
[[ -f "extlinux/extlinux.conf" ]] && {
sed -i -E \
-e "s|/dtb/amlogic.*|/dtb/amlogic/${FDTFILE}|" \
-e "s|UUID=[^ ]*|UUID=${ROOTFS1_UUID}|" \
-e "s|rootflags=compress=zstd:[^ ]*|rootflags=compress=zstd:${ZSTD_LEVEL}|" \
extlinux/extlinux.conf
}
rm -f s905_autoscript* aml_autoscript*
if [ ${K510} -eq 1 ]; then
if [ -f ${UBOOT_OVERLOAD} ]; then
cp -f -v ${UBOOT_OVERLOAD} u-boot.emmc
elif [ -f "u-boot.ext" ]; then
cp -f -v u-boot.ext u-boot.emmc
fi
fi
mv -f boot-emmc.ini boot.ini
mv -f boot-emmc.cmd boot.cmd
mv -f boot-emmc.scr boot.scr
sync
echo "complete."
cd /
umount -f /mnt/${EMMC_NAME}p1
break
fi
done
echo "complete."
echo "Wait for the rootfs file system to mount ... "
i=1
while [ $i -le $max_try ]; do
mount -t btrfs -o compress=zstd:${ZSTD_LEVEL} /dev/${EMMC_NAME}p2 /mnt/${EMMC_NAME}p2 2>/dev/null
sleep 2
mnt=$(lsblk -l -o MOUNTPOINT | grep /mnt/${EMMC_NAME}p2)
if [ "$mnt" == "" ]; then
if [ $i -lt $max_try ]; then
echo "Not mounted successfully, try again ..."
i=$((i + 1))
else
error_msg "Cannot mount rootfs file system, give up!"
fi
else
echo "Successfully mounted"
echo "Create folder ... "
cd /mnt/${EMMC_NAME}p2
btrfs subvolume create etc
mkdir -p bin boot dev lib opt mnt overlay proc rom root run sbin sys tmp usr www .reserved .snapshots
ln -sf lib/ lib64
ln -sf tmp/ var
sync
echo "complete."
COPY_SRC="root etc bin sbin lib opt usr www"
echo "Copy data ... "
for src in $COPY_SRC; do
echo "copy [ $src ] ..."
(cd / && tar cf - $src) | tar xf -
sync
done
echo "Copy complete."
sync
cat >etc/docker/daemon.json <<EOF
{
"bip": "172.31.0.1/24",
"data-root": "/mnt/${EMMC_NAME}p4/docker/",
"log-level": "warn",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
},
"registry-mirrors": [
"https://mirror.baidubce.com/",
"https://hub-mirror.c.163.com"
]
}
EOF
# change data_root value in /etc/config/dockerd
if [[ -f "/etc/init.d/dockerman" ]] && [[ -f "/etc/config/dockerd" ]]; then
sed -i "s|option data_root.*|option data_root '/mnt/${EMMC_NAME}p4/docker/'|g" etc/config/dockerd
fi
rm -rf opt/docker && ln -sf /mnt/${EMMC_NAME}p4/docker/ opt/docker >/dev/null
rm -rf usr/bin/AdGuardHome && ln -sf /mnt/${EMMC_NAME}p4/AdGuardHome usr/bin/ >/dev/null
echo "Edit configuration file ..."
#cd /mnt/${EMMC_NAME}p2/usr/bin/
#rm -f openwrt-install-amlogic openwrt-update-amlogic
cd /mnt/${EMMC_NAME}p2/etc/rc.d
ln -sf ../init.d/dockerd S99dockerd
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
cd /mnt/${EMMC_NAME}p2/etc
cat >fstab <<EOF
UUID=${ROOTFS1_UUID} / btrfs compress=zstd:${ZSTD_LEVEL} 0 1
LABEL=EMMC_BOOT /boot vfat defaults 0 2
#tmpfs /tmp tmpfs defaults,nosuid 0 0
EOF
cd /mnt/${EMMC_NAME}p2/etc/config
cat >fstab <<EOF
config global
option anon_swap '0'
option anon_mount '1'
option auto_swap '0'
option auto_mount '1'
option delay_root '5'
option check_fs '0'
config mount
option target '/rom'
option uuid '${ROOTFS1_UUID}'
option enabled '1'
option enabled_fsck '1'
option fstype 'btrfs'
option options 'compress=zstd:${ZSTD_LEVEL}'
config mount
option target '/boot'
option label 'EMMC_BOOT'
option enabled '1'
option enabled_fsck '1'
option fstype 'vfat'
EOF
echo -n "Create initial etc snapshot -> .snapshots/etc-000"
cd /mnt/${EMMC_NAME}p2 &&
btrfs subvolume snapshot -r etc .snapshots/etc-000
sync
cd /
umount -f /mnt/${EMMC_NAME}p2
break
fi
done
echo "complete."
echo "Create a shared file system."
mkdir -p /mnt/${EMMC_NAME}p4
# When there is no input parameter, select manually
if [[ -n "${SHARED_FSTYPE}" ]]; then
TARGET_SHARED_FSTYPE=${SHARED_FSTYPE}
else
cat <<EOF
---------------------------------------------------------------------------------
Please select the type of shared file system:
1. ext4: [Default options] suitable for general use.
2. btrfs: Which can extend the service life of ssd/mmc.
3. f2fs: Fast reading and writing speed, but the compatibility is slightly poor.
4. xfs: Very good file system, alternative to ext4.
---------------------------------------------------------------------------------
EOF
read -p "Please Input ID: " TARGET_SHARED_FSTYPE
fi
case $TARGET_SHARED_FSTYPE in
2 | btrfs)
mkfs.btrfs -f -L EMMC_SHARED -m single /dev/${EMMC_NAME}p4 >/dev/null
mount -t btrfs /dev/${EMMC_NAME}p4 /mnt/${EMMC_NAME}p4
;;
3 | f2fs)
mkfs.f2fs -f -l EMMC_SHARED /dev/${EMMC_NAME}p4 >/dev/null
mount -t f2fs /dev/${EMMC_NAME}p4 /mnt/${EMMC_NAME}p4
;;
4 | xfs)
mkfs.xfs -f -L EMMC_SHARED /dev/${EMMC_NAME}p4 >/dev/null
mount -t xfs /dev/${EMMC_NAME}p4 /mnt/${EMMC_NAME}p4
;;
*)
mkfs.ext4 -F -L EMMC_SHARED /dev/${EMMC_NAME}p4 >/dev/null
mount -t ext4 /dev/${EMMC_NAME}p4 /mnt/${EMMC_NAME}p4
;;
esac
mkdir -p /mnt/${EMMC_NAME}p4/docker /mnt/${EMMC_NAME}p4/AdGuardHome/data
sync
echo "Successful installed, please unplug the USB, re-insert the power supply to start the openwrt."
exit 0
+516
View File
@@ -0,0 +1,516 @@
#!/bin/bash
#====================================================================================
#
# Function: Update the kernel for OpenWrt (Amlogic s9xxx, Allwinner, Rockchip)
# Copyright (C) 2020-- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021-- https://github.com/ophub/luci-app-amlogic
#
# Support the kernel: boot-*.tar.gz, dtb-*.tar.gz, modules-*.tar.gz
# It is recommended to install MAINLINE_UBOOT for kernel versions above 5.10.y
# openwrt-kernel ${AUTO_MAINLINE_UBOOT}
# E.g: openwrt-kernel yes
# openwrt-kernel no
#
#================================== Functions list ==================================
#
# error_msg : Output error message
# get_textoffset : Get kernel TEXT_OFFSET
# init_var : Initialize all variables
# check_kernel : Check kernel files list
# chech_files_same : Check file consistency
# restore_kernel : Restore current kernel
# update_kernel : Update the kernel
# update_uboot : Update the uboot
#
#============================== Set default parameters ==============================
#
# Receive one-key command related parameters
AUTO_MAINLINE_UBOOT="no"
# Set the release check file
release_file="/etc/flippy-openwrt-release"
#
#====================================================================================
# Encountered a serious error, abort the script execution
error_msg() {
echo -e "[Error] ${1}"
exit 1
}
# Get the partition name of the /boot file system
get_boot_partition_name() {
local paths="/boot"
local partition_name
partition_name=$(df "${paths}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
echo "${partition_name}"
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
echo "${partition_name}"
}
# Get kernel TEXT_OFFSET, For u-boot.ext and u-boot.emmc
get_textoffset() {
boot_tgz_file="${1}"
vmlinuz_name="${2}"
K510="1"
temp_dir="$(mktemp -d)"
(
cd ${temp_dir}
tar --no-same-owner -xf "${boot_tgz_file}" "${vmlinuz_name}"
)
# With TEXT_OFFSET patch is [ 0108 ], without TEXT_OFFSET patch is [ 0000 ]
[[ "$(hexdump -n 15 -x "${temp_dir}/${vmlinuz_name}" 2>/dev/null | head -n 1 | awk '{print $7}')" == "0108" ]] && K510="0"
}
init_var() {
# Receive one-key command related parameters
[[ "${1}" == "yes" ]] && AUTO_MAINLINE_UBOOT="yes"
# Check dependencies
[[ -n "$(busybox which tar)" ]] || error_msg "Missing [ tar ] in OpenWrt firmware, unable to update kernel"
# Check release file
if [[ -s "${release_file}" ]]; then
source "${release_file}"
PLATFORM="${PLATFORM}"
MODEL_ID="${MODEL_ID}"
UBOOT_OVERLOAD="${UBOOT_OVERLOAD}"
MAINLINE_UBOOT="${MAINLINE_UBOOT}"
ANDROID_UBOOT="${ANDROID_UBOOT}"
SOC="${SOC}"
LOCK_KERNEL="${LOCK_KERNEL}"
else
error_msg "${release_file} file is missing!"
fi
[[ -n "${PLATFORM}" ]] || error_msg "Missing ${PLATFORM} value in ${release_file} file."
# Define supported platforms
support_platform=("allwinner" "rockchip" "amlogic" "qemu-aarch64")
[[ -n "$(echo "${support_platform[@]}" | grep -w "${PLATFORM}")" ]] || error_msg "[ ${PLATFORM} ] is not supported."
# Check and repair partition mount points
[[ -z "$(get_boot_partition_name)" || -z "$(get_root_partition_name)" ]] && {
echo -e "Partition mount point error, start repairing..."
openwrt-backup -g
}
# Set /boot/vmlinuz-* replication names for different SoCs
MYBOOT_VMLINUZ="$(ls -l /boot/*Image 2>/dev/null | awk '{print $9}' | head -n 1)"
MYBOOT_VMLINUZ="${MYBOOT_VMLINUZ##*/}"
[[ -n "${MYBOOT_VMLINUZ}" ]] || error_msg "Failed to get Image name: [ ${MYBOOT_VMLINUZ} ]"
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case "${ROOT_PTNAME}" in
mmcblk?p[1-9])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')"
PARTITION_NAME="p"
;;
[hsv]d[a-z][1-9])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')"
PARTITION_NAME=""
;;
nvme?n?p[1-9])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')"
PARTITION_NAME="p"
;;
*)
error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!"
;;
esac
P4_PATH="/mnt/${EMMC_NAME}${PARTITION_NAME}4"
# Move kernel related files to the ${P4_PATH} directory
mv -f /tmp/upload/* ${P4_PATH} 2>/dev/null
# Current device model
MYDEVICE_NAME="$(cat /proc/device-tree/model | tr -d '\000')"
[[ "${PLATFORM}" == "qemu-aarch64" ]] && MYDEVICE_NAME="KVM Virtual Machine"
echo -e "Current device: ${MYDEVICE_NAME} [ ${PLATFORM} ], Use in [ ${EMMC_NAME} ]"
sync && echo ""
}
# Check kernel files list
check_kernel() {
cd ${P4_PATH}
# Determine custom kernel filename
kernel_boot="$(ls boot-*.tar.gz 2>/dev/null | head -n 1)"
kernel_name="${kernel_boot:5:-7}"
KERNEL_VERSION="$(echo ${kernel_name} | grep -oE '^[1-9].[0-9]{1,3}.[0-9]+')"
echo -e "Kernel name: ${kernel_name}"
# check if kernel is locked
if [ -n "${LOCK_KERNEL}" ]; then
if [ "${LOCK_KERNEL}" != "${kernel_name}" ]; then
if ! echo "${kernel_name}" | grep -E '^5.10.\d+-.*?rk35.*?$' >/dev/null; then
error_msg "The kernel version is locked to [ ${LOCK_KERNEL} ], but your kernel is [ ${kernel_name} ]. "
fi
fi
fi
# Check if the file is added with TEXT_OFFSET patch
get_textoffset "${P4_PATH}/${kernel_boot}" "vmlinuz-${kernel_name}"
echo -e "K510 [ ${K510} ]"
if [[ "${PLATFORM}" == "amlogic" && "${K510}" -eq "1" ]]; then
[[ -n "${UBOOT_OVERLOAD}" && -f "/boot/${UBOOT_OVERLOAD}" ]] || error_msg "The UBOOT_OVERLOAD file is missing and cannot be update."
fi
# Check the sha256sums file
sha256sums_file="sha256sums"
sha256sums_check="1"
[[ -s "${sha256sums_file}" && -n "$(cat ${sha256sums_file})" ]] || sha256sums_check="0"
[[ -n "$(busybox which sha256sum)" ]] || sha256sums_check="0"
[[ "${sha256sums_check}" -eq "1" ]] && echo -e "Enable sha256sum checking..."
# Loop check file
i="1"
if [[ "${PLATFORM}" == "qemu-aarch64" ]]; then
kernel_list=("boot" "modules")
else
kernel_list=("boot" "dtb-${PLATFORM}" "modules")
fi
for kernel_file in ${kernel_list[*]}; do
# Set check filename
tmp_file="${kernel_file}-${kernel_name}.tar.gz"
# Check if file exists
[[ -s "${tmp_file}" ]] || error_msg "The [ ${kernel_file} ] file is missing."
# Check if the file sha256sum is correct
if [[ "${sha256sums_check}" -eq "1" ]]; then
tmp_sha256sum="$(sha256sum "${tmp_file}" | awk '{print $1}')"
tmp_checkcode="$(cat ${sha256sums_file} | grep ${tmp_file} | awk '{print $1}')"
[[ "${tmp_sha256sum}" == "${tmp_checkcode}" ]] || error_msg "${tmp_file}: sha256sum verification failed."
echo -e "(${i}/3) [ ${tmp_file} ] file sha256sum check same."
fi
let i++
done
sync && echo ""
}
# Check the consistency of amlogic device files
chech_files_same() {
i="0"
max_try="5"
while [[ "${i}" -le "${max_try}" ]]; do
if [[ "$(sha256sum "${1}" | awk '{print $1}')" == "$(sha256sum "${2}" | awk '{print $1}')" ]]; then
echo "" && break
else
cp -f ${1} ${2}
i="$((i + 1))"
fi
done
[[ "${i}" -gt "${max_try}" ]] && echo "God, it's different after ${max_try} copies: [ ${1} ]"
}
# Restore the kernel when the update fails
restore_kernel() {
(
cd /boot
rm -rf \
config-${kernel_name} \
System.map-${kernel_name} \
initrd.img-${kernel_name} \
uInitrd-${kernel_name} \
vmlinuz-${kernel_name} \
initrd.img \
uInitrd \
zImage \
Image \
vmlinuz \
dtb* 2>/dev/null
tar -xzf /tmp/boot-backup.tar.gz 2>/dev/null
)
error_msg "Kernel update failed and has been reverted."
}
# Update the kernel
update_kernel() {
local cur_kernel_name=$(uname -r)
local boot_fstype=$(df -T /boot | tail -n1 | awk '{print $2}')
echo -e "Start unpacking the kernel..."
# 01. for /boot five files
# Backup the current_kernel
(
cd /boot
tar -czf /tmp/boot-backup.tar.gz \
config-${cur_kernel_name} \
System.map-${cur_kernel_name} \
initrd.img-${cur_kernel_name} \
uInitrd-${cur_kernel_name} \
vmlinuz-${cur_kernel_name} \
initrd.img \
uInitrd \
zImage \
Image \
vmlinuz \
dtb* 2>/dev/null
rm -rf \
config-${cur_kernel_name} \
System.map-${cur_kernel_name} \
initrd.img-${cur_kernel_name} \
uInitrd-${cur_kernel_name} \
vmlinuz-${cur_kernel_name} \
initrd.img \
uInitrd \
zImage \
Image \
vmlinuz \
dtb* 2>/dev/null
)
# Extract the new kernel
tar --no-same-owner -xf ${P4_PATH}/boot-${kernel_name}.tar.gz -C /boot
# Check if the file exists
local valid_files
if [[ "${PLATFORM}" == "qemu-aarch64" ]]; then
valid_files="vmlinuz-${kernel_name} initrd.img-${kernel_name} config-${kernel_name} System.map-${kernel_name}"
rm -f /boot/uInitrd*
else
valid_files="vmlinuz-${kernel_name} uInitrd-${kernel_name} config-${kernel_name} System.map-${kernel_name}"
# wxy-oect: MODEL_ID numbers r306,r307 and w[0-9]{3}, require special handling of uInitrd
[[ "${MODEL_ID}" =~ ^(r306|r307|w[0-9]{3})$ ]] || rm -f /boot/initrd.img*
fi
for f in ${valid_files}; do [[ -f "/boot/${f}" ]] || restore_kernel; done
# Check if the files are the same
(
cd /boot
if [[ "${PLATFORM}" == "qemu-aarch64" ]]; then
ln -sf initrd.img-${kernel_name} initrd.img
ln -sf vmlinuz-${kernel_name} ${MYBOOT_VMLINUZ}
elif [[ "$boot_fstype" == "vfat" ]]; then
cp -f uInitrd-${kernel_name} uInitrd
[[ -z "$(chech_files_same uInitrd-${kernel_name} uInitrd)" ]] || restore_kernel
cp -f vmlinuz-${kernel_name} ${MYBOOT_VMLINUZ}
[[ -z "$(chech_files_same vmlinuz-${kernel_name} ${MYBOOT_VMLINUZ})" ]] || restore_kernel
else
ln -sf uInitrd-${kernel_name} uInitrd
ln -sf vmlinuz-${kernel_name} ${MYBOOT_VMLINUZ}
fi
# wxy-oect: MODEL_ID numbers r306 and r307, require special handling of uInitrd
[[ "${MODEL_ID}" =~ ^(r306|r307)$ ]] && ln -sf initrd.img-${kernel_name} uInitrd
)
echo -e "(1/3) Unpacking [ boot-${kernel_name}.tar.gz ] done."
if [[ "${PLATFORM}" == "qemu-aarch64" ]]; then
echo -e "(2/3) skip unpack dtb files."
else
# 02. for /boot/dtb/${PLATFORM}/*
if [[ "${boot_fstype}" == "vfat" ]]; then
(cd /boot && mkdir -p dtb/${PLATFORM})
else
(cd /boot && mkdir -p dtb-${kernel_name}/${PLATFORM} && ln -sf dtb-${kernel_name} dtb)
fi
tar --no-same-owner -xf ${P4_PATH}/dtb-${PLATFORM}-${kernel_name}.tar.gz -C /boot/dtb/${PLATFORM}
[[ "$(ls /boot/dtb/${PLATFORM} -l 2>/dev/null | grep "^-" | wc -l)" -ge "1" ]] || error_msg "/boot/dtb/${PLATFORM} file is missing."
echo -e "(2/3) Unpacking [ dtb-${PLATFORM}-${kernel_name}.tar.gz ] done."
fi
# 03. for /lib/modules/*
rm -rf /lib/modules/*
tar --no-same-owner -xf ${P4_PATH}/modules-${kernel_name}.tar.gz -C /lib/modules
(
cd /lib/modules/${kernel_name}
rm -f *.ko
find ./ -type f -name '*.ko' -exec ln -s {} ./ \;
sync && sleep 3
x=$(ls *.ko -l 2>/dev/null | grep "^l" | wc -l)
[[ "${x}" -eq "0" ]] && error_msg "Error *.ko Files not found."
)
echo -e "(3/3) Unpacking [ modules-${kernel_name}.tar.gz ] done."
# Delete kernel tmpfiles
rm -f ${P4_PATH}/*-${kernel_name}.tar.gz
rm -f ${P4_PATH}/sha256sums
sync && echo ""
}
# Update the uboot
update_uboot() {
# Only amlogic SoCs needs to be updated
if [[ "${PLATFORM}" == "amlogic" ]]; then
# Copy u-boot.ext and u-boot.emmc
if [[ "${K510}" -eq "1" && -n "${UBOOT_OVERLOAD}" && -f "/boot/${UBOOT_OVERLOAD}" ]]; then
[[ ! -f "/boot/u-boot.ext" ]] && cp -f "/boot/${UBOOT_OVERLOAD}" /boot/u-boot.ext && chmod +x /boot/u-boot.ext
[[ ! -f "/boot/u-boot.emmc" ]] && cp -f "/boot/u-boot.ext" /boot/u-boot.emmc && chmod +x /boot/u-boot.emmc
echo -e "The ${UBOOT_OVERLOAD} file copy is complete."
elif [[ "${K510}" -eq "0" ]]; then
rm -f "/boot/u-boot.ext" "/boot/u-boot.emmc"
fi
# Write Mainline bootloader
if [[ -f "${MAINLINE_UBOOT}" && "${AUTO_MAINLINE_UBOOT}" == "yes" ]]; then
echo -e "Write Mainline bootloader: [ ${MAINLINE_UBOOT} ] to [ /dev/${EMMC_NAME} ]"
dd if=${MAINLINE_UBOOT} of=/dev/${EMMC_NAME} bs=1 count=442 conv=fsync
dd if=${MAINLINE_UBOOT} of=/dev/${EMMC_NAME} bs=512 skip=1 seek=1 conv=fsync
echo -e "The MAINLINE_UBOOT file write is complete."
fi
fi
# Update release file
sed -i "s|^KERNEL_VERSION=.*|KERNEL_VERSION='${kernel_name}'|g" ${release_file} 2>/dev/null
# Update banner file
sed -i "s| Kernel.*| Kernel: ${kernel_name}|g" /etc/banner 2>/dev/null
sync && echo ""
}
# Rescue the kernel
sos_kernel() {
echo -e "Start rescuing kernel..."
# Supports specifying disks, such as: [ openwrt-kernel -s mmcblk1 ]
box_disk="${2}"
if [[ -n "${box_disk}" ]]; then
# Format the disk names
box_disk="${box_disk//\/dev\//}"
# Check if the disk exists
[[ -b "/dev/${box_disk}" ]] || error_msg "The specified disk [ ${box_disk} ] does not exist."
# Check if the disk is the same as the current system disk
[[ "${box_disk}" == "${EMMC_NAME}" ]] && error_msg "The specified disk [ ${box_disk} ] is the same as the current system disk [ ${EMMC_NAME} ]."
echo -e "The device name of the specified disk: [ ${box_disk} ]"
else
# Find emmc disk, first find emmc containing boot0 partition
box_disk="$(lsblk -l -o NAME | grep -oE '(mmcblk[0-9]?|nvme[0-9]?n[0-9]?|[hsv]d[a-z])' | grep -vE ^${EMMC_NAME} | sort -u | head -n 1)"
# Check if disk exists
[[ -z "${box_disk}" ]] && error_msg "Unable to locate the storage requiring rescue."
echo -e "The device name of the target disk: [ ${box_disk} ]"
fi
rescue_disk="/dev/${box_disk}"
echo -e "The current OpenWrt is running on [ /dev/${EMMC_NAME} ], and the target disk for restoration is [ ${rescue_disk} ]."
# Create a temporary mount directory
umount ${P4_PATH}/bootfs 2>/dev/null
umount ${P4_PATH}/rootfs 2>/dev/null
rm -rf ${P4_PATH}/bootfs ${P4_PATH}/rootfs 2>/dev/null
mkdir -p ${P4_PATH}/{bootfs/,rootfs/} && sync
[[ "${?}" -ne "0" ]] && error_msg "Failed to create temporary mount directory [ ${P4_PATH} ]"
# Mount target bootfs partition
[[ "${box_disk}" =~ ^([hsv]d[a-z]) ]] && rescue_disk_partition_name="" || rescue_disk_partition_name="p"
mount ${rescue_disk}${rescue_disk_partition_name}1 ${P4_PATH}/bootfs
[[ "${?}" -ne "0" ]] && error_msg "mount ${rescue_disk}${PARTITION_NAME}1 failed!"
echo -e "The [ ${rescue_disk}${rescue_disk_partition_name}1 ] partition is mounted on [ ${P4_PATH}/bootfs ]."
# Search uuid file
if [[ -f "${P4_PATH}/bootfs/uEnv.txt" ]]; then
search_file="uEnv.txt"
elif [[ -f "${P4_PATH}/bootfs/armbianEnv.txt" ]]; then
search_file="armbianEnv.txt"
elif [[ -f "${P4_PATH}/bootfs/extlinux/extlinux.conf" ]]; then
search_file="extlinux/extlinux.conf"
else
error_msg "The [ uEnv.txt, armbianEnv.txt, extlinux/extlinux.conf ] file does not exist, stop rescuing."
fi
# Get the target partition uuid and rootfs
target_parttion_uuid="$(grep '=UUID=' ${P4_PATH}/bootfs/${search_file} | sed -n 's/.*=UUID=\([a-f0-9-]*\).*/\1/p')"
[[ -z "${target_parttion_uuid}" ]] && error_msg "The [ ${search_file} ] file does not contain the UUID value."
target_rootfs="$(blkid | grep ${target_parttion_uuid} | awk -F':' '{print $1;}')"
[[ -z "${target_rootfs}" ]] && error_msg "The [ ${target_parttion_uuid} ] UUID does not exist in the system."
# Mount target rootfs partition
mount ${target_rootfs} ${P4_PATH}/rootfs
[[ "${?}" -ne "0" ]] && error_msg "mount ${rescue_disk}${PARTITION_NAME}2 failed!"
echo -e "The [ ${target_rootfs} ] partition is mounted on [ ${P4_PATH}/rootfs ]."
# Identify the current kernel files
kernel_signature="$(uname -r)"
# 01. For /boot files
[[ -d "${P4_PATH}/bootfs" ]] && {
cd ${P4_PATH}/bootfs
rm -rf config-* initrd.img-* System.map-* vmlinuz-* uInitrd* *Image dtb* u-boot.ext u-boot.emmc
[[ -f "/boot/u-boot.ext" ]] && {
cp -f /boot/u-boot.ext .
cp -f /boot/u-boot.ext u-boot.emmc
chmod +x u-boot.ext u-boot.emmc
}
cp -rf /boot/{*-${kernel_signature},uInitrd,*Image,dtb} .
[[ "${?}" -ne "0" ]] && error_msg "(1/2) [ boot ] kernel files rescue failed."
echo -e "(1/2) [ boot ] kernel files rescue succeeded."
[[ -f "/boot/emmc_autoscript.cmd" ]] && cp -f /boot/emmc_autoscript.cmd .
[[ -f "/boot/emmc_autoscript" ]] && cp -f /boot/emmc_autoscript .
[[ -f "/boot/s905_autoscript.cmd" ]] && cp -f /boot/s905_autoscript.cmd .
[[ -f "/boot/s905_autoscript" ]] && cp -f /boot/s905_autoscript .
} || error_msg "(1/2) The [ ${P4_PATH}/bootfs ] folder does not exist, stop rescuing."
# 02. For /lib/modules/${kernel_signature}
[[ -d "${P4_PATH}/rootfs/lib/modules" ]] && {
cd ${P4_PATH}/rootfs/lib/modules
rm -rf *
cp -rf /lib/modules/${kernel_signature} .
[[ "${?}" -ne "0" ]] && error_msg "(2/2) [ modules ] kernel files rescue failed."
echo -e "(2/2) [ modules ] kernel files rescue succeeded."
} || error_msg "(2/2) The [ ${P4_PATH}/rootfs/lib/modules ] folder does not exist, stop rescuing."
# Unmount the emmc partition
cd ${P4_PATH}
umount -f ${P4_PATH}/bootfs
[[ "${?}" -ne "0" ]] && error_msg "Failed to umount [ ${P4_PATH}/bootfs ]"
umount -f ${P4_PATH}/rootfs
[[ "${?}" -ne "0" ]] && error_msg "Failed to umount [ ${P4_PATH}/rootfs ]"
# Remove the temporary mount directory
rm -rf ${P4_PATH}/bootfs ${P4_PATH}/rootfs
sync && echo ""
}
echo -e "Welcome to the OpenWrt Kernel Management Tool."
# Operation environment check
[[ -x "/usr/sbin/openwrt-kernel" ]] || error_msg "Please grant execution permission: chmod +x /usr/sbin/openwrt-kernel"
# Execute relevant functions based on the options
if [[ "${@}" =~ ^-s(\s)* ]]; then
# Initialize all variables
init_var "${@}"
# Start rescuing the kernel
sos_kernel "${@}"
# Kernel restore successful
sync && sleep 3
echo -e "Kernel rescue successful, please remove the disk and restart the OpenWrt system."
exit 0
else
# Initialize all variables
init_var "${@}"
# Check kernel files list
check_kernel
# Update the kernel
update_kernel
# Update the uboot
update_uboot
# Kernel update successful
sync && sleep 3
echo "Successfully updated, automatic restarting..."
reboot
exit 0
fi
+662
View File
@@ -0,0 +1,662 @@
#!/bin/bash
#======================================================================================
# Function: Update openwrt to emmc for Allwinner STB
# Copyright (C) 2020-- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021-- https://github.com/ophub/luci-app-amlogic
#======================================================================================
#
# The script supports directly setting parameters for update, skipping interactive selection
# openwrt-update-allwinner ${OPENWRT_FILE} ${AUTO_MAINLINE_UBOOT} ${RESTORE_CONFIG}
# E.g: openwrt-update-allwinner openwrt_s905d.img.gz yes restore
# E.g: openwrt-update-allwinner openwrt_s905d.img.gz no no-restore
# You can also execute the script directly, and interactively select related functions
# E.g: openwrt-update-allwinner
#
#======================================================================================
# Encountered a serious error, abort the script execution
error_msg() {
echo -e "[ERROR] ${1}"
exit 1
}
# Get the partition name of the /boot file system
get_boot_partition_name() {
local paths="/boot"
local partition_name
partition_name=$(df "${paths}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
echo "${partition_name}"
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
echo "${partition_name}"
}
# Get the partition message of the root file system
get_root_partition_msg() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_msg=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ "^" "'"${path}"'" "$" {print $0}')
[[ -n "${partition_msg}" ]] && break
done
[[ -z "${partition_msg}" ]] && error_msg "Cannot find the root partition message!"
echo "${partition_msg}"
}
# Receive one-key command related parameters
IMG_NAME=${1}
AUTO_MAINLINE_UBOOT=${2}
BACKUP_RESTORE_CONFIG=${3}
# Current FDT file
if [[ -f "/boot/uEnv.txt" ]]; then
source /boot/uEnv.txt 2>/dev/null
MYDTB_FDTFILE=$(basename $FDT)
elif [[ -f "/boot/armbianEnv.txt" ]]; then
source /boot/armbianEnv.txt 2>/dev/null
MYDTB_FDTFILE="$(basename $fdtfile)"
elif [[ -f "/etc/flippy-openwrt-release" ]]; then
source /etc/flippy-openwrt-release 2>/dev/null
MYDTB_FDTFILE="${FDTFILE}"
fi
[[ -z "${MYDTB_FDTFILE}" ]] && error_msg "Invalid FDTFILE: [ ${MYDTB_FDTFILE} ]"
# Current device model
MYDEVICE_NAME=$(cat /proc/device-tree/model | tr -d '\000')
if [[ -z "${MYDEVICE_NAME}" ]]; then
error_msg "The device name is empty and cannot be recognized."
elif [[ "$(echo ${MYDEVICE_NAME} | grep "V-Plus Cloud")" == "" ]]; then
error_msg "[ ${MYDEVICE_NAME} ] is not [ V-Plus Cloud ] device, please select the correct script."
else
echo -e "Current device: ${MYDEVICE_NAME} [ vplus ]"
sleep 3
fi
# Check and repair partition mount points
[[ -z "$(get_boot_partition_name)" || -z "$(get_root_partition_name)" ]] && {
echo -e "Partition mount point error, start repairing..."
openwrt-backup -g
}
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case ${ROOT_PTNAME} in
mmcblk?p[1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')
PARTITION_NAME="p"
LB_PRE="EMMC_"
;;
[hsv]d[a-z][1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')
PARTITION_NAME=""
LB_PRE=""
;;
*)
error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!"
;;
esac
cd /mnt/${EMMC_NAME}${PARTITION_NAME}4/
mv -f /tmp/upload/* . 2>/dev/null && sync
if [[ "${IMG_NAME}" == *.img ]]; then
echo -e "Update using [ ${IMG_NAME} ] file. Please wait a moment ..."
elif [ $(ls *.img -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
IMG_NAME=$(ls *.img | head -n 1)
echo -e "Update using [ ${IMG_NAME} ] ] file. Please wait a moment ..."
elif [ $(ls *.img.xz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
xz_file=$(ls *.img.xz | head -n 1)
echo -e "Update using [ ${xz_file} ] file. Please wait a moment ..."
xz -d "${xz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.img.gz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.img.gz | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
gzip -df "${gz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.7z -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.7z | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
bsdtar -xmf "${gz_file}" 2>/dev/null
[ $? -eq 0 ] || 7z x "${gz_file}" -aoa -y 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.zip -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
zip_file=$(ls *.zip | head -n 1)
echo -e "Update using [ ${zip_file} ] file. Please wait a moment ..."
unzip -o "${zip_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
else
echo -e "Please upload or specify the update openwrt firmware file."
echo -e "Upload method: system menu → Amlogic Service → Manually Upload Update"
echo -e "Specify method: Place the openwrt firmware file in [ /mnt/${EMMC_NAME}${PARTITION_NAME}4/ ]"
echo -e "The supported file suffixes are: *.img, *.img.xz, *.img.gz, *.7z, *.zip"
echo -e "After upload the openwrt firmware file, run again."
exit 1
fi
sync
# check file
if [ ! -f "${IMG_NAME}" ]; then
error_msg "No update file found."
else
echo "Start update from [ ${IMG_NAME} ]"
fi
# find boot partition
BOOT_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ /^\/boot$/ {print $0}')
if [ "${BOOT_PART_MSG}" == "" ]; then
error_msg "The boot partition is not exists or not mounted, so it cannot be upgraded with this script!"
fi
BR_FLAG=1
echo -ne "Whether to backup and restore the current config files? y/n [y]\b\b"
if [[ ${BACKUP_RESTORE_CONFIG} == "restore" ]]; then
yn="y"
elif [[ ${BACKUP_RESTORE_CONFIG} == "no-restore" ]]; then
yn="n"
else
read yn
fi
case $yn in
n* | N*)
BR_FLAG=0
;;
esac
BOOT_NAME=$(echo ${BOOT_PART_MSG} | awk '{print $1}')
BOOT_PATH=$(echo ${BOOT_PART_MSG} | awk '{print $2}')
BOOT_UUID=$(echo ${BOOT_PART_MSG} | awk '{print $4}')
# find root partition
ROOT_PART_MSG="$(get_root_partition_msg)"
ROOT_NAME=$(echo ${ROOT_PART_MSG} | awk '{print $1}')
ROOT_PATH=$(echo ${ROOT_PART_MSG} | awk '{print $2}')
ROOT_UUID=$(echo ${ROOT_PART_MSG} | awk '{print $4}')
case ${ROOT_NAME} in
${EMMC_NAME}${PARTITION_NAME}2)
NEW_ROOT_NAME="${EMMC_NAME}${PARTITION_NAME}3"
NEW_ROOT_LABEL="${LB_PRE}ROOTFS2"
;;
${EMMC_NAME}${PARTITION_NAME}3)
NEW_ROOT_NAME="${EMMC_NAME}${PARTITION_NAME}2"
NEW_ROOT_LABEL="${LB_PRE}ROOTFS1"
;;
*)
error_msg "The root partition location is invalid, so it cannot be upgraded with this script!"
;;
esac
# find new root partition
NEW_ROOT_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | grep "${NEW_ROOT_NAME}" | awk '$3 ~ /^part$/ && $5 !~ /^\/$/ && $5 !~ /^\/boot$/ {print $0}')
if [ "${NEW_ROOT_PART_MSG}" == "" ]; then
error_msg "The new root partition is not exists, so it cannot be upgraded with this script!"
fi
NEW_ROOT_NAME=$(echo $NEW_ROOT_PART_MSG | awk '{print $1}')
NEW_ROOT_PATH=$(echo $NEW_ROOT_PART_MSG | awk '{print $2}')
NEW_ROOT_UUID=$(echo $NEW_ROOT_PART_MSG | awk '{print $4}')
NEW_ROOT_MP=$(echo $NEW_ROOT_PART_MSG | awk '{print $5}')
# losetup
losetup -f -P $IMG_NAME
if [ $? -eq 0 ]; then
LOOP_DEV=$(losetup | grep "$IMG_NAME" | awk '{print $1}')
if [ "$LOOP_DEV" == "" ]; then
error_msg "loop device not found!"
fi
else
error_msg "losetup $IMG_FILE failed!"
fi
# fix loopdev issue in kernel 5.19
function fix_loopdev() {
local parentdev=${1##*/}
if [ ! -d /sys/block/${parentdev} ]; then
return
fi
subdevs=$(lsblk -l -o NAME | grep -E "^${parentdev}.+\$")
for subdev in $subdevs; do
if [ ! -d /sys/block/${parentdev}/${subdev} ]; then
return
elif [ -b /dev/${sub_dev} ]; then
continue
fi
source /sys/block/${parentdev}/${subdev}/uevent
mknod /dev/${subdev} b ${MAJOR} ${MINOR}
unset MAJOR MINOR DEVNAME DEVTYPE DISKSEQ PARTN PARTNAME
done
}
fix_loopdev ${LOOP_DEV}
WAIT=3
echo -n "The loopdev is $LOOP_DEV, wait ${WAIT} seconds "
while [ $WAIT -ge 1 ]; do
echo -n "."
sleep 1
WAIT=$((WAIT - 1))
done
echo
# umount loop devices (openwrt will auto mount some partition)
MOUNTED_DEVS=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "$LOOP_DEV" | awk '$3 !~ /^$/ {print $2}')
for dev in $MOUNTED_DEVS; do
while :; do
echo -n "umount $dev ... "
umount -f $dev
sleep 1
mnt=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "$dev" | awk '$3 !~ /^$/ {print $2}')
if [ "$mnt" == "" ]; then
echo "ok"
break
else
echo "try again ..."
fi
done
done
# mount src part
WORK_DIR=$PWD
P1=${WORK_DIR}/boot
P2=${WORK_DIR}/root
mkdir -p $P1 $P2
echo -n "mount ${LOOP_DEV}p1 -> ${P1} ... "
mount -t vfat -o ro ${LOOP_DEV}p1 ${P1}
if [ $? -ne 0 ]; then
echo "mount failed"
losetup -D
exit 1
else
echo "ok"
fi
echo -n "mount ${LOOP_DEV}p2 -> ${P2} ... "
ZSTD_LEVEL=6
mount -t btrfs -o ro,compress=zstd:${ZSTD_LEVEL} ${LOOP_DEV}p2 ${P2}
if [ $? -ne 0 ]; then
echo "mount failed"
umount -f ${P1}
losetup -D
exit 1
else
echo "ok"
fi
# Prepare the dockerman config file
if [ -f ${P2}/etc/init.d/dockerman ] && [ -f ${P2}/etc/config/dockerd ]; then
flg=0
# get current docker data root
data_root=$(uci get dockerd.globals.data_root 2>/dev/null)
if [ "$data_root" == "" ]; then
flg=1
# get current config from /etc/docker/daemon.json
if [ -f "/etc/docker/daemon.json" ] && [ -x "/usr/bin/jq" ]; then
data_root=$(jq -r '."data-root"' /etc/docker/daemon.json)
bip=$(jq -r '."bip"' /etc/docker/daemon.json)
[ "$bip" == "null" ] && bip="172.31.0.1/24"
log_level=$(jq -r '."log-level"' /etc/docker/daemon.json)
[ "$log_level" == "null" ] && log_level="warn"
_iptables=$(jq -r '."iptables"' /etc/docker/daemon.json)
[ "$_iptables" == "null" ] && _iptables="true"
registry_mirrors=$(jq -r '."registry-mirrors"[]' /etc/docker/daemon.json 2>/dev/null)
fi
fi
if [ "$data_root" == "" ]; then
data_root="/opt/docker/" # the default data root
fi
if ! uci get dockerd.globals >/dev/null 2>&1; then
uci set dockerd.globals='globals'
uci commit
fi
# delete alter config , use inner config
if uci get dockerd.globals.alt_config_file >/dev/null 2>&1; then
uci delete dockerd.globals.alt_config_file
uci commit
fi
if [ $flg -eq 1 ]; then
uci set dockerd.globals.data_root=$data_root
[ "$bip" != "" ] && uci set dockerd.globals.bip=$bip
[ "$log_level" != "" ] && uci set dockerd.globals.log_level=$log_level
[ "$_iptables" != "" ] && uci set dockerd.globals.iptables=$_iptables
if [ "$registry_mirrors" != "" ]; then
for reg in $registry_mirrors; do
uci add_list dockerd.globals.registry_mirrors=$reg
done
fi
uci set dockerd.globals.auto_start='1'
uci commit
fi
fi
#format NEW_ROOT
echo "umount ${NEW_ROOT_MP}"
umount -f "${NEW_ROOT_MP}"
if [ $? -ne 0 ]; then
echo "umount failed, please reboot and try again!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
echo "format ${NEW_ROOT_PATH}"
NEW_ROOT_UUID=$(uuidgen)
mkfs.btrfs -f -U ${NEW_ROOT_UUID} -L ${NEW_ROOT_LABEL} ${NEW_ROOT_PATH}
if [ $? -ne 0 ]; then
echo "format ${NEW_ROOT_PATH} failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
echo "mount ${NEW_ROOT_PATH} to ${NEW_ROOT_MP}"
mount -t btrfs -o compress=zstd:${ZSTD_LEVEL} ${NEW_ROOT_PATH} ${NEW_ROOT_MP}
if [ $? -ne 0 ]; then
echo "mount ${NEW_ROOT_PATH} to ${NEW_ROOT_MP} failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
# begin copy rootfs
cd ${NEW_ROOT_MP}
echo "Start copy data from ${P2} to ${NEW_ROOT_MP} ..."
ENTRYS=$(ls)
for entry in $ENTRYS; do
if [ "$entry" == "lost+found" ]; then
continue
fi
echo -n "remove old $entry ... "
rm -rf $entry
if [ $? -eq 0 ]; then
echo "ok"
else
error_msg "failed"
fi
done
echo
echo "create etc subvolume ..."
btrfs subvolume create etc
echo -n "make dirs ... "
mkdir -p .snapshots .reserved bin boot dev lib opt mnt overlay proc rom root run sbin sys tmp usr www
ln -sf lib/ lib64
ln -sf tmp/ var
echo "done"
echo
COPY_SRC="root etc bin sbin lib opt usr www"
echo "copy data ... "
for src in $COPY_SRC; do
echo -n "copy $src ... "
(cd ${P2} && tar cf - $src) | tar xf -
sync
echo "done"
done
SHFS="/mnt/${EMMC_NAME}${PARTITION_NAME}4"
echo "Modify config files ... "
rm -f "./etc/rc.local.orig" "./etc/first_run.sh" "./etc/part_size"
rm -f ./etc/bench.log
if [ -x ./usr/sbin/balethirq.pl ]; then
if grep "balethirq.pl" "./etc/rc.local"; then
echo "balance irq is enabled"
else
echo "enable balance irq"
sed -e "/exit/i\/usr/sbin/balethirq.pl" -i ./etc/rc.local
fi
fi
cat >./etc/fstab <<EOF
UUID=${NEW_ROOT_UUID} / btrfs compress=zstd:${ZSTD_LEVEL} 0 1
LABEL=${LB_PRE}BOOT /boot vfat defaults 0 2
#tmpfs /tmp tmpfs defaults,nosuid 0 0
EOF
cat >./etc/config/fstab <<EOF
config global
option anon_swap '0'
option anon_mount '1'
option auto_swap '0'
option auto_mount '1'
option delay_root '5'
option check_fs '0'
config mount
option target '/rom'
option uuid '${NEW_ROOT_UUID}'
option enabled '1'
option enabled_fsck '1'
option fstype 'btrfs'
option options 'compress=zstd:${ZSTD_LEVEL}'
config mount
option target '/boot'
option label '${LB_PRE}BOOT'
option enabled '1'
option enabled_fsck '0'
option fstype 'vfat'
EOF
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
sync
echo "create the first etc snapshot -> .snapshots/etc-000"
btrfs subvolume snapshot -r etc .snapshots/etc-000
[ -d ${SHFS}/docker ] || mkdir -p ${SHFS}/docker
rm -rf opt/docker && ln -sf ${SHFS}/docker/ opt/docker
if [ -f /mnt/${NEW_ROOT_NAME}/etc/config/AdGuardHome ]; then
[ -d ${SHFS}/AdGuardHome/data ] || mkdir -p ${SHFS}/AdGuardHome/data
if [ ! -L /usr/bin/AdGuardHome ]; then
[ -d /usr/bin/AdGuardHome ] &&
cp -a /usr/bin/AdGuardHome/* ${SHFS}/AdGuardHome/
fi
ln -sf ${SHFS}/AdGuardHome /mnt/${NEW_ROOT_NAME}/usr/bin/AdGuardHome
fi
BOOTLOADER="./lib/u-boot/u-boot-sunxi-with-spl.bin"
if [ -f ${BOOTLOADER} ]; then
echo "update u-boot ... "
# erase from 8kb to 4mb
dd if=/dev/zero of=/dev/${EMMC_NAME} bs=1024 seek=8 count=4088 conv=fsync
# write u-boot
dd if=${BOOTLOADER} of=/dev/${EMMC_NAME} bs=1024 seek=8 conv=fsync
echo "done"
fi
sync
echo "copy done"
echo
BACKUP_LIST=$(${P2}/usr/sbin/openwrt-backup -p)
if [ $BR_FLAG -eq 1 ]; then
echo -n "Restore your old config files ... "
(
cd /
eval tar czf ${NEW_ROOT_MP}/.reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
)
tar xzf ${NEW_ROOT_MP}/.reserved/openwrt_config.tar.gz
[ -f ./etc/config/dockerman ] && sed -e "s/option wan_mode 'false'/option wan_mode 'true'/" -i ./etc/config/dockerman 2>/dev/null
[ -f ./etc/config/dockerd ] && sed -e "s/option wan_mode '0'/option wan_mode '1'/" -i ./etc/config/dockerd 2>/dev/null
[ -f ./etc/config/verysync ] && sed -e 's/config setting/config verysync/' -i ./etc/config/verysync
# 还原 fstab
cp -f .snapshots/etc-000/fstab ./etc/fstab
cp -f .snapshots/etc-000/config/fstab ./etc/config/fstab
# 还原 luci
cp -f .snapshots/etc-000/config/luci ./etc/config/luci
# 还原/etc/config/rpcd
cp -f .snapshots/etc-000/config/rpcd ./etc/config/rpcd
sync
echo "done"
echo
fi
rm -f ./etc/bench.log
cat >>./etc/crontabs/root <<EOF
17 3 * * * /etc/coremark.sh
EOF
sed -e 's/ttyAMA0/ttyS0/' -i ./etc/inittab
sss=$(date +%s)
ddd=$((sss / 86400))
sed -e "s/:0:0:99999:7:::/:${ddd}:0:99999:7:::/" -i ./etc/shadow
# 修复amule每次升级后重复添加条目的问题
sed -e "/amule:x:/d" -i ./etc/shadow
# 修复dropbear每次升级后重复添加sshd条目的问题
sed -e "/sshd:x:/d" -i ./etc/shadow
if ! grep "sshd:x:22:sshd" ./etc/group >/dev/null; then
echo "sshd:x:22:sshd" >>./etc/group
fi
if ! grep "sshd:x:22:22:sshd:" ./etc/passwd >/dev/null; then
echo "sshd:x:22:22:sshd:/var/run/sshd:/bin/false" >>./etc/passwd
fi
if ! grep "sshd:x:" ./etc/shadow >/dev/null; then
echo "sshd:x:${ddd}:0:99999:7:::" >>./etc/shadow
fi
if [ $BR_FLAG -eq 1 ]; then
if [ -x ./bin/bash ] && [ -f ./etc/profile.d/30-sysinfo.sh ]; then
sed -e 's/\/bin\/ash/\/bin\/bash/' -i ./etc/passwd
fi
sync
echo "done"
echo
fi
sed -e "s/option hw_flow '1'/option hw_flow '0'/" -i ./etc/config/turboacc
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
sync
eval tar czf .reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
rm -f ./etc/part_size ./etc/first_run.sh
mv ./etc/rc.local ./etc/rc.local.orig
cat >"./etc/rc.local" <<EOF
if ! ls /etc/rc.d/S??dockerd >/dev/null 2>&1;then
/etc/init.d/dockerd enable
/etc/init.d/dockerd start
fi
if ! ls /etc/rc.d/S??dockerman >/dev/null 2>&1 && [ -f /etc/init.d/dockerman ];then
/etc/init.d/dockerman enable
/etc/init.d/dockerman start
fi
opkg remove --force-removal-of-dependent-packages shairport-sync-openssl
mv /etc/rc.local.orig /etc/rc.local
chmod 755 /etc/rc.local
exec /etc/rc.local
exit
EOF
chmod 755 ./etc/rc.local*
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
echo "create the second etc snapshot -> .snapshots/etc-001"
btrfs subvolume snapshot -r etc .snapshots/etc-001
# 2021.04.01添加
# 强制锁定fstab,防止用户擅自修改挂载点
# 开启了快照功能之后,不再需要锁定fstab
#chattr +ia ./etc/config/fstab
cd ${WORK_DIR}
echo "Start copy data from ${P1} to /boot ..."
cd /boot
echo -n "remove old boot files ..."
rm -rf *
echo "done"
echo -n "copy new boot files ... "
(cd ${P1} && tar cf - .) | tar xf -
sync
echo "done"
echo
echo -n "Update boot args ... "
cat >uEnv.txt <<EOF
LINUX=/zImage
INITRD=/uInitrd
# Example:
# FDT=/dtb/allwinner/sun50i-h6-vplus-cloud.dtb
# FDT=/dtb/allwinner/sun50i-h6-vplus-cloud-2ghz.dtb
FDT=/dtb/allwinner/${MYDTB_FDTFILE}
APPEND=root=UUID=${NEW_ROOT_UUID} rootfstype=btrfs rootflags=compress=zstd:${ZSTD_LEVEL} console=ttyS0,115200n8 no_console_suspend consoleblank=0 fsck.fix=yes fsck.repair=yes net.ifnames=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
EOF
# Compatible with armbianEnv.txt file
[[ -f "armbianEnv.txt" ]] && {
echo "Update armbianEnv.txt settings."
sed -i "s|^rootdev=.*|rootdev=UUID=${NEW_ROOT_UUID}|g" armbianEnv.txt
rm -f uEnv.txt
}
# Replace the UUID for extlinux/extlinux.conf if it exists
[[ -f "extlinux/extlinux.conf" ]] && {
sed -i -E "s|UUID=[^ ]*|UUID=${NEW_ROOT_UUID}|" extlinux/extlinux.conf 2>/dev/null
}
sync
echo "done"
echo
cd $WORK_DIR
umount -f ${P1} ${P2} 2>/dev/null
losetup -D 2>/dev/null
rmdir ${P1} ${P2} 2>/dev/null
rm -f ${IMG_NAME} 2>/dev/null
rm -f sha256sums 2>/dev/null
sync
echo "Successfully updated, automatic restarting..."
sleep 3
reboot
exit 0
+859
View File
@@ -0,0 +1,859 @@
#!/bin/bash
#======================================================================================
# Function: Update openwrt to emmc for Amlogic S9xxx STB
# Copyright (C) 2020-- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021-- https://github.com/ophub/luci-app-amlogic
#======================================================================================
#
# The script supports directly setting parameters for update, skipping interactive selection
# openwrt-update-amlogic ${OPENWRT_FILE} ${AUTO_MAINLINE_UBOOT} ${RESTORE_CONFIG}
# E.g: openwrt-update-amlogic openwrt_s905d.img.gz yes restore
# E.g: openwrt-update-amlogic openwrt_s905d.img.gz no no-restore
# You can also execute the script directly, and interactively select related functions
# E.g: openwrt-update-amlogic
#
#======================================================================================
# Encountered a serious error, abort the script execution
error_msg() {
echo -e "[ERROR] ${1}"
exit 1
}
# Get the partition name of the /boot file system
get_boot_partition_name() {
local paths="/boot"
local partition_name
partition_name=$(df "${paths}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
echo "${partition_name}"
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
echo "${partition_name}"
}
# Get the partition message of the root file system
get_root_partition_msg() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_msg=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ "^" "'"${path}"'" "$" {print $0}')
[[ -n "${partition_msg}" ]] && break
done
[[ -z "${partition_msg}" ]] && error_msg "Cannot find the root partition message!"
echo "${partition_msg}"
}
# Receive one-key command related parameters
IMG_NAME="${1}"
AUTO_MAINLINE_UBOOT="${2}"
BACKUP_RESTORE_CONFIG="${3}"
# Current device model
MYDEVICE_NAME=$(cat /proc/device-tree/model | tr -d '\000')
if [[ -z "${MYDEVICE_NAME}" ]]; then
error_msg "The device name is empty and cannot be recognized."
elif [[ ! -f "/etc/flippy-openwrt-release" ]]; then
error_msg "The [ /etc/flippy-openwrt-release ] file is missing."
else
echo -e "Current device: ${MYDEVICE_NAME} [ amlogic ]"
sleep 3
fi
# Check and repair partition mount points
[[ -z "$(get_boot_partition_name)" || -z "$(get_root_partition_name)" ]] && {
echo -e "Partition mount point error, start repairing..."
openwrt-backup -g
}
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case ${ROOT_PTNAME} in
mmcblk?p[1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')
if lsblk -l -o NAME | grep "${EMMC_NAME}boot0" >/dev/null; then
ROOT_DISK_TYPE="EMMC"
else
ROOT_DISK_TYPE="SD"
fi
PARTITION_NAME="p"
LB_PRE="${ROOT_DISK_TYPE}_"
;;
[hsv]d[a-z][1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')
ROOT_DISK_TYPE="USB"
PARTITION_NAME=""
LB_PRE="${ROOT_DISK_TYPE}_"
;;
*)
error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!"
;;
esac
DOCKER_ROOT="/mnt/${EMMC_NAME}${PARTITION_NAME}4/docker/"
cd /mnt/${EMMC_NAME}${PARTITION_NAME}4/
mv -f /tmp/upload/* . 2>/dev/null && sync
if [[ "${IMG_NAME}" == *.img ]]; then
echo -e "Update using [ ${IMG_NAME} ] file. Please wait a moment ..."
elif [ $(ls *.img -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
IMG_NAME=$(ls *.img | head -n 1)
echo -e "Update using [ ${IMG_NAME} ] ] file. Please wait a moment ..."
elif [ $(ls *.img.xz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
xz_file=$(ls *.img.xz | head -n 1)
echo -e "Update using [ ${xz_file} ] file. Please wait a moment ..."
xz -d "${xz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.img.gz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.img.gz | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
gzip -df "${gz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.7z -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.7z | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
bsdtar -xmf "${gz_file}" 2>/dev/null
[ $? -eq 0 ] || 7z x "${gz_file}" -aoa -y 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.zip -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
zip_file=$(ls *.zip | head -n 1)
echo -e "Update using [ ${zip_file} ] file. Please wait a moment ..."
unzip -o "${zip_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
else
echo -e "Please upload or specify the update openwrt firmware file."
echo -e "Upload method: system menu → Amlogic Service → Manually Upload Update"
echo -e "Specify method: Place the openwrt firmware file in [ /mnt/${EMMC_NAME}${PARTITION_NAME}4/ ]"
echo -e "The supported file suffixes are: *.img, *.img.xz, *.img.gz, *.7z, *.zip"
echo -e "After upload the openwrt firmware file, run again."
exit 1
fi
sync
# check file
if [ ! -f "${IMG_NAME}" ]; then
error_msg "No update file found."
else
echo "Start update from [ ${IMG_NAME} ]"
fi
# Check the necessary dependencies
DEPENDS="lsblk uuidgen grep awk btrfs mkfs.fat mkfs.btrfs md5sum fatlabel"
echo "Check the necessary dependencies..."
for dep in ${DEPENDS}; do
WITCH=$(busybox which ${dep})
if [ "${WITCH}" == "" ]; then
error_msg "Dependent command: ${dep} does not exist, upgrade cannot be performed, only flash through U disk/TF card!"
else
echo "${dep} path: ${WITCH}"
fi
done
echo "Check passed"
# find boot partition
BOOT_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ /^\/boot$/ {print $0}')
if [ "${BOOT_PART_MSG}" == "" ]; then
error_msg "Boot The partition does not exist, so the update cannot be continued!"
fi
BOOT_NAME=$(echo ${BOOT_PART_MSG} | awk '{print $1}')
BOOT_PATH=$(echo ${BOOT_PART_MSG} | awk '{print $2}')
BOOT_UUID=$(echo ${BOOT_PART_MSG} | awk '{print $4}')
BR_FLAG=1
echo -ne "Whether to backup and restore the current config files? y/n [y]\b\b"
if [[ ${BACKUP_RESTORE_CONFIG} == "restore" ]]; then
yn="y"
elif [[ ${BACKUP_RESTORE_CONFIG} == "no-restore" ]]; then
yn="n"
else
read yn
fi
case $yn in
n* | N*)
BR_FLAG=0
;;
esac
# find root partition
ROOT_PART_MSG="$(get_root_partition_msg)"
ROOT_NAME=$(echo ${ROOT_PART_MSG} | awk '{print $1}')
ROOT_PATH=$(echo ${ROOT_PART_MSG} | awk '{print $2}')
ROOT_UUID=$(echo ${ROOT_PART_MSG} | awk '{print $4}')
case ${ROOT_NAME} in
${EMMC_NAME}${PARTITION_NAME}2)
NEW_ROOT_NAME="${EMMC_NAME}${PARTITION_NAME}3"
NEW_ROOT_LABEL="${LB_PRE}ROOTFS2"
;;
${EMMC_NAME}${PARTITION_NAME}3)
NEW_ROOT_NAME="${EMMC_NAME}${PARTITION_NAME}2"
NEW_ROOT_LABEL="${LB_PRE}ROOTFS1"
;;
*)
error_msg "ROOTFS The partition location is incorrect, so the update cannot continue!"
;;
esac
echo "NEW_ROOT_NAME: [ ${NEW_ROOT_NAME} ]"
# find new root partition
NEW_ROOT_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | grep "${NEW_ROOT_NAME}" | awk '$3 ~ /^part$/ && $5 !~ /^\/$/ && $5 !~ /^\/boot$/ {print $0}')
if [ "${NEW_ROOT_PART_MSG}" == "" ]; then
error_msg "The new ROOTFS partition does not exist, so the update cannot continue!"
fi
NEW_ROOT_NAME=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $1}')
NEW_ROOT_PATH=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $2}')
NEW_ROOT_UUID=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $4}')
NEW_ROOT_MP=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $5}')
echo "NEW_ROOT_MP: [ ${NEW_ROOT_MP} ]"
# backup old bootloader
if [ ! -f /root/BackupOldBootloader.img ]; then
echo "Backup bootloader -> [ BackupOldBootloader.img ] ... "
dd if=/dev/${EMMC_NAME} of=/root/BackupOldBootloader.img bs=1M count=4 conv=fsync
echo "Backup bootloader complete."
echo
fi
# losetup
losetup -f -P ${IMG_NAME}
if [ ${?} -eq 0 ]; then
LOOP_DEV=$(losetup | grep "${IMG_NAME}" | awk '{print $1}')
if [ "${LOOP_DEV}" == "" ]; then
error_msg "loop device not found!"
fi
else
error_msg "losetup [ ${IMG_FILE} ] failed!"
fi
# fix loopdev issue in kernel 5.19
function fix_loopdev() {
local parentdev=${1##*/}
if [ ! -d /sys/block/${parentdev} ]; then
return
fi
subdevs=$(lsblk -l -o NAME | grep -E "^${parentdev}.+\$")
for subdev in ${subdevs}; do
if [ ! -d /sys/block/${parentdev}/${subdev} ]; then
return
elif [ -b /dev/${sub_dev} ]; then
continue
fi
source /sys/block/${parentdev}/${subdev}/uevent
mknod /dev/${subdev} b ${MAJOR} ${MINOR}
unset MAJOR MINOR DEVNAME DEVTYPE DISKSEQ PARTN PARTNAME
done
}
fix_loopdev ${LOOP_DEV}
WAIT=3
echo "The loopdev is [ $LOOP_DEV ], wait [ ${WAIT} ] seconds. "
while [[ "${WAIT}" -ge "1" ]]; do
sleep 1
WAIT=$((WAIT - 1))
done
# umount loop devices (openwrt will auto mount some partition)
MOUNTED_DEVS=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "${LOOP_DEV}" | awk '$3 !~ /^$/ {print $2}')
for dev in ${MOUNTED_DEVS}; do
while :; do
echo "umount [ ${dev} ] ... "
umount -f ${dev}
sleep 1
mnt=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "${dev}" | awk '$3 !~ /^$/ {print $2}')
if [ "${mnt}" == "" ]; then
break
else
echo "Retry ..."
fi
done
done
# mount src part
WORK_DIR=${PWD}
P1=${WORK_DIR}/boot
P2=${WORK_DIR}/root
mkdir -p $P1 $P2
echo "Mount [ ${LOOP_DEV}p1 ] -> [ ${P1} ] ... "
mount -t vfat -o ro ${LOOP_DEV}p1 ${P1}
if [ $? -ne 0 ]; then
echo "Mount p1 [ ${LOOP_DEV}p1 ] failed!"
losetup -D
exit 1
fi
echo "Mount [ ${LOOP_DEV}p2 ] -> [ ${P2} ] ... "
ZSTD_LEVEL=6
mount -t btrfs -o ro,compress=zstd:${ZSTD_LEVEL} ${LOOP_DEV}p2 ${P2}
if [ $? -ne 0 ]; then
echo "Mount p2 [ ${LOOP_DEV}p2 ] failed!"
umount -f ${P1}
losetup -D
exit 1
fi
# Prepare the dockerman config file
if [ -f ${P2}/etc/init.d/dockerman ] && [ -f ${P2}/etc/config/dockerd ]; then
flg=0
# get current docker data root
data_root=$(uci get dockerd.globals.data_root 2>/dev/null)
if [ "${data_root}" == "" ]; then
flg=1
# get current config from /etc/docker/daemon.json
if [ -f "/etc/docker/daemon.json" ] && [ -x "/usr/bin/jq" ]; then
data_root=$(jq -r '."data-root"' /etc/docker/daemon.json)
bip=$(jq -r '."bip"' /etc/docker/daemon.json)
[ "${bip}" == "null" ] && bip="172.31.0.1/24"
log_level=$(jq -r '."log-level"' /etc/docker/daemon.json)
[ "${log_level}" == "null" ] && log_level="warn"
_iptables=$(jq -r '."iptables"' /etc/docker/daemon.json)
[ "${_iptables}" == "null" ] && _iptables="true"
registry_mirrors=$(jq -r '."registry-mirrors"[]' /etc/docker/daemon.json 2>/dev/null)
fi
fi
if [ "${data_root}" == "" ]; then
data_root="/opt/docker/" # the default data root
fi
if ! uci get dockerd.globals >/dev/null 2>&1; then
uci set dockerd.globals='globals'
uci commit
fi
# delete alter config , use inner config
if uci get dockerd.globals.alt_config_file >/dev/null 2>&1; then
uci delete dockerd.globals.alt_config_file
uci commit
fi
if [ ${flg} -eq 1 ]; then
uci set dockerd.globals.data_root=${data_root}
[ "${bip}" != "" ] && uci set dockerd.globals.bip=${bip}
[ "${log_level}" != "" ] && uci set dockerd.globals.log_level=${log_level}
[ "${_iptables}" != "" ] && uci set dockerd.globals.iptables=${_iptables}
if [ "${registry_mirrors}" != "" ]; then
for reg in ${registry_mirrors}; do
uci add_list dockerd.globals.registry_mirrors=${reg}
done
fi
uci set dockerd.globals.auto_start='1'
uci commit
fi
fi
#update version prompt
source /boot/uEnv.txt 2>/dev/null
CUR_FDTFILE=${FDT}
echo -e "FDT Value [ ${CUR_FDTFILE} ]"
cp /boot/uEnv.txt /tmp/uEnv.txt && sync
K510="1"
[[ "$(hexdump -n 15 -x "${P1}/zImage" 2>/dev/null | head -n 1 | awk '{print $7}')" == "0108" ]] && K510="0"
echo -e "K510 [ ${K510} ]"
# flippy-openwrt-release info
UBOOT_OVERLOAD=""
MAINLINE_UBOOT=""
ANDROID_UBOOT=""
env_openwrt_file=""
if [ -f "${P2}/etc/flippy-openwrt-release" ]; then
env_openwrt_file="${P2}/etc/flippy-openwrt-release"
elif [ -f "/etc/flippy-openwrt-release" ]; then
env_openwrt_file="/etc/flippy-openwrt-release"
else
env_openwrt_file=""
fi
if [ -n "${env_openwrt_file}" ]; then
source "${env_openwrt_file}" 2>/dev/null
# Update the parameters used
UBOOT_OVERLOAD=${UBOOT_OVERLOAD}
MAINLINE_UBOOT=${MAINLINE_UBOOT}
ANDROID_UBOOT=${ANDROID_UBOOT}
# Unused parameters
FDTFILE=${FDTFILE}
U_BOOT_EXT=${U_BOOT_EXT}
KERNEL_VERSION=${KERNEL_VERSION}
SOC=${SOC}
fi
#format NEW_ROOT
echo "umount [ ${NEW_ROOT_MP} ]"
umount -f "${NEW_ROOT_MP}"
if [[ "${?}" -ne "0" ]]; then
echo "Umount [ ${NEW_ROOT_MP} ] failed, Please restart and try again!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
# check and fix partition
function check_and_fix_partition() {
local target_dev_name=${1} # mmcblk2
local target_pt_name=${2} # p2
local target_pt_idx=${3} # 2
local safe_pt_begin_mb=${4} # 800
local safe_pt_begin_byte=$((${safe_pt_begin_mb} * 1024 * 1024))
local cur_pt_begin_sector=$(fdisk -l /dev/${target_dev_name} | grep ${target_dev_name}${target_pt_name} | awk '{printf $2}')
local cur_pt_begin_mb=$((${cur_pt_begin_sector} * 512 / 1024 / 1024))
if [ ${cur_pt_begin_mb} -ge ${safe_pt_begin_mb} ]; then
# check pass
return
fi
local cur_pt_end_sector=$(fdisk -l /dev/${target_dev_name} | grep ${target_dev_name}${target_pt_name} | awk '{printf $3}')
local cur_pt_end_byte=$(((${cur_pt_end_sector} + 1) * 512 - 1))
echo "Unsafe partition found, repairing ... "
parted /dev/${target_dev_name} rm ${target_pt_idx} ||
(
error_msg "rm partion ${target_pt_idx} failed"
)
parted /dev/${target_dev_name} mkpart primary btrfs "${safe_pt_begin_byte}b" "${cur_pt_end_byte}b" ||
(
error_msg "create new partion ${target_pt_idx} failed"
)
echo "Partition repaired"
}
# check if need fix partition
if [ "${NEW_ROOT_NAME}" == "mmcblk2p2" ]; then
if [ "${MYDEVICE_NAME}" == "Phicomm N1" ] || [ "${MYDEVICE_NAME}" == "Octopus Planet" ]; then
# 最新研究结果:
# Phicomm N1当采用官方 "天天链" 固件底包时,
# 796MB开始的 12 字节在每次重启后会被 bootloader 覆写,
# 因此把安全位置设定在800MB之后
# The latest research results:
# When Phicomm N1 uses the official "tian tian lian" firmware bottom package,
# the 12 bytes starting from 796MB will be overwritten by bootloader after each reboot,
# so the safe location is set after 800MB
SAFE_PT_BEGIN_MB=800
check_and_fix_partition "${EMMC_NAME}" "p2" 2 ${SAFE_PT_BEGIN_MB}
fi
fi
echo "Format [ ${NEW_ROOT_PATH} ]"
NEW_ROOT_UUID=$(uuidgen)
mkfs.btrfs -f -U ${NEW_ROOT_UUID} -L ${NEW_ROOT_LABEL} -m single ${NEW_ROOT_PATH}
if [ $? -ne 0 ]; then
echo "Format [ ${NEW_ROOT_PATH} ] failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
echo "Mount [ ${NEW_ROOT_PATH} ] -> [ ${NEW_ROOT_MP} ]"
mount -t btrfs -o compress=zstd:${ZSTD_LEVEL} ${NEW_ROOT_PATH} ${NEW_ROOT_MP}
if [ $? -ne 0 ]; then
echo "Mount [ ${NEW_ROOT_PATH} ] -> [ ${NEW_ROOT_MP} ] failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
# begin copy rootfs
cd ${NEW_ROOT_MP}
echo "Start copying data, From [ ${P2} ] TO [ ${NEW_ROOT_MP} ] ..."
ENTRYS=$(ls)
for entry in ${ENTRYS}; do
if [[ "${entry}" == "lost+found" ]]; then
continue
fi
echo "Remove old [ ${entry} ] ... "
rm -rf ${entry}
if [[ "${?}" -ne "0" ]]; then
error_msg "failed."
fi
done
echo "Create folder ... "
btrfs subvolume create etc
mkdir -p .snapshots .reserved bin boot dev lib opt mnt overlay proc rom root run sbin sys tmp usr www
ln -sf lib/ lib64
ln -sf tmp/ var
sync
COPY_SRC="root etc bin sbin lib opt usr www"
echo "Copy data begin ... "
for src in ${COPY_SRC}; do
echo "Copy [ ${src} ] ... "
(cd ${P2} && tar cf - ${src}) | tar xf -
sync
done
# if not backup, then force rewrite the etc/docker/daemon.json
if [ "${BR_FLAG}" -eq 0 ]; then
cat >./etc/docker/daemon.json <<EOF
{
"bip": "172.31.0.1/24",
"data-root": "${DOCKER_ROOT}",
"log-level": "warn",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
},
"registry-mirrors": [
"https://mirror.baidubce.com/",
"https://hub-mirror.c.163.com"
]
}
EOF
fi
cat >./etc/fstab <<EOF
UUID=${NEW_ROOT_UUID} / btrfs compress=zstd:${ZSTD_LEVEL} 0 1
LABEL=${LB_PRE}BOOT /boot vfat defaults 0 2
#tmpfs /tmp tmpfs defaults,nosuid 0 0
EOF
cat >./etc/config/fstab <<EOF
config global
option anon_swap '0'
option anon_mount '1'
option auto_swap '0'
option auto_mount '1'
option delay_root '5'
option check_fs '0'
config mount
option target '/rom'
option uuid '${NEW_ROOT_UUID}'
option enabled '1'
option enabled_fsck '1'
option fstype 'btrfs'
option options 'compress=zstd:${ZSTD_LEVEL}'
config mount
option target '/boot'
option label '${LB_PRE}BOOT'
option enabled '1'
option enabled_fsck '1'
option fstype 'vfat'
EOF
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
sync
echo "Create initial etc snapshot -> .snapshots/etc-000"
btrfs subvolume snapshot -r etc .snapshots/etc-000
sync
[ -d /mnt/${EMMC_NAME}${PARTITION_NAME}4/docker ] || mkdir -p /mnt/${EMMC_NAME}${PARTITION_NAME}4/docker
rm -rf opt/docker && ln -sf /mnt/${EMMC_NAME}${PARTITION_NAME}4/docker/ opt/docker
if [ -f /mnt/${NEW_ROOT_NAME}/etc/config/AdGuardHome ]; then
[ -d /mnt/${EMMC_NAME}${PARTITION_NAME}4/AdGuardHome/data ] || mkdir -p /mnt/${EMMC_NAME}${PARTITION_NAME}4/AdGuardHome/data
if [ ! -L /usr/bin/AdGuardHome ]; then
[ -d /usr/bin/AdGuardHome ] &&
cp -a /usr/bin/AdGuardHome/* /mnt/${EMMC_NAME}${PARTITION_NAME}4/AdGuardHome/
fi
ln -sf /mnt/${EMMC_NAME}${PARTITION_NAME}4/AdGuardHome /mnt/${NEW_ROOT_NAME}/usr/bin/AdGuardHome
fi
#rm -f /mnt/${NEW_ROOT_NAME}/usr/sbin/openwrt-install-amlogic
#rm -f /mnt/${NEW_ROOT_NAME}/usr/sbin/openwrt-update-amlogic
rm -f /mnt/${NEW_ROOT_NAME}/root/install-to-emmc.sh
sync
echo "Copy data complete ..."
BACKUP_LIST=$(${P2}/usr/sbin/openwrt-backup -p)
if [[ "${BR_FLAG}" -eq "1" && -n "${BACKUP_LIST}" ]]; then
echo -n "Start restoring configuration files ... "
(
cd /
eval tar czf ${NEW_ROOT_MP}/.reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
)
tar xzf ${NEW_ROOT_MP}/.reserved/openwrt_config.tar.gz
[ -f ./etc/config/dockerman ] && sed -e "s/option wan_mode 'false'/option wan_mode 'true'/" -i ./etc/config/dockerman 2>/dev/null
[ -f ./etc/config/dockerd ] && sed -e "s/option wan_mode '0'/option wan_mode '1'/" -i ./etc/config/dockerd 2>/dev/null
[ -f ./etc/config/verysync ] && sed -e 's/config setting/config verysync/' -i ./etc/config/verysync 2>/dev/null
# Restore fstab
cp -f .snapshots/etc-000/fstab ./etc/fstab
cp -f .snapshots/etc-000/config/fstab ./etc/config/fstab
# 还原 luci
cp -f .snapshots/etc-000/config/luci ./etc/config/luci
# 还原/etc/config/rpcd
cp -f .snapshots/etc-000/config/rpcd ./etc/config/rpcd
sync
echo "Restore configuration information complete."
fi
echo "Modify the configuration file ... "
rm -f "./etc/rc.local.orig" "./etc/first_run.sh" "./etc/part_size"
rm -rf "./opt/docker" && ln -sf "/mnt/${EMMC_NAME}${PARTITION_NAME}4/docker" "./opt/docker"
rm -f ./etc/bench.log
cat >>./etc/crontabs/root <<EOF
37 5 * * * /etc/coremark.sh
EOF
sed -e 's/ttyAMA0/ttyAML0/' -i ./etc/inittab
sed -e 's/ttyS0/tty0/' -i ./etc/inittab
sss=$(date +%s)
ddd=$((sss / 86400))
sed -e "s/:0:0:99999:7:::/:${ddd}:0:99999:7:::/" -i ./etc/shadow
# Fix the problem of repeatedly adding amule entries after each upgrade
sed -e "/amule:x:/d" -i ./etc/shadow
# Fix the problem of repeatedly adding sshd entries after each upgrade of dropbear
sed -e "/sshd:x:/d" -i ./etc/shadow
if ! grep "sshd:x:22:sshd" ./etc/group >/dev/null; then
echo "sshd:x:22:sshd" >>./etc/group
fi
if ! grep "sshd:x:22:22:sshd:" ./etc/passwd >/dev/null; then
echo "sshd:x:22:22:sshd:/var/run/sshd:/bin/false" >>./etc/passwd
fi
if ! grep "sshd:x:" ./etc/shadow >/dev/null; then
echo "sshd:x:${ddd}:0:99999:7:::" >>./etc/shadow
fi
if [ "${BR_FLAG}" -eq "1" ]; then
if [ -x ./bin/bash ] && [ -f ./etc/profile.d/30-sysinfo.sh ]; then
sed -e 's/\/bin\/ash/\/bin\/bash/' -i ./etc/passwd
fi
sync
fi
sed -e "s/option hw_flow '1'/option hw_flow '0'/" -i ./etc/config/turboacc
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
eval tar czf .reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
rm -f ./etc/part_size ./etc/first_run.sh
if [ -x ./usr/sbin/balethirq.pl ]; then
if grep "balethirq.pl" "./etc/rc.local"; then
echo "balance irq is enabled"
else
echo "enable balance irq"
sed -e "/exit/i\/usr/sbin/balethirq.pl" -i ./etc/rc.local
fi
fi
mv ./etc/rc.local ./etc/rc.local.orig
cat >"./etc/rc.local" <<EOF
if ! ls /etc/rc.d/S??dockerd >/dev/null 2>&1;then
/etc/init.d/dockerd enable
/etc/init.d/dockerd start
fi
if ! ls /etc/rc.d/S??dockerman >/dev/null 2>&1 && [ -f /etc/init.d/dockerman ];then
/etc/init.d/dockerman enable
/etc/init.d/dockerman start
fi
mv /etc/rc.local.orig /etc/rc.local
chmod 755 /etc/rc.local
exec /etc/rc.local
exit
EOF
chmod 755 ./etc/rc.local*
#Mainline U-BOOT detection
FLASH_MAINLINE_UBOOT=0
if [[ -n "${MAINLINE_UBOOT}" && -f "${P2}${MAINLINE_UBOOT}" ]]; then
cat <<EOF
----------------------------------------------------------------------------------
Found an available mainline bootloader (Mainline u-boot), you can flash into EMMC.
----------------------------------------------------------------------------------
EOF
while :; do
if [[ "${AUTO_MAINLINE_UBOOT}" == "yes" ]]; then
if [[ "${K510}" -eq "1" ]]; then
yn="y"
else
yn="n"
fi
elif [[ "${AUTO_MAINLINE_UBOOT}" == "no" ]]; then
yn="n"
else
read -p "Please choose whether to write the mainline bootloader to EMMC? y/n " yn
fi
case ${yn} in
y | Y)
FLASH_MAINLINE_UBOOT=1
break
;;
n | N)
FLASH_MAINLINE_UBOOT=0
break
;;
esac
done
fi
if [[ "${FLASH_MAINLINE_UBOOT}" -eq "1" ]]; then
echo -e "Write Mainline bootloader: [ ${MAINLINE_UBOOT} ]"
dd if=${P2}${MAINLINE_UBOOT} of=/dev/${EMMC_NAME} bs=1 count=444 conv=fsync
dd if=${P2}${MAINLINE_UBOOT} of=/dev/${EMMC_NAME} bs=512 skip=1 seek=1 conv=fsync
elif [[ -n "${ANDROID_UBOOT}" && -f "${P2}${ANDROID_UBOOT}" ]]; then
echo -e "Write Android bootloader: [ ${ANDROID_UBOOT} ]"
dd if=${P2}${ANDROID_UBOOT} of=/dev/${EMMC_NAME} bs=1 count=444 conv=fsync
dd if=${P2}${ANDROID_UBOOT} of=/dev/${EMMC_NAME} bs=512 skip=1 seek=1 conv=fsync
else
echo "Did not change the original bootloader."
fi
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
echo "Create etc snapshot -> .snapshots/etc-001"
btrfs subvolume snapshot -r etc .snapshots/etc-001
cd ${WORK_DIR}
echo "Change the label of ${BOOT_PATH} ... "
fatlabel ${BOOT_PATH} "${LB_PRE}BOOT"
echo "Start copying data, from [ ${P1} ] to [ /boot ] ..."
cd /boot
echo "Delete the old boot file ..."
rm -rf * && sync
echo "Copy the new boot file ... "
(cd ${P1} && tar cf - .) | tar xf - 2>/dev/null
sync
if [ -f ${P1}/uInitrd ]; then
i=1
max_try=10
while [ "${i}" -le "${max_try}" ]; do
uInitrd_original=$(md5sum ${P1}/uInitrd | awk '{print $1}')
uInitrd_new=$(md5sum uInitrd | awk '{print $1}')
if [ "${uInitrd_original}" = "${uInitrd_new}" ]; then
break
else
rm -f uInitrd && sync
cp -f ${P1}/uInitrd uInitrd 2>/dev/null && sync
let i++
continue
fi
done
[ "${i}" -eq "10" ] && error_msg "uInitrd file copy failed."
else
echo "uInitrd file is missing."
fi
if [ -f ${P1}/zImage ]; then
i=1
max_try=10
while [ "${i}" -le "${max_try}" ]; do
zImage_original=$(md5sum ${P1}/zImage | awk '{print $1}')
zImage_new=$(md5sum zImage | awk '{print $1}')
if [ "${zImage_original}" = "${zImage_new}" ]; then
break
else
rm -f zImage && sync
cp -f ${P1}/zImage zImage 2>/dev/null && sync
let i++
continue
fi
done
[ "${i}" -eq "10" ] && error_msg "zImage file copy failed."
else
error_msg "zImage file is missing."
fi
if [ ${ROOT_DISK_TYPE} == "EMMC" ]; then
rm -f s905_autoscript* aml_autoscript*
mv -f boot-emmc.ini boot.ini
mv -f boot-emmc.cmd boot.cmd
mv -f boot-emmc.scr boot.scr
fi
if [ "${K510}" -eq "1" ]; then
if [ -f "u-boot.ext" ]; then
cp -vf u-boot.ext u-boot.emmc
elif [ -f ${P1}/${UBOOT_OVERLOAD} ]; then
cp -vf ${P1}/${UBOOT_OVERLOAD} ${UBOOT_OVERLOAD}
cp -vf ${P1}/${UBOOT_OVERLOAD} u-boot.ext
cp -vf ${P1}/${UBOOT_OVERLOAD} u-boot.emmc
chmod +x u-boot*
fi
else
rm -f u-boot.ext 2>/dev/null
rm -f u-boot.emmc 2>/dev/null
fi
sync
echo "Update boot parameters ... "
if [ -f /tmp/uEnv.txt ]; then
lines=$(wc -l </tmp/uEnv.txt)
lines=$((lines - 1))
head -n $lines /tmp/uEnv.txt >uEnv.txt
cat >>uEnv.txt <<EOF
APPEND=root=UUID=${NEW_ROOT_UUID} rootfstype=btrfs rootflags=compress=zstd:${ZSTD_LEVEL} console=ttyAML0,115200n8 console=tty0 no_console_suspend consoleblank=0 fsck.fix=yes fsck.repair=yes net.ifnames=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
EOF
else
cat >uEnv.txt <<EOF
LINUX=/zImage
INITRD=/uInitrd
FDT=${CUR_FDTFILE}
APPEND=root=UUID=${NEW_ROOT_UUID} rootfstype=btrfs rootflags=compress=zstd:${ZSTD_LEVEL} console=ttyAML0,115200n8 console=tty0 no_console_suspend consoleblank=0 fsck.fix=yes fsck.repair=yes net.ifnames=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
EOF
fi
sync
# Replace the UUID for extlinux/extlinux.conf if it exists
[[ -f "extlinux/extlinux.conf" ]] && {
sed -i -E "s|UUID=[^ ]*|UUID=${NEW_ROOT_UUID}|" extlinux/extlinux.conf 2>/dev/null
}
cd $WORK_DIR
umount -f ${P1} ${P2} 2>/dev/null
losetup -D 2>/dev/null
rm -rf ${P1} ${P2} 2>/dev/null
rm -f ${IMG_NAME} 2>/dev/null
rm -f sha256sums 2>/dev/null
sync
wait
echo "Successfully updated, automatic restarting..."
sleep 3
reboot
exit 0
+674
View File
@@ -0,0 +1,674 @@
#!/bin/bash
#======================================================================================
# Function: Update the QEMU AARCH64 KVM vitual machine openwrt firmware
# Copyright (C) 2022-- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2022-- https://github.com/ophub
#======================================================================================
#
# The script supports directly setting parameters for update, skipping interactive selection
# openwrt-update-amlogic ${OPENWRT_FILE} ${AUTO_MAINLINE_UBOOT} ${RESTORE_CONFIG} ${WORK_DIR}
# E.g: openwrt-update-kvm openwrt_qemu-aarch64.img.gz no restore /mnt/vda4
# E.g: openwrt-update-kvm openwrt_qemu-aarch64.img.gz no no-restore /mnt/data
# You can also execute the script directly, and interactively select related functions
# E.g: openwrt-update-kvm
#
#======================================================================================
# Encountered a serious error, abort the script execution
error_msg() {
echo -e "[ERROR] ${1}"
exit 1
}
# Get the partition name of the /boot file system
get_boot_partition_name() {
local paths="/boot"
local partition_name
partition_name=$(df "${paths}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
echo "${partition_name}"
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
echo "${partition_name}"
}
# Get the partition message of the root file system
get_root_partition_msg() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_msg=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ "^" "'"${path}"'" "$" {print $0}')
[[ -n "${partition_msg}" ]] && break
done
[[ -z "${partition_msg}" ]] && error_msg "Cannot find the root partition message!"
echo "${partition_msg}"
}
# Receive one-key command related parameters
IMG_NAME="${1}"
AUTO_MAINLINE_UBOOT="${2}"
BACKUP_RESTORE_CONFIG="${3}"
WORK_DIR="${4}"
# Check the necessary dependencies
DEPENDS="lsblk uuidgen grep awk btrfs mkfs.fat mkfs.btrfs md5sum fatlabel jq"
echo "Check the necessary dependencies..."
for dep in ${DEPENDS}; do
WITCH=$(busybox which ${dep})
if [ "${WITCH}" == "" ]; then
error_msg "Dependent command: ${dep} does not exist, upgrade cannot be performed!"
else
echo "${dep} path: ${WITCH}"
fi
done
echo "Check passed"
# Current device model
source /etc/flippy-openwrt-release
if [[ -z "${PLATFORM}" ]]; then
error_msg "The platform is empty."
elif [[ "${PLATFORM}" != "qemu-aarch64" ]]; then
error_msg "The platform is missing, only support qemu-aarch64."
else
echo -e "Current platform: [ ${PLATFORM} ]"
sleep 3
fi
# Check and repair partition mount points
[[ -z "$(get_boot_partition_name)" || -z "$(get_root_partition_name)" ]] && {
echo -e "Partition mount point error, start repairing..."
openwrt-backup -g
}
# Find the partition where root is located
# vda2 or vda3
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports sd?? hd?? vd??
case ${ROOT_PTNAME} in
[hsv]d[a-z][1-4])
DISK_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')
PART_PRESTR=""
LABEL_PRESTR=""
;;
*)
error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!"
;;
esac
function get_docker_root {
local data_root
if [[ -f /etc/config/dockerd ]]; then
data_root=$(uci get dockerd.globals.data_root)
fi
if [[ -z "${data_root}" ]] && [[ -f /etc/docker/daemon.json ]]; then
data_root=$(jq -r '."data-root"' /etc/docker/daemon.json)
fi
echo "$data_root"
}
DOCKER_ROOT=$(get_docker_root)
if [[ -z "${WORK_DIR}" ]]; then
WORK_DIR="/mnt/${DISK_NAME}${PART_PRESTR}4/"
fi
if [[ ! -d "${WORK_DIR}" ]]; then
error_msg "the work directory is not exists. [ ${WORK_DIR} ]"
fi
cd "${WORK_DIR}"
if [[ -d "/tmp/upload" ]] && [[ -f "/tmp/upload/*img*" ]]; then
mv -f /tmp/upload/*img* .
if [[ $? -ne 0 ]]; then
error_msg "move file failed."
fi
fi
sync
if [[ "${IMG_NAME}" == *.img ]]; then
echo -e "Update using [ ${IMG_NAME} ] file. Please wait a moment ..."
elif [ $(ls *.img -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
IMG_NAME=$(ls *.img | head -n 1)
echo -e "Update using [ ${IMG_NAME} ] ] file. Please wait a moment ..."
elif [ $(ls *.img.xz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
xz_file=$(ls *.img.xz | head -n 1)
echo -e "Update using [ ${xz_file} ] file. Please wait a moment ..."
xz -d "${xz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.img.gz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.img.gz | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
gzip -df "${gz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.7z -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.7z | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
bsdtar -xmf "${gz_file}" 2>/dev/null
[ $? -eq 0 ] || 7z x "${gz_file}" -aoa -y 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.zip -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
zip_file=$(ls *.zip | head -n 1)
echo -e "Update using [ ${zip_file} ] file. Please wait a moment ..."
unzip -o "${zip_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
else
echo -e "Please upload or specify the update openwrt firmware file."
#echo -e "Upload method: system menu → Amlogic Service → Manually Upload Update"
echo -e "Specify method: Place the openwrt firmware file in [ ${WORK_DIR} ]"
echo -e "The supported file suffixes are: *.img, *.img.xz, *.img.gz, *.7z, *.zip"
echo -e "After upload the openwrt firmware file, run again."
exit 1
fi
sync
# check file
if [ ! -f "${IMG_NAME}" ]; then
error_msg "No update file found."
else
echo "Start update from [ ${IMG_NAME} ]"
fi
# find efi partition
EFI_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ /^\/boot\/efi$/ {print $0}')
if [ "${EFI_PART_MSG}" == "" ]; then
error_msg "The EFI partition does not exist, so the update cannot be continued!"
fi
EFI_NAME=$(echo $EFI_PART_MSG | awk '{print $1}')
EFI_DEV=$(echo $EFI_PART_MSG | awk '{print $2}')
EFI_UUID=$(echo $EFI_PART_MSG | awk '{print $4}')
BR_FLAG=1
if [[ ${BACKUP_RESTORE_CONFIG} == "restore" ]]; then
BR_FLAG=1
elif [[ ${BACKUP_RESTORE_CONFIG} == "no-restore" ]]; then
BR_FLAG=0
else
echo -ne "Whether to backup and restore the current config files? y/n [y]\b\b"
read yn
case $yn in
n* | N*) BR_FLAG=0 ;;
esac
fi
# find root partition
ROOT_PART_MSG="$(get_root_partition_msg)"
ROOT_NAME=$(echo ${ROOT_PART_MSG} | awk '{print $1}')
ROOT_DEV=$(echo ${ROOT_PART_MSG} | awk '{print $2}')
ROOT_UUID=$(echo ${ROOT_PART_MSG} | awk '{print $4}')
case ${ROOT_NAME} in
${DISK_NAME}${PART_PRESTR}2)
NEW_ROOT_NAME="${DISK_NAME}${PART_PRESTR}3"
NEW_ROOT_LABEL="${LABEL_PRESTR}ROOTFS2"
;;
${DISK_NAME}${PART_PRESTR}3)
NEW_ROOT_NAME="${DISK_NAME}${PART_PRESTR}2"
NEW_ROOT_LABEL="${LABEL_PRESTR}ROOTFS1"
;;
*)
error_msg "ROOTFS The partition location is incorrect, so the update cannot continue!"
;;
esac
echo "NEW_ROOT_NAME: [ ${NEW_ROOT_NAME} ]"
# find new root partition
NEW_ROOT_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | grep "${NEW_ROOT_NAME}" | awk '$3 ~ /^part$/ && $5 !~ /^\/$/ && $5 !~ /^\/boot$/ {print $0}')
if [ "${NEW_ROOT_PART_MSG}" == "" ]; then
error_msg "The new ROOTFS partition does not exist, so the update cannot continue!"
fi
NEW_ROOT_NAME=$(echo $NEW_ROOT_PART_MSG | awk '{print $1}')
NEW_ROOT_DEV=$(echo $NEW_ROOT_PART_MSG | awk '{print $2}')
NEW_ROOT_UUID=$(echo $NEW_ROOT_PART_MSG | awk '{print $4}')
NEW_ROOT_MOUNTPOINT=$(echo $NEW_ROOT_PART_MSG | awk '{print $5}')
echo "NEW_ROOT_MOUNTPOINT: [ ${NEW_ROOT_MOUNTPOINT} ]"
# losetup
losetup -f -P $IMG_NAME
if [ $? -eq 0 ]; then
LOOP_DEV=$(losetup | grep "$IMG_NAME" | awk '{print $1}')
if [ "$LOOP_DEV" == "" ]; then
error_msg "loop device not found!"
fi
else
error_msg "losetup [ $IMG_FILE ] failed!"
fi
# fix loopdev issue in kernel 5.19
function fix_loopdev() {
local parentdev=${1##*/}
if [ ! -d /sys/block/${parentdev} ]; then
return
fi
subdevs=$(lsblk -l -o NAME | grep -E "^${parentdev}.+\$")
for subdev in $subdevs; do
if [ ! -d /sys/block/${parentdev}/${subdev} ]; then
return
elif [ -b /dev/${sub_dev} ]; then
continue
fi
source /sys/block/${parentdev}/${subdev}/uevent
mknod /dev/${subdev} b ${MAJOR} ${MINOR}
unset MAJOR MINOR DEVNAME DEVTYPE DISKSEQ PARTN PARTNAME
done
}
fix_loopdev ${LOOP_DEV}
WAIT=3
echo "The loopdev is [ $LOOP_DEV ], wait [ ${WAIT} ] seconds. "
while [ $WAIT -ge 1 ]; do
sleep 1
WAIT=$((WAIT - 1))
done
# umount loop devices (openwrt will auto mount some partition)
MOUNTED_DEVS=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "$LOOP_DEV" | awk '$3 !~ /^$/ {print $2}')
for dev in $MOUNTED_DEVS; do
while :; do
echo "umount [ $dev ] ... "
umount -f $dev
sleep 1
mnt=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "$dev" | awk '$3 !~ /^$/ {print $2}')
if [ "$mnt" == "" ]; then
break
else
echo "Retry ..."
fi
done
done
# mount src part
P1=${WORK_DIR}/efi
P2=${WORK_DIR}/root
mkdir -p $P1 $P2
echo "Mount [ ${LOOP_DEV}p1 ] -> [ ${P1} ] ... "
mount -t vfat -o ro ${LOOP_DEV}p1 ${P1}
if [ $? -ne 0 ]; then
echo "Mount p1 [ ${LOOP_DEV}p1 ] failed!"
losetup -D
exit 1
fi
echo "Mount [ ${LOOP_DEV}p2 ] -> [ ${P2} ] ... "
ZSTD_LEVEL="6"
mount -t btrfs -o ro,compress=zstd:${ZSTD_LEVEL} ${LOOP_DEV}p2 ${P2}
if [ $? -ne 0 ]; then
echo "Mount p2 [ ${LOOP_DEV}p2 ] failed!"
umount -f ${P1}
losetup -D
exit 1
fi
# Prepare the dockerman config file
if [ -f ${P2}/etc/init.d/dockerman ] && [ -f ${P2}/etc/config/dockerd ]; then
flg=0
# get current docker data root
data_root=$(uci get dockerd.globals.data_root 2>/dev/null)
if [ "$data_root" == "" ]; then
flg=1
# get current config from /etc/docker/daemon.json
if [ -f "/etc/docker/daemon.json" ] && [ -x "/usr/bin/jq" ]; then
data_root=$(jq -r '."data-root"' /etc/docker/daemon.json)
bip=$(jq -r '."bip"' /etc/docker/daemon.json)
[ "$bip" == "null" ] && bip="172.31.0.1/24"
log_level=$(jq -r '."log-level"' /etc/docker/daemon.json)
[ "$log_level" == "null" ] && log_level="warn"
_iptables=$(jq -r '."iptables"' /etc/docker/daemon.json)
[ "$_iptables" == "null" ] && _iptables="true"
registry_mirrors=$(jq -r '."registry-mirrors"[]' /etc/docker/daemon.json 2>/dev/null)
fi
fi
if [ "$data_root" == "" ]; then
data_root="/opt/docker/" # the default data root
fi
if ! uci get dockerd.globals >/dev/null 2>&1; then
uci set dockerd.globals='globals'
uci commit
fi
# delete alter config , use inner config
if uci get dockerd.globals.alt_config_file >/dev/null 2>&1; then
uci delete dockerd.globals.alt_config_file
uci commit
fi
if [ $flg -eq 1 ]; then
uci set dockerd.globals.data_root=$data_root
[ "$bip" != "" ] && uci set dockerd.globals.bip=$bip
[ "$log_level" != "" ] && uci set dockerd.globals.log_level=$log_level
[ "$_iptables" != "" ] && uci set dockerd.globals.iptables=$_iptables
if [ "$registry_mirrors" != "" ]; then
for reg in $registry_mirrors; do
uci add_list dockerd.globals.registry_mirrors=$reg
done
fi
uci set dockerd.globals.auto_start='1'
uci commit
fi
fi
#format NEW_ROOT
echo "umount [ ${NEW_ROOT_MOUNTPOINT} ]"
umount -f "${NEW_ROOT_MOUNTPOINT}"
if [ $? -ne 0 ]; then
echo "Umount [ ${NEW_ROOT_MOUNTPOINT} ] failed, Please restart and try again!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
echo "Format [ ${NEW_ROOT_DEV} ]"
NEW_ROOT_UUID=$(uuidgen)
mkfs.btrfs -f -U ${NEW_ROOT_UUID} -L ${NEW_ROOT_LABEL} ${NEW_ROOT_DEV}
if [ $? -ne 0 ]; then
echo "Format [ ${NEW_ROOT_DEV} ] failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
echo "Mount [ ${NEW_ROOT_DEV} ] -> [ ${NEW_ROOT_MOUNTPOINT} ]"
mount -t btrfs -o compress=zstd:${ZSTD_LEVEL} ${NEW_ROOT_DEV} ${NEW_ROOT_MOUNTPOINT}
if [ $? -ne 0 ]; then
echo "Mount [ ${NEW_ROOT_DEV} ] -> [ ${NEW_ROOT_MOUNTPOINT} ] failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
# begin copy rootfs
cd ${NEW_ROOT_MOUNTPOINT}
echo "Start copying data, From [ ${P2} ] TO [ ${NEW_ROOT_MOUNTPOINT} ] ..."
ENTRYS=$(ls)
for entry in $ENTRYS; do
if [ "$entry" == "lost+found" ]; then
continue
fi
echo "Remove old [ $entry ] ... "
rm -rf $entry
if [ $? -ne 0 ]; then
error_msg "failed."
fi
done
echo "Create folder ... "
btrfs subvolume create etc
mkdir -p .snapshots .reserved bin boot dev lib opt mnt overlay proc rom root run sbin sys tmp usr www
ln -sf lib/ lib64
ln -sf tmp/ var
sync
COPY_SRC="boot root etc bin sbin lib opt usr www"
echo "Copy data begin ... "
for src in $COPY_SRC; do
echo "Copy [ $src ] ... "
(cd ${P2} && tar cf - $src) | tar xf -
sync
done
# if not backup, then force rewrite the etc/docker/daemon.json
if [ "${BR_FLAG}" -eq 0 ]; then
cat >./etc/docker/daemon.json <<EOF
{
"bip": "172.31.0.1/24",
"data-root": "${DOCKER_ROOT}",
"log-level": "warn",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
},
"registry-mirrors": [
"https://mirror.baidubce.com/",
"https://hub-mirror.c.163.com"
]
}
EOF
fi
cat >./etc/fstab <<EOF
UUID=${NEW_ROOT_UUID} / btrfs compress=zstd:${ZSTD_LEVEL} 0 1
LABEL=EFI /boot/efi vfat defaults 0 2
#tmpfs /tmp tmpfs defaults,nosuid 0 0
EOF
cat >./etc/config/fstab <<EOF
config global
option anon_swap '0'
option anon_mount '1'
option auto_swap '0'
option auto_mount '1'
option delay_root '5'
option check_fs '0'
config mount
option target '/rom'
option uuid '${NEW_ROOT_UUID}'
option enabled '1'
option enabled_fsck '1'
option fstype 'btrfs'
option options 'compress=zstd:${ZSTD_LEVEL}'
config mount
option target '/boot/efi'
option label 'EFI'
option enabled '1'
option enabled_fsck '1'
option fstype 'vfat'
EOF
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
sync
echo "Create initial etc snapshot -> .snapshots/etc-000"
btrfs subvolume snapshot -r etc .snapshots/etc-000
sync
[ -d /mnt/${DISK_NAME}${PART_PRESTR}4/docker ] || mkdir -p /mnt/${DISK_NAME}${PART_PRESTR}4/docker
rm -rf opt/docker && ln -sf /mnt/${DISK_NAME}${PART_PRESTR}4/docker/ opt/docker
if [ -f /mnt/${NEW_ROOT_NAME}/etc/config/AdGuardHome ]; then
[ -d /mnt/${DISK_NAME}${PART_PRESTR}4/AdGuardHome/data ] || mkdir -p /mnt/${DISK_NAME}${PART_PRESTR}4/AdGuardHome/data
if [ ! -L /usr/bin/AdGuardHome ]; then
[ -d /usr/bin/AdGuardHome ] &&
cp -a /usr/bin/AdGuardHome/* /mnt/${DISK_NAME}${PART_PRESTR}4/AdGuardHome/
fi
ln -sf /mnt/${DISK_NAME}${PART_PRESTR}4/AdGuardHome /mnt/${NEW_ROOT_NAME}/usr/bin/AdGuardHome
fi
sync
echo "Copy data complete ..."
BACKUP_LIST=$(${P2}/usr/sbin/openwrt-backup -p)
if [[ "${BR_FLAG}" -eq "1" && -n "${BACKUP_LIST}" ]]; then
echo -n "Start restoring configuration files ... "
(
cd /
eval tar czf ${NEW_ROOT_MOUNTPOINT}/.reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
)
tar xzf ${NEW_ROOT_MOUNTPOINT}/.reserved/openwrt_config.tar.gz
[ -f ./etc/config/dockerman ] && sed -e "s/option wan_mode 'false'/option wan_mode 'true'/" -i ./etc/config/dockerman 2>/dev/null
[ -f ./etc/config/dockerd ] && sed -e "s/option wan_mode '0'/option wan_mode '1'/" -i ./etc/config/dockerd 2>/dev/null
[ -f ./etc/config/verysync ] && sed -e 's/config setting/config verysync/' -i ./etc/config/verysync 2>/dev/null
# Restore fstab
cp -f .snapshots/etc-000/fstab ./etc/fstab
cp -f .snapshots/etc-000/config/fstab ./etc/config/fstab
# 还原 luci
cp -f .snapshots/etc-000/config/luci ./etc/config/luci
# 还原/etc/config/rpcd
cp -f .snapshots/etc-000/config/rpcd ./etc/config/rpcd
sync
echo "Restore configuration information complete."
fi
echo "Modify the configuration file ... "
rm -f "./etc/rc.local.orig" "./etc/first_run.sh" "./etc/part_size"
rm -rf "./opt/docker" && ln -sf "/mnt/${DISK_NAME}${PART_PRESTR}4/docker" "./opt/docker"
rm -f ./etc/bench.log
cat >>./etc/crontabs/root <<EOF
37 5 * * * /etc/coremark.sh
EOF
sss=$(date +%s)
ddd=$((sss / 86400))
sed -e "s/:0:0:99999:7:::/:${ddd}:0:99999:7:::/" -i ./etc/shadow
# Fix the problem of repeatedly adding amule entries after each upgrade
sed -e "/amule:x:/d" -i ./etc/shadow
# Fix the problem of repeatedly adding sshd entries after each upgrade of dropbear
sed -e "/sshd:x:/d" -i ./etc/shadow
if ! grep "sshd:x:22:sshd" ./etc/group >/dev/null; then
echo "sshd:x:22:sshd" >>./etc/group
fi
if ! grep "sshd:x:22:22:sshd:" ./etc/passwd >/dev/null; then
echo "sshd:x:22:22:sshd:/var/run/sshd:/bin/false" >>./etc/passwd
fi
if ! grep "sshd:x:" ./etc/shadow >/dev/null; then
echo "sshd:x:${ddd}:0:99999:7:::" >>./etc/shadow
fi
if [ "${BR_FLAG}" -eq "1" ]; then
if [ -x ./bin/bash ] && [ -f ./etc/profile.d/30-sysinfo.sh ]; then
sed -e 's/\/bin\/ash/\/bin\/bash/' -i ./etc/passwd
fi
sync
fi
sed -e "s/option hw_flow '1'/option hw_flow '0'/" -i ./etc/config/turboacc
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
eval tar czf .reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
rm -f ./etc/part_size ./etc/first_run.sh
if [ -x ./usr/sbin/balethirq.pl ]; then
if grep "balethirq.pl" "./etc/rc.local"; then
echo "balance irq is enabled"
else
echo "enable balance irq"
sed -e "/exit/i\/usr/sbin/balethirq.pl" -i ./etc/rc.local
fi
fi
mv ./etc/rc.local ./etc/rc.local.orig
cat >"./etc/rc.local" <<EOF
if ! ls /etc/rc.d/S??dockerd >/dev/null 2>&1;then
/etc/init.d/dockerd enable
/etc/init.d/dockerd start
fi
if ! ls /etc/rc.d/S??dockerman >/dev/null 2>&1 && [ -f /etc/init.d/dockerman ];then
/etc/init.d/dockerman enable
/etc/init.d/dockerman start
fi
mv /etc/rc.local.orig /etc/rc.local
chmod 755 /etc/rc.local
exec /etc/rc.local
exit
EOF
chmod 755 ./etc/rc.local*
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
echo "Create etc snapshot -> .snapshots/etc-001"
btrfs subvolume snapshot -r etc .snapshots/etc-001
cd ${WORK_DIR}
echo "Copy efi files ..."
fatlabel ${EFI_DEV} "EFI"
cd /boot/efi
mv ./EFI/BOOT/grub.cfg /tmp/grub.cfg.prev
rm -rf *
cp -a ${P1}/* .
# 如果想启动上一个版本的openwrt,可以把 /boot/efi/EFI/BOOT/grub.cfg.prev 复制成 /boot/efi/EFI/BOOT/grub.cfg
mv /tmp/grub.cfg.prev ./EFI/BOOT/
echo "Modify efi configuration ... "
cd /boot/efi/EFI/BOOT
cat >grub.cfg <<EOF
echo "search fs_uuid ${NEW_ROOT_UUID} ..."
search.fs_uuid ${NEW_ROOT_UUID} root
echo "root=\$root"
echo "set prefix ... "
set prefix=(\$root)'/boot/grub2'
echo "prefix=\$prefix"
source \${prefix}/grub.cfg
EOF
echo "Modify boot configuration ... "
cd ${NEW_ROOT_MOUNTPOINT}/boot/grub2
cat >grub.cfg <<EOF
insmod gzio
insmod part_gpt
insmod zstd
insmod btrfs
terminal_input console
terminal_output console
set default="0"
set timeout=3
menuentry "OpenWrt" {
echo 'Loading linux kernel ...'
linux /boot/vmlinuz root=UUID=${NEW_ROOT_UUID} rootfstype=btrfs rootflags=compress=zstd:${ZSTD_LEVEL} console=ttyAMA0,115200n8 no_console_suspend consoleblank=0 fsck.fix=yes fsck.repair=yes net.ifnames=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img
}
EOF
sync
cd $WORK_DIR
umount -f ${P1} ${P2} 2>/dev/null
losetup -D 2>/dev/null
rm -rf ${P1} ${P2} 2>/dev/null
rm -f ${IMG_NAME} 2>/dev/null
rm -f sha256sums 2>/dev/null
sync
wait
echo "Successfully updated, automatic restarting..."
sleep 3
reboot
exit 0
+924
View File
@@ -0,0 +1,924 @@
#!/bin/bash
#======================================================================================
# Function: Update openwrt to emmc for Rockchip STB
# Copyright (C) 2020-- https://github.com/unifreq/openwrt_packit
# Copyright (C) 2021-- https://github.com/ophub/luci-app-amlogic
#======================================================================================
#
# The script supports directly setting parameters for update, skipping interactive selection
# openwrt-update-rockchip ${OPENWRT_FILE} ${AUTO_MAINLINE_UBOOT} ${RESTORE_CONFIG}
# E.g: openwrt-update-rockchip openwrt_s905d.img.gz yes restore
# E.g: openwrt-update-rockchip openwrt_s905d.img.gz no no-restore
# You can also execute the script directly, and interactively select related functions
# E.g: openwrt-update-rockchip
#
#======================================================================================
# Encountered a serious error, abort the script execution
error_msg() {
echo -e "[ERROR] ${1}"
exit 1
}
# Get the partition name of the /boot file system
get_boot_partition_name() {
local paths="/boot"
local partition_name
partition_name=$(df "${paths}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
echo "${partition_name}"
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
echo "${partition_name}"
}
# Get the partition message of the root file system
get_root_partition_msg() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_msg=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ "^" "'"${path}"'" "$" {print $0}')
[[ -n "${partition_msg}" ]] && break
done
[[ -z "${partition_msg}" ]] && error_msg "Cannot find the root partition message!"
echo "${partition_msg}"
}
# Receive one-key command related parameters
IMG_NAME="${1}"
AUTO_MAINLINE_UBOOT="${2}"
BACKUP_RESTORE_CONFIG="${3}"
# Current device model
if [ -f /boot/armbianEnv.txt ]; then
source /boot/armbianEnv.txt 2>/dev/null
CURRENT_FDTFILE=$(basename ${fdtfile})
fi
MYDEVICE_NAME=$(cat /proc/device-tree/model | tr -d '\000')
case $MYDEVICE_NAME in
"")
error_msg "The device name is empty and cannot be recognized."
;;
"Chainedbox L1 Pro")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3328-l1pro-1296mhz.dtb"
fi
SOC="l1pro"
;;
"BeikeYun")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3328-beikeyun-1296mhz.dtb"
fi
SOC="beikeyun"
;;
"ZCuble1 Max")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3399-zcube1-max.dtb"
fi
SOC="zcube1 max"
;;
"JP TVbox 3566")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3566-jp-tvbox.dtb"
fi
SOC="jp-tvbox"
;;
"Radxa CM3 RPI CM4 IO")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3566-radxa-cm3-rpi-cm4-io.dtb"
fi
SOC="radxa-cm3-rpi-cm4-io"
;;
"FastRhino R66S" | "Lunzn FastRhino R66S")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-fastrhino-r66s.dtb"
fi
SOC="r66s"
;;
"FastRhino R68S" | "Lunzn FastRhino R68S")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-fastrhino-r68s.dtb"
fi
SOC="r68s"
;;
"RK3568 EC-X")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-ec-x.dtb"
fi
SOC="ec-x"
;;
"HINLINK OPC-H66K Board" | "Hlink H66K")
if [ -n "${CURRENT_FDTFILE}" ] && [ "${CURRENT_FDTFILE}" != "rk3568-opc-h66k.dtb" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-hlink-h66k.dtb"
fi
SOC="h66k"
;;
"HINLINK OPC-H68K Board" | "Hlink H68K")
if [ -n "${CURRENT_FDTFILE}" ] && [ "${CURRENT_FDTFILE}" != "rk3568-opc-h68k.dtb" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-hlink-h68k.dtb"
fi
SOC="h68k"
;;
"HINLINK OPC-H69K Board" | "Hlink H69K")
if [ -n "${CURRENT_FDTFILE}" ] && [ "${CURRENT_FDTFILE}" != "rk3568-opc-h69k.dtb" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-hlink-h69k.dtb"
fi
SOC="h69k"
;;
"Radxa E25" | "Radxa E25 Carrier Board")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-radxa-e25.dtb"
fi
SOC="e25"
;;
"HINLINK OWL H88K-V3 Board" | "Hlink H88K-V3")
if [ -n "${CURRENT_FDTFILE}" ] && [ "${CURRENT_FDTFILE}" != "rk3568-hinlink-h88k-v3.dtb" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3588-hlink-h88k-v3.dtb"
fi
SOC="h88k-v3"
;;
"Hlink H88K V3.1")
if [ -n "${CURRENT_FDTFILE}" ] && [ "${CURRENT_FDTFILE}" != "rk3568-hinlink-h88k-v31.dtb" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3588-hlink-h88k-v31.dtb"
fi
SOC="h88k-v3"
;;
"HINLINK OWL H88K Board" | "Hlink H88K")
if [ -n "${CURRENT_FDTFILE}" ] && [ "${CURRENT_FDTFILE}" != "rk3568-hinlink-h88k.dtb" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3588-hlink-h88k.dtb"
fi
SOC="ak88/h88k"
;;
"Hlink H28K")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3528-hlink-h28k.dtb"
fi
SOC="h28k"
;;
"Hlink HT2")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3528-hlink-ht2.dtb"
fi
SOC="ht2"
;;
"XYLink XR3528")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3528-xylink-xr3528.dtb"
fi
SOC="xylink-xr3528"
;;
"Radxa E20C")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3528-radxa-e20c.dtb"
fi
SOC="e20c"
;;
"Radxa E24C")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3528-radxa-e24c.dtb"
fi
SOC="e24c"
;;
"YiXun Rs6Pro")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3528-yixun-rs6pro.dtb"
fi
SOC="rs6pro"
;;
"Ariaboard Photonicat")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-photonicat.dtb"
fi
SOC="photonicat"
;;
"NLnet Watermelon Pi V3")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-watermelon-pi-v3.dtb"
fi
SOC="watermelon-pi-v3"
;;
"NLnet Watermelon Pi")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3568-watermelon-pi.dtb"
fi
SOC="watermelon-pi"
;;
"Rockchip RK3576 DshanPi A1 Board")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3576-100ask-dshanpi-a1.dtb"
fi
SOC="100ask-dshanpi-a1"
;;
"Radxa ROCK 5B" | "Radxa ROCK 5 Model B")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3588-rock-5b.dtb"
fi
SOC="rock5b"
;;
"Radxa ROCK 5C" | "Radxa ROCK 5 Model C")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3588s-rock-5c.dtb"
fi
SOC="rock5c"
;;
"Radxa E52C")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3588s-radxa-e52c.dtb"
fi
SOC="e52c"
;;
"Radxa E54C")
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="rk3588s-radxa-e54c.dtb"
fi
SOC="e54c"
;;
*) #default
if [ -f "/etc/flippy-openwrt-release" ]; then
source /etc/flippy-openwrt-release 2>/dev/null
if [ -n "${CURRENT_FDTFILE}" ]; then
MYDTB_FDTFILE="${CURRENT_FDTFILE}"
else
MYDTB_FDTFILE="${FDTFILE}"
fi
SOC="${SOC}"
else
error_msg "Unknown device: [ ${MYDEVICE_NAME} ], Not supported."
fi
;;
esac
[[ -z "${MYDTB_FDTFILE}" || -z "${SOC}" ]] && {
error_msg "Invalid FDTFILE or SOC: [ ${MYDTB_FDTFILE} / ${SOC} ]"
}
echo -e "Current device: ${MYDEVICE_NAME} [ ${SOC} ]"
sleep 3
# Check and repair partition mount points
[[ -z "$(get_boot_partition_name)" || -z "$(get_root_partition_name)" ]] && {
echo -e "Partition mount point error, start repairing..."
openwrt-backup -g
}
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case ${ROOT_PTNAME} in
mmcblk?p[1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')
PARTITION_NAME="p"
LB_PRE="EMMC_"
;;
nvme?n?p?)
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')
PARTITION_NAME="p"
LB_PRE="NVME_"
;;
[hsv]d[a-z][1-4])
EMMC_NAME=$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')
PARTITION_NAME=""
LB_PRE="USB_"
;;
*)
error_msg "Unable to recognize the disk type of ${ROOT_PTNAME}!"
;;
esac
cd /mnt/${EMMC_NAME}${PARTITION_NAME}4/
mv -f /tmp/upload/* . 2>/dev/null && sync
if [[ "${IMG_NAME}" == *.img ]]; then
echo -e "Update using [ ${IMG_NAME} ] file. Please wait a moment ..."
elif [ $(ls *.img -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
IMG_NAME=$(ls *.img | head -n 1)
echo -e "Update using [ ${IMG_NAME} ] ] file. Please wait a moment ..."
elif [ $(ls *.img.xz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
xz_file=$(ls *.img.xz | head -n 1)
echo -e "Update using [ ${xz_file} ] file. Please wait a moment ..."
xz -d "${xz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.img.gz -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.img.gz | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
gzip -df "${gz_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.7z -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
gz_file=$(ls *.7z | head -n 1)
echo -e "Update using [ ${gz_file} ] file. Please wait a moment ..."
bsdtar -xmf "${gz_file}" 2>/dev/null
[ $? -eq 0 ] || 7z x "${gz_file}" -aoa -y 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
elif [ $(ls *.zip -l 2>/dev/null | grep "^-" | wc -l) -ge 1 ]; then
zip_file=$(ls *.zip | head -n 1)
echo -e "Update using [ ${zip_file} ] file. Please wait a moment ..."
unzip -o "${zip_file}" 2>/dev/null
IMG_NAME=$(ls *.img | head -n 1)
else
echo -e "Please upload or specify the update openwrt firmware file."
echo -e "Upload method: system menu → Amlogic Service → Manually Upload Update"
echo -e "Specify method: Place the openwrt firmware file in [ /mnt/${EMMC_NAME}${PARTITION_NAME}4/ ]"
echo -e "The supported file suffixes are: *.img, *.img.xz, *.img.gz, *.7z, *.zip"
echo -e "After upload the openwrt firmware file, run again."
exit 1
fi
sync
# check file
if [ ! -f "${IMG_NAME}" ]; then
error_msg "No update file found."
else
echo "Start update from [ ${IMG_NAME} ]"
fi
# find boot partition
BOOT_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | awk '$3~/^part$/ && $5 ~ /^\/boot$/ {print $0}')
if [ "${BOOT_PART_MSG}" == "" ]; then
error_msg "The boot partition is not exists or not mounted, so it cannot be upgraded with this script!"
fi
BR_FLAG=1
echo -ne "Whether to backup and restore the current config files? y/n [y]\b\b"
if [[ ${BACKUP_RESTORE_CONFIG} == "restore" ]]; then
yn="y"
elif [[ ${BACKUP_RESTORE_CONFIG} == "no-restore" ]]; then
yn="n"
else
read yn
fi
case ${yn} in
n* | N*)
BR_FLAG=0
;;
esac
BOOT_NAME=$(echo ${BOOT_PART_MSG} | awk '{print $1}')
BOOT_PATH=$(echo ${BOOT_PART_MSG} | awk '{print $2}')
BOOT_UUID=$(echo ${BOOT_PART_MSG} | awk '{print $4}')
# find root partition
ROOT_PART_MSG="$(get_root_partition_msg)"
ROOT_NAME=$(echo ${ROOT_PART_MSG} | awk '{print $1}')
ROOT_PATH=$(echo ${ROOT_PART_MSG} | awk '{print $2}')
ROOT_UUID=$(echo ${ROOT_PART_MSG} | awk '{print $4}')
case ${ROOT_NAME} in
${EMMC_NAME}${PARTITION_NAME}2)
NEW_ROOT_NAME="${EMMC_NAME}${PARTITION_NAME}3"
NEW_ROOT_LABEL="${LB_PRE}ROOTFS2"
;;
${EMMC_NAME}${PARTITION_NAME}3)
NEW_ROOT_NAME="${EMMC_NAME}${PARTITION_NAME}2"
NEW_ROOT_LABEL="${LB_PRE}ROOTFS1"
;;
*)
error_msg "The root partition location is invalid, so it cannot be upgraded with this script!"
;;
esac
# find new root partition
NEW_ROOT_PART_MSG=$(lsblk -l -o NAME,PATH,TYPE,UUID,MOUNTPOINT | grep "${NEW_ROOT_NAME}" | awk '$3 ~ /^part$/ && $5 !~ /^\/$/ && $5 !~ /^\/boot$/ {print $0}')
if [ "${NEW_ROOT_PART_MSG}" == "" ]; then
error_msg "The new root partition is not exists, so it cannot be upgraded with this script!"
fi
NEW_ROOT_NAME=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $1}')
NEW_ROOT_PATH=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $2}')
NEW_ROOT_UUID=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $4}')
NEW_ROOT_MP=$(echo ${NEW_ROOT_PART_MSG} | awk '{print $5}')
# losetup
losetup -f -P ${IMG_NAME}
if [[ "${?}" -eq "0" ]]; then
LOOP_DEV=$(losetup | grep "${IMG_NAME}" | awk '{print $1}')
if [ "$LOOP_DEV" == "" ]; then
error_msg "loop device not found!"
fi
else
error_msg "losetup ${IMG_FILE} failed!"
fi
# fix loopdev issue in kernel 5.19
function fix_loopdev() {
local parentdev=${1##*/}
if [ ! -d /sys/block/${parentdev} ]; then
return
fi
subdevs=$(lsblk -l -o NAME | grep -E "^${parentdev}.+\$")
for subdev in ${subdevs}; do
if [ ! -d /sys/block/${parentdev}/${subdev} ]; then
return
elif [ -b /dev/${sub_dev} ]; then
continue
fi
source /sys/block/${parentdev}/${subdev}/uevent
mknod /dev/${subdev} b ${MAJOR} ${MINOR}
unset MAJOR MINOR DEVNAME DEVTYPE DISKSEQ PARTN PARTNAME
done
}
fix_loopdev ${LOOP_DEV}
WAIT=3
echo -n "The loopdev is ${LOOP_DEV}, wait ${WAIT} seconds "
while [ ${WAIT} -ge 1 ]; do
echo -n "."
sleep 1
WAIT=$((WAIT - 1))
done
echo
# umount loop devices (openwrt will auto mount some partition)
MOUNTED_DEVS=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "${LOOP_DEV}" | awk '$3 !~ /^$/ {print $2}')
for dev in ${MOUNTED_DEVS}; do
while :; do
echo -n "umount ${dev} ... "
umount -f ${dev}
sleep 1
mnt=$(lsblk -l -o NAME,PATH,MOUNTPOINT | grep "${dev}" | awk '$3 !~ /^$/ {print $2}')
if [ "${mnt}" == "" ]; then
echo "ok"
break
else
echo "try again ..."
fi
done
done
# mount src part
WORK_DIR=${PWD}
P1=${WORK_DIR}/boot
P2=${WORK_DIR}/root
mkdir -p $P1 $P2
echo -n "mount ${LOOP_DEV}p1 -> ${P1} ... "
mount -t ext4 -o ro ${LOOP_DEV}p1 ${P1}
if [[ "${?}" -ne "0" ]]; then
echo "mount failed"
losetup -D
exit 1
else
echo "ok"
fi
echo -n "mount ${LOOP_DEV}p2 -> ${P2} ... "
ZSTD_LEVEL=6
mount -t btrfs -o ro,compress=zstd:${ZSTD_LEVEL} ${LOOP_DEV}p2 ${P2}
if [[ "${?}" -ne "0" ]]; then
echo "mount failed"
umount -f ${P1}
losetup -D
exit 1
else
echo "ok"
fi
# Prepare the dockerman config file
if [ -f ${P2}/etc/init.d/dockerman ] && [ -f ${P2}/etc/config/dockerd ]; then
flg=0
# get current docker data root
data_root=$(uci get dockerd.globals.data_root 2>/dev/null)
if [ "${data_root}" == "" ]; then
flg=1
# get current config from /etc/docker/daemon.json
if [ -f "/etc/docker/daemon.json" ] && [ -x "/usr/bin/jq" ]; then
data_root=$(jq -r '."data-root"' /etc/docker/daemon.json)
bip=$(jq -r '."bip"' /etc/docker/daemon.json)
[ "${bip}" == "null" ] && bip="172.31.0.1/24"
log_level=$(jq -r '."log-level"' /etc/docker/daemon.json)
[ "${log_level}" == "null" ] && log_level="warn"
_iptables=$(jq -r '."iptables"' /etc/docker/daemon.json)
[ "${_iptables}" == "null" ] && _iptables="true"
registry_mirrors=$(jq -r '."registry-mirrors"[]' /etc/docker/daemon.json 2>/dev/null)
fi
fi
if [ "${data_root}" == "" ]; then
data_root="/opt/docker/" # the default data root
fi
if ! uci get dockerd.globals >/dev/null 2>&1; then
uci set dockerd.globals='globals'
uci commit
fi
# delete alter config , use inner config
if uci get dockerd.globals.alt_config_file >/dev/null 2>&1; then
uci delete dockerd.globals.alt_config_file
uci commit
fi
if [[ "${flg}" -eq "1" ]]; then
uci set dockerd.globals.data_root=${data_root}
[ "${bip}" != "" ] && uci set dockerd.globals.bip=${bip}
[ "${log_level}" != "" ] && uci set dockerd.globals.log_level=${log_level}
[ "${_iptables}" != "" ] && uci set dockerd.globals.iptables=${_iptables}
if [ "${registry_mirrors}" != "" ]; then
for reg in ${registry_mirrors}; do
uci add_list dockerd.globals.registry_mirrors=${reg}
done
fi
uci set dockerd.globals.auto_start='1'
uci commit
fi
fi
#format NEW_ROOT
echo "umount ${NEW_ROOT_MP}"
umount -f "${NEW_ROOT_MP}"
if [[ "${?}" -ne "0" ]]; then
echo "umount failed, please reboot and try again!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
echo "format ${NEW_ROOT_PATH}"
NEW_ROOT_UUID=$(uuidgen)
mkfs.btrfs -f -U ${NEW_ROOT_UUID} -L ${NEW_ROOT_LABEL} -m single ${NEW_ROOT_PATH}
if [[ "${?}" -ne "0" ]]; then
echo "format ${NEW_ROOT_PATH} failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
echo "mount ${NEW_ROOT_PATH} to ${NEW_ROOT_MP}"
mount -t btrfs -o compress=zstd:${ZSTD_LEVEL} ${NEW_ROOT_PATH} ${NEW_ROOT_MP}
if [[ "${?}" -ne "0" ]]; then
echo "mount ${NEW_ROOT_PATH} to ${NEW_ROOT_MP} failed!"
umount -f ${P1}
umount -f ${P2}
losetup -D
exit 1
fi
# begin copy rootfs
cd ${NEW_ROOT_MP}
echo "Start copy data from ${P2} to ${NEW_ROOT_MP} ..."
ENTRYS=$(ls)
for entry in ${ENTRYS}; do
if [ "${entry}" == "lost+found" ]; then
continue
fi
echo -n "remove old ${entry} ... "
rm -rf ${entry}
if [ $? -eq 0 ]; then
echo "ok"
else
echo "failed"
exit 1
fi
done
echo
echo "create etc subvolume ..."
btrfs subvolume create etc
echo -n "make dirs ... "
mkdir -p .snapshots .reserved bin boot dev lib opt mnt overlay proc rom root run sbin sys tmp usr www
ln -sf lib/ lib64
ln -sf tmp/ var
echo "done"
echo
COPY_SRC="root etc bin sbin lib opt usr www"
echo "copy data ... "
for src in ${COPY_SRC}; do
echo -n "copy ${src} ... "
(cd ${P2} && tar cf - ${src}) | tar xf -
sync
echo "done"
done
SHFS="/mnt/${EMMC_NAME}${PARTITION_NAME}4"
echo "Modify config files ... "
if [ -x ./usr/sbin/balethirq.pl ]; then
if grep "balethirq.pl" "./etc/rc.local"; then
echo "balance irq is enabled"
else
echo "enable balance irq"
sed -e "/exit/i\/usr/sbin/balethirq.pl" -i ./etc/rc.local
fi
fi
rm -f ./etc/bench.log
cat >./etc/fstab <<EOF
UUID=${NEW_ROOT_UUID} / btrfs compress=zstd:${ZSTD_LEVEL} 0 1
UUID=${BOOT_UUID} /boot ext4 defaults 0 2
#tmpfs /tmp tmpfs defaults,nosuid 0 0
EOF
cat >./etc/config/fstab <<EOF
config global
option anon_swap '0'
option anon_mount '1'
option auto_swap '0'
option auto_mount '1'
option delay_root '5'
option check_fs '0'
config mount
option target '/rom'
option uuid '${NEW_ROOT_UUID}'
option enabled '1'
option enabled_fsck '1'
option fstype 'btrfs'
option options 'compress=zstd:${ZSTD_LEVEL}'
config mount
option target '/boot'
option uuid '${BOOT_UUID}'
option enabled '1'
option enabled_fsck '0'
option fstype 'ext4'
EOF
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
sync
echo "create the first etc snapshot -> .snapshots/etc-000"
btrfs subvolume snapshot -r etc .snapshots/etc-000
[ -d ${SHFS}/docker ] || mkdir -p ${SHFS}/docker
rm -rf opt/docker && ln -sf ${SHFS}/docker/ opt/docker
if [ -f /mnt/${NEW_ROOT_NAME}/etc/config/AdGuardHome ]; then
[ -d ${SHFS}/AdGuardHome/data ] || mkdir -p ${SHFS}/AdGuardHome/data
if [ ! -L /usr/bin/AdGuardHome ]; then
[ -d /usr/bin/AdGuardHome ] &&
cp -a /usr/bin/AdGuardHome/* ${SHFS}/AdGuardHome/
fi
ln -sf ${SHFS}/AdGuardHome /mnt/${NEW_ROOT_NAME}/usr/bin/AdGuardHome
fi
sync
echo "copy done"
echo
BACKUP_LIST=$(${P2}/usr/sbin/openwrt-backup -p)
if [ $BR_FLAG -eq 1 ]; then
echo -n "Restore your old config files ... "
(
cd /
eval tar czf ${NEW_ROOT_MP}/.reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
)
tar xzf ${NEW_ROOT_MP}/.reserved/openwrt_config.tar.gz
[ -f ./etc/config/dockerman ] && sed -e "s/option wan_mode 'false'/option wan_mode 'true'/" -i ./etc/config/dockerman 2>/dev/null
[ -f ./etc/config/dockerd ] && sed -e "s/option wan_mode '0'/option wan_mode '1'/" -i ./etc/config/dockerd 2>/dev/null
[ -f ./etc/config/verysync ] && sed -e 's/config setting/config verysync/' -i ./etc/config/verysync
# 还原 fstab
cp -f .snapshots/etc-000/fstab ./etc/fstab
cp -f .snapshots/etc-000/config/fstab ./etc/config/fstab
# 还原 luci
cp -f .snapshots/etc-000/config/luci ./etc/config/luci
# 还原/etc/config/rpcd
cp -f .snapshots/etc-000/config/rpcd ./etc/config/rpcd
sync
echo "done"
echo
fi
cat >>./etc/crontabs/root <<EOF
17 3 * * * /etc/coremark.sh
EOF
sed -e 's/ttyAMA0/ttyS2/' -i ./etc/inittab
[ "${SOC}" != "h28k" ] && \
[ "${SOC}" != "h29k" ] && \
[ "${SOC}" != "ht2" ] && \
[ "${SOC}" != "e20c" ] && \
[ "${SOC}" != "e24c" ] && \
sed -e 's/ttyS0/tty1/' -i ./etc/inittab
sss=$(date +%s)
ddd=$((sss / 86400))
sed -e "s/:0:0:99999:7:::/:${ddd}:0:99999:7:::/" -i ./etc/shadow
# 修复amule每次升级后重复添加条目的问题
sed -e "/amule:x:/d" -i ./etc/shadow
# 修复dropbear每次升级后重复添加sshd条目的问题
sed -e "/sshd:x:/d" -i ./etc/shadow
if ! grep "sshd:x:22:sshd" ./etc/group >/dev/null; then
echo "sshd:x:22:sshd" >>./etc/group
fi
if ! grep "sshd:x:22:22:sshd:" ./etc/passwd >/dev/null; then
echo "sshd:x:22:22:sshd:/var/run/sshd:/bin/false" >>./etc/passwd
fi
if ! grep "sshd:x:" ./etc/shadow >/dev/null; then
echo "sshd:x:${ddd}:0:99999:7:::" >>./etc/shadow
fi
if [ $BR_FLAG -eq 1 ]; then
if [ -x ./bin/bash ] && [ -f ./etc/profile.d/30-sysinfo.sh ]; then
sed -e 's/\/bin\/ash/\/bin\/bash/' -i ./etc/passwd
fi
sync
echo "done"
echo
fi
sed -e "s/option hw_flow '1'/option hw_flow '0'/" -i ./etc/config/turboacc
(
cd etc/rc.d
rm -f S??shortcut-fe
if grep "sfe_flow '1'" ../config/turboacc >/dev/null; then
if find ../../lib/modules -name 'shortcut-fe-cm.ko'; then
ln -sf ../init.d/shortcut-fe S99shortcut-fe
fi
fi
)
# move /etc/config/balance_irq to /etc/balance_irq
[ -f "./etc/config/balance_irq" ] && mv ./etc/config/balance_irq ./etc/
rm -f "./etc/rc.local.orig" "./etc/first_run.sh" "./etc/part_size"
eval tar czf .reserved/openwrt_config.tar.gz "${BACKUP_LIST}" 2>/dev/null
sync
mv ./etc/rc.local ./etc/rc.local.orig
cat >"./etc/rc.local" <<EOF
if ! ls /etc/rc.d/S??dockerd >/dev/null 2>&1;then
/etc/init.d/dockerd enable
/etc/init.d/dockerd start
fi
if ! ls /etc/rc.d/S??dockerman >/dev/null 2>&1 && [ -f /etc/init.d/dockerman ];then
/etc/init.d/dockerman enable
/etc/init.d/dockerman start
fi
mv /etc/rc.local.orig /etc/rc.local
chmod 755 /etc/rc.local
exec /etc/rc.local
exit
EOF
chmod 755 ./etc/rc.local*
echo "create the second etc snapshot -> .snapshots/etc-001"
btrfs subvolume snapshot -r etc .snapshots/etc-001
# 2021.04.01添加
# 强制锁定fstab,防止用户擅自修改挂载点
# 开启了快照功能之后,不再需要锁定fstab
#chattr +ia ./etc/config/fstab
cd ${WORK_DIR}
echo "Start copy data from ${P2} to /boot ..."
cd /boot
echo -n "backup armbianEnv.txt ..."
cp armbianEnv.txt /tmp/
echo -n "backup current dtb file ..."
cp -v dtb/rockchip/${CURRENT_FDTFILE} /tmp/
echo -n "remove old boot files ..."
rm -rf *
echo "done"
echo -n "copy new boot files ... "
(cd ${P1} && tar cf - .) | tar xf -
sync
echo "done"
echo
echo -n "Update boot args ... "
if [ -f /tmp/armbianEnv.txt ]; then
ORIG_OVERLAYS=$(grep '^overlays=' /tmp/armbianEnv.txt)
ORIG_USER_OVERLAYS=$(grep '^user_overlays=' /tmp/armbianEnv.txt)
sed -e '/user_overlays=/d' -i armbianEnv.txt
sed -e '/overlays=/d' -i armbianEnv.txt
echo "$ORIG_OVERLAYS" >>armbianEnv.txt
echo "$ORIG_USER_OVERLAYS" >>armbianEnv.txt
fi
sed -e '/fdtfile=/d' -i armbianEnv.txt
sed -e '/rootdev=/d' -i armbianEnv.txt
sed -e '/rootfstype=/d' -i armbianEnv.txt
sed -e '/rootflags=/d' -i armbianEnv.txt
case $SOC in
l1pro | beikeyun)
echo "fdtfile=/dtb/rockchip/${MYDTB_FDTFILE}" >>armbianEnv.txt
;;
*)
echo "fdtfile=rockchip/${MYDTB_FDTFILE}" >>armbianEnv.txt
;;
esac
cat >>armbianEnv.txt <<EOF
rootdev=UUID=${NEW_ROOT_UUID}
rootfstype=btrfs
rootflags=compress=zstd:${ZSTD_LEVEL}
EOF
# Replace the UUID for extlinux/extlinux.conf if it exists
[[ -f "extlinux/extlinux.conf" ]] && {
sed -i -E "s|UUID=[^ ]*|UUID=${NEW_ROOT_UUID}|" extlinux/extlinux.conf 2>/dev/null
}
# 如果新的dtb文件不存在,则用旧的代替
if [ ! -f "dtb/rockchip/${MYDTB_FDTFILE}" ]; then
echo "The new dtb file does not exist, replace it with the old one."
cp -v /tmp/${CURRENT_FDTFILE} dtb/rockchip/${MYDTB_FDTFILE}
fi
sync
echo "done"
echo
cd $WORK_DIR
umount -f ${P1} ${P2} 2>/dev/null
losetup -D 2>/dev/null
rmdir ${P1} ${P2} 2>/dev/null
rm -f ${IMG_NAME} 2>/dev/null
rm -f sha256sums 2>/dev/null
sync
echo "Successfully updated, automatic restarting..."
sleep 3
reboot
exit 0
@@ -0,0 +1,293 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# amlogic_check_firmware.sh — check and download OpenWrt firmware updates.
#
# Purpose: query the configured GitHub repository for the latest firmware
# release, compare with the local version, and download to /tmp/amlogic
# if a newer version is available.
# Set a fixed value
check_option="${1}"
download_version="${2}"
TMP_CHECK_DIR="/tmp/amlogic"
AMLOGIC_SOC_FILE="/etc/flippy-openwrt-release"
START_LOG="${TMP_CHECK_DIR}/amlogic_check_firmware.log"
RUNNING_LOG="${TMP_CHECK_DIR}/amlogic_running_script.log"
LOG_FILE="${TMP_CHECK_DIR}/amlogic.log"
support_platform=("allwinner" "rockchip" "amlogic" "qemu-aarch64")
LOGTIME="$(date "+%Y-%m-%d %H:%M:%S")"
[[ -d ${TMP_CHECK_DIR} ]] || mkdir -p ${TMP_CHECK_DIR}
# Clean the running log
clean_running() {
echo -e '' >${RUNNING_LOG} 2>/dev/null && sync
}
# Add log
tolog() {
echo -e "${1}" >${START_LOG}
echo -e "${LOGTIME} ${1}" >>${LOG_FILE}
[[ -n "${2}" && "${2}" -eq "1" ]] && clean_running && exit 1
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
[[ -z "${partition_name}" ]] && tolog "Cannot find the root partition!" "1"
echo "${partition_name}"
}
# Check running scripts, prohibit running concurrently
this_running_log="3@OpenWrt update in progress, try again later!"
running_script="$(cat ${RUNNING_LOG} 2>/dev/null | xargs)"
if [[ -n "${running_script}" ]]; then
run_num="$(echo "${running_script}" | awk -F "@" '{print $1}')"
run_log="$(echo "${running_script}" | awk -F "@" '{print $2}')"
fi
if [[ -n "${run_log}" && "${run_num}" -ne "3" ]]; then
echo -e "${run_log}" >${START_LOG} 2>/dev/null && sync && exit 1
else
echo -e "${this_running_log}" >${RUNNING_LOG} 2>/dev/null && sync
fi
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case "${ROOT_PTNAME}" in
mmcblk?p[1-4])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')"
PARTITION_NAME="p"
;;
[hsv]d[a-z][1-4])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')"
PARTITION_NAME=""
;;
nvme?n?p[1-4])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')"
PARTITION_NAME="p"
;;
*)
tolog "Unable to recognize the disk type of ${ROOT_PTNAME}!" "1"
;;
esac
# Set the default download path
FIRMWARE_DOWNLOAD_PATH="/mnt/${EMMC_NAME}${PARTITION_NAME}4"
[ -d "${FIRMWARE_DOWNLOAD_PATH}/.luci-app-amlogic" ] || mkdir -p "${FIRMWARE_DOWNLOAD_PATH}/.luci-app-amlogic"
# Check release file
if [[ -s "${AMLOGIC_SOC_FILE}" ]]; then
source "${AMLOGIC_SOC_FILE}" 2>/dev/null
PLATFORM="${PLATFORM}"
BOARD="${BOARD}"
else
tolog "${AMLOGIC_SOC_FILE} file is missing!" "1"
fi
if [[ -z "${PLATFORM}" || -z "$(echo "${support_platform[@]}" | grep -w "${PLATFORM}")" || -z "${BOARD}" ]]; then
tolog "Missing [ PLATFORM / BOARD ] value in ${AMLOGIC_SOC_FILE} file." "1"
fi
tolog "PLATFORM: [ ${PLATFORM} ], BOARD: [ ${BOARD} ], Use in [ ${EMMC_NAME} ]"
sleep 2
# 01. Query local version information
tolog "01. Query version information..."
# 01.01 Query the current version
current_kernel_v="$(uname -r 2>/dev/null | grep -oE '^[1-9].[0-9]{1,3}.[0-9]+')"
tolog "01.01 current version: ${current_kernel_v}"
sleep 2
# 01.01 Version comparison
main_line_version="$(echo ${current_kernel_v} | awk -F '.' '{print $1"."$2}')"
# 01.02. Query the selected branch in the settings
server_kernel_branch="$(uci get amlogic.config.amlogic_kernel_branch 2>/dev/null | grep -oE '^[1-9].[0-9]{1,3}')"
if [[ -z "${server_kernel_branch}" ]]; then
server_kernel_branch="${main_line_version}"
uci set amlogic.config.amlogic_kernel_branch="${main_line_version}" 2>/dev/null
uci commit amlogic 2>/dev/null
fi
if [[ "${server_kernel_branch}" != "${main_line_version}" ]]; then
main_line_version="${server_kernel_branch}"
tolog "01.02 Select branch: ${main_line_version}"
sleep 2
fi
# 01.03. Download server version documentation
server_firmware_url="$(uci get amlogic.config.amlogic_firmware_repo 2>/dev/null)"
[[ ! -z "${server_firmware_url}" ]] || tolog "01.03 The custom firmware download repo is invalid." "1"
releases_tag_keywords="$(uci get amlogic.config.amlogic_firmware_tag 2>/dev/null)"
[[ ! -z "${releases_tag_keywords}" ]] || tolog "01.04 The custom firmware tag keywords is invalid." "1"
firmware_suffix="$(uci get amlogic.config.amlogic_firmware_suffix 2>/dev/null)"
[[ ! -z "${firmware_suffix}" ]] || tolog "01.05 The custom firmware suffix is invalid." "1"
if [[ "${server_firmware_url}" == http* ]]; then
server_firmware_url="${server_firmware_url#*com\/}"
fi
# 02. Check Updated
check_updated() {
tolog "02.01 Search for tags in the first 5 pages of releases..."
# Get the tags list
firmware_tags_array=()
for i in {1..5}; do
while IFS= read -r firmware_tags_name; do
firmware_tags_name="$(echo "${firmware_tags_name}" | sed 's/releases\/tag\///g')"
if [[ -n "${firmware_tags_name}" ]]; then
firmware_tags_array+=("${firmware_tags_name}")
fi
done < <(
curl -fsSL -m 10 \
https://github.com/${server_firmware_url}/releases?page=${i} |
grep -oE 'releases/tag/([^" ]+)'
)
done
if [[ "${#firmware_tags_array[*]}" -eq "0" ]]; then
tolog "02.01.01 Unable to retrieve a list of firmware tags." "1"
fi
tolog "02.02 Search for tags containing the keyword..."
# Search for tags containing the keyword
for i in "${firmware_tags_array[@]}"; do
if [[ "${i}" == *"${releases_tag_keywords}"* ]]; then
firmware_releases_tag="${i}"
break
fi
done
if [[ -n "${firmware_releases_tag}" ]]; then
tolog "02.02.01 Tags: ${firmware_releases_tag}"
sleep 2
else
tolog "02.02.01 No matching tags found." "1"
fi
tolog "02.03 Start searching for firmware download links..."
# Retrieve the HTML code of the tags list page
html_code="$(
curl -fsSL -m 10 \
https://github.com/${server_firmware_url}/releases/expanded_assets/${firmware_releases_tag}
)"
# Set the regular expression for the OpenWrt filename
op_file_pattern=".*_${BOARD}_.*k${main_line_version}\.[0-9]+.*${firmware_suffix}"
# Find the <li> list item where the OpenWrt file is located
li_block=$(awk -v pattern="${op_file_pattern}" -v RS='</li>' '$0 ~ pattern { print $0 "</li>"; exit }' <<<"${html_code}")
[[ -z "${li_block}" ]] && tolog "02.03.01 No matching download links found." "1"
# Find the OpenWrt filename
latest_url=$(echo "${li_block}" | grep -oE "/[^\"]*_${BOARD}_.*k${main_line_version}\.[0-9]+[^\"]*${firmware_suffix}" | sort -urV | head -n 1 | xargs basename 2>/dev/null)
tolog "02.03.02 OpenWrt file: ${latest_url}"
# Find the date of the latest update
latest_updated_at=$(echo "${li_block}" | grep -o 'datetime="[^"]*"' | sed 's/datetime="//; s/"//')
tolog "02.03.03 Latest updated at: ${latest_updated_at}"
# Format the date for display
date_display_format="$(echo ${latest_updated_at} | tr 'T' '(' | tr 'Z' ')')"
[[ -z "${latest_url}" || -z "${latest_updated_at}" ]] && tolog "02.03.04 The download URL or date is invalid." "1"
# Find the firmware sha256 value
latest_firmware_sha256="$(echo "${li_block}" | grep -o 'value="sha256:[^"]*' | sed 's/value="sha256://')"
tolog "02.03.05 OpenWrt sha256: ${latest_firmware_sha256}"
# Check the firmware update code
down_check_code="${latest_updated_at}.${main_line_version}"
op_release_code="${FIRMWARE_DOWNLOAD_PATH}/.luci-app-amlogic/op_release_code"
if [[ -s "${op_release_code}" ]]; then
update_check_code="$(cat ${op_release_code} | xargs)"
if [[ -n "${update_check_code}" && "${update_check_code}" == "${down_check_code}" ]]; then
tolog "02.04 Already the latest version, no need to update." "1"
fi
fi
# Prompt to update
if [[ -n "${latest_url}" ]]; then
tolog '<input type="button" class="cbi-button cbi-button-reload" value="Download" onclick="return b_check_firmware(this, '"'download_${latest_updated_at}@${firmware_releases_tag}/${latest_url}@${latest_firmware_sha256}'"')"/> Latest updated: '${date_display_format}'' "1"
else
tolog "02.05 No OpenWrt available, please use another kernel branch." "1"
fi
exit 0
}
# 03. Download Openwrt
download_firmware() {
tolog "03. Start download the Openwrt..."
# Get the openwrt firmware download path
if [[ "${download_version}" == "download_"* ]]; then
tolog "03.01 Start downloading..."
else
tolog "03.01 Invalid parameter." "1"
fi
# Delete other residual firmware files
rm -f ${FIRMWARE_DOWNLOAD_PATH}/*${firmware_suffix} 2>/dev/null && sync
rm -f ${FIRMWARE_DOWNLOAD_PATH}/*.img 2>/dev/null && sync
rm -f ${FIRMWARE_DOWNLOAD_PATH}/sha256sums 2>/dev/null && sync
# OpenWrt make data
latest_updated_at="$(echo ${download_version} | awk -F'@' '{print $1}' | sed -e s'|download_||'g)"
down_check_code="${latest_updated_at}.${main_line_version}"
# Download firmware
opfile_path="$(echo ${download_version} | awk -F'@' '{print $2}')"
# Restore converted characters in file names(%2B to +)
firmware_download_oldname="${opfile_path//%2B/+}"
latest_url="https://github.com/${server_firmware_url}/releases/download/${firmware_download_oldname}"
# Find the firmware sha256 value
releases_firmware_sha256sums="$(echo ${download_version} | awk -F'@' '{print $3}')"
# Download to OpenWrt file
firmware_download_name="openwrt_${BOARD}_k${main_line_version}_github${firmware_suffix}"
curl -fsSL "${latest_url}" -o "${FIRMWARE_DOWNLOAD_PATH}/${firmware_download_name}"
if [[ "$?" -eq "0" && -s "${FIRMWARE_DOWNLOAD_PATH}/${firmware_download_name}" ]]; then
tolog "03.02 OpenWrt downloaded successfully."
else
tolog "03.02 OpenWrt download failed." "1"
fi
# Verify sha256sums if available
if [[ -n "${releases_firmware_sha256sums}" ]]; then
tolog "03.03 Perform sha256 checksum verification."
# If there is a sha256sum file, compare it
download_firmware_sha256sums="$(sha256sum ${FIRMWARE_DOWNLOAD_PATH}/${firmware_download_name} | awk '{print $1}')"
if [[ "${releases_firmware_sha256sums}" != "${download_firmware_sha256sums}" ]]; then
tolog "03.03.01 sha256sum verification mismatched." "1"
else
tolog "03.03.02 sha256sum verification succeeded."
fi
fi
sync && sleep 3
tolog "You can update."
tolog '<input type="button" class="cbi-button cbi-button-reload" value="Update" onclick="return amlogic_update(this, '"'${firmware_download_name}@${down_check_code}@${FIRMWARE_DOWNLOAD_PATH}'"')"/>' "1"
exit 0
}
getopts 'cd' opts
case "${opts}" in
c | check)
check_updated
;;
* | download)
download_firmware
;;
esac
@@ -0,0 +1,271 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# amlogic_check_kernel.sh — check and download OpenWrt kernel updates.
#
# Purpose: query the configured GitHub repository for the latest kernel
# packages, compare with the running kernel version, and download to
# /tmp/amlogic if a newer version is available.
# Set a fixed value
check_option="${1}"
download_version="${2}"
TMP_CHECK_DIR="/tmp/amlogic"
AMLOGIC_SOC_FILE="/etc/flippy-openwrt-release"
START_LOG="${TMP_CHECK_DIR}/amlogic_check_kernel.log"
RUNNING_LOG="${TMP_CHECK_DIR}/amlogic_running_script.log"
LOG_FILE="${TMP_CHECK_DIR}/amlogic.log"
support_platform=("allwinner" "rockchip" "amlogic" "qemu-aarch64")
LOGTIME="$(date "+%Y-%m-%d %H:%M:%S")"
[[ -d ${TMP_CHECK_DIR} ]] || mkdir -p ${TMP_CHECK_DIR}
# Clean the running log
clean_running() {
echo -e '' >${RUNNING_LOG} 2>/dev/null && sync
}
# Add log
tolog() {
echo -e "${1}" >${START_LOG}
echo -e "${LOGTIME} ${1}" >>${LOG_FILE}
[[ -n "${2}" && "${2}" -eq "1" ]] && clean_running && exit 1
}
# Get the partition name of the root file system
get_root_partition_name() {
local paths=("/" "/overlay" "/rom")
local partition_name
for path in "${paths[@]}"; do
partition_name=$(df "${path}" | awk 'NR==2 {print $1}' | awk -F '/' '{print $3}')
[[ -n "${partition_name}" ]] && break
done
[[ -z "${partition_name}" ]] && tolog "Cannot find the root partition!" "1"
echo "${partition_name}"
}
# Check running scripts, prohibit running concurrently
this_running_log="2@Kernel update in progress, try again later!"
running_script="$(cat ${RUNNING_LOG} 2>/dev/null | xargs)"
if [[ -n "${running_script}" ]]; then
run_num="$(echo "${running_script}" | awk -F "@" '{print $1}')"
run_log="$(echo "${running_script}" | awk -F "@" '{print $2}')"
fi
if [[ -n "${run_log}" && "${run_num}" -ne "2" ]]; then
echo -e "${run_log}" >${START_LOG} 2>/dev/null && sync && exit 1
else
echo -e "${this_running_log}" >${RUNNING_LOG} 2>/dev/null && sync
fi
# Find the partition where root is located
ROOT_PTNAME="$(get_root_partition_name)"
# Find the disk where the partition is located, only supports mmcblk?p? sd?? hd?? vd?? and other formats
case "${ROOT_PTNAME}" in
mmcblk?p[1-4])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')"
PARTITION_NAME="p"
;;
[hsv]d[a-z][1-4])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-1)}')"
PARTITION_NAME=""
;;
nvme?n?p[1-4])
EMMC_NAME="$(echo ${ROOT_PTNAME} | awk '{print substr($1, 1, length($1)-2)}')"
PARTITION_NAME="p"
;;
*)
tolog "Unable to recognize the disk type of ${ROOT_PTNAME}!" "1"
;;
esac
# Set the default download path
KERNEL_DOWNLOAD_PATH="/mnt/${EMMC_NAME}${PARTITION_NAME}4"
# Check release file
if [[ -s "${AMLOGIC_SOC_FILE}" ]]; then
source "${AMLOGIC_SOC_FILE}" 2>/dev/null
PLATFORM="${PLATFORM}"
else
tolog "${AMLOGIC_SOC_FILE} file is missing!" "1"
fi
if [[ -z "${PLATFORM}" || -z "$(echo "${support_platform[@]}" | grep -w "${PLATFORM}")" ]]; then
tolog "Missing [ PLATFORM ] value in ${AMLOGIC_SOC_FILE} file." "1"
fi
tolog "PLATFORM: [ ${PLATFORM} ], Use in [ ${EMMC_NAME} ]"
sleep 2
# Step 1. Set the kernel query api
tolog "01. Start checking the kernel repository."
firmware_repo="$(uci get amlogic.config.amlogic_firmware_repo 2>/dev/null)"
[[ -n "${firmware_repo}" ]] || tolog "01.01 The custom kernel download repo is invalid." "1"
kernel_repo="$(uci get amlogic.config.amlogic_kernel_path 2>/dev/null)"
[[ -n "${kernel_repo}" ]] || tolog "01.02 The custom kernel download repo is invalid." "1"
if [[ "${kernel_repo}" == "opt/kernel" ]]; then
uci set amlogic.config.amlogic_kernel_path="${firmware_repo}" 2>/dev/null
uci commit amlogic 2>/dev/null
kernel_repo="${firmware_repo}"
fi
# Convert kernel repo to api format
[[ "${kernel_repo}" =~ ^https: ]] && kernel_repo="$(echo ${kernel_repo} | awk -F'/' '{print $4"/"$5}')"
kernel_api="https://github.com/${kernel_repo}"
tolog "01.03 Kernel repo: ${kernel_repo}"
# Get the current kernel uname
kernel_uname="$(uname -r 2>/dev/null)"
tolog "01.04 Current kernel uname: ${kernel_uname}"
# Get the kernel tag from uci config
op_kernel_tags="$(uci get amlogic.config.amlogic_kernel_tags 2>/dev/null)"
# Determine the kernel tag
if [[ -n "${op_kernel_tags}" ]]; then
kernel_tag="${op_kernel_tags/kernel_/}"
else
# Determine the kernel tag based on the current kernel uname
if [[ "${kernel_uname}" =~ -rk3588 ]]; then
kernel_tag="rk3588"
elif [[ "${kernel_uname}" =~ -rk35xx ]]; then
kernel_tag="rk35xx"
elif [[ "${kernel_uname}" =~ -flippy ]]; then
kernel_tag="flippy"
elif [[ "${kernel_uname}" =~ -beta ]]; then
kernel_tag="beta"
else
kernel_tag="stable"
fi
# Save the kernel tag to uci config
uci set amlogic.config.amlogic_kernel_tags="kernel_${kernel_tag}" 2>/dev/null
uci commit amlogic 2>/dev/null
fi
tolog "01.05 Kernel tag: kernel_${kernel_tag}"
sleep 2
# Step 2: Check if there is the latest kernel version
check_kernel() {
# 02. Query local version information
tolog "02. Start checking the kernel version."
# 02.01 Get current kernel version
[[ ! "${kernel_tag}" =~ ^(rk3588|rk35xx)$ ]] && kernel_uname="$(echo "${kernel_uname}" | cut -d'-' -f1)"
[[ -n "${kernel_uname}" ]] || tolog "02.01 The current kernel version is not detected." "1"
tolog "02.01 current version: ${kernel_uname}"
sleep 2
# 02.02 Version comparison
main_line_version="$(echo ${kernel_uname} | awk -F '.' '{print $1"."$2}')"
# 02.03 Query the selected branch in the settings
server_kernel_branch="$(uci get amlogic.config.amlogic_kernel_branch 2>/dev/null | grep -oE '^[1-9].[0-9]{1,3}')"
if [[ -z "${server_kernel_branch}" ]]; then
server_kernel_branch="${main_line_version}"
uci set amlogic.config.amlogic_kernel_branch="${main_line_version}" 2>/dev/null
uci commit amlogic 2>/dev/null
fi
if [[ "${server_kernel_branch}" != "${main_line_version}" ]]; then
main_line_version="${server_kernel_branch}"
main_line_now="0"
tolog "02.02 Select branch: ${main_line_version}"
sleep 2
fi
# Check the version on the server
latest_version="$(
curl -fsSL -m 10 \
${kernel_api}/releases/expanded_assets/kernel_${kernel_tag} |
grep -oE "${main_line_version}\.[0-9]+[^\"]*\.tar\.gz" | sed 's/.tar.gz//' |
sort -urV | head -n 1
)"
[[ -n "${latest_version}" ]] || tolog "02.03 No kernel available, please use another kernel branch." "1"
tolog "02.04 current version: ${kernel_uname}, Latest version: ${latest_version}"
sleep 2
# Get the sha256 value of the latest version
latest_kernel_sha256="$(
curl -fsSL -m 10 \
${kernel_api}/releases/expanded_assets/kernel_${kernel_tag} |
awk -v pattern="${latest_version}\.tar\.gz" -v RS='</li>' '$0 ~ pattern { print $0 "</li>"; exit }' |
grep -o 'value="sha256:[^"]*' | sed 's/value="sha256://'
)"
[[ -n "${latest_kernel_sha256}" ]] && tolog "02.05 Kernel sha256: ${latest_kernel_sha256}"
if [[ "${latest_version}" == "${kernel_uname}" ]]; then
tolog "02.06 Already the latest version, no need to update." "1"
sleep 2
else
tolog '<input type="button" class="cbi-button cbi-button-reload" value="Download" onclick="return b_check_kernel(this, '"'download_${latest_version}_${latest_kernel_sha256}'"')"/> Latest version: '${latest_version}'' "1"
fi
exit 0
}
# Step 3: Download the latest kernel version
download_kernel() {
tolog "03. Start download the kernel file."
if [[ "${download_version}" == "download_"* ]]; then
tolog "03.01 Start downloading..."
else
tolog "03.01 Invalid parameter" "1"
fi
# Get the kernel file name
kernel_file_name="$(echo "${download_version}" | cut -d '_' -f2)"
# Restore converted characters in file names(%2B to +)
kernel_file_name="${kernel_file_name//%2B/+}"
# Get the sha256 value
kernel_file_sha256="$(echo "${download_version}" | cut -d '_' -f3)"
# Delete other residual kernel files
rm -f ${KERNEL_DOWNLOAD_PATH}/*.tar.gz
rm -f ${KERNEL_DOWNLOAD_PATH}/sha256sums
rm -rf ${KERNEL_DOWNLOAD_PATH}/${kernel_file_name}*
kernel_down_from="https://github.com/${kernel_repo}/releases/download/kernel_${kernel_tag}/${kernel_file_name}.tar.gz"
curl -fsSL "${kernel_down_from}" -o ${KERNEL_DOWNLOAD_PATH}/${kernel_file_name}.tar.gz
[[ "${?}" -ne "0" ]] && tolog "03.02 The kernel download failed." "1"
# Verify sha256
if [[ -n "${kernel_file_sha256}" ]]; then
tolog "03.03 Perform sha256 checksum verification."
download_kernel_sha256sums="$(sha256sum ${KERNEL_DOWNLOAD_PATH}/${kernel_file_name}.tar.gz | awk '{print $1}')"
if [[ "${kernel_file_sha256}" != "${download_kernel_sha256sums}" ]]; then
tolog "03.03.01 sha256sum verification mismatched." "1"
else
tolog "03.03.02 sha256sum verification succeeded."
fi
fi
# Decompress the kernel package
tolog "03.04 Start decompressing the kernel package..."
tar -xf ${KERNEL_DOWNLOAD_PATH}/${kernel_file_name}.tar.gz -C ${KERNEL_DOWNLOAD_PATH}
[[ "${?}" -ne "0" ]] && tolog "03.05 Kernel decompression failed." "1"
mv -f ${KERNEL_DOWNLOAD_PATH}/${kernel_file_name}/* ${KERNEL_DOWNLOAD_PATH}/
sync && sleep 3
# Delete the downloaded kernel file
rm -f ${KERNEL_DOWNLOAD_PATH}/${kernel_file_name}.tar.gz
rm -rf ${KERNEL_DOWNLOAD_PATH}/${kernel_file_name}
tolog "03.06 The kernel is ready, you can update."
sleep 2
#echo '<a href="javascript:;" onclick="return amlogic_kernel(this)">Update</a>' >$START_LOG
tolog '<input type="button" class="cbi-button cbi-button-reload" value="Update" onclick="return amlogic_kernel(this)"/>' "1"
exit 0
}
getopts 'cd' opts
case "${opts}" in
c | check)
check_kernel
;;
* | download)
download_kernel
;;
esac
@@ -0,0 +1,271 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# amlogic_check_plugin.sh — check and download luci-app-amlogic plugin updates.
#
# Purpose: query the configured GitHub repository for the latest plugin
# release (JS or Lua branch), compare with the installed version, and
# download the IPK/APK package to /tmp/amlogic if a newer version is available.
# Set a fixed value
check_option="${1}"
download_version="${2}"
TMP_CHECK_DIR="/tmp/amlogic"
AMLOGIC_SOC_FILE="/etc/flippy-openwrt-release"
AMLOGIC_CONFIG_FILE="/etc/config/amlogic"
START_LOG="${TMP_CHECK_DIR}/amlogic_check_plugin.log"
RUNNING_LOG="${TMP_CHECK_DIR}/amlogic_running_script.log"
LOG_FILE="${TMP_CHECK_DIR}/amlogic.log"
support_platform=("allwinner" "rockchip" "amlogic" "qemu-aarch64")
LOGTIME="$(date "+%Y-%m-%d %H:%M:%S")"
[[ -d ${TMP_CHECK_DIR} ]] || mkdir -p ${TMP_CHECK_DIR}
# Clean the running log
clean_running() {
echo -e '' >${RUNNING_LOG} 2>/dev/null && sync
}
# Add log
tolog() {
echo -e "${1}" >${START_LOG}
echo -e "${LOGTIME} ${1}" >>${LOG_FILE}
[[ -n "${2}" && "${2}" -eq "1" ]] && clean_running && exit 1
}
# Check running scripts, prohibit running concurrently
this_running_log="1@Plugin update in progress, try again later!"
running_script="$(cat ${RUNNING_LOG} 2>/dev/null | xargs)"
if [[ -n "${running_script}" ]]; then
run_num="$(echo "${running_script}" | awk -F "@" '{print $1}')"
run_log="$(echo "${running_script}" | awk -F "@" '{print $2}')"
fi
if [[ -n "${run_log}" && "${run_num}" -ne "1" ]]; then
echo -e "${run_log}" >${START_LOG} 2>/dev/null && sync && exit 1
else
echo -e "${this_running_log}" >${RUNNING_LOG} 2>/dev/null && sync
fi
# Check release file
if [[ -s "${AMLOGIC_SOC_FILE}" ]]; then
source "${AMLOGIC_SOC_FILE}" 2>/dev/null
PLATFORM="${PLATFORM}"
else
tolog "${AMLOGIC_SOC_FILE} file is missing!" "1"
fi
if [[ -z "${PLATFORM}" || -z "$(echo "${support_platform[@]}" | grep -w "${PLATFORM}")" ]]; then
tolog "Missing [ PLATFORM ] value in ${AMLOGIC_SOC_FILE} file." "1"
fi
tolog "PLATFORM: [ ${PLATFORM} ]"
sleep 2
# Read and resolve plugin_branch from UCI config.
# Normalise to canonical values: "main" (JS) or "lua".
# Priority: UCI value → auto-detect from system.
plugin_branch="$(uci get amlogic.config.amlogic_plugin_branch 2>/dev/null | tr '[:upper:]' '[:lower:]' | xargs)"
# Normalize aliases (js / javascript / main → "main"; anything else → "lua")
case "${plugin_branch}" in
js | javascript | main)
plugin_branch="main"
;;
lua)
plugin_branch="lua"
;;
*)
# Empty, unknown, or missing → auto-detect from system
if [[ -f "/www/luci-static/resources/luci.js" ]]; then
plugin_branch="main"
else
plugin_branch="lua"
fi
;;
esac
# Safety check: "main" requires JS LuCI; fall back when absent
if [[ "${plugin_branch}" == "main" && ! -f "/www/luci-static/resources/luci.js" ]]; then
plugin_branch="lua"
tolog "Warning: JS LuCI not found, falling back to lua branch."
fi
# Persist the resolved value back to UCI (covers migration + branch-switch cases)
uci set amlogic.config.amlogic_plugin_branch="${plugin_branch}" 2>/dev/null
uci commit amlogic 2>/dev/null
tolog "Plugin branch: [ ${plugin_branch} ]"
sleep 1
get_plugin_info() {
package_manager=""
current_plugin_v=""
current_plugin_release=""
if command -v opkg >/dev/null 2>&1; then
package_manager="ipk"
# Full version string e.g. "3.1.295-1" or "3.1.295-2"
local full_v
full_v="$(opkg list-installed | grep '^luci-app-amlogic ' | awk '{print $3}')"
current_plugin_v="$(echo "${full_v}" | cut -d'-' -f1)"
# Strip optional "r" prefix from release (e.g. "r2" -> "2")
current_plugin_release="$(echo "${full_v}" | cut -d'-' -f2 | sed 's/^r//')"
elif command -v apk >/dev/null 2>&1; then
package_manager="apk"
# Package name e.g. "luci-app-amlogic-3.1.295-r2"
# Fields: luci(1) app(2) amlogic(3) 3.1.295(4) r2(5)
local pkg_name
pkg_name="$(apk list --installed | grep '^luci-app-amlogic-' | awk '{print $1}')"
current_plugin_v="$(echo "${pkg_name}" | cut -d'-' -f4)"
# Extract release number: "r2" -> "2"
current_plugin_release="$(echo "${pkg_name}" | cut -d'-' -f5 | sed 's/^r//')"
fi
}
# Step 2: Check if there is the latest plugin version
check_plugin() {
tolog "01. Query current version information."
get_plugin_info
if [[ -z "${package_manager}" || -z "${current_plugin_v}" ]]; then
tolog "01.01 Plugin 'luci-app-amlogic' not found or package manager unknown." "1"
else
tolog "01.01 Using [${package_manager}]. Current version: ${current_plugin_v}, Release: ${current_plugin_release:-unknown}"
fi
sleep 2
tolog "02. Start querying plugin version..."
if [[ "${plugin_branch}" == "main" ]]; then
# JS branch: match tags ending with -js (e.g. 3.1.305-js)
latest_version="$(
curl -fsSL -m 10 \
https://github.com/ophub/luci-app-amlogic/releases |
grep -oE 'expanded_assets/[0-9]+\.[0-9]+\.[0-9]+-js' | sed 's|expanded_assets/||g' |
sort -urV | head -n 1
)"
else
# Lua branch: match tags with digits only, no suffix (e.g. 3.1.305)
latest_version="$(
curl -fsSL -m 10 \
https://github.com/ophub/luci-app-amlogic/releases |
grep -oE 'expanded_assets/[0-9]+\.[0-9]+\.[0-9]+' | sed 's|expanded_assets/||g' |
grep -v -- '-' |
sort -urV | head -n 1
)"
fi
if [[ -z "${latest_version}" ]]; then
tolog "02.01 Query failed, please try again." "1"
fi
tolog "02.01 Current version: ${current_plugin_v}, Latest version: ${latest_version}"
sleep 2
# Strip variant suffix (e.g. "-lua") from latest_version to get the numeric part.
latest_version_base="${latest_version%%-*}"
# Determine target PKG_RELEASE for the selected branch:
# main branch -> release 2 (tag: 3.x.xxx-js)
# lua branch -> release 1 (tag: 3.x.xxx, no suffix)
if [[ "${plugin_branch}" == "main" ]]; then
target_release="2"
else
target_release="1"
fi
# Only report "already latest" when BOTH the version number AND the installed
# branch (PKG_RELEASE) match the selected branch. If the user switched branches
# (same version number but different release), we still offer an update.
if [[ "${current_plugin_v}" == "${latest_version_base}" && "${current_plugin_release}" == "${target_release}" ]]; then
tolog "02.02 Already the latest version, no need to update." "1"
else
tolog '<input type="button" class="cbi-button cbi-button-reload" value="Download" onclick="return b_check_plugin(this, '"'download_${latest_version}'"')"/> Latest version: '${latest_version}'' "1"
fi
exit 0
}
# Step 3: Download the latest plugin version
download_plugin() {
tolog "03. Start downloading the plugin file."
if [[ "${download_version}" == "download_"* ]]; then
tolog "03.01 Start downloading..."
else
tolog "03.01 Invalid parameter." "1"
fi
# Extract version from parameter (e.g. "download_3.1.290" -> "3.1.290")
latest_version="$(echo "${download_version}" | cut -d '_' -f2-)"
get_plugin_info
if [[ -z "${package_manager}" ]]; then
tolog "03.02 Package manager not found." "1"
fi
tolog "03.02 Package manager: ${package_manager}, Version to download: ${latest_version}"
# Clean up previous downloads
rm -f ${TMP_CHECK_DIR}/*.ipk 2>/dev/null
rm -f ${TMP_CHECK_DIR}/*.apk 2>/dev/null
rm -f ${TMP_CHECK_DIR}/sha256sums 2>/dev/null
sync
download_repo="https://github.com/ophub/luci-app-amlogic/releases/download"
# Use GitHub API to find exact filenames
if ! command -v jq >/dev/null 2>&1; then
tolog "03.03 jq not found, cannot query GitHub API." "1"
fi
tolog "03.03 Querying GitHub API for release assets..."
api_url="https://api.github.com/repos/ophub/luci-app-amlogic/releases/tags/${latest_version}"
asset_list="$(curl -fsSL -m 15 "${api_url}" | jq -r '.assets[].name' | xargs)"
if [[ -z "${asset_list}" ]]; then
tolog "03.03 Failed to fetch release assets from GitHub API." "1"
fi
plugin_file_name="$(echo "${asset_list}" | tr ' ' '\n' | grep -oE "^luci-app-amlogic.*${package_manager}$" | head -n 1)"
lang_file_list=($(echo "${asset_list}" | tr ' ' '\n' | grep -oE "^luci-i18n-amlogic.*${package_manager}$"))
if [[ -z "${plugin_file_name}" ]]; then
tolog "03.04 Could not find plugin file (.${package_manager}) in release assets." "1"
fi
tolog "03.04 Plugin file: ${plugin_file_name}"
# Download the main plugin file
plugin_full_url="${download_repo}/${latest_version}/${plugin_file_name}"
tolog "03.05 Downloading main plugin..."
curl -fsSL "${plugin_full_url}" -o "${TMP_CHECK_DIR}/${plugin_file_name}"
[[ "${?}" -ne "0" ]] && tolog "03.05 Plugin [ ${plugin_file_name} ] download failed." "1"
# Download language packs
for langfile in "${lang_file_list[@]}"; do
lang_full_url="${download_repo}/${latest_version}/${langfile}"
tolog "03.06 Downloading language pack [ ${langfile} ]..."
curl -fsSL "${lang_full_url}" -o "${TMP_CHECK_DIR}/${langfile}"
[[ "${?}" -ne "0" ]] && tolog "03.06 Language pack [ ${langfile} ] download failed." "1"
done
# The .apk filename uses tilde (~) instead of dot before the hash suffix
for file in ${TMP_CHECK_DIR}/*.apk; do
[[ -f "${file}" ]] || continue
base_name="$(basename "${file}")"
new_name="$(echo "${base_name}" | sed -E 's/\.([a-f0-9]{7}\.apk)/~\1/')"
if [[ "${base_name}" != "${new_name}" ]]; then
mv -f "${file}" "${TMP_CHECK_DIR}/${new_name}" || true
fi
done
sync && sleep 2
tolog "03.07 The plugin is ready, you can update."
sleep 2
tolog '<input type="button" class="cbi-button cbi-button-reload" value="Update" onclick="return amlogic_plugin(this)"/> Latest version: '${latest_version}'' "1"
exit 0
}
getopts 'cd' opts
case "${opts}" in
c | check)
check_plugin
;;
* | download)
download_plugin
;;
esac
@@ -0,0 +1,77 @@
{
"admin/system/amlogic": {
"title": "Amlogic Service",
"order": 88,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-amlogic" ],
"fs": { "/etc/config/amlogic": "file" }
}
},
"admin/system/amlogic/info": {
"title": "Amlogic Service",
"order": 1,
"action": { "type": "view", "path": "amlogic/info" }
},
"admin/system/amlogic/install": {
"title": "Install OpenWrt",
"order": 2,
"action": { "type": "view", "path": "amlogic/install" },
"depends": {
"uci": { "amlogic": { "config": { "menu_install": "yes" } } }
}
},
"admin/system/amlogic/upload": {
"title": "Manually Upload Update",
"order": 3,
"action": { "type": "view", "path": "amlogic/upload" }
},
"admin/system/amlogic/check": {
"title": "Online Download Update",
"order": 4,
"action": { "type": "view", "path": "amlogic/check" }
},
"admin/system/amlogic/backup": {
"title": "Backup Firmware Config",
"order": 5,
"action": { "type": "view", "path": "amlogic/backup" }
},
"admin/system/amlogic/backuplist": {
"action": { "type": "view", "path": "amlogic/backuplist" }
},
"admin/system/amlogic/armcpu": {
"title": "CPU Settings",
"order": 6,
"action": { "type": "view", "path": "amlogic/armcpu" },
"depends": {
"uci": { "amlogic": { "config": { "menu_armcpu": "yes" } } }
}
},
"admin/system/amlogic/config": {
"title": "Plugin Settings",
"order": 7,
"action": { "type": "view", "path": "amlogic/config" }
},
"admin/system/amlogic/log": {
"title": "Server Logs",
"order": 8,
"action": { "type": "view", "path": "amlogic/log" }
},
"admin/system/amlogic/poweroff": {
"title": "PowerOff",
"order": 9,
"action": { "type": "view", "path": "amlogic/poweroff" }
}
}
@@ -0,0 +1,75 @@
{
"luci-app-amlogic": {
"description": "Grant UCI/file/RPC access for the Amlogic Service plugin",
"read": {
"ubus": {
"luci.amlogic": [
"state",
"author",
"model_database",
"platform_info",
"snapshot_list",
"check_upfiles",
"upload_path",
"list_uploads",
"read_log_tail",
"read_log_full",
"cpu_policies"
]
},
"uci": [ "amlogic" ],
"file": {
"/tmp/amlogic/amlogic.log": [ "read" ],
"/tmp/amlogic/amlogic_check_install.log": [ "read" ],
"/tmp/amlogic/amlogic_check_upfiles.log": [ "read" ],
"/tmp/amlogic/amlogic_check_plugin.log": [ "read" ],
"/tmp/amlogic/amlogic_check_kernel.log": [ "read" ],
"/tmp/amlogic/amlogic_check_firmware.log": [ "read" ],
"/tmp/amlogic/amlogic_check_rescue.log": [ "read" ],
"/tmp/amlogic/amlogic_running_script.log": [ "read" ],
"/etc/amlogic_backup_list.conf": [ "read" ],
"/etc/flippy-openwrt-release": [ "read" ],
"/etc/model_database.txt": [ "read" ],
"/.reserved/openwrt_config.tar.gz": [ "read" ]
}
},
"write": {
"ubus": {
"luci.amlogic": [
"snapshot_create",
"snapshot_delete",
"snapshot_restore",
"check_plugin",
"check_kernel",
"check_firmware",
"start_plugin",
"start_kernel",
"start_update",
"start_install",
"start_rescue",
"del_log",
"poweroff",
"kvm_switch",
"delete_upload",
"install_upload",
"upload_chunk",
"backup_create",
"save_backup_list",
"prime_backup_list",
"reload_cpu",
"sync_menu"
]
},
"uci": [ "amlogic" ],
"file": {
"/etc/amlogic_backup_list.conf": [ "write" ],
"/tmp/upload/*": [ "write" ],
"/tmp/amlogic/*": [ "write" ],
"/mnt/mmcblk*p4/*": [ "write" ],
"/mnt/sd*4/*": [ "write" ],
"/mnt/hd*4/*": [ "write" ],
"/mnt/vd*4/*": [ "write" ]
}
}
}
}
@@ -0,0 +1,785 @@
// SPDX-License-Identifier: GPL-2.0
// luci.amlogic — ucode RPC bridge for luci-app-amlogic (JS branch).
//
// Purpose: expose all plugin operations (install, update, backup, snapshot,
// CPU policy, log read, upload) as ubus methods consumed by the JS views.
// Each method is registered in the `methods` table at the bottom of this file.
'use strict';
import { popen, readfile, writefile, mkstemp, stat, glob, error as fserror,
unlink, mkdir, access, open as fsopen } from 'fs';
const LOG_DIR = '/tmp/amlogic';
const LOG_FILE = LOG_DIR + '/amlogic.log';
const LOG_RUNNING = LOG_DIR + '/amlogic_running_script.log';
const LOG_INSTALL = LOG_DIR + '/amlogic_check_install.log';
const LOG_UPFILES = LOG_DIR + '/amlogic_check_upfiles.log';
const LOG_PLUGIN = LOG_DIR + '/amlogic_check_plugin.log';
const LOG_KERNEL = LOG_DIR + '/amlogic_check_kernel.log';
const LOG_FIRMWARE = LOG_DIR + '/amlogic_check_firmware.log';
const LOG_RESCUE = LOG_DIR + '/amlogic_check_rescue.log';
const UPLOAD_TMP = '/tmp/upload';
const PKG_NAME = 'luci-app-amlogic';
const BACKUP_LIST = '/etc/amlogic_backup_list.conf';
// Map of safe log names -> file paths (for read_log_tail/read_log_full)
const LOG_MAP = {
'main': LOG_FILE,
'install': LOG_INSTALL,
'upfiles': LOG_UPFILES,
'plugin': LOG_PLUGIN,
'kernel': LOG_KERNEL,
'firmware': LOG_FIRMWARE,
'rescue': LOG_RESCUE,
'running': LOG_RUNNING
};
// Run a shell command, return trimmed stdout
function sh(cmd) {
const p = popen(cmd, 'r');
if (!p) return '';
const out = p.read('all') ?? '';
p.close();
return trim(out);
}
// Run a shell command, return exit code (0 on success)
function shcall(cmd) {
const out = sh('(' + cmd + ') >/dev/null 2>&1; echo $?');
const lines = split(out, '\n');
const last = lines[length(lines) - 1] ?? '1';
return int(last);
}
// Run cmd in the background; close stdin so shbg does not block rpcd.
function shbg(cmd) {
const wrapper = '(' + cmd + ') </dev/null &';
const p = popen(wrapper, 'r');
if (p) p.close();
}
// Shell-quote a string for embedding into a /bin/sh command line
function shq(s) {
return "'" + replace(s ?? '', "'", "'\\''") + "'";
}
// Ensure runtime tmp dirs exist.
function ensure_dirs() {
mkdir(LOG_DIR);
mkdir(UPLOAD_TMP);
}
// Read flippy-openwrt-release platform identifier
function flippy_var(name) {
return sh("cat /etc/flippy-openwrt-release 2>/dev/null | grep " + shq(name) +
" | awk -F= '{print $2}' | tr -d '\"' | xargs");
}
function platform() {
const v = flippy_var('PLATFORM');
const m = match(v, /(amlogic|rockchip|allwinner|qemu)/);
return m ? m[1] : 'Unknown';
}
function show_install_menu() {
const v = flippy_var('SHOW_INSTALL_MENU');
const m = match(v, /(yes|no)/);
return m ? m[1] : 'Unknown';
}
// Detect whether the root fs is on internal storage (eMMC/NVMe/fixed disk).
// Returns true if already installed; sd* removable flag distinguishes USB vs SATA.
function root_on_internal_storage() {
const root_pt = trim(sh("df / | tail -n1 | awk '{print $1}' | awk -F '/' '{print $3}'"));
if (!root_pt) return false;
// Only hide the install menu when root is already on eMMC (device/type == "MMC").
// All other boot media (TF/SD, NVMe, SATA, USB) should still show the install menu.
if (match(root_pt, /^mmcblk[0-9]+p[0-9]+$/)) {
const base_dev = replace(root_pt, /p[0-9]+$/, ''); // e.g. mmcblk1p2 → mmcblk1
const mmc_type = trim(sh('cat /sys/block/' + shq(base_dev) + '/device/type 2>/dev/null'));
return (mmc_type == 'MMC');
}
return false;
}
// Return platform-specific helper script names for install/update/kernel operations.
function platform_scripts() {
const p = platform();
if (index(p, 'rockchip') >= 0)
return { install: '', update: 'openwrt-update-rockchip', kernel: 'openwrt-kernel' };
if (index(p, 'allwinner') >= 0)
return { install: 'openwrt-install-allwinner', update: 'openwrt-update-allwinner', kernel: 'openwrt-kernel' };
if (index(p, 'qemu') >= 0)
return { install: '', update: 'openwrt-update-kvm', kernel: 'openwrt-kernel' };
return { install: 'openwrt-install-amlogic', update: 'openwrt-update-amlogic', kernel: 'openwrt-kernel' };
}
// Read an option from the [config] section of /etc/config/amlogic
function uci_cfg(opt) {
return sh('uci -q get amlogic.config.' + opt);
}
// --- Version helpers (identical logic to controller's *_version() funcs) ---
function current_firmware_version() {
const v = sh("uname -r 2>/dev/null | grep -oE '^[1-9]\\.[0-9]{1,3}\\.[0-9]+'");
return v != '' ? v : 'Invalid value.';
}
function current_kernel_version() { return current_firmware_version(); }
function current_kernel_branch() {
const def = sh("uname -r 2>/dev/null | grep -oE '^[1-9]\\.[0-9]{1,3}'");
const cfg = sh("uci -q get amlogic.config.amlogic_kernel_branch | grep -oE '^[1-9]\\.[0-9]{1,3}'");
return cfg != '' ? cfg : def;
}
function current_plugin_version() {
let v = '';
if (shcall('command -v opkg') == 0)
v = sh("opkg list-installed | grep '^" + PKG_NAME + "' | awk '{print $3}' | cut -d'-' -f1");
if (v == '' && shcall('command -v apk') == 0)
v = sh("apk list --installed 2>/dev/null | grep '^" + PKG_NAME + "' | awk '{print $1}' | cut -d'-' -f4");
return v != '' ? v : 'Invalid value.';
}
// --- Config-driven flags used when invoking helper scripts ---
function update_restore_config() {
const v = uci_cfg('amlogic_firmware_config');
return (v == '' || int(v) != 0) ? 'restore' : 'no-restore';
}
function auto_write_bootloader() {
const v = uci_cfg('amlogic_write_bootloader');
return (v == '' || int(v) != 0) ? 'yes' : 'no';
}
function auto_shared_fstype() {
const v = trim(uci_cfg('amlogic_shared_fstype'));
return v != '' ? v : 'ext4';
}
// Read tail / full content of a known log file
function _safe_log_path(name) {
return LOG_MAP[name] ?? null;
}
function read_log_tail(name) {
const p = _safe_log_path(name);
if (!p) return '';
const pipe = popen('cat ' + p + ' 2>/dev/null | tail -c 4096', 'r');
if (!pipe) return '';
const content = pipe.read('all') ?? '';
pipe.close();
if (!content) return '';
// Find the last non-empty line
const lines = split(content, '\n');
for (let i = length(lines) - 1; i >= 0; i--) {
const line = trim(lines[i]);
if (line !== '') return line;
}
return '';
}
function read_log_full(name) {
const p = _safe_log_path(name ?? 'main');
if (!p) return '';
// Use popen+cat to bypass rpcd process-level file cache.
const pipe = popen('cat ' + p + ' 2>/dev/null', 'r');
if (!pipe) return '';
const t = pipe.read('all') ?? '';
pipe.close();
return t;
}
// Lazily seed the main log on first read (matches the Lua action_refresh_log
// behaviour: when the log is missing, populate it with `uname -a`).
function ensure_main_log() {
ensure_dirs();
if (!access(LOG_FILE)) {
shcall("uname -a > " + LOG_FILE);
let _logs1 = [LOG_INSTALL, LOG_UPFILES, LOG_PLUGIN, LOG_KERNEL,
LOG_FIRMWARE, LOG_RESCUE, LOG_RUNNING];
for (let i = 0; i < length(_logs1); i++)
writefile(_logs1[i], '');
}
}
// Helper: clear (truncate) a file
function clear(path) { writefile(path, ''); }
// Cast/sanitise an `amlogic_install_sel` token: form is
// "<soc_id>@<dtb>:<socname>:<ubootname>" (from old install view)
// The Lua original passed res[2] verbatim (colon-string) to the install
// script as the dtb_filename argument when soc_id == 99.
function parse_install_sel(s) {
const parts = split(s ?? '', '@');
const soc_id = (parts[0] ?? '99');
let dtb = 'auto_dtb';
if (int(parts[0]) == 99 && length(parts) > 1 && parts[1] != '')
dtb = parts[1];
return { soc_id, dtb };
}
// Helper: parse `amlogic_update_sel` form
// "<firmware_name>@<updated>@<write_path>"
function parse_update_sel(s) {
const a = split(s ?? '', '@');
return {
name: a[0] ?? 'auto',
updated: a[1] ?? 'updated',
path: a[2] ?? '/tmp'
};
}
// === ubus method bodies ===
function m_state() {
return {
current_firmware_version: current_firmware_version(),
current_plugin_version: current_plugin_version(),
current_kernel_version: current_kernel_version(),
current_kernel_branch: current_kernel_branch(),
kernel_release: sh('uname -r'),
has_luci_js: access('/www/luci-static/resources/luci.js') ? true : false,
plugin_branch: access('/usr/lib/lua/luci/controller/amlogic.lua') ? 'lua' : 'main'
};
}
function m_author() {
const r = uci_cfg('amlogic_firmware_repo');
return { openwrt_author: r != '' ? r : 'Invalid value.' };
}
function m_platform_info() {
const p = platform();
const m = show_install_menu();
const has_kvm = access('/boot/efi/EFI/BOOT/grub.cfg.prev') ? true : false;
// can_install: amlogic or allwinner have an install script; also forced by SHOW_INSTALL_MENU=yes
const can_install = (index(p, 'amlogic') >= 0 || index(p, 'allwinner') >= 0 || index(m, 'yes') >= 0);
// can_armcpu: all platforms except qemu support CPU frequency settings
const can_armcpu = (index(p, 'qemu') < 0);
// is_installed: root fs on internal storage means already installed; hide Install menu.
const is_installed = root_on_internal_storage();
return {
platform: p,
install_menu: m,
can_install,
can_armcpu,
has_kvm,
is_installed: is_installed ? true : false
};
}
function m_model_database() {
const p = platform();
let pat;
if (index(p, 'allwinner') >= 0)
pat = "^w[0-9]{1,9}.*:";
else
pat = "^[0-9]{1,9}.*:";
const out = sh("cat /etc/model_database.txt 2>/dev/null | grep -E " + shq(pat) +
" | awk -F ':' '{print $1\"###\"$2}' | tr ' ' '~'");
const entries = [];
for (let line in split(out, '\n')) {
const t = trim(line);
if (t == '') continue;
const kv = split(t, '###');
if (length(kv) >= 2)
push(entries, { id: trim(kv[0]), name: replace(trim(kv[1]), '~', ' ') });
}
return { entries };
}
function m_snapshot_list() {
const out = sh("btrfs subvolume list -rt / 2>/dev/null | awk '{print $4}' | grep .snapshots");
const names = [];
for (let l in split(out, '\n')) {
const n = replace(trim(l), '.snapshots/', '');
if (n != '') push(names, n);
}
return { names };
}
// Seed /etc/amlogic_backup_list.conf from the default BACKUP_LIST in openwrt-backup if absent.
function m_prime_backup_list() {
shcall("[ -s " + BACKUP_LIST + " ] || sed -n \"/BACKUP_LIST='/,/.*'$/p\" /usr/sbin/openwrt-backup | sed -e \"s/BACKUP_LIST=\\(.*\\)/\\1/; s/'//g; s/\\\\\\\\//g; s/ //g\" > " + BACKUP_LIST + " 2>/dev/null");
return { code: 0 };
}
// Create a read-only btrfs snapshot of /etc into /.snapshots/.
function m_snapshot_create() {
shcall("btrfs subvolume snapshot -r /etc /.snapshots/etc-$(date +%m.%d.%H%M%S) && sync");
return { code: 0 };
}
function m_snapshot_delete(req) {
const n = req.args?.name ?? '';
if (!match(n, /^[A-Za-z0-9._-]+$/))
return { code: 1, error: 'invalid name' };
const rc = shcall("btrfs subvolume delete -c /.snapshots/etc-" + n + " && sync");
return { code: rc };
}
function m_snapshot_restore(req) {
const n = req.args?.name ?? '';
if (!match(n, /^[A-Za-z0-9._-]+$/))
return { error: 'invalid name' };
shcall("btrfs subvolume snapshot /.snapshots/etc-" + n + " /etc && sync");
// trigger a reboot like the Lua version (echo b > /proc/sysrq-trigger)
shcall("(sleep 1 && echo b > /proc/sysrq-trigger) &");
return { code: 0 };
}
function m_check_plugin(req) {
ensure_dirs();
const opt = req.args?.options ?? 'check';
writefile(LOG_PLUGIN, '');
shcall('chmod +x /usr/share/amlogic/amlogic_check_plugin.sh');
if (opt === 'check') {
shbg('/usr/share/amlogic/amlogic_check_plugin.sh -check');
return { code: 0 };
}
// download option: e.g. "download_3.1.290" — pass verbatim to the script
shbg('/usr/share/amlogic/amlogic_check_plugin.sh -download ' + shq(opt));
return { code: 0 };
}
function m_check_kernel(req) {
ensure_dirs();
const opt = req.args?.options ?? 'check';
writefile(LOG_KERNEL, '');
shcall('chmod +x /usr/share/amlogic/amlogic_check_kernel.sh');
if (opt == 'check') {
shbg('/usr/share/amlogic/amlogic_check_kernel.sh -check');
return { code: 0 };
}
// download option: pass the full param string verbatim to the script.
shbg('/usr/share/amlogic/amlogic_check_kernel.sh -download ' + shq(opt));
return { code: 0 };
}
function m_check_firmware(req) {
ensure_dirs();
const opt = req.args?.options ?? 'check';
writefile(LOG_FIRMWARE, '');
shcall('chmod +x /usr/share/amlogic/amlogic_check_firmware.sh');
if (opt == 'check') {
shbg('/usr/share/amlogic/amlogic_check_firmware.sh -check');
return { code: 0 };
}
// download option: may contain @, /, dots, etc. — pass via shq
shbg('/usr/share/amlogic/amlogic_check_firmware.sh -download ' + shq(opt));
return { code: 0 };
}
function m_start_plugin() {
ensure_dirs();
const cfg = '/etc/config/amlogic';
const cfg_bak = '/etc/config/amlogic_bak';
const script_path = LOG_DIR + '/amlogic_install_plugin.sh';
writefile(LOG_RUNNING, '1@Plugin update in progress, try again later!\n');
writefile(LOG_PLUGIN, '');
// Write install script to a temp file; shbg closes stdin so heredoc cannot be used.
const script = '#!/bin/sh\n'
+ 'LOG=' + shq(LOG_PLUGIN) + '\n'
+ 'LOG_RUNNING=' + shq(LOG_RUNNING) + '\n'
+ 'cfg=' + shq(cfg) + '\n'
+ 'cfg_bak=' + shq(cfg_bak) + '\n'
+ '[ -f "$cfg" ] && cp -f "$cfg" "$cfg_bak"\n'
+ '# Detect new release from downloaded file name (r1=lua, r2=js)\n'
+ 'new_release=""\n'
+ 'cur_release=""\n'
+ 'if command -v opkg >/dev/null 2>&1; then\n'
+ ' _ipk="$(ls /tmp/amlogic/luci-app-amlogic_*.ipk 2>/dev/null | head -n 1)"\n'
+ ' new_release="$(echo "${_ipk}" | grep -oE \'\\-r[0-9]+_\' | grep -oE \'[0-9]+\')"\n'
+ ' _raw="$(opkg list-installed 2>/dev/null | grep \'^luci-app-amlogic \' | awk \'{print $3}\' | cut -d\'-\' -f2)"\n'
+ ' cur_release="${_raw#r}"\n'
+ 'elif command -v apk >/dev/null 2>&1; then\n'
+ ' _apk="$(ls /tmp/amlogic/luci-app-amlogic_*.apk 2>/dev/null | head -n 1)"\n'
+ ' new_release="$(echo "${_apk}" | grep -oE \'\\-r[0-9]+[-~]\' | grep -oE \'[0-9]+\')"\n'
+ ' cur_release="$(apk list --installed 2>/dev/null | grep \'^luci-app-amlogic-\' | awk \'{print $1}\' | cut -d\'-\' -f5 | sed \'s/^r//\')"\n'
+ 'fi\n'
+ '# When switching branches (r1<->r2), remove old package first so that\n'
+ '# opkg/apk registers all new files. plain remove does NOT touch dep packages.\n'
+ 'if [ -n "$cur_release" ] && [ -n "$new_release" ] && [ "$cur_release" != "$new_release" ]; then\n'
+ ' echo "Branch switch detected (r${cur_release} -> r${new_release}), removing old package first..." >> "$LOG"\n'
+ ' if command -v opkg >/dev/null 2>&1; then\n'
+ ' opkg remove luci-app-amlogic --force-depends >> "$LOG" 2>&1 || true\n'
+ ' elif command -v apk >/dev/null 2>&1; then\n'
+ ' apk del luci-app-amlogic >> "$LOG" 2>&1 || true\n'
+ ' fi\n'
+ ' echo "Old package removed (dependencies kept)." >> "$LOG"\n'
+ 'fi\n'
+ 'if command -v opkg >/dev/null 2>&1; then\n'
+ ' echo "System uses opkg." >> "$LOG"\n'
+ ' opkg --force-reinstall --force-downgrade install /tmp/amlogic/*.ipk >> "$LOG" 2>&1\n'
+ ' rc=$?\n'
+ 'elif command -v apk >/dev/null 2>&1; then\n'
+ ' echo "System uses apk." >> "$LOG"\n'
+ ' apk add --force-overwrite --allow-untrusted /tmp/amlogic/*.apk >> "$LOG" 2>&1\n'
+ ' rc=$?\n'
+ 'else\n'
+ ' echo "Error: No package manager." >> "$LOG"\n'
+ ' rc=1\n'
+ 'fi\n'
+ 'if [ "$rc" = "0" ]; then\n'
+ ' [ -f "$cfg_bak" ] && cp -f "$cfg_bak" "$cfg"\n'
+ ' rm -f /etc/config/amlogic.apk-new /etc/config/amlogic.ipk-old /etc/config/amlogic-opkg\n'
+ ' rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/* "$cfg_bak"\n'
+ ' echo "" > "$LOG_RUNNING"\n'
+ ' echo "Successful Update" > "$LOG"\n'
+ ' # Cross-branch cleanup: run in background after a delay so the browser\n'
+ ' # can poll "Successful Update" before any files are removed.\n'
+ ' (sleep 3\n'
+ ' new_release=""\n'
+ ' if command -v opkg >/dev/null 2>&1; then\n'
+ ' _raw="$(opkg list-installed | grep \'^luci-app-amlogic \' | awk \'{print $3}\' | cut -d\'-\' -f2)"\n'
+ ' new_release="${_raw#r}"\n'
+ ' elif command -v apk >/dev/null 2>&1; then\n'
+ ' new_release="$(apk list --installed 2>/dev/null | grep \'^luci-app-amlogic-\' | awk \'{print $1}\' | cut -d\'-\' -f5 | sed \'s/^r//\')"\n'
+ ' fi\n'
+ ' if [ "${new_release}" = "2" ]; then\n'
+ ' rm -f /usr/lib/lua/luci/controller/amlogic.lua\n'
+ ' rm -rf /usr/lib/lua/luci/model/cbi/amlogic\n'
+ ' rm -rf /usr/lib/lua/luci/view/amlogic\n'
+ ' elif [ "${new_release}" = "1" ]; then\n'
+ ' rm -f /usr/share/rpcd/ucode/luci.amlogic\n'
+ ' rm -f /www/luci-static/resources/view/amlogic/*.js\n'
+ ' rm -f /usr/share/luci/menu.d/luci-app-amlogic.json\n'
+ ' fi\n'
+ ' # Auto-set plugin branch in UCI config if not already configured\n'
+ ' _cur_branch="$(uci -q get amlogic.config.amlogic_plugin_branch 2>/dev/null)"\n'
+ ' if [ -z "${_cur_branch}" ]; then\n'
+ ' if [ -f "/www/luci-static/resources/luci.js" ]; then\n'
+ ' uci set amlogic.config.amlogic_plugin_branch=\'main\' 2>/dev/null\n'
+ ' else\n'
+ ' uci set amlogic.config.amlogic_plugin_branch=\'lua\' 2>/dev/null\n'
+ ' fi\n'
+ ' uci commit amlogic 2>/dev/null\n'
+ ' fi\n'
+ ' # Restart services so the newly installed files take effect immediately,\n'
+ ' # avoiding the need for users to manually clear browser cache.\n'
+ ' /etc/init.d/rpcd restart >> "$LOG" 2>&1\n'
+ ' /etc/init.d/uhttpd restart >> "$LOG" 2>&1\n'
+ ' ) &\n'
+ 'else\n'
+ ' echo "--- INSTALLATION FAILED! ---" >> "$LOG"\n'
+ ' [ -f "$cfg_bak" ] && cp -f "$cfg_bak" "$cfg"\n'
+ ' rm -f "$cfg_bak"\n'
+ ' echo "" > "$LOG_RUNNING"\n'
+ 'fi\n';
writefile(script_path, script);
shbg('sh ' + shq(script_path));
return { code: 0 };
}
function m_start_kernel() {
ensure_dirs();
const sc = platform_scripts();
writefile(LOG_RUNNING, '2@Kernel update in progress, try again later!\n');
shcall('chmod +x /usr/sbin/' + sc.kernel);
shbg('/usr/sbin/' + sc.kernel + ' ' + auto_write_bootloader() + ' > ' + LOG_KERNEL + ' 2>&1 && sync && echo "" > ' + LOG_RUNNING);
return { code: 0 };
}
function m_start_update(req) {
ensure_dirs();
const sc = platform_scripts();
writefile(LOG_RUNNING, '3@OpenWrt update in progress, try again later!\n');
shcall('chmod +x /usr/sbin/' + sc.update);
const u = parse_update_sel(req.args?.amlogic_update_sel);
shcall('mkdir -p ' + shq(u.path + '/.luci-app-amlogic'));
shcall('echo ' + shq(u.updated) + ' > ' + shq(u.path + '/.luci-app-amlogic/op_release_code') + ' && sync');
shbg('/usr/sbin/' + sc.update + ' ' + shq(u.name) + ' ' +
auto_write_bootloader() + ' ' + update_restore_config() +
' > ' + LOG_FIRMWARE + ' 2>&1 && sync && echo "" > ' + LOG_RUNNING);
return { code: 0 };
}
function m_start_install(req) {
ensure_dirs();
const sc = platform_scripts();
if (sc.install == '') return { error: 'install not supported on platform' };
shcall('chmod +x /usr/sbin/' + sc.install);
const i = parse_install_sel(req.args?.amlogic_install_sel);
if (!match(i.soc_id, /^[0-9]+$/) || !match(i.dtb, /^[A-Za-z0-9._:-]+$/))
return { error: 'invalid install sel' };
writefile(LOG_INSTALL, '');
shbg('/usr/sbin/' + sc.install + ' ' + auto_write_bootloader() + ' ' +
i.soc_id + ' ' + i.dtb + ' ' + auto_shared_fstype() +
' > ' + LOG_INSTALL + ' 2>&1 && sync');
return { code: 0 };
}
function m_start_rescue() {
ensure_dirs();
const sc = platform_scripts();
writefile(LOG_RUNNING, '4@Kernel rescue in progress, try again later!\n');
shcall('chmod +x /usr/sbin/' + sc.kernel);
shbg('/usr/sbin/' + sc.kernel + ' -s > ' + LOG_RESCUE + ' 2>&1 && sync && echo "" > ' + LOG_RUNNING);
return { code: 0 };
}
function m_read_log_tail(req) {
return { line: read_log_tail(req.args?.name ?? '') };
}
function m_read_log_full(req) {
ensure_main_log();
return { content: read_log_full(req.args?.name ?? 'main') };
}
function m_del_log() {
let _logs2 = [LOG_FILE, LOG_INSTALL, LOG_UPFILES, LOG_PLUGIN, LOG_KERNEL,
LOG_FIRMWARE, LOG_RESCUE, LOG_RUNNING];
for (let i = 0; i < length(_logs2); i++)
writefile(_logs2[i], '');
return { code: 0 };
}
function m_poweroff() {
shcall('(sleep 1 && /sbin/poweroff) &');
return { code: 0 };
}
function m_kvm_switch() {
shcall('[ -f /boot/efi/EFI/BOOT/grub.cfg.prev ] && (cd /boot/efi/EFI/BOOT/ && ' +
'mv -f grub.cfg grub.cfg.bak && mv -f grub.cfg.prev grub.cfg && ' +
'mv -f grub.cfg.bak grub.cfg.prev)');
shcall('(sync && sleep 1 && reboot) &');
return { code: 0 };
}
function m_check_upfiles() {
const t = readfile(LOG_UPFILES) ?? '';
return { value: trim(t) };
}
// Pick upload directory using exactly the same heuristic as the Lua version
function compute_upload_path() {
const root_pt = sh("df / | tail -n1 | awk '{print $1}' | awk -F '/' '{print $3}'");
if (root_pt != '' && match(root_pt, /^mmcblk[0-4]p[1-4]$/)) {
const emmc = sh('echo ' + shq(root_pt) + " | awk '{print substr($1,1,length($1)-2)}'");
return '/mnt/' + emmc + 'p4/';
}
if (root_pt != '' && match(root_pt, /^[hsv]d[a-z][0-9]+$/)) {
const emmc = sh('echo ' + shq(root_pt) + " | awk '{print substr($1,1,length($1)-1)}'");
return '/mnt/' + emmc + '4/';
}
return '/tmp/upload/';
}
function m_upload_path() {
const p = compute_upload_path();
mkdir(p);
return { path: p };
}
function _human_size(n) {
const units = [' kB', ' MB', ' GB', ' TB'];
let i = -1;
let s = +n;
while (s > 1024 && i < length(units) - 1) {
s = s / 1024;
i++;
}
if (i < 0) { s = n / 1024; i = 0; }
return sprintf('%.1f', s) + units[i];
}
function m_list_uploads() {
const dir = compute_upload_path();
mkdir(dir);
const items = [];
let firmware = false, boot = false, dtb = false, modules = false, cfg = false;
const files = glob(dir + '*') ?? [];
for (let f in files) {
const st = stat(f);
if (!st || st.type != 'file') continue;
const base = replace(f, /^.*\//, '');
const lcname = lc(base);
if (match(lcname, /\.img\.gz$/) || match(lcname, /\.img\.xz$/) ||
match(lcname, /\.7z$/) || match(lcname, /\.img$/))
firmware = true;
if (match(lcname, /^boot-/)) boot = true;
if (match(lcname, /^dtb-/)) dtb = true;
if (match(lcname, /^modules-/)) modules = true;
if (lcname == 'openwrt_config.tar.gz') cfg = true;
push(items, {
name: base,
mtime: sprintf('%s', sh('date -d @' + st.mtime + ' +"%Y-%m-%d %H:%M:%S"')),
modestr: st.modestr ?? '',
size: _human_size(st.size),
ipk: match(lcname, /\.(ipk|apk)$/) ? true : false,
cfg: (lcname == 'openwrt_config.tar.gz')
});
}
// Match the Lua page side-effect: write the upfiles status flag.
ensure_dirs();
if (firmware) writefile(LOG_UPFILES, 'firmware\n');
else if (boot && dtb && modules) writefile(LOG_UPFILES, 'kernel\n');
else writefile(LOG_UPFILES, '');
return { path: dir, items, has_firmware: firmware,
has_kernel: (boot && dtb && modules),
has_config: cfg };
}
// Strip directory prefix; reject empty, dot/dotdot, or paths still containing separators.
function _safe_basename(s) {
const b = replace(s ?? '', /^.*[\\\/]/, '');
if (b == '' || b == '.' || b == '..') return null;
if (index(b, '/') >= 0 || index(b, '\\') >= 0) return null;
return b;
}
// Receive one base64-encoded chunk and write (or append) it to the upload file.
// First call (append=false) truncates/creates; subsequent calls append.
function m_upload_chunk(req) {
const dir = compute_upload_path();
mkdir(dir);
const b = _safe_basename(req.args?.name);
if (!b) return { ok: false, error: 'invalid name: ' + (req.args?.name ?? '') };
const full = dir + b;
const data = b64dec(req.args?.data ?? '');
if (data === null) return { ok: false, error: 'invalid data' };
const append = !!req.args?.append;
const fp = fsopen(full, append ? 'a' : 'w');
if (!fp) return { ok: false, error: 'open failed: ' + (fserror() ?? '') };
const n = fp.write(data);
fp.close();
if (n === null) return { ok: false, error: 'write failed: ' + (fserror() ?? '') };
return { ok: true, written: n };
}
function m_delete_upload(req) {
const dir = compute_upload_path();
const b = _safe_basename(req.args?.name);
if (!b) return { error: 'invalid name' };
return { ok: (unlink(dir + b) ? true : false) };
}
function m_install_upload(req) {
const dir = compute_upload_path();
const b = _safe_basename(req.args?.name);
if (!b) return { error: 'invalid name' };
const full = dir + b;
const lc_b = lc(b);
if (lc_b == 'openwrt_config.tar.gz') {
shcall('chmod +x /usr/sbin/openwrt-backup');
// Use shbg so the restore+reboot does not block the rpcd thread.
shbg('/usr/sbin/openwrt-backup -r > ' + LOG_FILE + ' 2>&1 && sync');
return { code: 0, kind: 'config' };
}
if (match(lc_b, /\.ipk$/) || match(lc_b, /\.apk$/)) {
let out = '';
if (shcall('command -v opkg') == 0)
out = sh('opkg --force-reinstall --force-downgrade install ' + shq(full) + ' 2>&1');
else if (shcall('command -v apk') == 0)
out = sh('apk add --force-overwrite --allow-untrusted ' + shq(full) + ' 2>&1');
else
out = "Error: Neither 'opkg' nor 'apk' package manager found on the system.";
shcall('rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/*');
return { code: 0, kind: 'package', output: out };
}
return { error: 'unsupported file type' };
}
function m_backup_create() {
ensure_dirs();
shcall('chmod +x /usr/sbin/openwrt-backup');
shcall('/usr/sbin/openwrt-backup -b > ' + LOG_FILE + ' && sync');
const path = '/.reserved/openwrt_config.tar.gz';
const ok = access(path);
return { ok: ok ? true : false, path };
}
function m_save_backup_list(req) {
let v = req.args?.content ?? '';
// strip leading whitespace per line
v = replace(v, /\r/g, '');
const lines = split(v, '\n');
const out = [];
for (let l in lines) {
let t = replace(l, /^\s+/, '');
// strip trailing backslash continuation
t = replace(t, /\s*\\\s*$/, '');
if (t != '') push(out, t);
}
writefile(BACKUP_LIST, join('\n', out) + '\n');
return { ok: true };
}
function m_reload_cpu() {
// Apply governor / freq settings synchronously so they take effect before
// the RPC response is sent back to the browser.
const rc = shcall('/etc/init.d/amlogic start');
return { code: rc };
}
// Sync menu_install and menu_armcpu UCI flags based on runtime platform detection.
function m_sync_menu() {
const p = platform();
const m = show_install_menu();
// Detect whether OpenWrt has already been installed to internal storage
const is_installed = root_on_internal_storage();
const can_install = (index(p, 'amlogic') >= 0 || index(p, 'allwinner') >= 0 || index(m, 'yes') >= 0);
const show_install = (can_install && !is_installed) ? 'yes' : 'no';
const show_armcpu = (index(p, 'qemu') < 0) ? 'yes' : 'no';
shcall('uci -q set amlogic.config.menu_install=' + shq(show_install) +
' && uci -q set amlogic.config.menu_armcpu=' + shq(show_armcpu) +
' && uci commit amlogic' +
' && rm -f /tmp/luci-indexcache* && rm -rf /tmp/luci-modulecache/*');
return { code: 0, show_install, show_armcpu };
}
// Enumerate cpufreq policies and their available frequencies and governors.
function m_cpu_policies() {
const policies_str = sh("ls /sys/devices/system/cpu/cpufreq 2>/dev/null | grep -E 'policy[0-9]{1,3}' | xargs");
let names = split(policies_str, ' ');
if (length(names) == 1 && names[0] == '') names = ['policy0'];
const out = [];
for (let n in names) {
const name = trim(n);
if (name == '') continue;
const id = replace(name, 'policy', '');
const freqs_raw = readfile('/sys/devices/system/cpu/cpufreq/' + name + '/scaling_available_frequencies') ?? '';
const govs_raw = readfile('/sys/devices/system/cpu/cpufreq/' + name + '/scaling_available_governors') ?? '';
const freqs = []; for (let v in split(trim(freqs_raw), ' ')) if (v != '') push(freqs, v);
const govs = []; for (let v in split(trim(govs_raw), ' ')) if (v != '') push(govs, v);
const core_type = sh('cat /sys/devices/system/cpu/cpu' + id + "/uevent | grep -E '^OF_COMPATIBLE_0' | sed 's/^OF_COMPATIBLE_0=//' | xargs");
push(out, { name, id, freqs, governors: govs, core_type: core_type != '' ? core_type : 'Unknown' });
}
return { policies: out };
}
const methods = {
state: { call: m_state },
author: { call: m_author },
platform_info: { call: m_platform_info },
model_database: { call: m_model_database },
snapshot_list: { call: m_snapshot_list },
snapshot_create: { call: m_snapshot_create },
snapshot_delete: { args: { name: '' }, call: m_snapshot_delete },
snapshot_restore:{ args: { name: '' }, call: m_snapshot_restore },
check_plugin: { args: { options: '' }, call: m_check_plugin },
check_kernel: { args: { options: '' }, call: m_check_kernel },
check_firmware: { args: { options: '' }, call: m_check_firmware },
start_plugin: { call: m_start_plugin },
start_kernel: { call: m_start_kernel },
start_update: { args: { amlogic_update_sel: '' }, call: m_start_update },
start_install: { args: { amlogic_install_sel: '' }, call: m_start_install },
start_rescue: { call: m_start_rescue },
read_log_tail: { args: { name: '' }, call: m_read_log_tail },
read_log_full: { args: { name: '' }, call: m_read_log_full },
del_log: { call: m_del_log },
poweroff: { call: m_poweroff },
kvm_switch: { call: m_kvm_switch },
check_upfiles: { call: m_check_upfiles },
upload_path: { call: m_upload_path },
upload_chunk: { args: { name: '', data: '', append: false }, call: m_upload_chunk },
list_uploads: { call: m_list_uploads },
delete_upload: { args: { name: '' }, call: m_delete_upload },
install_upload: { args: { name: '' }, call: m_install_upload },
backup_create: { call: m_backup_create },
save_backup_list:{ args: { content: '' }, call: m_save_backup_list },
prime_backup_list:{ call: m_prime_backup_list },
reload_cpu: { call: m_reload_cpu },
sync_menu: { call: m_sync_menu },
cpu_policies: { call: m_cpu_policies }
};
return { 'luci.amlogic': methods };