diff --git a/package/diy/luci-app-ota/Makefile b/package/diy/luci-app-ota/Makefile new file mode 100755 index 0000000000..7c4a09e61b --- /dev/null +++ b/package/diy/luci-app-ota/Makefile @@ -0,0 +1,12 @@ + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI for OTA upgrade +LUCI_PKGARCH:=all +PKG_VERSION:=1.2.2 +PKG_RELEASE:=1 +PKG_MAINTAINER:=jjm2473 + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/package/diy/luci-app-ota/luasrc/controller/admin/ota.lua b/package/diy/luci-app-ota/luasrc/controller/admin/ota.lua new file mode 100644 index 0000000000..8b2bca4258 --- /dev/null +++ b/package/diy/luci-app-ota/luasrc/controller/admin/ota.lua @@ -0,0 +1,176 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2021 jjm2473 +]]-- + +module("luci.controller.admin.ota",package.seeall) +require "luci.util" + +function index() + entry({"admin", "system", "ota"}, post_on({ apply = "1" }, "action_ota"), _("OTA"), 69) + entry({"admin", "system", "ota", "check"}, post("action_check")) + entry({"admin", "system", "ota", "download"}, post("action_download")) + entry({"admin", "system", "ota", "progress"}, call("action_progress")) + entry({"admin", "system", "ota", "cancel"}, post("action_cancel")) +end + +local function ota_exec(cmd) + local nixio = require "nixio" + local os = require "os" + local fs = require "nixio.fs" + local rshift = nixio.bit.rshift + + local oflags = nixio.open_flags("wronly", "creat") + local lock, code, msg = nixio.open("/var/lock/ota_api.lock", oflags) + if not lock then + return 255, "", "Open stdio lock failed: " .. msg + end + + -- Acquire lock + local stat, code, msg = lock:lock("tlock") + if not stat then + lock:close() + return 255, "", "Lock stdio failed: " .. msg + end + + local r = os.execute(cmd .. " >/var/log/ota.stdout 2>/var/log/ota.stderr") + local e = fs.readfile("/var/log/ota.stderr") + local o = fs.readfile("/var/log/ota.stdout") + + fs.unlink("/var/log/ota.stderr") + fs.unlink("/var/log/ota.stdout") + + lock:lock("ulock") + lock:close() + + e = e or "" + if r == 256 and e == "" then + e = "os.execute failed, is /var/log full or not existed?" + end + return rshift(r, 8), o or "", e or "" +end + +local function image_supported(image) + return (os.execute("sysupgrade -T %q >/dev/null" % image) == 0) +end + +function action_ota() + local image_tmp = "/tmp/firmware.img" + local http = require "luci.http" + if http.formvalue("apply") == "1" then + if not luci.dispatcher.test_post_security() then + return + end + if not image_supported(image_tmp) then + luci.template.render("admin_system/ota", {image_invalid = true}) + return + end + local keep = (http.formvalue("keep") == "1") and "" or "-n" + luci.template.render("admin_system/ota_flashing", { + title = luci.i18n.translate("Flashing…"), + msg = luci.i18n.translate("The system is flashing now.
DO NOT POWER OFF THE DEVICE!
Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings."), + addr = (#keep > 0) and "192.168.1.1" or nil + }) + fork_exec("sleep 1; killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp }) + else + luci.template.render("admin_system/ota") + end +end + +function action_check() + local r,o,e = ota_exec("ota check") + local ret = { + code = 500, + msg = "Unknown" + } + if r == 0 then + ret.code = 0 + ret.msg = o + elseif r == 1 then + ret.code = 1 + ret.msg = o + else + ret.code = 500 + ret.msg = e + end + luci.http.prepare_content("application/json") + luci.http.write_json(ret) +end + +function action_download() + local r,o,e = ota_exec("ota download") + local ret = { + code = 500, + msg = "Unknown" + } + if r == 0 then + ret.code = 0 + ret.msg = "" + else + ret.code = 500 + ret.msg = e + end + luci.http.prepare_content("application/json") + luci.http.write_json(ret) +end + +function action_progress() + local r,o,e = ota_exec("ota progress") + local ret = { + code = 500, + msg = "Unknown" + } + if r == 0 then + ret.code = 0 + ret.msg = "done" + elseif r == 1 or r == 2 then + ret.code = r + ret.msg = o + else + ret.code = 500 + ret.msg = e + end + luci.http.prepare_content("application/json") + luci.http.write_json(ret) +end + +function action_cancel() + local r,o,e = ota_exec("ota cancel") + local ret = { + code = 500, + msg = "Unknown" + } + if r == 0 then + ret.code = 0 + ret.msg = "ok" + else + ret.code = 500 + ret.msg = e + end + luci.http.prepare_content("application/json") + luci.http.write_json(ret) +end + +function fork_exec(command) + local pid = nixio.fork() + if pid > 0 then + return + elseif pid == 0 then + -- change to root dir + nixio.chdir("/") + + -- patch stdin, out, err to /dev/null + local null = nixio.open("/dev/null", "w+") + if null then + nixio.dup(null, nixio.stderr) + nixio.dup(null, nixio.stdout) + nixio.dup(null, nixio.stdin) + if null:fileno() > 2 then + null:close() + end + end + + -- replace with target command + nixio.exec("/bin/sh", "-c", command) + end +end diff --git a/package/diy/luci-app-ota/luasrc/view/admin_system/ota.htm b/package/diy/luci-app-ota/luasrc/view/admin_system/ota.htm new file mode 100644 index 0000000000..160e5dba48 --- /dev/null +++ b/package/diy/luci-app-ota/luasrc/view/admin_system/ota.htm @@ -0,0 +1,224 @@ +<%# + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +<% + local fs = require "nixio.fs" +-%> + +

<%:OTA%>

+ + +
+
+ <%:Upgrade firmware On the Air%> +
<%:Check and upgrade firmware from the Internet%>
+ <% if image_invalid then %> +
<%:The image file does not contain a supported format. Maybe try again later.%>
+ <% end %> +
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ <% if fs.access("/usr/lib/lua/luci/controller/admin/system.lua") then %> +
+ + +
+ +
+ +
+
+
+ <% else %> +
+ + +
+ +
+ +
+
+
+ +
+ +
+
+
+ <% end %> +
+
+
+
+

<%:The latest firmware%>:

+
+
+
+
+
+
+
+
+
+ + + +
+ +<%+footer%> diff --git a/package/diy/luci-app-ota/luasrc/view/admin_system/ota_flashing.htm b/package/diy/luci-app-ota/luasrc/view/admin_system/ota_flashing.htm new file mode 100644 index 0000000000..409bf0d7cf --- /dev/null +++ b/package/diy/luci-app-ota/luasrc/view/admin_system/ota_flashing.htm @@ -0,0 +1,52 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + + + + <%=luci.sys.hostname()%> - <%= title or translate("Rebooting...") %> + + + + +
+
+
+

<%=luci.sys.hostname() or "?"%>

+
+
+
+   +
+
+
+

<%:System%> - <%= title or translate("Rebooting...") %>

+
+
+ <%= msg or translate("Changes applied.") %> +
+
+ <%:Loading%> + <%:Waiting for device...%> +
+
+
+
+
+ + diff --git a/package/diy/luci-app-ota/po/en/ota.po b/package/diy/luci-app-ota/po/en/ota.po new file mode 100644 index 0000000000..e05d6f4cdf --- /dev/null +++ b/package/diy/luci-app-ota/po/en/ota.po @@ -0,0 +1,56 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "OTA" +msgstr "" + +msgid "Already the latest firmware" +msgstr "" + +msgid "Check update" +msgstr "" + +msgid "Found new firmware" +msgstr "" + +msgid "Download firmware" +msgstr "" + +msgid "Cancel download" +msgstr "" + +msgid "Firmware downloaded" +msgstr "" + +msgid "Flash image..." +msgstr "" + +msgid "Checking..." +msgstr "" + +msgid "Check failed" +msgstr "" + +msgid "Downloading" +msgstr "" + +msgid "Download canceled" +msgstr "" + +msgid "Download failed" +msgstr "" + +msgid "Upgrade firmware On the Air" +msgstr "" + +msgid "Check and upgrade firmware from the Internet" +msgstr "" + +msgid "The latest firmware" +msgstr "" + +msgid "The image file does not contain a supported format. Maybe try again later." +msgstr "" + +msgid "History Log" +msgstr "" diff --git a/package/diy/luci-app-ota/po/templates/ota.pot b/package/diy/luci-app-ota/po/templates/ota.pot new file mode 100644 index 0000000000..e05d6f4cdf --- /dev/null +++ b/package/diy/luci-app-ota/po/templates/ota.pot @@ -0,0 +1,56 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "OTA" +msgstr "" + +msgid "Already the latest firmware" +msgstr "" + +msgid "Check update" +msgstr "" + +msgid "Found new firmware" +msgstr "" + +msgid "Download firmware" +msgstr "" + +msgid "Cancel download" +msgstr "" + +msgid "Firmware downloaded" +msgstr "" + +msgid "Flash image..." +msgstr "" + +msgid "Checking..." +msgstr "" + +msgid "Check failed" +msgstr "" + +msgid "Downloading" +msgstr "" + +msgid "Download canceled" +msgstr "" + +msgid "Download failed" +msgstr "" + +msgid "Upgrade firmware On the Air" +msgstr "" + +msgid "Check and upgrade firmware from the Internet" +msgstr "" + +msgid "The latest firmware" +msgstr "" + +msgid "The image file does not contain a supported format. Maybe try again later." +msgstr "" + +msgid "History Log" +msgstr "" diff --git a/package/diy/luci-app-ota/po/zh-cn/ota.po b/package/diy/luci-app-ota/po/zh-cn/ota.po new file mode 100644 index 0000000000..eb0367b9d9 --- /dev/null +++ b/package/diy/luci-app-ota/po/zh-cn/ota.po @@ -0,0 +1,56 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "OTA" +msgstr "在线升级" + +msgid "Already the latest firmware" +msgstr "已经是最新固件" + +msgid "Check update" +msgstr "检查更新" + +msgid "Found new firmware" +msgstr "发现新固件" + +msgid "Download firmware" +msgstr "下载固件" + +msgid "Cancel download" +msgstr "取消下载" + +msgid "Firmware downloaded" +msgstr "固件已下载" + +msgid "Flash image..." +msgstr "刷写固件..." + +msgid "Checking..." +msgstr "检查中..." + +msgid "Check failed" +msgstr "检查失败" + +msgid "Downloading" +msgstr "下载中" + +msgid "Download canceled" +msgstr "下载已取消" + +msgid "Download failed" +msgstr "下载失败" + +msgid "Upgrade firmware On the Air" +msgstr "在线升级固件" + +msgid "Check and upgrade firmware from the Internet" +msgstr "通过网络检查和更新固件" + +msgid "The latest firmware" +msgstr "最新固件" + +msgid "The image file does not contain a supported format. Maybe try again later." +msgstr "镜像文件格式不支持或损坏。也许稍候再试试" + +msgid "History Log" +msgstr "历史更新日志" diff --git a/package/diy/luci-app-ota/root/bin/ota b/package/diy/luci-app-ota/root/bin/ota new file mode 100755 index 0000000000..b11c9fb8ef --- /dev/null +++ b/package/diy/luci-app-ota/root/bin/ota @@ -0,0 +1,187 @@ +#!/bin/sh +# author jjm2473 +if [ -f /lib/upgrade/ota.sh ]; then + . /lib/upgrade/ota.sh + export_ota_url +fi + +if [ -z "$OTA_URL_BASE" ]; then + echo "Unsupported device." >&2 + exit 255 +fi + +WRLOCK=/var/lock/ota_background.lock + +action=${1} +shift + +sha256() { + sha256sum $1 | cut -d' ' -f1 +} + +fcurl() { + curl --fail --show-error "$@" +} + +download() { + local img=$1 + local sum=$2 + touch /tmp/firmware.img.part + echo >/tmp/firmware.img.progress + rm -f /tmp/firmware.img.sha256sum + if [ -f /tmp/firmware.img ]; then + echo "Checking existed firmware.img..." >> /tmp/firmware.img.progress + if [ "`sha256 /tmp/firmware.img`" = "$sum" ]; then + echo $sum > /tmp/firmware.img.sha256sum; + rm -f /tmp/firmware.img.part + rm -f /tmp/firmware.img.progress + return 0; + else + echo "Check failed, redownload" >> /tmp/firmware.img.progress + rm -f /tmp/firmware.img + fi + fi + curl --fail --show-error --connect-timeout 5 --progress-bar -o /tmp/firmware.img.part "$OTA_URL_BASE/$img" 2>/tmp/firmware.img.progress & + echo "$!" > /var/run/ota/download.pid + if wait $!; then + rm -f /var/run/ota/download.pid + echo "Checking new firmware.img.part..." > /tmp/firmware.img.progress + if [ "`sha256 /tmp/firmware.img.part`" = "$sum" ]; then + mv /tmp/firmware.img.part /tmp/firmware.img && echo $sum > /tmp/firmware.img.sha256sum + rm -f /tmp/firmware.img.progress + return 0 + else + echo "Checksum failed!" >>/tmp/firmware.img.progress + fi + fi + rm -f /var/run/ota/download.pid + rm -f /tmp/firmware.img.part + return 1 +} + +lock_download() { + local lock="$WRLOCK" + exec 200>$lock + flock -n 200 || return + download $1 $2 + flock -u 200 +} + +prepare_check() { + local line + local release=`grep 'DISTRIB_RELEASE=' /etc/openwrt_release | sed -E "s/.*'(.+)'.*/\1/"` + local revision=`grep 'DISTRIB_REVISION=' /etc/openwrt_release | sed -E "s/.*'(.+)'.*/\1/" | cut -d'-' -f1` + local current="$release-$revision" + local latest=0 + cat /dev/null > /var/run/ota/ota.log + fcurl --connect-timeout 5 -o /var/run/ota/ota.latest "$OTA_URL_BASE/version.latest" || { rm -f /var/run/ota/ota.latest ; return 255; } + if grep -Fq "$current" /var/run/ota/ota.latest; then + latest=1 + current="" + fi + fcurl --connect-timeout 5 -o /var/run/ota/ota.index "$OTA_URL_BASE/version.index" || return 255; + head -5 /var/run/ota/ota.index | while read line; do + if [ -z "$line" -o "$line" = "$current" ]; then + break + fi + echo "

`echo $line | sed 's/-/ /'`

" >> /var/run/ota/ota.log
+        fcurl --connect-timeout 1 "$OTA_URL_BASE/log/$line.log" >> /var/run/ota/ota.log
+        echo "
" >> /var/run/ota/ota.log + done + [ "`wc -l /var/run/ota/ota.log | cut -d' ' -f1`" -eq 0 ] && return 1 + [ "`wc -l /var/run/ota/ota.index | cut -d' ' -f1`" -gt 5 ] && echo "More" >> /var/run/ota/ota.log + + return $latest +} + +# 0: found newer fw, 1: already newest fw, *: err +do_check() { + local ret=0 + local lock="$WRLOCK" + exec 200>$lock + flock -n 200 || return 255 + prepare_check + ret=$? + flock -u 200 >/dev/null 2>&1 + + if [ "$ret" -eq 0 ]; then + # output + echo "
"
+        sed -E 's/.*\((.+)\).*/\1/' /var/run/ota/ota.latest
+        echo "
" + echo "

ChangeLog:

" + fi + [ "$ret" -eq 0 -o "$ret" -eq 1 ] && cat /var/run/ota/ota.log + return $ret +} + +# async download +do_download(){ + local img=`head -1 /var/run/ota/ota.latest | sed -E 's/.*\((.+)\).*/\1/'` + local sum=`grep 'SHA256: ' /var/run/ota/ota.latest | cut -d' ' -f2` + [ -z "$img" -o -z "$sum" ] && { echo "do check first" >&2 ; return 254; } + lock_download $img $sum & + return 0 +} + +# 0: done, 1: downloading, 2: failed, *: err +do_progress() { + local sum=`grep 'SHA256: ' /var/run/ota/ota.latest | cut -d' ' -f2` + [ -z "$sum" ] && { echo "do check first" >&2 ; return 254; } + [ -f /tmp/firmware.img.sha256sum ] && [ "`cat /tmp/firmware.img.sha256sum`" = "$sum" ] && return 0 + [ -f /tmp/firmware.img.progress ] || { echo "download not in progress" >&2 ; return 254; } + [ -f /tmp/firmware.img.part ] && { cat /tmp/firmware.img.progress | tr '\r' '\n' | tail -n1; return 1; } + tail -1 /tmp/firmware.img.progress | grep -Fq 'Canceled!' && { echo "Canceled"; return 2; } + grep -v '\r' /tmp/firmware.img.progress >&2 + return 3 +} + +do_cancel() { + if [ -f /var/run/ota/download.pid ]; then + local pid=`cat /var/run/ota/download.pid` + if [ -n "$pid" ]; then + kill -TERM $pid; + while kill -0 $pid >/dev/null 2>&1; do + if ! sleep 1; then + break + fi + done + echo "" >> /tmp/firmware.img.progress + echo "Canceled!" >> /tmp/firmware.img.progress + fi + fi + return 0 +} + +ota_init(){ + mkdir -p /var/run/ota >/dev/null 2>&1 || true +} + +usage() { + echo "usage: ota sub-command" + echo "where sub-command is one of:" + echo " check Check firmware upgrade" + echo " download Download latest firmware" + echo " progress Download progress" + echo " cancel Cancel download" +} + +ota_init || exit 255 + +case $action in + "check") + do_check + ;; + "download") + do_download + ;; + "progress") + do_progress + ;; + "cancel") + do_cancel + ;; + *) + usage + ;; +esac