#!/bin/sh set -eu FEED_BASE_URL="https://down.dllkids.xyz/openwrt-feed/clashoo" GITHUB_API_URL="https://api.github.com/repos/kenzok8/openwrt-clashoo/releases/latest" GITHUB_PROXY_PREFIX="${GITHUB_PROXY_PREFIX:-https://ghfast.top/}" TMP_DIR="/tmp/clashoo-install" fetch_text() { url="$1" if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" return $? fi if command -v wget >/dev/null 2>&1; then wget -qO- "$url" return $? fi return 127 } download_file() { url="$1" out="$2" if command -v curl >/dev/null 2>&1; then curl -fL "$url" -o "$out" return $? fi wget -qO "$out" "$url" } download_url() { url="$1" case "$url" in https://github.com/*) printf '%s%s\n' "$GITHUB_PROXY_PREFIX" "$url" ;; *) printf '%s\n' "$url" ;; esac } detect_manager() { if command -v opkg >/dev/null 2>&1; then echo opkg return fi if command -v apk >/dev/null 2>&1; then echo apk return fi echo "unsupported" } detect_arch() { pm="$1" if [ "$pm" = "opkg" ]; then opkg print-architecture | awk '/^arch / {print $2}' | tail -n 1 return fi # apk --print-arch 只回 CPU family(如 aarch64),丢掉 subtarget 后缀; # feed/release 产物用完整 target arch(aarch64_cortex-a53),优先读 DISTRIB_ARCH distrib_arch="$(sed -n "s/^DISTRIB_ARCH=['\"]\([^'\"]*\)['\"].*/\1/p" /etc/openwrt_release 2>/dev/null | head -n 1)" if [ -n "$distrib_arch" ]; then printf '%s\n' "$distrib_arch" else apk --print-arch fi } detect_sdk() { if [ ! -r /etc/openwrt_release ]; then return 1 fi release="$(sed -n "s/^DISTRIB_RELEASE=['\"]\\([^'\"]*\\)['\"]$/\\1/p" /etc/openwrt_release | head -n 1)" [ -n "$release" ] || return 1 sdk="$(printf '%s\n' "$release" | grep -Eo '[0-9]+\.[0-9]+' | head -n 1)" [ -n "$sdk" ] || return 1 printf '%s\n' "$sdk" } find_feed_filename() { package_name="$1" packages_text="$2" printf '%s\n' "$packages_text" | awk -v pkg="$package_name" ' $0 == "Package: " pkg { in_pkg = 1 next } in_pkg && /^Filename: / { sub(/^Filename: /, "") print exit } in_pkg && /^$/ { in_pkg = 0 } ' } find_manifest_value() { key="$1" manifest_text="$2" printf '%s\n' "$manifest_text" | sed -n "s/^${key}=//p" | head -n 1 } append_unique_word() { value="$1" list="$2" [ -n "$value" ] || { printf '%s\n' "$list" return } case " $list " in *" $value "*) ;; *) list="${list}${list:+ }${value}" ;; esac printf '%s\n' "$list" } build_sdk_candidates() { pm="$1" candidates="" detected_sdk="$(detect_sdk || true)" candidates="$(append_unique_word "$detected_sdk" "$candidates")" if [ "$pm" = "opkg" ]; then for sdk in 24.10 23.05 22.03 21.02; do candidates="$(append_unique_word "$sdk" "$candidates")" done else for sdk in 25.12 24.10; do candidates="$(append_unique_word "$sdk" "$candidates")" done fi printf '%s\n' "$candidates" } # Feed 结构:/// —— 与 dllkids setup.sh 对齐,不嵌 feedname 子目录 feed_base_for() { printf '%s/%s/%s' "$FEED_BASE_URL" "$1" "$2" } load_manifest_urls() { sdk="$1" arch="$2" base="$(feed_base_for "$sdk" "$arch")" manifest_url="${base}/manifest-clashoo.txt" manifest_text="$(fetch_text "$manifest_url" || true)" [ -n "$manifest_text" ] || return 1 core_file="$(find_manifest_value "core" "$manifest_text")" luci_file="$(find_manifest_value "luci" "$manifest_text")" i18n_file="$(find_manifest_value "i18n" "$manifest_text")" [ -n "$core_file" ] || return 1 [ -n "$luci_file" ] || return 1 CORE_URL="${base}/${core_file}" LUCI_URL="${base}/${luci_file}" I18N_URL="" [ -n "$i18n_file" ] && I18N_URL="${base}/${i18n_file}" return 0 } load_opkg_feed_urls() { sdk="$1" arch="$2" base="$(feed_base_for "$sdk" "$arch")" packages_url="${base}/Packages.gz" packages_text="$(fetch_text "$packages_url" || true)" if ! printf '%s' "$packages_text" | grep -q '^Package: '; then return 1 fi core_file="$(find_feed_filename "clashoo" "$packages_text")" luci_file="$(find_feed_filename "luci-app-clashoo" "$packages_text")" i18n_file="$(find_feed_filename "luci-i18n-clashoo-zh-cn" "$packages_text")" [ -n "$core_file" ] || return 1 [ -n "$luci_file" ] || return 1 CORE_URL="${base}/${core_file}" LUCI_URL="${base}/${luci_file}" I18N_URL="" [ -n "$i18n_file" ] && I18N_URL="${base}/${i18n_file}" return 0 } load_github_urls() { arch="$1" ext="$2" payload="$(fetch_text "$GITHUB_API_URL" || true)" [ -n "$payload" ] || return 1 urls="$(printf '%s\n' "$payload" | sed -n 's/.*"browser_download_url":[[:space:]]*"\([^"]*\)".*/\1/p')" [ -n "$urls" ] || return 1 if [ "$ext" = "apk" ]; then CORE_URL="$(printf '%s\n' "$urls" | grep -E '/clashoo-[^-]+.*-r[0-9]+-'"$arch"'\.apk$' | head -n 1)" LUCI_URL="$(printf '%s\n' "$urls" | grep -E '/luci-app-clashoo-[^-]+.*-r[0-9]+-('"$arch"'|all)\.apk$' | head -n 1)" I18N_URL="$(printf '%s\n' "$urls" | grep -E '/luci-i18n-clashoo-zh-cn-[^-]+.*-r[0-9]+-('"$arch"'|all)\.apk$' | head -n 1)" else CORE_URL="$(printf '%s\n' "$urls" | grep -E '/clashoo_.*_'"$arch"'\.ipk$' | head -n 1)" LUCI_URL="$(printf '%s\n' "$urls" | grep -E '/luci-app-clashoo_.*_all\.ipk$' | head -n 1)" I18N_URL="$(printf '%s\n' "$urls" | grep -E '/luci-i18n-clashoo-zh-cn_.*_all\.ipk$' | head -n 1)" fi [ -n "$CORE_URL" ] || return 1 [ -n "$LUCI_URL" ] || return 1 return 0 } PM="$(detect_manager)" if [ "$PM" = "unsupported" ]; then echo "No supported package manager found (opkg/apk)." exit 1 fi ARCH="$(detect_arch "$PM")" [ -n "$ARCH" ] || { echo "Cannot detect architecture"; exit 1; } SDK_CANDIDATES="$(build_sdk_candidates "$PM")" EXT="ipk" [ "$PM" = "apk" ] && EXT="apk" CORE_URL="" LUCI_URL="" I18N_URL="" if [ -n "$SDK_CANDIDATES" ]; then for SDK in $SDK_CANDIDATES; do if load_manifest_urls "$SDK" "$ARCH"; then echo "Using R2 feed manifest: ${SDK}/${ARCH}" break fi if [ "$PM" = "opkg" ] && load_opkg_feed_urls "$SDK" "$ARCH"; then echo "Using R2 feed: ${SDK}/${ARCH}" break fi done fi if [ -z "$CORE_URL" ] || [ -z "$LUCI_URL" ]; then if load_github_urls "$ARCH" "$EXT"; then echo "Using GitHub latest release" else echo "Cannot find required packages for arch: $ARCH" echo "Detected package manager: $PM" echo "Tried SDKs: $SDK_CANDIDATES" exit 1 fi fi rm -rf "$TMP_DIR" mkdir -p "$TMP_DIR" echo "Downloading packages..." download_file "$(download_url "$CORE_URL")" "$TMP_DIR/core.${EXT}" download_file "$(download_url "$LUCI_URL")" "$TMP_DIR/luci.${EXT}" if [ -n "$I18N_URL" ]; then download_file "$(download_url "$I18N_URL")" "$TMP_DIR/i18n.${EXT}" fi echo "Installing packages with $PM..." if [ "$PM" = "opkg" ]; then # Refresh package lists so deps (yq, coreutils-nohup, kmod-*) resolve on fresh systems opkg update || echo "Warning: opkg update failed, dependency resolution may break" opkg install "$TMP_DIR/core.${EXT}" "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} else apk update || echo "Warning: apk update failed, dependency resolution may break" apk add --allow-untrusted "$TMP_DIR/core.${EXT}" "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} fi echo "Install complete."