diff --git a/hickory-dns/Makefile b/hickory-dns/Makefile index 85a08ab5..3f94efb5 100644 --- a/hickory-dns/Makefile +++ b/hickory-dns/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=hickory-dns PKG_VERSION:=0.26.1 -PKG_RELEASE:=25 +PKG_RELEASE:=26 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/hickory-dns/hickory-dns/tar.gz/v$(PKG_VERSION)? diff --git a/hickory-dns/files/etc/init.d/hickory-dns b/hickory-dns/files/etc/init.d/hickory-dns index 1f2acb47..d7bf6eca 100755 --- a/hickory-dns/files/etc/init.d/hickory-dns +++ b/hickory-dns/files/etc/init.d/hickory-dns @@ -6,38 +6,62 @@ STOP=51 PROG=/usr/bin/hickory-dns CONF=/etc/hickory-dns/forwarder.toml +CONF_DIR=/etc/hickory-dns TSIG_KEY=/etc/hickory-dns/update.key -NEIGH_PROG=/usr/bin/ipv6-neigh + +RUN_USER=nobody +RUN_GROUP=nogroup + +STATE_DIR=/var/lib/hickory-dns +LAN_JOURNAL="${STATE_DIR}/lan.jrnl" + +prepare_config_dir() { + [ -d "$CONF_DIR" ] || mkdir -p "$CONF_DIR" + + chown -R "$RUN_USER:$RUN_GROUP" "$CONF_DIR" 2>/dev/null || true + + find "$CONF_DIR" -type d -exec chmod 755 {} \; 2>/dev/null || true + find "$CONF_DIR" -type f -exec chmod 644 {} \; 2>/dev/null || true +} generate_tsig_key() { - [ -f "$TSIG_KEY" ] && return 0 - dd if=/dev/urandom bs=32 count=1 of="$TSIG_KEY" 2>/dev/null - chown nobody:nogroup "$TSIG_KEY" 2>/dev/null || true - chmod 400 "$TSIG_KEY" + if [ ! -f "$TSIG_KEY" ]; then + dd if=/dev/urandom bs=32 count=1 of="$TSIG_KEY" 2>/dev/null + fi + + chown "$RUN_USER:$RUN_GROUP" "$TSIG_KEY" 2>/dev/null || true + chmod 400 "$TSIG_KEY" 2>/dev/null || true +} + +prepare_runtime_dirs() { + mkdir -p "$STATE_DIR" + + chown "$RUN_USER:$RUN_GROUP" "$STATE_DIR" 2>/dev/null || true + chmod 755 "$STATE_DIR" 2>/dev/null || true +} + +cleanup_stale_state() { + # Remove stale journal so hickory-dns always starts from the zone file. + # ipv6-neigh re-registers all dynamic entries on startup, so the journal + # has no durable value and may contain corrupt state after unclean shutdown. + rm -f "$LAN_JOURNAL" } start_service() { + prepare_config_dir generate_tsig_key - mkdir -p /var/lib/hickory-dns - chown nobody:nogroup /var/lib/hickory-dns - # Remove stale journal so hickory-dns always starts from the zone file. - # ipv6-neigh re-registers all dynamic entries on startup, so the journal - # has no durable value and may contain a corrupt state after unclean shutdown. - rm -f /var/lib/hickory-dns/lan.jrnl + prepare_runtime_dirs + cleanup_stale_state + procd_open_instance hickory-dns - procd_set_param command $PROG -c $CONF -q + procd_set_param command "$PROG" -c "$CONF" -q procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}" procd_close_instance hickory-dns - for i in $(seq 1 10); do - if ss -ltn | grep -q ':53'; then - break - fi - sleep 1 - done + procd_open_instance ipv6-neigh - procd_set_param command $NEIGH_PROG --private-subnet-v4 -d "[::1]:53" -z lan --router-alias router + procd_set_param command /usr/bin/ipv6-neigh-wrapper procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}" diff --git a/hickory-dns/files/usr/bin/ipv6-neigh-wrapper b/hickory-dns/files/usr/bin/ipv6-neigh-wrapper new file mode 100644 index 00000000..6acb91f8 --- /dev/null +++ b/hickory-dns/files/usr/bin/ipv6-neigh-wrapper @@ -0,0 +1,21 @@ +#!/bin/sh + +NEIGH_PROG=/usr/bin/ipv6-neigh +RETRY=30 + +trap 'exit 1' TERM INT + +logger -t ipv6-neigh-wrapper "waiting for DNS port 53" + +i=0 +while [ "$i" -lt "$RETRY" ]; do + if ss -lnt | grep -q ':53'; then + logger -t ipv6-neigh-wrapper "DNS port 53 ready, starting ipv6-neigh" + exec "$NEIGH_PROG" --private-subnet-v4 -d "[::1]:53" -z lan --router-alias router + fi + i=$((i + 1)) + sleep 1 +done + +logger -t ipv6-neigh-wrapper "DNS port 53 not ready after ${RETRY}s, exiting" +exit 1 diff --git a/kai/Makefile b/kai/Makefile index 2650ae1b..00cde69e 100644 --- a/kai/Makefile +++ b/kai/Makefile @@ -10,10 +10,10 @@ include $(TOPDIR)/rules.mk PKG_ARCH_kai:=$(ARCH) PKG_NAME:=kai -PKG_VERSION:=0.0.5 -PKG_RELEASE:=2 +PKG_VERSION:=0.0.2 +PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://github.com/linkease/istore-packages/releases/download/prebuilt/ +PKG_SOURCE_URL:=https://github.com/Carseason/openwrt-packages/releases/download/prebuilt/ PKG_HASH:=skip PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_VERSION) diff --git a/kai/files/kai.config b/kai/files/kai.config index 38b8756a..9bcd0805 100644 --- a/kai/files/kai.config +++ b/kai/files/kai.config @@ -1,4 +1,3 @@ config kai option 'enabled' '0' - option 'data_dir' '' - option 'port' '8197' + option 'cwd' '/tmp/kai' diff --git a/kai/files/kai.init b/kai/files/kai.init index f6ba508f..c11b5e70 100755 --- a/kai/files/kai.init +++ b/kai/files/kai.init @@ -5,95 +5,24 @@ USE_PROCD=1 get_config() { config_get_bool enabled $1 enabled 1 - config_get data_dir $1 data_dir "" - config_get port $1 port "8197" -} - -http_get() { - local url="$1" - if command -v uclient-fetch >/dev/null 2>&1; then - uclient-fetch -O /dev/stdout "$url" 2>/dev/null - return $? - fi - if command -v wget >/dev/null 2>&1; then - wget -qO- "$url" 2>/dev/null - return $? - fi - if command -v curl >/dev/null 2>&1; then - curl -fsL "$url" 2>/dev/null - return $? - fi - return 1 -} - -maybe_refresh_opencode_skills_cache() { - [ -n "$data_dir" ] || return 0 - [ -n "$port" ] || return 0 - command -v jsonfilter >/dev/null 2>&1 || return 0 - - local cache_root="${data_dir}/cache/opencode/skills" - local state_root="${data_dir}/state/opencode" - local version_file="${state_root}/skills.manifest.version" - local managed_file="${state_root}/skills.managed.list" - local manifest_url="http://127.0.0.1:${port}/agentconf/skills/manifest.json" - - [ -d "$cache_root" ] || mkdir -p "$cache_root" - [ -d "$state_root" ] || mkdir -p "$state_root" - - local manifest version prev_version skills - manifest="$(http_get "$manifest_url")" || return 0 - version="$(printf '%s' "$manifest" | jsonfilter -e '@.version' 2>/dev/null)" - [ -n "$version" ] || return 0 - - prev_version="" - [ -f "$version_file" ] && prev_version="$(cat "$version_file" 2>/dev/null)" - [ "$version" = "$prev_version" ] && return 0 - - logger -t kai "opencode skills manifest changed; refreshing managed skill cache" - - if [ -f "$managed_file" ]; then - while IFS= read -r s; do - [ -n "$s" ] || continue - rm -rf "${cache_root}/${s}" - done <"$managed_file" - fi - - skills="$(printf '%s' "$manifest" | jsonfilter -e '@.skills[*]' 2>/dev/null)" - if [ -n "$skills" ]; then - printf '%s\n' "$skills" >"$managed_file" - printf '%s\n' "$skills" | while IFS= read -r s; do - [ -n "$s" ] || continue - rm -rf "${cache_root}/${s}" - done - else - : >"$managed_file" - fi - - printf '%s\n' "$version" >"$version_file" - return 0 + config_get cwd $1 cwd "/tmp/kai" } start_kai_bin(){ procd_open_instance kai_bin - procd_set_param command /usr/sbin/kai_bin -a 0.0.0.0 -p "$port" + procd_set_param command /usr/sbin/kai_bin procd_set_param stderr 1 procd_set_param respawn procd_close_instance } mkdir_cwd(){ - [ -d "$1" ] || mkdir -p "$1" + if [ ! -d $1 ]; then + mkdir -p $1 + fi } start_kai_session(){ procd_open_instance kai_session - local root="${data_dir}" - local cwd="${data_dir}/cwd" - procd_set_param env \ - OPENCODE_CWD="${cwd}" \ - OPENCODE_CONFIG="http://127.0.0.1:${port}/agentconf/opencode.json" \ - XDG_CACHE_HOME="${root}/cache" \ - XDG_DATA_HOME="${root}/data" \ - XDG_CONFIG_HOME="${root}/config" \ - XDG_STATE_HOME="${root}/state" + procd_set_param env OPENCODE_CWD=$1 OPENCODE_CONFIG=http://127.0.0.1:8197/config/opencode/opencode.json procd_set_param command /usr/sbin/kai_session procd_append_param command serve --port "8196" --hostname "127.0.0.1" procd_set_param stderr 1 @@ -106,24 +35,14 @@ start_service() { if [ $enabled != 1 ]; then return 1 fi - if [ -z "${data_dir}" ]; then - logger -t kai "data_dir is empty; please set option data_dir in /etc/config/kai" - return 1 - fi logger -t kai "Starting KAI Service" start_kai_bin sleep 1 - mkdir_cwd "${data_dir}" - mkdir_cwd "${data_dir}/cwd" - mkdir_cwd "${data_dir}/cache" - mkdir_cwd "${data_dir}/data" - mkdir_cwd "${data_dir}/config" - mkdir_cwd "${data_dir}/state" - maybe_refresh_opencode_skills_cache - start_kai_session + mkdir_cwd $cwd + start_kai_session $cwd logger -t kai "Starting KAI Service Completed" } service_triggers() { procd_add_reload_trigger "kai" -} +} \ No newline at end of file diff --git a/kai_session/Makefile b/kai_session/Makefile index b07044c8..19898beb 100644 --- a/kai_session/Makefile +++ b/kai_session/Makefile @@ -10,10 +10,10 @@ include $(TOPDIR)/rules.mk PKG_ARCH_kai_session:=$(ARCH) PKG_NAME:=kai_session -PKG_VERSION:=0.0.5 -PKG_RELEASE:=2 +PKG_VERSION:=0.0.2 +PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://github.com/linkease/istore-packages/releases/download/prebuilt/ +PKG_SOURCE_URL:=https://github.com/Carseason/openwrt-packages/releases/download/prebuilt/ PKG_HASH:=skip PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_VERSION) diff --git a/luci-app-amlogic/Makefile b/luci-app-amlogic/Makefile index 6f3d46d3..3ee43926 100644 --- a/luci-app-amlogic/Makefile +++ b/luci-app-amlogic/Makefile @@ -16,8 +16,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-amlogic -PKG_VERSION:=3.1.303 -PKG_RELEASE:=8 +PKG_VERSION:=3.1.309 +PKG_RELEASE:=9 PKG_LICENSE:=GPL-2.0 License PKG_MAINTAINER:=ophub diff --git a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/check.js b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/check.js index ef60a732..1d7facea 100644 --- a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/check.js +++ b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/check.js @@ -461,9 +461,10 @@ return view.extend({ 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.'))); - dom.content(branchSpan, s && s.current_kernel_branch - ? ' [ ' + s.current_kernel_branch + '.y ] ' - : ' [ ' + _('Invalid value.') + ' ]'); + var branchLabel = s && s.plugin_branch + ? (s.plugin_branch === 'lua' ? 'Lua' : 'JavaScript') + : (s && s.has_luci_js ? 'JavaScript' : 'Lua'); + dom.content(branchSpan, ' [ ' + branchLabel + ' ] '); // Rescue row: show same kernel version directly via the verRescue span dom.content(verRescue, s && s.current_kernel_version diff --git a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js index 0ca72ad2..3c02c4cf 100644 --- a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js +++ b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js @@ -130,11 +130,14 @@ return view.extend({ // 8. Plugin branch const pbranch = o.option(form.ListValue, 'amlogic_plugin_branch', _('Set plugin branch:'), - _('Set the branch of the luci-app-amlogic plugin used in [Only update Amlogic Service]. Default (empty) uses the main (JavaScript) branch.')); - pbranch.value('', _('main [JavaScript version]')); + 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 = ''; - pbranch.rmempty = true; + pbranch.default = state.has_luci_js ? 'main' : 'lua'; + pbranch.rmempty = false; // 9. Keep config const fcfg = o.option(form.Flag, 'amlogic_firmware_config', diff --git a/luci-app-amlogic/po/ru/amlogic.po b/luci-app-amlogic/po/ru/amlogic.po index 76171864..3ad9c237 100644 --- a/luci-app-amlogic/po/ru/amlogic.po +++ b/luci-app-amlogic/po/ru/amlogic.po @@ -425,8 +425,8 @@ msgstr "Укажите ветку версии для файлов OpenWrt и я msgid "Set plugin branch:" msgstr "Ветка плагина:" -msgid "Set the branch of the luci-app-amlogic plugin used in [Only update Amlogic Service]. Default (empty) uses the main (JavaScript) branch." -msgstr "Укажите ветку плагина luci-app-amlogic для раздела [Только обновить сервис Amlogic]. По умолчанию (пусто) используется ветка main (JavaScript)." +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 версия]" diff --git a/luci-app-amlogic/po/zh_Hans/amlogic.po b/luci-app-amlogic/po/zh_Hans/amlogic.po index 9421aa7b..a74405c6 100644 --- a/luci-app-amlogic/po/zh_Hans/amlogic.po +++ b/luci-app-amlogic/po/zh_Hans/amlogic.po @@ -425,8 +425,8 @@ msgstr "设置 [在线下载更新] 时 OpenWrt 文件与内核所选用的版 msgid "Set plugin branch:" msgstr "设置插件分支:" -msgid "Set the branch of the luci-app-amlogic plugin used in [Only update Amlogic Service]. Default (empty) uses the main (JavaScript) branch." -msgstr "设置 [仅更新晶晨宝盒] 时使用的插件分支。默认(空)使用 main (JavaScript) 分支。" +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 版本]" diff --git a/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_plugin.sh b/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_plugin.sh index d33a3948..6edd6996 100755 --- a/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_plugin.sh +++ b/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_plugin.sh @@ -64,19 +64,40 @@ fi tolog "PLATFORM: [ ${PLATFORM} ]" sleep 2 -# Read plugin branch from UCI config; default to "" (main-js) when missing or empty -# When amlogic_plugin_branch is missing, add it with empty value (main-js default) -if [[ -f "${AMLOGIC_CONFIG_FILE}" ]]; then - plugin_branch="$(uci get amlogic.config.amlogic_plugin_branch 2>/dev/null | xargs)" - if ! grep -q "amlogic_plugin_branch" "${AMLOGIC_CONFIG_FILE}" 2>/dev/null; then - uci set amlogic.config.amlogic_plugin_branch='' 2>/dev/null - uci commit amlogic 2>/dev/null - plugin_branch="" +# 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 -else - plugin_branch="" + ;; +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 -tolog "Plugin branch: [ ${plugin_branch:-main-js} ]" + +# 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="" @@ -114,19 +135,21 @@ check_plugin() { sleep 2 tolog "02. Start querying plugin version..." - if [[ "${plugin_branch}" == "lua" ]]; then + 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]+-lua' | sed 's|expanded_assets/||g' | + 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 '\-lua' | + grep -v -- '-' | sort -urV | head -n 1 )" fi @@ -141,12 +164,12 @@ check_plugin() { latest_version_base="${latest_version%%-*}" # Determine target PKG_RELEASE for the selected branch: - # js branch (default) -> release 2 - # lua branch -> release 1 - if [[ "${plugin_branch}" == "lua" ]]; then - target_release="1" - else + # 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 diff --git a/luci-app-amlogic/root/usr/share/rpcd/ucode/luci.amlogic b/luci-app-amlogic/root/usr/share/rpcd/ucode/luci.amlogic index 808b04a2..a9bac26b 100644 --- a/luci-app-amlogic/root/usr/share/rpcd/ucode/luci.amlogic +++ b/luci-app-amlogic/root/usr/share/rpcd/ucode/luci.amlogic @@ -92,6 +92,31 @@ function show_install_menu() { return m ? m[1] : 'Unknown'; } +// Detect whether OpenWrt's root fs is on internal storage (eMMC/NVMe/fixed disk). +// Returns true → already installed, hide the Install menu. +// Returns false → running from USB/SD, show the Install menu. +// +// sd* devices can be either USB (removable=1) or internal SATA (removable=0); +// we read /sys/block//removable to distinguish them. +// mmcblk* and nvme* are always internal storage on these platforms. +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; + + if (match(root_pt, /^mmcblk[0-9]+p[0-9]+$/) || match(root_pt, /^nvme[0-9]+n[0-9]+p[0-9]+$/)) { + // eMMC and NVMe are always internal + return true; + } + if (match(root_pt, /^[hsv]d[a-z][0-9]+$/)) { + // Strip trailing partition digit(s) to get base device (e.g. sda2 → sda) + const base_dev = replace(root_pt, /[0-9]+$/, ''); + const removable = trim(sh('cat /sys/block/' + shq(base_dev) + '/removable 2>/dev/null')); + // removable=0 → internal SATA/fixed disk; removable=1 → USB/removable + return (removable == '0'); + } + return false; +} + // Pick the helper script names for the current platform (as the original // controller did). function platform_scripts() { @@ -224,7 +249,9 @@ function m_state() { current_plugin_version: current_plugin_version(), current_kernel_version: current_kernel_version(), current_kernel_branch: current_kernel_branch(), - kernel_release: sh('uname -r') + 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' }; } @@ -241,14 +268,10 @@ function m_platform_info() { 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 is on an internal storage device (mmcblk/sd/hd/vd), - // meaning OpenWrt has already been installed to eMMC/NVMe/disk. When true, - // the Install OpenWrt menu should be hidden. - const root_pt = sh("df / | tail -n1 | awk '{print $1}' | awk -F '/' '{print $3}'"); - const is_installed = (root_pt != '' && - (match(root_pt, /^mmcblk[0-9]+p[1-4]$/) || - match(root_pt, /^[hsv]d[a-z][0-9]+$/) || - match(root_pt, /^nvme[0-9]+n[0-9]+p[0-9]+$/))); + // is_installed: root fs is on an internal storage device (eMMC/NVMe/fixed disk). + // sd* devices may be USB (removable=1) or internal SATA (removable=0); check + // /sys/block//removable to distinguish. When true the Install menu is hidden. + const is_installed = root_on_internal_storage(); return { platform: p, install_menu: m, @@ -382,6 +405,30 @@ function m_start_plugin() { + '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' @@ -396,7 +443,7 @@ function m_start_plugin() { + '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\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' @@ -418,6 +465,16 @@ function m_start_plugin() { + ' 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' + 'else\n' + ' echo "--- INSTALLATION FAILED! ---" >> "$LOG"\n' @@ -684,11 +741,7 @@ function m_sync_menu() { const p = platform(); const m = show_install_menu(); // Detect whether OpenWrt has already been installed to internal storage - const root_pt = sh("df / | tail -n1 | awk '{print $1}' | awk -F '/' '{print $3}'"); - const is_installed = (root_pt != '' && - (match(root_pt, /^mmcblk[0-9]+p[1-4]$/) || - match(root_pt, /^[hsv]d[a-z][0-9]+$/) || - match(root_pt, /^nvme[0-9]+n[0-9]+p[0-9]+$/))); + 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'; diff --git a/luci-app-kai/Makefile b/luci-app-kai/Makefile index 663ca9a9..8a9d1ff0 100755 --- a/luci-app-kai/Makefile +++ b/luci-app-kai/Makefile @@ -7,9 +7,9 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=kai -PKG_VERSION:=1.0.1 -PKG_RELEASE:=3 -LUCI_DEPENDS:=+kai +kai_session +luci-compat +PKG_VERSION:=1.0.0 +PKG_RELEASE:=4 +LUCI_DEPENDS:=+kai +kai_session LUCI_MINIFY_CSS:=0 LUCI_MINIFY_JS:=0 @@ -17,7 +17,7 @@ define Package/luci-app-kai/conffiles /etc/config/kai endef -define Package/luci-app-kai/postrm +define Package/luci-nginxer/postrm #!/bin/sh rm -f /tmp/luci-indexcache exit 0 @@ -25,4 +25,4 @@ endef include $(TOPDIR)/feeds/luci/luci.mk -# call BuildPackage - OpenWrt buildroot signature +# call BuildPackage - OpenWrt buildroot signature \ No newline at end of file diff --git a/luci-app-kai/luasrc/model/cbi/kai.lua b/luci-app-kai/luasrc/model/cbi/kai.lua index 8c8fb48a..997cd678 100644 --- a/luci-app-kai/luasrc/model/cbi/kai.lua +++ b/luci-app-kai/luasrc/model/cbi/kai.lua @@ -1,6 +1,6 @@ local m, s -m = Map("kai", translate("KAI"), translate("KAI is an efficient AI tool.")) +m = Map("kai", translate("KAI"), translate("KAI is an efficient data transfer tool.")) m:section(SimpleSection).template = "kai/kai_status" s=m:section(TypedSection, "kai", translate("Global settings")) @@ -8,25 +8,6 @@ s.addremove=false s.anonymous=true s:option(Flag, "enabled", translate("Enable")).rmempty=false - -local kai_model = require "luci.model.kai" -local blocks = kai_model.blocks() -local home = kai_model.home() - -local data_dir = s:option(Value, "data_dir", translate("Data directory")) -data_dir.rmempty = false -data_dir.description = translate("Required. KAI session will store cwd/cache/data/config/state under this directory (subfolders: cwd, cache, data, config, state).") - -local paths, default_path = kai_model.find_paths(blocks, home, "Configs") -for _, val in pairs(paths) do - data_dir:value(val, val) -end -data_dir.default = default_path - -local port = s:option(Value, "port", translate("API port")) -port.default = "8197" -port.rmempty = false -port.datatype = "port" -port.description = translate("Port for KAI HTTP server (kai_bin). kai_session will read OPENCODE_CONFIG via 127.0.0.1:.") - return m + + diff --git a/luci-app-kai/luasrc/model/kai.lua b/luci-app-kai/luasrc/model/kai.lua deleted file mode 100644 index b4ef8499..00000000 --- a/luci-app-kai/luasrc/model/kai.lua +++ /dev/null @@ -1,54 +0,0 @@ -local jsonc = require "luci.jsonc" - -local kai = {} - -kai.blocks = function() - local f = io.popen("lsblk -s -f -b -o NAME,FSSIZE,MOUNTPOINT --json", "r") - local vals = {} - if f then - local ret = f:read("*all") - f:close() - local obj = jsonc.parse(ret) - for _, val in pairs(obj and obj["blockdevices"] or {}) do - local fsize = val["fssize"] - if fsize ~= nil and string.len(fsize) > 10 and val["mountpoint"] then - vals[#vals + 1] = val["mountpoint"] - end - end - end - return vals -end - -kai.home = function() - local uci = require "luci.model.uci".cursor() - local home_dirs = {} - home_dirs["main_dir"] = uci:get_first("quickstart", "main", "main_dir", "/root") - home_dirs["Configs"] = uci:get_first("quickstart", "main", "conf_dir", home_dirs["main_dir"] .. "/Configs") - home_dirs["Public"] = uci:get_first("quickstart", "main", "pub_dir", home_dirs["main_dir"] .. "/Public") - home_dirs["Downloads"] = uci:get_first("quickstart", "main", "dl_dir", home_dirs["Public"] .. "/Downloads") - home_dirs["Caches"] = uci:get_first("quickstart", "main", "tmp_dir", home_dirs["main_dir"] .. "/Caches") - return home_dirs -end - -kai.find_paths = function(blocks, home_dirs, path_name) - local default_path = "" - local paths = {} - - default_path = home_dirs[path_name] .. "/KAI" - if #blocks == 0 then - table.insert(paths, default_path) - else - for _, val in pairs(blocks) do - table.insert(paths, val .. "/" .. path_name .. "/KAI") - end - local without_conf_dir = "/root/" .. path_name .. "/KAI" - if default_path == without_conf_dir then - default_path = paths[1] - end - end - - return paths, default_path -end - -return kai - diff --git a/luci-app-kai/luasrc/view/kai/kai_status.htm b/luci-app-kai/luasrc/view/kai/kai_status.htm index be616836..f2f50aab 100644 --- a/luci-app-kai/luasrc/view/kai/kai_status.htm +++ b/luci-app-kai/luasrc/view/kai/kai_status.htm @@ -14,7 +14,7 @@ XHR.poll(5, '<%=url("admin/services/kai_status")%>', null,function(x, st){ //]]>
- <%:Status%> + 状态

<%:Collecting data...%>

diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index ccb3520e..60da0e3a 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.5.11 -PKG_RELEASE:=118 +PKG_RELEASE:=119 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua index bf5a2252..7e1ec3c0 100755 --- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua +++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua @@ -1775,12 +1775,12 @@ local function select_node(nodes, config, parentConfig) end end end - -- 第一优先级 类型 + 备注 + IP + 端口 + -- 第一优先级 类型 + 备注 + IP + 端口 + 分组 if not server then for index, node in pairs(nodes) do if config.currentNode.type and config.currentNode.remarks and config.currentNode.address and config.currentNode.port then if node.type and node.remarks and node.address and node.port then - if node.type == config.currentNode.type and node.remarks == config.currentNode.remarks and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then + if node.type == config.currentNode.type and node.remarks == config.currentNode.remarks and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) and node.group == config.currentNode.group then if config.log == nil or config.log == true then log('更新【' .. config.remarks .. '】第一匹配节点:' .. node.remarks) end @@ -1791,12 +1791,12 @@ local function select_node(nodes, config, parentConfig) end end end - -- 第二优先级 类型 + IP + 端口 + -- 第二优先级 类型 + IP + 端口 + 分组 if not server then for index, node in pairs(nodes) do if config.currentNode.type and config.currentNode.address and config.currentNode.port then if node.type and node.address and node.port then - if node.type == config.currentNode.type and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then + if node.type == config.currentNode.type and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) and node.group == config.currentNode.group then if config.log == nil or config.log == true then log('更新【' .. config.remarks .. '】第二匹配节点:' .. node.remarks) end @@ -1807,12 +1807,12 @@ local function select_node(nodes, config, parentConfig) end end end - -- 第三优先级 IP + 端口 + -- 第三优先级 IP + 端口 + 分组 if not server then for index, node in pairs(nodes) do if config.currentNode.address and config.currentNode.port then if node.address and node.port then - if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port then + if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port and node.group == config.currentNode.group then if config.log == nil or config.log == true then log('更新【' .. config.remarks .. '】第三匹配节点:' .. node.remarks) end @@ -1823,12 +1823,12 @@ local function select_node(nodes, config, parentConfig) end end end - -- 第四优先级 IP + -- 第四优先级 IP + 分组 if not server then for index, node in pairs(nodes) do if config.currentNode.address then if node.address then - if node.address == config.currentNode.address then + if node.address == config.currentNode.address and node.group == config.currentNode.group then if config.log == nil or config.log == true then log('更新【' .. config.remarks .. '】第四匹配节点:' .. node.remarks) end @@ -1839,12 +1839,12 @@ local function select_node(nodes, config, parentConfig) end end end - -- 第五优先级备注 + -- 第五优先级备注 + 分组 if not server then for index, node in pairs(nodes) do if config.currentNode.remarks then if node.remarks then - if node.remarks == config.currentNode.remarks then + if node.remarks == config.currentNode.remarks and node.group == config.currentNode.group then if config.log == nil or config.log == true then log('更新【' .. config.remarks .. '】第五匹配节点:' .. node.remarks) end diff --git a/luci-nginxer/Makefile b/luci-nginxer/Makefile index cdbf7669..e3fd41e1 100644 --- a/luci-nginxer/Makefile +++ b/luci-nginxer/Makefile @@ -8,17 +8,17 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=Nginxer LUCI_DESCRIPTION:=Standard OpenWrt set including full admin with ppp support and the default Bootstrap theme -PKG_VERSION:=0.0.2 -PKG_RELEASE:=1 +PKG_VERSION:=0.0.1 +PKG_RELEASE:=2 LUCI_DEPENDS:=+luci-nginx define Package/luci-nginxer/postrm #!/bin/sh -/etc/init.d/uhttpd enable +/etc/init.d/uhttpd enabled /etc/init.d/uhttpd start exit 0 endef include $(TOPDIR)/feeds/luci/luci.mk -# call BuildPackage - OpenWrt buildroot signature +# call BuildPackage - OpenWrt buildroot signature \ No newline at end of file diff --git a/luci-nginxer/root/etc/uci-defaults/50_luci-nginxer b/luci-nginxer/root/etc/uci-defaults/50_luci-nginxer index 18e28cfd..9d01f398 100644 --- a/luci-nginxer/root/etc/uci-defaults/50_luci-nginxer +++ b/luci-nginxer/root/etc/uci-defaults/50_luci-nginxer @@ -8,11 +8,7 @@ if [ "$nginxer" != 1 ]; then uci set nginx._redirect2ssl.access_log='off; # logd openwrt' uci set nginx.global.nginxer='1' uci commit nginx - - # fix firmware upload failed - sed -i 's/client_max_body_size 128M;/client_max_body_size 256M;/g' /etc/nginx/uci.conf.template - # /etc/init.d/uhttpd running || /etc/init.d/uhttpd disable /etc/init.d/nginx reload fi -exit 0 +exit 0 \ No newline at end of file diff --git a/nexttrace/Makefile b/nexttrace/Makefile index 454f418b..f7ce8c3b 100644 --- a/nexttrace/Makefile +++ b/nexttrace/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=nexttrace -PKG_VERSION:=1.6.4 -PKG_RELEASE:=4 +PKG_VERSION:=1.6.5 +PKG_RELEASE:=5 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)? diff --git a/qminfo/Makefile b/qminfo/Makefile index d16ee3e9..3537f1b7 100644 --- a/qminfo/Makefile +++ b/qminfo/Makefile @@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=qminfo -PKG_VERSION=0.0.1 -PKG_RELEASE:=2 +PKG_VERSION=0.0.2 +PKG_RELEASE:=3 include $(INCLUDE_DIR)/package.mk diff --git a/qminfo/src/Makefile b/qminfo/src/Makefile index 01253c87..0c1b8649 100644 --- a/qminfo/src/Makefile +++ b/qminfo/src/Makefile @@ -1,37 +1,97 @@ # ───────────────────────────────────────────────────────────────── # Makefile for qminfo # -# Usage: -# make — compile -# make install — install /usr/local/bin -# make uninstall — remove -# make clean — remove build -# make run [DEV=/dev/cdc-wdm0] — run text mode -# make json [DEV=/dev/cdc-wdm0] — run JSON output +# Build: +# make +# +# Cross Build: +# make ARCH=aarch64 +# make ARCH=armv7 +# make ARCH=armv5 +# make ARCH=mipsle +# +# С sysroot (Buildroot/Yocto/OpenWRT): +# make ARCH=mipsle SYSROOT=/path/to/staging # ───────────────────────────────────────────────────────────────── -CC := gcc -TARGET := qminfo -SRC := qminfo.c -OBJ := $(SRC:.c=.o) +ARCH ?= +SYSROOT ?= -PKG := qmi-glib -CFLAGS := $(shell pkg-config --cflags $(PKG)) -Wall -Wextra -O2 -LIBS := $(shell pkg-config --libs $(PKG)) -lm +ifeq ($(ARCH),aarch64) + CROSS_TRIPLE := aarch64-linux-gnu + ARCH_CFLAGS := +else ifeq ($(ARCH),armv7) + CROSS_TRIPLE := arm-linux-gnueabihf + ARCH_CFLAGS := -march=armv7-a -mfpu=vfpv3 -mfloat-abi=hard +else ifeq ($(ARCH),armv5) + CROSS_TRIPLE := arm-linux-gnueabi + ARCH_CFLAGS := -march=armv5te -mfloat-abi=soft +else ifeq ($(ARCH),mipsle) + CROSS_TRIPLE := mipsel-linux-gnu + ARCH_CFLAGS := -march=mips32 -mabi=32 -EL +else + CROSS_TRIPLE := + ARCH_CFLAGS := +endif -PREFIX := /usr/local -BINDIR := $(PREFIX)/bin -DEV := /dev/cdc-wdm0 +ifdef CROSS_TRIPLE + CC := $(CROSS_TRIPLE)-gcc + STRIP := $(CROSS_TRIPLE)-strip +else + CC := gcc + STRIP := strip +endif -# ───────────────────────────────────────────────────────────────── +TARGET := qminfo +SRC := qminfo.c +OBJ := $(SRC:.c=.o) -.PHONY: all clean install uninstall run json check-pkg +# pkg-config +PKG_QMI := qmi-glib +PKG_MBIM := mbim-glib + +ifdef SYSROOT + PKG_CONFIG := PKG_CONFIG_SYSROOT_DIR=$(SYSROOT) \ + PKG_CONFIG_LIBDIR=$(SYSROOT)/usr/lib/pkgconfig:$(SYSROOT)/usr/share/pkgconfig \ + PKG_CONFIG_PATH= \ + pkg-config + SYSROOT_FLAGS := --sysroot=$(SYSROOT) +else ifdef CROSS_TRIPLE + PKG_CONFIG := PKG_CONFIG_PATH=/usr/lib/$(CROSS_TRIPLE)/pkgconfig \ + pkg-config + SYSROOT_FLAGS := +else + PKG_CONFIG := pkg-config + SYSROOT_FLAGS := +endif + +# if mbim-glib — link it (need for QMI over MBIM) +MBIM_LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_MBIM) 2>/dev/null) +MBIM_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_MBIM) 2>/dev/null) + +CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_QMI)) \ + $(MBIM_CFLAGS) \ + $(ARCH_CFLAGS) \ + $(SYSROOT_FLAGS) \ + -Wall -Wextra -O2 -pipe + +LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_QMI)) \ + $(MBIM_LIBS) \ + -lm + +PREFIX := /usr/local +BINDIR := $(PREFIX)/bin + +.PHONY: all clean install uninstall run json run-mbim json-mbim strip help check-pkg all: check-pkg $(TARGET) check-pkg: - @pkg-config --exists $(PKG) || \ - { echo "Error: $(PKG) not found. Install libqmi-glib-dev"; exit 1; } + @$(PKG_CONFIG) --exists $(PKG_QMI) || \ + { echo "Error: $(PKG_QMI) not found. Install libqmi-glib-dev"; exit 1; } + @$(PKG_CONFIG) --exists $(PKG_MBIM) && \ + echo " mbim-glib: found (QMI over MBIM supported)" || \ + echo " mbim-glib: not found (QMI over MBIM disabled)" $(OBJ): $(SRC) $(CC) $(CFLAGS) -c -o $@ $< @@ -39,6 +99,9 @@ $(OBJ): $(SRC) $(TARGET): $(OBJ) $(CC) $(CFLAGS) -o $@ $^ $(LIBS) +strip: $(TARGET) + $(STRIP) --strip-all $(TARGET) + install: $(TARGET) install -d $(DESTDIR)$(BINDIR) install -m 755 $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET) @@ -46,11 +109,5 @@ install: $(TARGET) uninstall: rm -f $(DESTDIR)$(BINDIR)/$(TARGET) -run: $(TARGET) - sudo ./$(TARGET) $(DEV) - -json: $(TARGET) - sudo ./$(TARGET) $(DEV) --json - clean: rm -f $(OBJ) $(TARGET) diff --git a/qminfo/src/qminfo.c b/qminfo/src/qminfo.c index b7ed0009..a5180db6 100644 --- a/qminfo/src/qminfo.c +++ b/qminfo/src/qminfo.c @@ -9,8 +9,11 @@ * gcc $(pkg-config --cflags --libs qmi-glib) -lm -o qminfo qminfo.c * * run: - * sudo ./qminfo /dev/cdc-wdm0 - * sudo ./qminfo /dev/cdc-wdm0 --json + * sudo ./qminfo /dev/cdc-wdm0 # auto-detect (QMI first) + * sudo ./qminfo /dev/cdc-wdm0 --json # JSON output + * sudo ./qminfo /dev/cdc-wdm0 --qmi # force native QMI + * sudo ./qminfo /dev/cdc-wdm0 --mbim # force QMI over MBIM + * sudo ./qminfo /dev/cdc-wdm0 --mbim --json */ #include @@ -86,6 +89,14 @@ static ModemInfo info; static gboolean json_mode = FALSE; static const char *dev_path = NULL; static gint pending = 0; +typedef enum { + MODE_AUTO, /* try QMI first, fallback to MBIM */ + MODE_QMI, /* force native QMI */ + MODE_MBIM, /* force QMI over MBIM */ +} DeviceMode; + +static DeviceMode device_mode = MODE_AUTO; +static gint open_retries = 0; /* retry counter for auto mode */ /* ═══════════════════════════════════════════════════════════════ * helpers @@ -604,7 +615,7 @@ static void on_lte_ca(QmiClientNas *client, GAsyncResult *res, gpointer ud) snprintf(tmp, sizeof(tmp), "B%u", active_band_to_lte_band(el->lte_band)); g_strlcat(info.scc_bands, tmp, sizeof(info.scc_bands)); /* For json mode: +40 */ - snprintf(tmp, sizeof(tmp), "+%u ", active_band_to_lte_band(el->lte_band)); + snprintf(tmp, sizeof(tmp), "+%u", active_band_to_lte_band(el->lte_band)); g_strlcat(info.scc_bands_json, tmp, sizeof(info.scc_bands_json)); total += bw_index_to_khz((guint8)el->dl_bandwidth); @@ -1052,21 +1063,69 @@ static void on_dms_client(QmiDevice *dev, GAsyncResult *res, gpointer ud) start_dms_chain(); } +static void try_open_device(void); /* forward */ + static void on_device_open(QmiDevice *dev, GAsyncResult *res, gpointer ud) { (void)dev; (void)ud; GError *err = NULL; if (!qmi_device_open_finish(device, res, &err)) { + if (device_mode == MODE_AUTO && open_retries == 0) { + /* QMI failed in auto mode — retry with MBIM */ + g_clear_error(&err); + open_retries++; + try_open_device(); + return; + } fprintf(stderr, "Open error: %s\n", err ? err->message : "?"); g_clear_error(&err); g_main_loop_quit(loop); return; } + + /* Restore normal log handler after successful open in auto mode */ + if (device_mode == MODE_AUTO) + g_log_set_handler("Qmi", + G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL, + g_log_default_handler, NULL); + qmi_device_allocate_client(device, QMI_SERVICE_DMS, QMI_CID_NONE, 10, NULL, (GAsyncReadyCallback)on_dms_client, NULL); } +/* Open device with flags depending on mode / retry state */ +static void try_open_device(void) +{ + QmiDeviceOpenFlags flags = QMI_DEVICE_OPEN_FLAGS_PROXY; + gboolean mbim; + + switch (device_mode) { + case MODE_MBIM: + mbim = TRUE; + break; + case MODE_QMI: + mbim = FALSE; + break; + case MODE_AUTO: + default: + /* First attempt: QMI; after retry: MBIM */ + mbim = (open_retries > 0); + break; + } + + if (mbim) { + flags |= QMI_DEVICE_OPEN_FLAGS_MBIM; + //fprintf(stderr, "Opening as QMI over MBIM\n"); + } else { + flags |= QMI_DEVICE_OPEN_FLAGS_NET_802_3 | + QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER; + } + + qmi_device_open(device, flags, 15, NULL, + (GAsyncReadyCallback)on_device_open, NULL); +} + static void on_device_new(GObject *src, GAsyncResult *res, gpointer ud) { (void)src; (void)ud; @@ -1078,12 +1137,19 @@ static void on_device_new(GObject *src, GAsyncResult *res, gpointer ud) g_main_loop_quit(loop); return; } - qmi_device_open(device, - QMI_DEVICE_OPEN_FLAGS_PROXY | - QMI_DEVICE_OPEN_FLAGS_NET_802_3 | - QMI_DEVICE_OPEN_FLAGS_NET_NO_QOS_HEADER, - 15, NULL, - (GAsyncReadyCallback)on_device_open, NULL); + try_open_device(); +} + +/* ═══════════════════════════════════════════════════════════════ + * Silent log handler — suppresses GLib/QMI warnings + * ═══════════════════════════════════════════════════════════════ */ +static void log_silent(const gchar *domain, + GLogLevelFlags level, + const gchar *message, + gpointer user_data) +{ + (void)domain; (void)level; (void)message; (void)user_data; + /* intentionally empty */ } /* ═══════════════════════════════════════════════════════════════ @@ -1092,19 +1158,44 @@ static void on_device_new(GObject *src, GAsyncResult *res, gpointer ud) int main(int argc, char *argv[]) { if (argc < 2) { - fprintf(stderr, "Usage: %s [--json]\n", argv[0]); + fprintf(stderr, "Usage: %s [--json] [--qmi|--mbim]\n", argv[0]); + fprintf(stderr, " --json JSON output\n"); + fprintf(stderr, " --qmi force native QMI mode\n"); + fprintf(stderr, " --mbim force QMI over MBIM mode\n"); + fprintf(stderr, " (default: auto-detect, try QMI first)\n"); fprintf(stderr, "Example: %s /dev/cdc-wdm0 --json\n", argv[0]); + fprintf(stderr, " %s /dev/cdc-wdm0 --mbim --json\n", argv[0]); return EXIT_FAILURE; } - dev_path = argv[1]; - json_mode = (argc >= 3 && strcmp(argv[2], "--json") == 0); + dev_path = argv[1]; + json_mode = FALSE; + device_mode = MODE_AUTO; + + for (int i = 2; i < argc; i++) { + if (strcmp(argv[i], "--json") == 0) + json_mode = TRUE; + else if (strcmp(argv[i], "--mbim") == 0) + device_mode = MODE_MBIM; + else if (strcmp(argv[i], "--qmi") == 0) + device_mode = MODE_QMI; + } memset(&info, 0, sizeof(info)); info.chip_temp = -999; info.csq = -1; info.is_umts = FALSE; + /* In auto-detect mode, suppress QMI warnings that are expected + * when probing QMI on an MBIM-only device: + * "requested QMI mode but unexpected transport type found" + * "Cannot read from istream: connection broken" */ + if (device_mode == MODE_AUTO) { + g_log_set_handler("Qmi", + G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL, + log_silent, NULL); + } + loop = g_main_loop_new(NULL, FALSE); GFile *f = g_file_new_for_path(dev_path);