Files
2026-10-11 23:31:38 +03:00

326 lines
13 KiB
Bash

#!/bin/sh
[ -f "/etc/openwrt_release" ] || { printf "\033[1;31m[!] Ошибка: Эта система не OpenWrt.\033[0m\n"; exit 1; }
. /etc/openwrt_release
# ---------------------------------------------------------------
# Зафиксированная версия: ставится только обычный (не compressed)
# архив linux-<arch>.tar.gz из релиза shtorm-7/sing-box-extended.
# ---------------------------------------------------------------
TAG="v1.14.0-extended-2.7.1"
VER="${TAG#v}"
# Свой Gitea: сюда загружены те же файлы, что и в релизе GitHub
# (Releases -> вложения). Пока адрес не заменён - шаг пропускается.
GITEA_BASE="https://gitea.example.com/USER/REPO/releases/download/${TAG}"
GITHUB_BASE="https://github.com/shtorm-7/sing-box-extended/releases/download/${TAG}"
PROXY_PREFIX="https://ghproxy.net/"
DEST_FILE="/usr/bin/sing-box"
REAL_BIN="/usr/libexec/sing-box-core"
VERSION_CACHE="/etc/sing-box-version.cache"
WORK_DIR="/tmp/sing-box-install"
DNS_RESTORE=0
R="\033[1;31m"
G="\033[1;32m"
Y="\033[1;33m"
C="\033[1;36m"
N="\033[0m"
restore_dns() {
if [ "$DNS_RESTORE" = "1" ]; then
printf "${C}[*] Восстанавливаю настройки DNS (Ignore resolv = 1)...${N}\n"
uci set dhcp.@dnsmasq[0].noresolv='1'
uci commit dhcp
/etc/init.d/dnsmasq restart >/dev/null 2>&1
DNS_RESTORE=0
fi
}
trap 'printf "\n${R}[!] Установка прервана.${N}\n"; restore_dns; rm -rf "$WORK_DIR"; [ "$SERVICE_STOPPED" = "1" ] && /etc/init.d/"$SERVICE_NAME" start >/dev/null 2>&1; [ "$ZB_STOPPED" = "1" ] && /etc/init.d/zeroblock start >/dev/null 2>&1; exit 1' INT TERM
fail() {
printf "${R}[!] Ошибка: %s${N}\n" "$1"
restore_dns
rm -rf "$WORK_DIR"
[ "$SERVICE_STOPPED" = "1" ] && /etc/init.d/"$SERVICE_NAME" start >/dev/null 2>&1
[ "$ZB_STOPPED" = "1" ] && /etc/init.d/zeroblock start >/dev/null 2>&1
exit 1
}
if command -v curl >/dev/null 2>&1; then
DOWNLOAD="curl -fsSL --insecure --connect-timeout 15 -o"
elif command -v wget >/dev/null 2>&1; then
DOWNLOAD="wget -q --no-check-certificate --timeout=15 -O"
elif command -v uclient-fetch >/dev/null 2>&1; then
DOWNLOAD="uclient-fetch -q --no-check-certificate --timeout=15 -O"
else
fail "Не найден curl, wget или uclient-fetch."
fi
# Архитектура -> суффикс файла и sha256 обычного архива этой версии
case "$DISTRIB_ARCH" in
aarch64*)
ARCH_SUFFIX="arm64"
EXPECTED_SHA="968aa00ae3c8bd2d593c506626449629a2fae744d6848ce8dc331aa8deb49a1d" ;;
arm_cortex-a7* | arm_cortex-a15*)
ARCH_SUFFIX="armv7"
EXPECTED_SHA="e6ff11e5091d24b8a63fcee0359720747abb42b6ba7f43f0be40f105aa4817ab" ;;
arm_*)
ARCH_SUFFIX="armv6"
EXPECTED_SHA="6c2d43c9e642c97986bc830399251228471b03b3d0c928621aa43e709d5c30ee" ;;
x86_64)
ARCH_SUFFIX="amd64"
EXPECTED_SHA="6bae7a6f43f55e229741737b31ea7490757ae20e7b2fddc93674b4d8694951f9" ;;
i386*)
ARCH_SUFFIX="386"
EXPECTED_SHA="bb45b69c8397cbc0776137c2c7c659117cee14478a63a5445cf1c0ccaaf77d74" ;;
mipsel_*)
ARCH_SUFFIX="mipsle-softfloat"
EXPECTED_SHA="bcf1acc4bf5e60a9cd734acc1258dd8d80962abbfe395711c70f5b543edccc1a" ;;
mips64el_*)
ARCH_SUFFIX="mips64le"
EXPECTED_SHA="0f2ccb1afa1ef5c07105b618eba9b8990b6d5998cebd5e7e5324ee214cc0ca19" ;;
mips64_*)
ARCH_SUFFIX="mips64"
EXPECTED_SHA="9efaf0160aba9a930f011552dfd8b120775373cbdb025a3bd8c6c8d5ef5c7ea7" ;;
mips_*)
ARCH_SUFFIX="mips-softfloat"
EXPECTED_SHA="c73b0b0e30bee1ed250fa7e4ae17b498b1d737189abb21f7e7f7b302f7c11414" ;;
riscv64*)
ARCH_SUFFIX="riscv64"
EXPECTED_SHA="1bf8967d9dda812b8e5adc47e13eb220f5786a06a7dfac7165f274058c834a18" ;;
*) fail "Архитектура $DISTRIB_ARCH не поддерживается." ;;
esac
if [ -f "/etc/init.d/podkop" ]; then
SERVICE_NAME="podkop"
else
SERVICE_NAME="sing-box"
fi
FILE_NAME="sing-box-${VER}-linux-${ARCH_SUFFIX}.tar.gz"
case "$GITEA_BASE" in
*example.com*) GITEA_BASE="" ;;
esac
CURRENT_VER=$("$DEST_FILE" version 2>/dev/null | head -n 1 | awk '{print $NF}')
printf "\n${C}========================================${N}\n"
printf "${C} OpenWrt sing-box extended installer${N}\n"
printf "${C}========================================${N}\n"
printf " Версия ОС: ${Y}%s${N}\n" "${DISTRIB_RELEASE:-неизвестно}"
printf " Архитектура: ${Y}%s${N} -> ${Y}%s${N}\n" "$DISTRIB_ARCH" "$ARCH_SUFFIX"
printf " Сервис: ${Y}%s${N}\n" "$SERVICE_NAME"
printf " Устанавливаю: ${Y}%s${N} (обычная версия)\n\n" "$TAG"
if [ "$CURRENT_VER" = "$VER" ]; then
printf "${Y}[!] Эта версия уже установлена. Выполняю переустановку.${N}\n"
fi
# Свободное место: обычный архив ~30 МБ + распакованный бинарник ~65 МБ
REQ_KB=65000
TMP_FREE=$(df -Pk /tmp 2>/dev/null | awk 'NR==2 {print $4}')
ROOT_FREE=$(df -Pk /root 2>/dev/null | awk 'NR==2 {print $4}')
[ -z "$TMP_FREE" ] && TMP_FREE=0
[ -z "$ROOT_FREE" ] && ROOT_FREE=0
if [ "$TMP_FREE" -ge "$REQ_KB" ]; then
WORK_DIR="/tmp/sing-box-install"
elif [ "$ROOT_FREE" -ge "$REQ_KB" ]; then
WORK_DIR="/root/sing-box-install"
printf "${Y}[!] Мало места в оперативной памяти (/tmp). Использую диск (/root) для скачивания.${N}\n"
else
WORK_DIR="/tmp/sing-box-install"
printf "${R}[!] Внимание: критически мало места! Возможна ошибка при скачивании или распаковке.${N}\n"
fi
rm -rf "$WORK_DIR" && mkdir -p "$WORK_DIR"
cd "$WORK_DIR" || fail "Не удалось перейти в $WORK_DIR."
# Пробует по очереди: свой Gitea -> GitHub -> зеркало ghproxy
try_download() {
for url in "${GITEA_BASE:+$GITEA_BASE/$FILE_NAME}" "$GITHUB_BASE/$FILE_NAME" "${PROXY_PREFIX}${GITHUB_BASE}/$FILE_NAME"; do
[ -z "$url" ] && continue
printf "${C}[*] Скачиваю: %s${N}\n" "$url"
rm -f "$FILE_NAME"
if $DOWNLOAD "$FILE_NAME" "$url" && [ -s "$FILE_NAME" ]; then
return 0
fi
printf "${Y}[!] Не удалось, пробую следующий источник...${N}\n"
done
return 1
}
if ! try_download; then
OLD_NORESOLV=$(uci get dhcp.@dnsmasq[0].noresolv 2>/dev/null)
if [ "$OLD_NORESOLV" = "1" ]; then
printf "${Y}[!] Источники недоступны. Пробую временно пустить DNS-трафик через провайдера...${N}\n"
uci set dhcp.@dnsmasq[0].noresolv='0'
uci commit dhcp
/etc/init.d/dnsmasq restart >/dev/null 2>&1
DNS_RESTORE=1
sleep 3
try_download || fail "Не удалось скачать $FILE_NAME ни из одного источника."
else
fail "Не удалось скачать $FILE_NAME ни из одного источника."
fi
fi
# Проверка целостности (архив мог прийти через зеркало / по --insecure)
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL_SHA=$(sha256sum "$FILE_NAME" | awk '{print $1}')
[ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] || fail "Контрольная сумма не совпадает (получено $ACTUAL_SHA)."
printf "${G}[+] Контрольная сумма совпала.${N}\n"
else
printf "${Y}[!] sha256sum не найден, проверка целостности пропущена.${N}\n"
fi
stop_service() {
[ "$SERVICE_STOPPED" = "1" ] && return
if [ -f "/etc/init.d/zeroblock" ]; then
printf "${C}[*] Останавливаю Zero-Block...${N}\n"
/etc/init.d/zeroblock stop >/dev/null 2>&1
ZB_STOPPED=1
fi
printf "${C}[*] Останавливаю %s...${N}\n" "$SERVICE_NAME"
/etc/init.d/"$SERVICE_NAME" stop >/dev/null 2>&1
# Если службой управляет podkop, останавливаем и сам сервис sing-box,
# чтобы procd не перезапустил стоковый sing-box при удалении бинарника
if [ "$SERVICE_NAME" != "sing-box" ] && [ -f /etc/init.d/sing-box ]; then
/etc/init.d/sing-box stop >/dev/null 2>&1
fi
killall sing-box >/dev/null 2>&1
SERVICE_STOPPED=1
sleep 2
}
printf "${C}[*] Распаковываю архив...${N}\n"
tar -xzf "$FILE_NAME" || fail "Ошибка распаковки архива."
# Архив больше не нужен: освобождаем место (/tmp - это оперативная память)
rm -f "$FILE_NAME"
BIN_PATH=$(find . -type f -name sing-box | head -n 1)
[ -z "$BIN_PATH" ] && fail "Бинарник не найден внутри архива."
stop_service
printf "${C}[*] Устанавливаю...${N}\n"
# Убираем следы прежней сжатой версии (враппер + реальный бинарник + кэш версии)
rm -f "$REAL_BIN" "$VERSION_CACHE"
DEST_DIR=$(dirname "$DEST_FILE")
# Места на флеше мало (бинарник ~70 МБ), старый и новый вместе не поместятся.
# Поэтому сначала удаляем старый бинарник (в том числе стоковый из пакета
# sing-box), и только потом копируем новый.
rm -f "$DEST_FILE"
# Недокопированные временные файлы от прерванных/старых установок
# (скрытые, ls их не показывает) тоже занимают место на флеше
rm -f "$DEST_DIR"/.sing-box.tmp.*
cp "$BIN_PATH" "$DEST_FILE" || { rm -f "$DEST_FILE"; fail "Недостаточно места на целевом диске ($DEST_DIR). sing-box не установлен."; }
chmod +x "$DEST_FILE"
sync
NEW_VER=$("$DEST_FILE" version 2>/dev/null | head -n 1 | awk '{print $NF}')
[ "$NEW_VER" != "$VER" ] && fail "Версия после установки ($NEW_VER) не совпадает с ожидаемой ($VER)."
cd /
rm -rf "$WORK_DIR"
restore_dns
# Проверяем, что есть всё, чего ждёт PodKop от пакета sing-box
# (init-скрипт, UCI-конфиг, рабочие папки). Если стоковый пакет не ставился,
# например PodKop поставлен без зависимостей, создаём недостающее.
ensure_service_files() {
printf "${C}[*] Проверяю файлы службы sing-box...${N}\n"
mkdir -p /etc/sing-box /usr/share/sing-box
if [ -f /etc/config/sing-box ]; then
printf "${G}[+] /etc/config/sing-box найден.${N}\n"
else
printf "${Y}[!] /etc/config/sing-box не найден, создаю.${N}\n"
cat > /etc/config/sing-box <<'EOF'
config sing-box 'main'
option enabled '0'
option user 'root'
option conffile '/etc/sing-box/config.json'
option workdir '/usr/share/sing-box'
# list ifaces 'wan'
# list ifaces 'wan6'
EOF
fi
if [ -f /etc/init.d/sing-box ]; then
printf "${G}[+] /etc/init.d/sing-box найден.${N}\n"
else
printf "${Y}[!] /etc/init.d/sing-box не найден, создаю.${N}\n"
cat > /etc/init.d/sing-box <<'EOF'
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=99
STOP=10
PROG=/usr/bin/sing-box
start_service() {
config_load sing-box
local enabled user conffile workdir ifaces
config_get_bool enabled "main" "enabled" "0"
[ "$enabled" -eq 1 ] || return 0
config_get user "main" "user" "root"
config_get conffile "main" "conffile" "/etc/sing-box/config.json"
config_get workdir "main" "workdir" "/usr/share/sing-box"
config_get ifaces "main" "ifaces"
mkdir -p "$workdir"
[ "$user" = "root" ] || chown "$user:$(id -ng "$user")" "$workdir"
procd_open_instance
procd_set_param command "$PROG" run -c "$conffile" -D "$workdir"
procd_set_param user "$user"
procd_set_param file "$conffile"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
}
service_triggers() {
local ifaces iface
config_load sing-box
config_get ifaces "main" "ifaces"
procd_open_trigger
for iface in $ifaces; do
procd_add_interface_trigger "interface.*.up" "$iface" /etc/init.d/sing-box restart
done
procd_close_trigger
procd_add_reload_trigger "sing-box"
}
EOF
chmod +x /etc/init.d/sing-box
fi
}
ensure_service_files
printf "${C}[*] Запускаю сервис...${N}\n"
/etc/init.d/"$SERVICE_NAME" start >/dev/null 2>&1 || printf "${Y}[!] Не удалось запустить службу. Проверьте логи.${N}\n"
if [ "$ZB_STOPPED" = "1" ]; then
printf "${C}[*] Запускаю Zero-Block...${N}\n"
/etc/init.d/zeroblock start >/dev/null 2>&1 || printf "${Y}[!] Не удалось запустить Zero-Block.${N}\n"
fi
printf "\n${G}[+] Установка завершена:${N} ${Y}${CURRENT_VER:-н/д}${N} -> ${Y}${NEW_VER}${N}\n"