Compare commits

..

No commits in common. "main" and "bins" have entirely different histories.
main ... bins

6 changed files with 58 additions and 119 deletions

View file

@ -27,10 +27,8 @@ cd nikovpn_openwrt
echo 'login:password' > /root/creds echo 'login:password' > /root/creds
``` ```
Бинарники при установке качаются из [релизов репозитория] Бинарники при установке качаются с f.n1ko.dev (каталог `bin/` ~150 MB
(https://git.n1ko.dev/Niko/nikovpn_openwrt/releases/tag/bins) — каталог в репозиторий не входит; если он есть рядом с install.sh — берётся из него).
`bin/` в клон не входит; если он есть рядом с install.sh — берётся из него.
f.n1ko.dev остаётся резервным источником.
## Команды ## Команды

View file

@ -18,9 +18,8 @@
# Требования: # Требования:
# - креды "login:password" (без кавычек): файл /root/creds # - креды "login:password" (без кавычек): файл /root/creds
# на роутере или переменная окружения NIKOVPN_CREDS # на роутере или переменная окружения NIKOVPN_CREDS
# - даунлоадер: curl, wget или uclient-fetch # - доступ в интернет (скрипт тянет всё только с f.n1ko.dev и vpnd.n1ko.dev;
# - доступ в интернет (бинарники — из релизов git.n1ko.dev, # если рядом с install.sh есть каталог bin/ — бинарники берутся из него)
# резервом f.n1ko.dev; если рядом с install.sh есть bin/ — из него)
# #
# Скрипт сам определяет архитектуру (openwrt_release/apk/opkg/uname), # Скрипт сам определяет архитектуру (openwrt_release/apk/opkg/uname),
# выбирает лучший протокол и сервер (Hysteria2 > TUIC > Naive > VLESS > SS) # выбирает лучший протокол и сервер (Hysteria2 > TUIC > Naive > VLESS > SS)
@ -28,7 +27,6 @@
# ============================================================================ # ============================================================================
BASE_URL="https://f.n1ko.dev/nikovpn_openwrt" BASE_URL="https://f.n1ko.dev/nikovpn_openwrt"
REL_URL="https://git.n1ko.dev/Niko/nikovpn_openwrt/releases/download/bins"
API_URL="https://vpnd.n1ko.dev/api/v1" API_URL="https://vpnd.n1ko.dev/api/v1"
WORK_DIR="/etc/nikovpn" WORK_DIR="/etc/nikovpn"
CREDS_FILE="/root/creds" CREDS_FILE="/root/creds"
@ -54,8 +52,8 @@ need_root() { [ "$(id -u)" = "0" ] || die "нужны права root"; }
# --- downloader --- # --- downloader ---
DL="" DL=""
for t in curl wget uclient-fetch; do have "$t" && DL="$t" && break; done for t in curl wget; do have "$t" && DL="$t" && break; done
[ -n "$DL" ] || die "нужен curl, wget или uclient-fetch" [ -n "$DL" ] || die "нужен curl или wget"
dl() { # $1=url $2=out ; резерв на http:// для f.n1ko.dev dl() { # $1=url $2=out ; резерв на http:// для f.n1ko.dev
local url="$1" out="$2" ok="" local url="$1" out="$2" ok=""
@ -63,47 +61,14 @@ dl() { # $1=url $2=out ; резерв на http:// для f.n1ko.dev
curl -fsSL --max-time 120 -o "$out" "$url" && ok=1 curl -fsSL --max-time 120 -o "$out" "$url" && ok=1
[ -z "$ok" ] && case "$url" in https://f.n1ko.dev/*) [ -z "$ok" ] && case "$url" in https://f.n1ko.dev/*)
curl -fsSL --max-time 120 -o "$out" "http://${url#https://}" && ok=1 ;; esac curl -fsSL --max-time 120 -o "$out" "http://${url#https://}" && ok=1 ;; esac
elif [ "$DL" = wget ]; then else
wget -q -T 120 -O "$out" "$url" && ok=1 wget -q -T 120 -O "$out" "$url" && ok=1
[ -z "$ok" ] && case "$url" in https://f.n1ko.dev/*) [ -z "$ok" ] && case "$url" in https://f.n1ko.dev/*)
wget -q -T 120 -O "$out" "http://${url#https://}" && ok=1 ;; esac wget -q -T 120 -O "$out" "http://${url#https://}" && ok=1 ;; esac
else
uclient-fetch -q --timeout=120 -O "$out" "$url" && ok=1
[ -z "$ok" ] && case "$url" in https://f.n1ko.dev/*)
uclient-fetch -q --timeout=120 -O "$out" "http://${url#https://}" && ok=1 ;; esac
fi fi
[ -n "$ok" ] [ -n "$ok" ]
} }
pipe_dl() { # $1=url -> поток в stdout (для больших файлов: без staging в tmpfs,
# /tmp на OpenWRT — это RAM, крупный бинарник туда класть нельзя)
if [ "$DL" = curl ]; then
curl -fsSL --max-time 600 "$1"
elif [ "$DL" = wget ]; then
wget -q -T 600 -O - "$1"
else
uclient-fetch -q --timeout=600 -O - "$1"
fi
}
fetch_gz() { # $1=имя-файла $2=dest ; источники: локальный bin/ -> релиз -> f.n1ko.dev
local f="$1" out="$2" ok=0
if [ -f "$REPO_DIR/bin/$f" ]; then
gzip -dc "$REPO_DIR/bin/$f" > "$out" 2>/dev/null && [ -s "$out" ] && ok=1
fi
if [ $ok != 1 ]; then
log "Качаю $f из релизов git.n1ko.dev ..."
pipe_dl "$REL_URL/$f" | gzip -dc > "$out" 2>/dev/null
[ -s "$out" ] && ok=1
fi
if [ $ok != 1 ]; then
log "Качаю $f с f.n1ko.dev ..."
pipe_dl "$BASE_URL/bin/$f" | gzip -dc > "$out" 2>/dev/null
[ -s "$out" ] && ok=1
fi
[ $ok = 1 ]
}
# --- API (HTTP Basic auth) --- # --- API (HTTP Basic auth) ---
API_AUTH=""; API_AUTH_V="" API_AUTH=""; API_AUTH_V=""
api_init_auth() { api_init_auth() {
@ -133,13 +98,11 @@ api_get() {
} }
read_creds() { read_creds() {
# ВАЖНО: не использовать tr '[:space:]' — старые busybox не знают
# POSIX-классы и вырезают литеральные буквы из кредов
if [ -n "$NIKOVPN_CREDS" ]; then if [ -n "$NIKOVPN_CREDS" ]; then
CRED=$(printf '%s' "$NIKOVPN_CREDS" | tr -d ' \t\r\n') CRED=$(printf '%s' "$NIKOVPN_CREDS" | tr -d '[:space:]')
else else
[ -f "$CREDS_FILE" ] || die "нет кредов: запишите 'login:password' в $CREDS_FILE или задайте env NIKOVPN_CREDS" [ -f "$CREDS_FILE" ] || die "нет кредов: запишите 'login:password' в $CREDS_FILE или задайте env NIKOVPN_CREDS"
CRED=$(grep -v '^[[:space:]]*#' "$CREDS_FILE" 2>/dev/null | grep -v '^[[:space:]]*$' | head -1 | tr -d ' \t\r\n') CRED=$(grep -v '^[[:space:]]*#' "$CREDS_FILE" 2>/dev/null | grep -v '^[[:space:]]*$' | head -1 | tr -d '[:space:]')
[ -n "$CRED" ] || die "$CREDS_FILE пуст — запишите 'login:password'" [ -n "$CRED" ] || die "$CREDS_FILE пуст — запишите 'login:password'"
fi fi
case "$CRED" in *:*) ;; *) die "в кредах нет ':' — формат login:password" ;; esac case "$CRED" in *:*) ;; *) die "в кредах нет ':' — формат login:password" ;; esac
@ -496,16 +459,26 @@ install_bins() {
if [ "$skip_sb" != 1 ]; then if [ "$skip_sb" != 1 ]; then
log "Получаю sing-box ($SB_ARCH) ..." log "Получаю sing-box ($SB_ARCH) ..."
fetch_gz "sing-box-$SB_ARCH.gz" "$SB_PATH" || die "не удалось скачать sing-box" dl "$BASE_URL/bin/sing-box-$SB_ARCH.gz" "$TMP/sing-box.gz" || \
chmod +x "$SB_PATH" cp "$REPO_DIR/bin/sing-box-$SB_ARCH.gz" "$TMP/sing-box.gz" 2>/dev/null
"$SB_PATH" version >/dev/null 2>&1 || die "sing-box не запускается на этой системе (архитектура?)" [ -s "$TMP/sing-box.gz" ] || die "не удалось скачать sing-box"
gzip -dc "$TMP/sing-box.gz" > "$TMP/sing-box" 2>/dev/null || die "повреждённый архив sing-box"
chmod +x "$TMP/sing-box"
"$TMP/sing-box" version >/dev/null 2>&1 || die "sing-box не запускается на этой системе (архитектура?)"
mv "$TMP/sing-box" "$SB_PATH"
rm -f "$TMP/sing-box.gz"
fi fi
if [ "$TUNNEL" = "ipt2socks" ] && [ "$skip_ipt" != 1 ]; then if [ "$TUNNEL" = "ipt2socks" ] && [ "$skip_ipt" != 1 ]; then
log "Получаю ipt2socks ($IPT_ARCH) ..." log "Получаю ipt2socks ($IPT_ARCH) ..."
fetch_gz "ipt2socks-$IPT_ARCH.gz" "$IPT_PATH" || die "не удалось скачать ipt2socks" dl "$BASE_URL/bin/ipt2socks-$IPT_ARCH.gz" "$TMP/ipt2socks.gz" || \
chmod +x "$IPT_PATH" cp "$REPO_DIR/bin/ipt2socks-$IPT_ARCH.gz" "$TMP/ipt2socks.gz" 2>/dev/null
"$IPT_PATH" -V >/dev/null 2>&1 || die "ipt2socks не запускается на этой системе (архитектура?)" [ -s "$TMP/ipt2socks.gz" ] || die "не удалось скачать ipt2socks"
gzip -dc "$TMP/ipt2socks.gz" > "$TMP/ipt2socks" 2>/dev/null || die "повреждённый архив ipt2socks"
chmod +x "$TMP/ipt2socks"
"$TMP/ipt2socks" -V >/dev/null 2>&1 || die "ipt2socks не запускается на этой системе (архитектура?)"
mv "$TMP/ipt2socks" "$IPT_PATH"
rm -f "$TMP/ipt2socks.gz"
fi fi
} }
@ -767,10 +740,9 @@ status() {
echo "Сервер: $HOST:$PORT" echo "Сервер: $HOST:$PORT"
echo "Архитектура: $ARCH (sing-box $SB_VER, ipt2socks $IPT_VER)" echo "Архитектура: $ARCH (sing-box $SB_VER, ipt2socks $IPT_VER)"
echo "Режим: $MODE / $TUNNEL / $FW" echo "Режим: $MODE / $TUNNEL / $FW"
# pgrep без -x: busybox с argv[0]=/usr/bin/sing-box не матчит -x pgrep -x sing-box >/dev/null 2>&1 && echo "sing-box: RUNNING" || echo "sing-box: STOPPED"
pgrep sing-box >/dev/null 2>&1 && echo "sing-box: RUNNING" || echo "sing-box: STOPPED"
if [ "$TUNNEL" = ipt2socks ]; then if [ "$TUNNEL" = ipt2socks ]; then
pgrep ipt2socks >/dev/null 2>&1 && echo "ipt2socks: RUNNING" || echo "ipt2socks: STOPPED" pgrep -x ipt2socks >/dev/null 2>&1 && echo "ipt2socks: RUNNING" || echo "ipt2socks: STOPPED"
fi fi
if [ -r /var/log/nikovpn.log ]; then if [ -r /var/log/nikovpn.log ]; then
echo "--- последние строки /var/log/nikovpn.log ---" echo "--- последние строки /var/log/nikovpn.log ---"
@ -782,7 +754,7 @@ status() {
tu=$(sed -n 's/.*"traffic_used": *\([0-9]*\).*/\1/p' "$TMP/me") tu=$(sed -n 's/.*"traffic_used": *\([0-9]*\).*/\1/p' "$TMP/me")
tt=$(sed -n 's/.*"traffic_total": *\([0-9]*\).*/\1/p' "$TMP/me") tt=$(sed -n 's/.*"traffic_total": *\([0-9]*\).*/\1/p' "$TMP/me")
td=$(sed -n 's/.*"traffic_day": *\([0-9]*\).*/\1/p' "$TMP/me") td=$(sed -n 's/.*"traffic_day": *\([0-9]*\).*/\1/p' "$TMP/me")
echo "Трафик: использовано $tu байт, за сутки $td, всего $tt" echo "Трафик: использовано $tu, за сутки $td, лимит $tt байт"
fi fi
} }
@ -795,8 +767,6 @@ uninstall() {
/etc/init.d/nikovpn stop 2>/dev/null /etc/init.d/nikovpn stop 2>/dev/null
/etc/init.d/nikovpn disable 2>/dev/null /etc/init.d/nikovpn disable 2>/dev/null
fi fi
kill $(pgrep sing-box) 2>/dev/null
kill $(pgrep ipt2socks) 2>/dev/null
nft delete table inet nikovpn 2>/dev/null nft delete table inet nikovpn 2>/dev/null
rm -f /etc/nftables.d/10-nikovpn.nft rm -f /etc/nftables.d/10-nikovpn.nft
for c in NIKOVPN NIKOVPN_TP NIKOVPN_UDP; do for c in NIKOVPN NIKOVPN_TP NIKOVPN_UDP; do
@ -828,11 +798,9 @@ install() {
mkdir -p "$WORK_DIR" "$TMP" mkdir -p "$WORK_DIR" "$TMP"
log "Проверяю обновления ..." log "Проверяю обновления с f.n1ko.dev ..."
dl "$REL_URL/versions" "$TMP/versions" || \ dl "$BASE_URL/versions" "$TMP/versions" || cp "$REPO_DIR/versions" "$TMP/versions" 2>/dev/null
dl "$BASE_URL/versions" "$TMP/versions" || \ [ -s "$TMP/versions" ] || die "не могу скачать $BASE_URL/versions (нет интернета?)"
cp "$REPO_DIR/versions" "$TMP/versions" 2>/dev/null
[ -s "$TMP/versions" ] || die "не могу скачать versions (нет интернета?)"
SB_VER=""; IPT_VER="" SB_VER=""; IPT_VER=""
while IFS='=' read -r k v; do while IFS='=' read -r k v; do
case "$k" in sing-box) SB_VER="$v" ;; ipt2socks) IPT_VER="$v" ;; esac case "$k" in sing-box) SB_VER="$v" ;; ipt2socks) IPT_VER="$v" ;; esac
@ -871,14 +839,10 @@ install() {
esac esac
fi fi
# остановить старый сервис и процессы вне procd (иначе Text file busy # остановить старый сервис перед перезаписью
# при перезаписи бинарников и занятые порты после старта)
if [ -x /etc/init.d/nikovpn ]; then if [ -x /etc/init.d/nikovpn ]; then
/etc/init.d/nikovpn stop 2>/dev/null /etc/init.d/nikovpn stop 2>/dev/null
fi fi
kill $(pgrep sing-box) 2>/dev/null
kill $(pgrep ipt2socks) 2>/dev/null
sleep 1
install_bins install_bins
log "Генерирую конфиг ..." log "Генерирую конфиг ..."
@ -891,14 +855,11 @@ install() {
chmod +x "$WORK_DIR/install.sh" chmod +x "$WORK_DIR/install.sh"
log "Запускаю сервис ..." log "Запускаю сервис ..."
kill $(pgrep sing-box) 2>/dev/null
kill $(pgrep ipt2socks) 2>/dev/null
sleep 1
/etc/init.d/nikovpn enable 2>/dev/null /etc/init.d/nikovpn enable 2>/dev/null
/etc/init.d/nikovpn start 2>/dev/null /etc/init.d/nikovpn start 2>/dev/null
sleep 5 sleep 3
if pgrep sing-box >/dev/null 2>&1; then if pgrep -x sing-box >/dev/null 2>&1; then
log "sing-box запущен. Прозрачный прокси активен." log "sing-box запущен. Прозрачный прокси активен."
else else
warn "sing-box не запустился. Смотрите: cat /var/log/nikovpn.log" warn "sing-box не запустился. Смотрите: cat /var/log/nikovpn.log"

View file

@ -35,7 +35,6 @@ from pathlib import Path
from urllib.parse import parse_qs, unquote, urlsplit from urllib.parse import parse_qs, unquote, urlsplit
BASE_URL = "https://f.n1ko.dev/nikovpn_openwrt" BASE_URL = "https://f.n1ko.dev/nikovpn_openwrt"
REL_URL = "https://git.n1ko.dev/Niko/nikovpn_openwrt/releases/download/bins"
API_URL = "https://vpnd.n1ko.dev/api/v1" API_URL = "https://vpnd.n1ko.dev/api/v1"
WORK_DIR = "/etc/nikovpn" WORK_DIR = "/etc/nikovpn"
CREDS_FILE = "/root/creds" CREDS_FILE = "/root/creds"
@ -636,10 +635,9 @@ def install_bins(rt: Router, sb_arch: str, ipt_arch: str, tunnel: str,
if local.exists(): if local.exists():
push_gz(rt, local, SB_PATH, "{b} version", "sing-box") push_gz(rt, local, SB_PATH, "{b} version", "sing-box")
else: else:
log(f"скачиваю sing-box ({sb_arch}) на роутере ...") log(f"скачиваю sing-box ({sb_arch}) на роутере с f.n1ko.dev ...")
gz = f"/tmp/nikovpn.{os.getpid()}.sb.gz" gz = f"/tmp/nikovpn.{os.getpid()}.sb.gz"
if not (router_download(rt, f"{REL_URL}/sing-box-{sb_arch}.gz", gz) or if not router_download(rt, f"{BASE_URL}/bin/sing-box-{sb_arch}.gz", gz):
router_download(rt, f"{BASE_URL}/bin/sing-box-{sb_arch}.gz", gz)):
die("не удалось скачать sing-box") die("не удалось скачать sing-box")
rt.run(f"gzip -dc {gz} > {SB_PATH} && chmod +x {SB_PATH}", check=True) rt.run(f"gzip -dc {gz} > {SB_PATH} && chmod +x {SB_PATH}", check=True)
if rt.run(f"{SB_PATH} version")[0] != 0: if rt.run(f"{SB_PATH} version")[0] != 0:
@ -651,10 +649,9 @@ def install_bins(rt: Router, sb_arch: str, ipt_arch: str, tunnel: str,
if local.exists(): if local.exists():
push_gz(rt, local, IPT_PATH, "{b} -V", "ipt2socks") push_gz(rt, local, IPT_PATH, "{b} -V", "ipt2socks")
else: else:
log(f"скачиваю ipt2socks ({ipt_arch}) на роутере ...") log(f"скачиваю ipt2socks ({ipt_arch}) на роутере с f.n1ko.dev ...")
gz = f"/tmp/nikovpn.{os.getpid()}.ipt.gz" gz = f"/tmp/nikovpn.{os.getpid()}.ipt.gz"
if not (router_download(rt, f"{REL_URL}/ipt2socks-{ipt_arch}.gz", gz) or if not router_download(rt, f"{BASE_URL}/bin/ipt2socks-{ipt_arch}.gz", gz):
router_download(rt, f"{BASE_URL}/bin/ipt2socks-{ipt_arch}.gz", gz)):
die("не удалось скачать ipt2socks") die("не удалось скачать ipt2socks")
rt.run(f"gzip -dc {gz} > {IPT_PATH} && chmod +x {IPT_PATH}", check=True) rt.run(f"gzip -dc {gz} > {IPT_PATH} && chmod +x {IPT_PATH}", check=True)
if rt.run(f"{IPT_PATH} -V")[0] != 0: if rt.run(f"{IPT_PATH} -V")[0] != 0:
@ -821,9 +818,7 @@ def do_install(rt: Router, args, update=False):
IPT_VER = v IPT_VER = v
if not SB_VER: if not SB_VER:
rc, out, _ = rt.run( rc, out, _ = rt.run(
f"(curl -fsSL {REL_URL}/versions 2>/dev/null || " f"(curl -fsSL {BASE_URL}/versions 2>/dev/null || "
f"wget -q -O - {REL_URL}/versions 2>/dev/null || "
f"curl -fsSL {BASE_URL}/versions 2>/dev/null || "
f"wget -q -O - {BASE_URL}/versions 2>/dev/null)") f"wget -q -O - {BASE_URL}/versions 2>/dev/null)")
if rc == 0: if rc == 0:
for line in out.decode(errors="replace").splitlines(): for line in out.decode(errors="replace").splitlines():
@ -952,7 +947,7 @@ def do_status(rt: Router, args):
me = json.loads(body) me = json.loads(body)
print(f"Трафик: использовано {human(me.get('traffic_used'))}, " print(f"Трафик: использовано {human(me.get('traffic_used'))}, "
f"за сутки {human(me.get('traffic_day'))}, " f"за сутки {human(me.get('traffic_day'))}, "
f"всего {human(me.get('traffic_total'))}") f"лимит {human(me.get('traffic_total'))}")
except SystemExit: except SystemExit:
pass pass

View file

@ -2,17 +2,12 @@
# NikoVPN: статус VPN # NikoVPN: статус VPN
[ -f /etc/nikovpn/install.sh ] && exec /etc/nikovpn/install.sh status [ -f /etc/nikovpn/install.sh ] && exec /etc/nikovpn/install.sh status
TMP=/tmp/nikovpn-status.sh TMP=/tmp/nikovpn-status.sh
for URL in \ URL="https://f.n1ko.dev/nikovpn_openwrt/install.sh"
"https://git.n1ko.dev/Niko/nikovpn_openwrt/releases/download/bins/install.sh" \ if command -v curl >/dev/null 2>&1; then
"https://f.n1ko.dev/nikovpn_openwrt/install.sh"; do curl -fsSL "$URL" -o "$TMP" 2>/dev/null
if command -v curl >/dev/null 2>&1; then elif command -v wget >/dev/null 2>&1; then
curl -fsSL "$URL" -o "$TMP" 2>/dev/null wget -q "$URL" -O "$TMP" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then fi
wget -q "$URL" -O "$TMP" 2>/dev/null
fi
[ -s "$TMP" ] && break
rm -f "$TMP"
done
if [ -s "$TMP" ]; then if [ -s "$TMP" ]; then
chmod +x "$TMP" chmod +x "$TMP"
exec "$TMP" status exec "$TMP" status

View file

@ -2,17 +2,12 @@
# NikoVPN: удаление VPN (останавливает сервисы и чистит firewall) # NikoVPN: удаление VPN (останавливает сервисы и чистит firewall)
[ -f /etc/nikovpn/install.sh ] && exec /etc/nikovpn/install.sh uninstall [ -f /etc/nikovpn/install.sh ] && exec /etc/nikovpn/install.sh uninstall
TMP=/tmp/nikovpn-uninstall.sh TMP=/tmp/nikovpn-uninstall.sh
for URL in \ URL="https://f.n1ko.dev/nikovpn_openwrt/install.sh"
"https://git.n1ko.dev/Niko/nikovpn_openwrt/releases/download/bins/install.sh" \ if command -v curl >/dev/null 2>&1; then
"https://f.n1ko.dev/nikovpn_openwrt/install.sh"; do curl -fsSL "$URL" -o "$TMP" 2>/dev/null
if command -v curl >/dev/null 2>&1; then elif command -v wget >/dev/null 2>&1; then
curl -fsSL "$URL" -o "$TMP" 2>/dev/null wget -q "$URL" -O "$TMP" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then fi
wget -q "$URL" -O "$TMP" 2>/dev/null
fi
[ -s "$TMP" ] && break
rm -f "$TMP"
done
if [ -s "$TMP" ]; then if [ -s "$TMP" ]; then
chmod +x "$TMP" chmod +x "$TMP"
exec "$TMP" uninstall exec "$TMP" uninstall

View file

@ -2,17 +2,12 @@
# NikoVPN: обновление (переустановка) — скачивает свежий install.sh с f.n1ko.dev # NikoVPN: обновление (переустановка) — скачивает свежий install.sh с f.n1ko.dev
[ -f /etc/nikovpn/install.sh ] && exec /etc/nikovpn/install.sh update [ -f /etc/nikovpn/install.sh ] && exec /etc/nikovpn/install.sh update
TMP=/tmp/nikovpn-update.sh TMP=/tmp/nikovpn-update.sh
for URL in \ URL="https://f.n1ko.dev/nikovpn_openwrt/install.sh"
"https://git.n1ko.dev/Niko/nikovpn_openwrt/releases/download/bins/install.sh" \ if command -v curl >/dev/null 2>&1; then
"https://f.n1ko.dev/nikovpn_openwrt/install.sh"; do curl -fsSL "$URL" -o "$TMP" 2>/dev/null
if command -v curl >/dev/null 2>&1; then elif command -v wget >/dev/null 2>&1; then
curl -fsSL "$URL" -o "$TMP" 2>/dev/null wget -q "$URL" -O "$TMP" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then fi
wget -q "$URL" -O "$TMP" 2>/dev/null
fi
[ -s "$TMP" ] && break
rm -f "$TMP"
done
if [ -s "$TMP" ]; then if [ -s "$TMP" ]; then
chmod +x "$TMP" chmod +x "$TMP"
exec "$TMP" update exec "$TMP" update