326 lines
13 KiB
Bash
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" |