Add zapret2 section action support

This commit is contained in:
ushan0v
2026-06-02 22:07:40 +03:00
parent 5cf4183550
commit e677d992d6
27 changed files with 4774 additions and 1073 deletions
+11
View File
@@ -2,6 +2,13 @@ name: Differential ShellCheck
on:
workflow_dispatch:
inputs:
base:
description: Base revision for manual differential scan
required: true
head:
description: Head revision for manual differential scan
required: true
push:
branches:
- main
@@ -27,6 +34,7 @@ permissions:
jobs:
shellcheck:
name: Differential ShellCheck
if: ${{ github.event_name != 'push' || github.event.forced != true }}
runs-on: ubuntu-24.04
permissions:
@@ -42,6 +50,9 @@ jobs:
- name: Differential ShellCheck
uses: redhat-plumbers-in-action/differential-shellcheck@v5.5.5
with:
triggering-event: ${{ github.event_name == 'workflow_dispatch' && 'manual' || github.event_name }}
base: ${{ github.event.inputs.base }}
head: ${{ github.event.inputs.head }}
severity: error
include-path: |
podkop/files/usr/bin/podkop
+25 -8
View File
@@ -32,14 +32,13 @@ sh <(wget -O - https://raw.githubusercontent.com/ushan0v/podkop-plus/main/instal
### Что нового в этом форке
- Добавлена поддержка подписок.
- Интеграция Zapret и ByeDPI как отдельного действия.
- Интеграция Zapret, Zapret2 и ByeDPI как отдельных действий секции.
- Поддержка sing-box extended и транспорта XHTTP.
- Обновленный LuCI-интерфейс и расширенное управление секциями.
- Новые условия маршрутизации.
- Режим сервера с возможностю поднять собственный VPN/Proxy для подключения внешних устройств.
- Менеджер обновлений и установки компонентов.
- Встроенный мониторинг соединений.
- Менеджер обновлений.
- Переход на нативный для OpenWrt `ucode`
### Подписки
@@ -100,11 +99,11 @@ Podkop Plus позволяет поднять собственный VPN/Proxy
### Интеграция Zapret
Zapret доступен как действие отдельной секции. Используется [remittor/zapret-openwrt](https://github.com/remittor/zapret-openwrt/releases). Интеграция не конфликтует с отдельным полноценным пакетом zapret (`luci-app-zapret`).
Zapret доступен как действие отдельной секции. Используется [bol-van/zapret](https://github.com/bol-van/zapret) в OpenWrt-пакете `zapret` из релизов [remittor/zapret-openwrt](https://github.com/remittor/zapret-openwrt/releases).
> Вы можете найти адаптированные и проверенные стратегии обхода для Podkop Plus в специальном репозитории: [ushan0v/podkop-plus-zapret-strategies](https://github.com/ushan0v/podkop-plus-zapret-strategies).
> Вы можете найти адаптированные и проверенные стратегии zapret для Podkop Plus в специальном репозитории: [ushan0v/podkop-plus-zapret-strategies](https://github.com/ushan0v/podkop-plus-zapret-strategies).
Реализация: `sing-box` отбирает трафик секции и отправляет его в `direct` outbound с отдельным `routing_mark` -> `zapret` принимает помеченные пакеты из очереди и применяет выбранную стратегию DPI-обхода.
Реализация: `sing-box` отбирает трафик секции и отправляет его в `direct` outbound с отдельным `routing_mark` -> `zapret` принимает помеченные пакеты из очереди (`4000-4255`) и применяет выбранную стратегию DPI-обхода.
Для стратегии `zapret` намеренно запрещены:
@@ -113,11 +112,29 @@ Zapret доступен как действие отдельной секции.
- ручное управление очередью и fwmark: **`--qnum`**, **`--dpi-desync-fwmark`**;
- режимы, которые ломают lifecycle процесса: **`--daemon`**;
- режимы, которые не должны быть итоговой стратегией запуска: **`--dry-run`**, **`--version`**;
- внешние конфиги вида **`@file`** или **`$file`**, которые обходят встроенную валидацию и управление очередями.
- внешние файлы аргументов вида **`@file`** или **`$file`**, которые обходят встроенную валидацию и управление очередями.
### Интеграция Zapret2
Zapret2 доступен как отдельное действие секции `zapret2`. Используется [bol-van/zapret2](https://github.com/bol-van/zapret2) в OpenWrt-пакете `zapret2` из релизов [remittor/zapret-openwrt](https://github.com/remittor/zapret-openwrt/releases).
Реализация: `sing-box` отбирает трафик секции и отправляет его в `direct` outbound с отдельным `routing_mark` -> `zapret2` принимает помеченные пакеты из очереди (`4300-4555`) и применяет выбранную стратегию DPI-обхода.
`NFQWS2 Strategy` принимает параметры `nfqws2` без `--qnum` и без базовых Lua-инициализаций: Podkop Plus добавляет очередь, `--fwmark` и стандартные `zapret2` Lua-модули самостоятельно. Свои `--blob` и `--lua-init` допустимы для расширенных стратегий.
Для стратегии `zapret2` намеренно запрещены:
- шаблоны и hostlist placeholders: **`<HOSTLIST>`**, **`<HOSTLIST_NOAUTO>`**;
- hostname/IP selectors внутри самой стратегии: **`--hostlist*`**, **`--hostlist-auto*`**, **`--ipset*`**;
- ручное управление очередью и fwmark: **`--qnum`**, **`--fwmark`**, **`--dpi-desync-fwmark`**;
- режимы, несовместимые с управляемой NFQUEUE-архитектурой: **`--fuzz`**, **`--intercept=0`**;
- режимы, которые ломают lifecycle процесса: **`--daemon`**;
- режимы, которые не должны быть итоговой стратегией запуска: **`--dry-run`**, **`--version`**;
- внешние файлы аргументов вида **`@file`** или **`$file`**, которые обходят встроенную валидацию и управление очередями.
### Интеграция ByeDPI
ByeDPI доступен как действие отдельной секции. Используется [DPITrickster/ByeDPI-OpenWrt](https://github.com/DPITrickster/ByeDPI-OpenWrt/releases). Процесс работает под управлением Podkop Plus.
ByeDPI доступен как действие отдельной секции. Используется [DPITrickster/ByeDPI-OpenWrt](https://github.com/DPITrickster/ByeDPI-OpenWrt/releases).
Реализация: `sing-box` отбирает трафик секции и отправляет его в локальный SOCKS5 outbound -> `ciadpi` принимает соединение на `127.0.0.1:1080+` и применяет выбранную стратегию DPI-обхода.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+101 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-06-02 17:11+0300\n"
"PO-Revision-Date: 2026-06-02 17:11+0300\n"
"POT-Creation-Date: 2026-06-02 18:22+0300\n"
"PO-Revision-Date: 2026-06-02 18:22+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -299,6 +299,9 @@ msgstr "Отключить QUIC"
msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming"
msgstr "Отключить QUIC протокол для улучшения совместимости или исправления видео стриминга"
msgid "Disabling interception is incompatible with action=zapret2 because Podkop Plus sends matched traffic through NFQUEUE."
msgstr "Отключение перехвата несовместимо с action=zapret2, потому что Podkop Plus отправляет совпавший трафик через NFQUEUE."
msgid "DNS on router"
msgstr "DNS на роутере"
@@ -443,6 +446,9 @@ msgstr "Открыть эту секцию как локальный HTTP+SOCKS
msgid "External nfqws config files bypass Podkop Plus queue management and explicit validation."
msgstr "Внешние конфиги nfqws обходят управление очередями и явную валидацию Podkop Plus."
msgid "External nfqws2 config files bypass Podkop Plus queue management and explicit validation."
msgstr "Внешние конфиги nfqws2 обходят управление очередями и явную валидацию Podkop Plus."
msgid "Failed to close connection"
msgstr "Не удалось закрыть подключение"
@@ -485,6 +491,9 @@ msgstr "Proxy protocol для фронтинга"
msgid "Fully Routed IPs"
msgstr "Полностью маршрутизированные IP-адреса"
msgid "Fuzzing is not supported here because Podkop Plus needs deterministic runtime validation."
msgstr "Fuzzing здесь не поддерживается, потому что Podkop Plus нужна предсказуемая runtime-валидация."
msgid "Generated inbound"
msgstr "Созданный inbound"
@@ -608,6 +617,9 @@ msgstr "Неверный IP-адрес"
msgid "Invalid JSON format"
msgstr "Неверный формат JSON"
msgid "Invalid NFQWS2 strategy"
msgstr "Некорректная стратегия NFQWS2"
msgid "Invalid path format. Path must start with \"/\" and contain valid characters"
msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы"
@@ -803,6 +815,12 @@ msgstr "Стратегия NFQWS"
msgid "NFQWS strategy cannot be empty"
msgstr "Стратегия NFQWS не может быть пустой"
msgid "NFQWS2 Strategy"
msgstr "Стратегия NFQWS2"
msgid "NFQWS2 strategy cannot be empty"
msgstr "Стратегия NFQWS2 не может быть пустой"
msgid "No active connections"
msgstr "Нет активных подключений"
@@ -827,6 +845,9 @@ msgstr "Нет правил, использующих ByeDPI"
msgid "No rules use Zapret"
msgstr "Нет правил, использующих Zapret"
msgid "No rules use Zapret2"
msgstr "Нет правил, использующих Zapret2"
msgid "None"
msgstr "Нет"
@@ -884,6 +905,9 @@ msgstr "Параметр \"%s\" содержит некорректное зна
msgid "Optional values must be attached with '=' here; a separate token would be ignored by nfqws."
msgstr "Необязательное значение здесь нужно передавать только через '='; отдельный токен nfqws проигнорирует."
msgid "Optional values must be attached with '=' here; a separate token would be ignored by nfqws2."
msgstr "Необязательные значения здесь должны задаваться через '='; отдельный токен будет проигнорирован nfqws2."
msgid "Outbound JSON"
msgstr "JSON outbound"
@@ -938,6 +962,9 @@ msgstr "Podkop Plus сам управляет жизненным циклом ci
msgid "Podkop Plus manages the nfqws process lifecycle itself, so daemon mode is not allowed here."
msgstr "Podkop Plus сам управляет жизненным циклом nfqws, поэтому режим daemon здесь запрещён."
msgid "Podkop Plus manages the nfqws2 process lifecycle itself, so daemon mode is not allowed here."
msgstr "Podkop Plus сам управляет жизненным циклом nfqws2, поэтому режим daemon здесь запрещён."
msgid "Podkop Plus rules"
msgstr "Правила Podkop Plus"
@@ -968,6 +995,15 @@ msgstr "NFQWS Podkop Plus не запущен"
msgid "Podkop Plus-managed nfqws runtime is ready"
msgstr "NFQWS Podkop Plus готов"
msgid "Podkop Plus-managed nfqws2 runtime is not ready"
msgstr "NFQWS2 Podkop Plus не готов"
msgid "Podkop Plus-managed nfqws2 runtime is not running"
msgstr "NFQWS2 Podkop Plus не запущен"
msgid "Podkop Plus-managed nfqws2 runtime is ready"
msgstr "NFQWS2 Podkop Plus готов"
msgid "Port cannot be empty"
msgstr "Порт не может быть пустым"
@@ -1052,9 +1088,15 @@ msgstr "Определять страны серверов через country.is
msgid "Resource selection by hostname inside nfqws is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по hostname внутри nfqws здесь не поддерживается; ресурсы выбирает sing-box до NFQUEUE."
msgid "Resource selection by hostname inside nfqws2 is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по hostname внутри nfqws2 здесь не поддерживается; sing-box выбирает ресурсы до NFQUEUE."
msgid "Resource selection by IP or CIDR inside nfqws is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по IP или CIDR внутри nfqws здесь не поддерживается; ресурсы выбирает sing-box до NFQUEUE."
msgid "Resource selection by IP or CIDR inside nfqws2 is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по IP или CIDR внутри nfqws2 здесь не поддерживается; sing-box выбирает ресурсы до NFQUEUE."
msgid "Restart Podkop Plus"
msgstr "Перезапустить Podkop Plus"
@@ -1277,6 +1319,18 @@ msgstr "Отдельная служба Zapret активна"
msgid "Standalone Zapret service is inactive"
msgstr "Отдельная служба Zapret неактивна"
msgid "Standalone Zapret2 autostart is enabled"
msgstr "Автозапуск отдельной службы Zapret2 включён"
msgid "Standalone Zapret2 is active together with Podkop Plus Zapret2 rules"
msgstr "Отдельная служба Zapret2 активна вместе с правилами Zapret2 в Podkop Plus"
msgid "Standalone Zapret2 service is active"
msgstr "Отдельная служба Zapret2 активна"
msgid "Standalone Zapret2 service is inactive"
msgstr "Отдельная служба Zapret2 неактивна"
msgid "Start Podkop Plus"
msgstr "Запустить Podkop Plus"
@@ -1346,6 +1400,9 @@ msgstr "Есть правила, использующие ByeDPI"
msgid "There are rules using Zapret"
msgstr "Есть правила использующие Zapret"
msgid "There are rules using Zapret2"
msgstr "Есть правила, использующие Zapret2"
msgid "This field is required"
msgstr "Заполните это поле"
@@ -1358,6 +1415,12 @@ msgstr "Это поле должно запускать рабочую стра
msgid "This field must start a working nfqws strategy; --version exits immediately and is not allowed here."
msgstr "Это поле должно запускать рабочую стратегию nfqws; --version сразу завершает процесс и здесь запрещён."
msgid "This field must start a working nfqws2 strategy; --dry-run exits immediately and is not allowed here."
msgstr "Это поле должно запускать рабочую стратегию nfqws2; --dry-run сразу завершает работу и здесь запрещён."
msgid "This field must start a working nfqws2 strategy; --version exits immediately and is not allowed here."
msgstr "Это поле должно запускать рабочую стратегию nfqws2; --version сразу завершает работу и здесь запрещён."
msgid "This flag does not accept a value."
msgstr "Этот флаг не принимает значение."
@@ -1418,6 +1481,9 @@ msgstr "Не удалось проверить стратегию ByeDPI чер
msgid "Unable to validate the NFQWS strategy through the backend parser."
msgstr "Не удалось проверить стратегию NFQWS через backend-парсер."
msgid "Unable to validate the NFQWS2 strategy through the backend parser."
msgstr "Не удалось проверить стратегию NFQWS2 через backend-парсер."
msgid "Unexpected ByeDPI strategy argument: %s"
msgstr "Неожиданный аргумент стратегии ByeDPI: %s"
@@ -1427,6 +1493,9 @@ msgstr "Обнаружен лишний ciadpi Podkop Plus"
msgid "Unexpected Podkop Plus-managed nfqws runtime is running"
msgstr "Обнаружен лишний NFQWS Podkop Plus"
msgid "Unexpected Podkop Plus-managed nfqws2 runtime is running"
msgstr "Обнаружен лишний NFQWS2 Podkop Plus"
msgid "Unexpected standalone token. Use explicit flags such as --name or --name=value."
msgstr "Неожиданный отдельный токен. Используйте явные флаги вида --имя или --имя=значение."
@@ -1448,6 +1517,9 @@ msgstr "Неизвестная ошибка"
msgid "Unknown NFQWS flag."
msgstr "Неизвестный флаг NFQWS."
msgid "Unknown NFQWS2 flag."
msgstr "Неизвестный флаг NFQWS2."
msgid "Unsupported security mode for this protocol"
msgstr "Этот протокол не поддерживает выбранный режим защиты"
@@ -1591,3 +1663,30 @@ msgstr "Бинарник провайдера Zapret доступен"
msgid "Zapret provider binary is not available"
msgstr "Бинарник провайдера Zapret недоступен"
msgid "Zapret2 hostlist templates are not supported here because Podkop Plus does not expand them for per-rule NFQWS2 strategies."
msgstr "Шаблоны hostlist Zapret2 здесь не поддерживаются, потому что Podkop Plus не разворачивает их для per-rule стратегий NFQWS2."
msgid "Zapret2 package is installed"
msgstr "Пакет Zapret2 установлен"
msgid "Zapret2 package is not installed"
msgstr "Пакет Zapret2 не установлен"
msgid "Zapret2 provider binary is available"
msgstr "Бинарник провайдера Zapret2 доступен"
msgid "Zapret2 provider binary is not available"
msgstr "Бинарник провайдера Zapret2 недоступен"
msgid "Zapret2 route rules are configured"
msgstr "Правила маршрутизации Zapret2 настроены"
msgid "Zapret2 route rules are not configured"
msgstr "Правила маршрутизации Zapret2 не настроены"
msgid "Zapret2 sing-box outbound is configured"
msgstr "sing-box outbound для Zapret2 настроен"
msgid "Zapret2 sing-box outbound is not configured"
msgstr "sing-box outbound для Zapret2 не настроен"
@@ -134,6 +134,10 @@ export const PodkopShellMethods = {
callBaseMethod<Podkop.ZapretCheckResult>(
Podkop.AvailableMethods.CHECK_ZAPRET_RUNTIME,
),
checkZapret2Runtime: async () =>
callBaseMethod<Podkop.Zapret2CheckResult>(
Podkop.AvailableMethods.CHECK_ZAPRET2_RUNTIME,
),
checkByedpiRuntime: async () =>
callBaseMethod<Podkop.ByedpiCheckResult>(
Podkop.AvailableMethods.CHECK_BYEDPI_RUNTIME,
@@ -180,6 +184,10 @@ export const PodkopShellMethods = {
callBaseMethod<Podkop.GetZapretStatus>(
Podkop.AvailableMethods.GET_ZAPRET_STATUS,
),
getZapret2Status: async () =>
callBaseMethod<Podkop.GetZapret2Status>(
Podkop.AvailableMethods.GET_ZAPRET2_STATUS,
),
getByedpiStatus: async () =>
callBaseMethod<Podkop.GetByedpiStatus>(
Podkop.AvailableMethods.GET_BYEDPI_STATUS,
@@ -196,6 +196,8 @@ export interface StoreType {
sing_box_extended: number;
zapret_version: string;
zapret_installed: number;
zapret2_version: string;
zapret2_installed: number;
byedpi_version: string;
byedpi_installed: number;
server_inbounds_enabled_count: number;
@@ -212,6 +214,9 @@ export interface StoreType {
zapretCheck: { loading: boolean };
zapretInstall: { loading: boolean };
zapretRemove: { loading: boolean };
zapret2Check: { loading: boolean };
zapret2Install: { loading: boolean };
zapret2Remove: { loading: boolean };
byedpiCheck: { loading: boolean };
byedpiInstall: { loading: boolean };
byedpiRemove: { loading: boolean };
@@ -13,6 +13,8 @@ const UNKNOWN_SYSTEM_INFO: StoreType['diagnosticsSystemInfo'] = {
sing_box_extended: 0,
zapret_version: _('unknown'),
zapret_installed: 0,
zapret2_version: _('unknown'),
zapret2_installed: 0,
byedpi_version: _('unknown'),
byedpi_installed: 0,
server_inbounds_enabled_count: -1,
@@ -94,6 +96,7 @@ export async function ensureSystemInfo({
loaded: false,
providerInfoLoaded: latestSystemInfo.providerInfoLoaded,
zapret_installed: latestSystemInfo.zapret_installed,
zapret2_installed: latestSystemInfo.zapret2_installed,
byedpi_installed: latestSystemInfo.byedpi_installed,
server_inbounds_enabled_count:
latestSystemInfo.server_inbounds_enabled_count,
@@ -5,6 +5,7 @@ export enum DIAGNOSTICS_CHECKS {
SINGBOX = 'SINGBOX',
NFT = 'NFT',
ZAPRET = 'ZAPRET',
ZAPRET2 = 'ZAPRET2',
BYEDPI = 'BYEDPI',
FAKEIP = 'FAKEIP',
OUTBOUNDS = 'OUTBOUNDS',
@@ -36,17 +37,22 @@ export const DIAGNOSTICS_CHECKS_MAP: Record<
code: DIAGNOSTICS_CHECKS.ZAPRET,
},
[DIAGNOSTICS_CHECKS.BYEDPI]: {
order: 6,
order: 7,
title: getCheckTitle('ByeDPI'),
code: DIAGNOSTICS_CHECKS.BYEDPI,
},
[DIAGNOSTICS_CHECKS.ZAPRET2]: {
order: 6,
title: getCheckTitle('Zapret2'),
code: DIAGNOSTICS_CHECKS.ZAPRET2,
},
[DIAGNOSTICS_CHECKS.OUTBOUNDS]: {
order: 7,
order: 8,
title: getCheckTitle('Outbounds'),
code: DIAGNOSTICS_CHECKS.OUTBOUNDS,
},
[DIAGNOSTICS_CHECKS.FAKEIP]: {
order: 8,
order: 9,
title: getCheckTitle('FakeIP'),
code: DIAGNOSTICS_CHECKS.FAKEIP,
},
@@ -0,0 +1,167 @@
import { DIAGNOSTICS_CHECKS_MAP } from './contstants';
import { PodkopShellMethods } from '../../../methods';
import { updateCheckStore } from './updateCheckStore';
import { IDiagnosticsChecksItem } from '../../../services';
type CheckState = 'success' | 'warning' | 'error';
export async function runZapret2Check() {
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.ZAPRET2;
updateCheckStore({
order,
code,
title,
description: _('Checking, please wait'),
state: 'loading',
items: [],
});
const zapret2Status = await PodkopShellMethods.getZapret2Status();
if (!zapret2Status.success) {
updateCheckStore({
order,
code,
title,
description: _('Cannot receive checks result'),
state: 'error',
items: [],
});
throw new Error('Zapret2 checks failed');
}
const data = zapret2Status.data;
const providerAvailable = Boolean(data.provider_available ?? data.installed);
const packageInstalled = Boolean(data.package_installed);
const hasZapret2Rules = Number(data.enabled_rule_count || 0) > 0;
const ready = Boolean(data.ready);
const queueOverlap = Boolean(data.queue_overlap);
const expectedProcesses = Number(data.expected_process_count || 0);
const runningProcesses = Number(data.running_process_count || 0);
const supervisorProcesses = Number(data.supervisor_process_count || 0);
const podkopRuntimeReady =
!hasZapret2Rules ||
(runningProcesses === expectedProcesses &&
supervisorProcesses === expectedProcesses);
const unexpectedRuntime =
!hasZapret2Rules && (runningProcesses > 0 || supervisorProcesses > 0);
const outboundsConfigured = Boolean(data.outbounds_configured);
const routesConfigured = Boolean(data.routes_configured);
const standaloneServiceEnabled = Boolean(data.standalone_service_enabled);
const standaloneServiceRunning = Boolean(data.standalone_service_running);
const standaloneConflict = hasZapret2Rules && standaloneServiceRunning;
const standaloneAutostartRisk =
hasZapret2Rules && standaloneServiceEnabled && !standaloneServiceRunning;
let state: CheckState = 'success';
let description = _('Checks passed');
if (hasZapret2Rules && !providerAvailable) {
state = 'error';
description = _('Checks failed');
} else if (hasZapret2Rules && !ready) {
state = 'error';
description = _('Checks failed');
} else if (queueOverlap) {
state = 'error';
description = _('Checks failed');
} else if (standaloneConflict) {
state = 'error';
description = _('Checks failed');
} else if (standaloneAutostartRisk || !packageInstalled) {
state = 'warning';
description = _('Issues detected');
}
const items: Array<IDiagnosticsChecksItem> = [
{
state: providerAvailable
? 'success'
: hasZapret2Rules
? 'error'
: 'warning',
key: providerAvailable
? _('Zapret2 provider binary is available')
: _('Zapret2 provider binary is not available'),
value: '',
},
{
state: packageInstalled
? 'success'
: hasZapret2Rules
? 'error'
: 'warning',
key: packageInstalled
? _('Zapret2 package is installed')
: _('Zapret2 package is not installed'),
value: '',
},
{
state: hasZapret2Rules && !providerAvailable ? 'error' : 'success',
key: hasZapret2Rules
? _('There are rules using Zapret2')
: _('No rules use Zapret2'),
value: '',
},
{
state: unexpectedRuntime || !podkopRuntimeReady ? 'error' : 'success',
key: hasZapret2Rules
? podkopRuntimeReady
? _('Podkop Plus-managed nfqws2 runtime is ready')
: _('Podkop Plus-managed nfqws2 runtime is not ready')
: unexpectedRuntime
? _('Unexpected Podkop Plus-managed nfqws2 runtime is running')
: _('Podkop Plus-managed nfqws2 runtime is not running'),
value: hasZapret2Rules ? `${runningProcesses}/${expectedProcesses}` : '',
},
{
state: queueOverlap ? 'error' : 'success',
key: queueOverlap
? _('NFQUEUE range overlaps with another rule')
: _('NFQUEUE range is available'),
value: `${Number(data.queue_base || 0)}-${Number(data.queue_range_end || 0)}`,
},
{
state: !hasZapret2Rules || outboundsConfigured ? 'success' : 'error',
key: outboundsConfigured
? _('Zapret2 sing-box outbound is configured')
: _('Zapret2 sing-box outbound is not configured'),
value: '',
},
{
state: !hasZapret2Rules || routesConfigured ? 'success' : 'error',
key: routesConfigured
? _('Zapret2 route rules are configured')
: _('Zapret2 route rules are not configured'),
value: '',
},
{
state: standaloneConflict
? 'error'
: standaloneAutostartRisk
? 'warning'
: 'success',
key: standaloneServiceRunning
? hasZapret2Rules
? _(
'Standalone Zapret2 is active together with Podkop Plus Zapret2 rules',
)
: _('Standalone Zapret2 service is active')
: standaloneAutostartRisk
? _('Standalone Zapret2 autostart is enabled')
: _('Standalone Zapret2 service is inactive'),
value: '',
},
];
updateCheckStore({
order,
code,
title,
description,
state,
items,
});
}
@@ -6,6 +6,7 @@ import { IDiagnosticsChecksStoreItem, StoreType } from '../../services';
export interface DiagnosticsProviderOptions {
includeZapret?: boolean;
includeZapret2?: boolean;
includeByedpi?: boolean;
includeInbounds?: boolean;
}
@@ -42,6 +43,10 @@ export function getDiagnosticsChecks(
checks.push(DIAGNOSTICS_CHECKS.ZAPRET);
}
if (options.includeZapret2) {
checks.push(DIAGNOSTICS_CHECKS.ZAPRET2);
}
if (options.includeByedpi) {
checks.push(DIAGNOSTICS_CHECKS.BYEDPI);
}
@@ -79,6 +84,8 @@ export const initialDiagnosticStore: Pick<
sing_box_extended: 0,
zapret_version: 'loading',
zapret_installed: 0,
zapret2_version: 'loading',
zapret2_installed: 0,
byedpi_version: 'loading',
byedpi_installed: 0,
server_inbounds_enabled_count: -1,
@@ -123,6 +130,9 @@ export const initialDiagnosticStore: Pick<
zapretCheck: { loading: false },
zapretInstall: { loading: false },
zapretRemove: { loading: false },
zapret2Check: { loading: false },
zapret2Install: { loading: false },
zapret2Remove: { loading: false },
byedpiCheck: { loading: false },
byedpiInstall: { loading: false },
byedpiRemove: { loading: false },
@@ -131,6 +141,7 @@ export const initialDiagnosticStore: Pick<
podkop: { status: null, latest_version: '', release_url: '' },
sing_box: { status: null, latest_version: '', release_url: '' },
zapret: { status: null, latest_version: '', release_url: '' },
zapret2: { status: null, latest_version: '', release_url: '' },
byedpi: { status: null, latest_version: '', release_url: '' },
},
};
@@ -5,6 +5,7 @@ import { runInboundsCheck } from './checks/runInboundsCheck';
import { runNftCheck } from './checks/runNftCheck';
import { runFakeIPCheck } from './checks/runFakeIPCheck';
import { runZapretCheck } from './checks/runZapretCheck';
import { runZapret2Check } from './checks/runZapret2Check';
import { runByedpiCheck } from './checks/runByedpiCheck';
import {
DiagnosticsProviderOptions,
@@ -47,11 +48,15 @@ function sleep(ms: number) {
function getDiagnosticsProviderOptions(
systemInfo: Pick<
StoreType['diagnosticsSystemInfo'],
'zapret_installed' | 'byedpi_installed' | 'server_inbounds_enabled_count'
| 'zapret_installed'
| 'zapret2_installed'
| 'byedpi_installed'
| 'server_inbounds_enabled_count'
> = store.get().diagnosticsSystemInfo,
): DiagnosticsProviderOptions {
return {
includeZapret: Boolean(systemInfo.zapret_installed),
includeZapret2: Boolean(systemInfo.zapret2_installed),
includeByedpi: Boolean(systemInfo.byedpi_installed),
includeInbounds: systemInfo.server_inbounds_enabled_count !== 0,
};
@@ -220,11 +225,13 @@ async function fetchDiagnosticsProviderInfo({
const requestId = ++latestProviderInfoRequestId;
try {
const [zapretRuntime, byedpiRuntime, inboundsConfig] = await Promise.all([
PodkopShellMethods.checkZapretRuntime(),
PodkopShellMethods.checkByedpiRuntime(),
PodkopShellMethods.checkInboundsConfig(),
]);
const [zapretRuntime, zapret2Runtime, byedpiRuntime, inboundsConfig] =
await Promise.all([
PodkopShellMethods.checkZapretRuntime(),
PodkopShellMethods.checkZapret2Runtime(),
PodkopShellMethods.checkByedpiRuntime(),
PodkopShellMethods.checkInboundsConfig(),
]);
if (requestId !== latestProviderInfoRequestId) {
return;
@@ -237,6 +244,9 @@ async function fetchDiagnosticsProviderInfo({
zapret_installed: zapretRuntime.success
? zapretRuntime.data.zapret_installed
: currentSystemInfo.zapret_installed,
zapret2_installed: zapret2Runtime.success
? zapret2Runtime.data.zapret2_installed
: currentSystemInfo.zapret2_installed,
byedpi_installed: byedpiRuntime.success
? byedpiRuntime.data.byedpi_installed
: currentSystemInfo.byedpi_installed,
@@ -249,6 +259,14 @@ async function fetchDiagnosticsProviderInfo({
logger.error('[DIAGNOSTIC]', 'fetchZapretRuntime failed', zapretRuntime);
}
if (!zapret2Runtime.success) {
logger.error(
'[DIAGNOSTIC]',
'fetchZapret2Runtime failed',
zapret2Runtime,
);
}
if (!byedpiRuntime.success) {
logger.error('[DIAGNOSTIC]', 'fetchByedpiRuntime failed', byedpiRuntime);
}
@@ -265,6 +283,10 @@ async function fetchDiagnosticsProviderInfo({
nextSystemInfo.zapret_version = 'not installed';
}
if (!nextSystemInfo.zapret2_installed) {
nextSystemInfo.zapret2_version = 'not installed';
}
if (!nextSystemInfo.byedpi_installed) {
nextSystemInfo.byedpi_version = 'not installed';
}
@@ -632,6 +654,13 @@ function renderDiagnosticSystemInfoWidget() {
});
}
if (diagnosticsSystemInfo.zapret2_installed) {
items.push({
key: 'Zapret2',
value: diagnosticsSystemInfo.zapret2_version,
});
}
if (diagnosticsSystemInfo.byedpi_installed) {
items.push({
key: 'ByeDPI',
@@ -702,6 +731,7 @@ async function runChecks() {
...(providerOptions.includeInbounds ? [runInboundsCheck] : []),
runNftCheck,
...(providerOptions.includeZapret ? [runZapretCheck] : []),
...(providerOptions.includeZapret2 ? [runZapret2Check] : []),
...(providerOptions.includeByedpi ? [runByedpiCheck] : []),
runSectionsCheck,
runFakeIPCheck,
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest';
import { DIAGNOSTICS_CHECKS } from '../checks/contstants';
import { getDiagnosticsChecks } from '../diagnostic.store';
describe('diagnostic store provider checks', () => {
it('adds Zapret2 as a separate provider check between Zapret and ByeDPI', () => {
const checks = getDiagnosticsChecks('Pending', {
includeZapret: true,
includeZapret2: true,
includeByedpi: true,
includeInbounds: false,
});
expect(checks.map((check) => check.code)).toEqual([
DIAGNOSTICS_CHECKS.DNS,
DIAGNOSTICS_CHECKS.SINGBOX,
DIAGNOSTICS_CHECKS.NFT,
DIAGNOSTICS_CHECKS.ZAPRET,
DIAGNOSTICS_CHECKS.ZAPRET2,
DIAGNOSTICS_CHECKS.BYEDPI,
DIAGNOSTICS_CHECKS.OUTBOUNDS,
DIAGNOSTICS_CHECKS.FAKEIP,
]);
});
it('keeps Zapret2 hidden when the provider is not available', () => {
const checks = getDiagnosticsChecks('Pending', {
includeZapret: true,
includeZapret2: false,
includeByedpi: true,
includeInbounds: false,
});
expect(checks.map((check) => check.code)).not.toContain(
DIAGNOSTICS_CHECKS.ZAPRET2,
);
});
});
@@ -14,8 +14,7 @@ import { ensureSystemInfo } from '../../services/systemInfo.service';
import { Podkop } from '../../types';
type UpdatesActionKey = keyof StoreType['updatesActions'];
type UpdateStatus =
StoreType['updatesChecks'][Podkop.ComponentName]['status'];
type UpdateStatus = StoreType['updatesChecks'][Podkop.ComponentName]['status'];
interface ComponentActionButton {
key: UpdatesActionKey;
@@ -138,7 +137,9 @@ function getExpectedLatestVersionForAction(button: ComponentActionButton) {
return undefined;
}
return store.get().updatesChecks[button.component].latest_version || undefined;
return (
store.get().updatesChecks[button.component].latest_version || undefined
);
}
function getCheckToastMessage(status: UpdateStatus) {
@@ -168,6 +169,7 @@ function notifyActionProvidersAvailabilityChanged(
new CustomEvent(PODKOP_ACTION_PROVIDERS_AVAILABILITY_EVENT, {
detail: {
zapretInstalled: Boolean(systemInfo.zapret_installed),
zapret2Installed: Boolean(systemInfo.zapret2_installed),
byedpiInstalled: Boolean(systemInfo.byedpi_installed),
},
}),
@@ -214,6 +216,18 @@ function patchSystemInfoAfterMutation(result: Podkop.ComponentActionResult) {
}
}
if (result.component === 'zapret2') {
nextSystemInfo.providerInfoLoaded = true;
if (result.action === 'remove') {
nextSystemInfo.zapret2_installed = 0;
nextSystemInfo.zapret2_version = 'not installed';
} else {
nextSystemInfo.zapret2_installed = 1;
nextSystemInfo.zapret2_version = version;
}
}
if (result.component === 'byedpi') {
nextSystemInfo.providerInfoLoaded = true;
@@ -230,7 +244,11 @@ function patchSystemInfoAfterMutation(result: Podkop.ComponentActionResult) {
diagnosticsSystemInfo: nextSystemInfo,
});
if (result.component === 'zapret' || result.component === 'byedpi') {
if (
result.component === 'zapret' ||
result.component === 'zapret2' ||
result.component === 'byedpi'
) {
notifyActionProvidersAvailabilityChanged(nextSystemInfo);
}
}
@@ -332,6 +350,7 @@ function getComponentCards(): ComponentCard[] {
const systemInfo = store.get().diagnosticsSystemInfo;
const systemInfoLoading = isSystemInfoLoading();
const zapretInstalled = Boolean(systemInfo.zapret_installed);
const zapret2Installed = Boolean(systemInfo.zapret2_installed);
const byedpiInstalled = Boolean(systemInfo.byedpi_installed);
const singBoxInstallAction: ComponentActionButton =
systemInfo.sing_box_extended
@@ -368,11 +387,7 @@ function getComponentCards(): ComponentCard[] {
releaseUrl: getGitHubReleaseUrl('sing_box'),
tag: getCheckTag('sing_box'),
actions: [
getPrimaryUpdateAction(
'sing_box',
'singBoxCheck',
'singBoxInstall',
),
getPrimaryUpdateAction('sing_box', 'singBoxCheck', 'singBoxInstall'),
singBoxInstallAction,
],
},
@@ -406,6 +421,36 @@ function getComponentCards(): ComponentCard[] {
},
],
},
{
title: 'Zapret2',
version: systemInfoLoading
? 'loading'
: zapret2Installed
? systemInfo.zapret2_version
: _('Not installed'),
releaseUrl: getGitHubReleaseUrl('zapret2'),
tag: zapret2Installed ? getCheckTag('zapret2') : undefined,
actions: zapret2Installed
? [
getPrimaryUpdateAction('zapret2', 'zapret2Check', 'zapret2Install'),
{
key: 'zapret2Remove',
text: _('Remove'),
icon: renderXIcon24,
component: 'zapret2',
action: 'remove',
},
]
: [
{
key: 'zapret2Install',
text: _('Install'),
icon: renderRotateCcwIcon24,
component: 'zapret2',
action: 'install',
},
],
},
{
title: 'ByeDPI',
version: systemInfoLoading
@@ -503,11 +548,7 @@ function renderComponentCard(card: ComponentCard) {
}
return E('div', { class: 'pdk_updates-page__component' }, [
E(
'div',
{ class: 'pdk_updates-page__component__header' },
headerChildren,
),
E('div', { class: 'pdk_updates-page__component__header' }, headerChildren),
E('div', { class: 'pdk_updates-page__component__version' }, [
E(
'span',
+47 -1
View File
@@ -56,6 +56,7 @@ export namespace Podkop {
CHECK_FAKEIP = 'check_fakeip',
CHECK_NFT_RULES = 'check_nft_rules',
CHECK_ZAPRET_RUNTIME = 'check_zapret_runtime',
CHECK_ZAPRET2_RUNTIME = 'check_zapret2_runtime',
CHECK_BYEDPI_RUNTIME = 'check_byedpi_runtime',
CHECK_INBOUNDS_CONFIG = 'check_inbounds_config',
GET_STATUS = 'get_status',
@@ -67,6 +68,7 @@ export namespace Podkop {
CHECK_INBOUNDS = 'check_inbounds',
GET_SING_BOX_STATUS = 'get_sing_box_status',
GET_ZAPRET_STATUS = 'get_zapret_status',
GET_ZAPRET2_STATUS = 'get_zapret2_status',
GET_BYEDPI_STATUS = 'get_byedpi_status',
CLASH_API = 'clash_api',
RESTART = 'restart',
@@ -153,6 +155,7 @@ export namespace Podkop {
| 'direct'
| 'block'
| 'zapret'
| 'zapret2'
| 'byedpi';
type LegacyConnectionType = 'proxy' | 'vpn' | 'block' | 'exclusion';
type ProxyConfigType =
@@ -179,6 +182,7 @@ export namespace Podkop {
update_interval?: string;
proxy_string?: string;
nfqws_opt?: string;
nfqws2_opt?: string;
byedpi_cmd_opts?: string;
cmd_opts?: string;
selector_proxy_links?: string[];
@@ -326,6 +330,8 @@ export namespace Podkop {
sing_box_extended: 0 | 1;
zapret_version: string;
zapret_installed: 0 | 1;
zapret2_version: string;
zapret2_installed: 0 | 1;
byedpi_version: string;
byedpi_installed: 0 | 1;
openwrt_version: string;
@@ -340,11 +346,17 @@ export namespace Podkop {
export interface GetUiCapabilities {
sing_box_extended: 0 | 1;
zapret_installed: 0 | 1;
zapret2_installed: 0 | 1;
byedpi_installed: 0 | 1;
server_inbounds_enabled_count: number;
}
export type ComponentName = 'podkop' | 'sing_box' | 'zapret' | 'byedpi';
export type ComponentName =
| 'podkop'
| 'sing_box'
| 'zapret'
| 'zapret2'
| 'byedpi';
export type ComponentAction =
| 'check_update'
@@ -422,6 +434,40 @@ export namespace Podkop {
zapret_provider_path: string;
}
export interface GetZapret2Status {
installed: 0 | 1;
package_installed: 0 | 1;
provider_available: 0 | 1;
provider_path: string;
files_available: 0 | 1;
ipset_available: 0 | 1;
version: string;
configured: 0 | 1;
enabled_rule_count: number;
expected_process_count: number;
running_process_count: number;
supervisor_process_count: number;
standalone_service_enabled: 0 | 1;
standalone_service_running: 0 | 1;
standalone_config_present: 0 | 1;
standalone_conflict: 0 | 1;
luci_app_installed: 0 | 1;
queue_base: number;
queue_range_end: number;
queue_overlap: 0 | 1;
ready: 0 | 1;
conflict: 0 | 1;
outbounds_configured: 0 | 1;
routes_configured: 0 | 1;
status_message: string;
}
export interface Zapret2CheckResult {
zapret2_installed: 0 | 1;
zapret2_package_installed: 0 | 1;
zapret2_provider_path: string;
}
export interface GetByedpiStatus {
installed: 0 | 1;
package_installed: 0 | 1;
@@ -549,6 +549,12 @@ function validateHysteria2Url(url) {
message: _("Invalid HY2 URL: sni cannot be empty")
};
}
if (paramsKeys.includes("mport") && (!params.mport || !params.mport.split(",").every(isValidPortEntry))) {
return {
valid: false,
message: _("Invalid HY2 URL: invalid port number")
};
}
}
return { valid: true, message: _("Valid") };
} catch (_e) {
@@ -2074,6 +2080,7 @@ var Podkop;
AvailableMethods2["CHECK_FAKEIP"] = "check_fakeip";
AvailableMethods2["CHECK_NFT_RULES"] = "check_nft_rules";
AvailableMethods2["CHECK_ZAPRET_RUNTIME"] = "check_zapret_runtime";
AvailableMethods2["CHECK_ZAPRET2_RUNTIME"] = "check_zapret2_runtime";
AvailableMethods2["CHECK_BYEDPI_RUNTIME"] = "check_byedpi_runtime";
AvailableMethods2["CHECK_INBOUNDS_CONFIG"] = "check_inbounds_config";
AvailableMethods2["GET_STATUS"] = "get_status";
@@ -2085,6 +2092,7 @@ var Podkop;
AvailableMethods2["CHECK_INBOUNDS"] = "check_inbounds";
AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status";
AvailableMethods2["GET_ZAPRET_STATUS"] = "get_zapret_status";
AvailableMethods2["GET_ZAPRET2_STATUS"] = "get_zapret2_status";
AvailableMethods2["GET_BYEDPI_STATUS"] = "get_byedpi_status";
AvailableMethods2["CLASH_API"] = "clash_api";
AvailableMethods2["RESTART"] = "restart";
@@ -2216,6 +2224,9 @@ var PodkopShellMethods = {
checkZapretRuntime: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_ZAPRET_RUNTIME
),
checkZapret2Runtime: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_ZAPRET2_RUNTIME
),
checkByedpiRuntime: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_BYEDPI_RUNTIME
),
@@ -2251,6 +2262,9 @@ var PodkopShellMethods = {
getZapretStatus: async () => callBaseMethod(
Podkop.AvailableMethods.GET_ZAPRET_STATUS
),
getZapret2Status: async () => callBaseMethod(
Podkop.AvailableMethods.GET_ZAPRET2_STATUS
),
getByedpiStatus: async () => callBaseMethod(
Podkop.AvailableMethods.GET_BYEDPI_STATUS
),
@@ -3020,17 +3034,22 @@ var DIAGNOSTICS_CHECKS_MAP = {
code: "ZAPRET" /* ZAPRET */
},
["BYEDPI" /* BYEDPI */]: {
order: 6,
order: 7,
title: getCheckTitle("ByeDPI"),
code: "BYEDPI" /* BYEDPI */
},
["ZAPRET2" /* ZAPRET2 */]: {
order: 6,
title: getCheckTitle("Zapret2"),
code: "ZAPRET2" /* ZAPRET2 */
},
["OUTBOUNDS" /* OUTBOUNDS */]: {
order: 7,
order: 8,
title: getCheckTitle("Outbounds"),
code: "OUTBOUNDS" /* OUTBOUNDS */
},
["FAKEIP" /* FAKEIP */]: {
order: 8,
order: 9,
title: getCheckTitle("FakeIP"),
code: "FAKEIP" /* FAKEIP */
},
@@ -3062,6 +3081,9 @@ function getDiagnosticsChecks(description, options = {}) {
if (options.includeZapret) {
checks.push("ZAPRET" /* ZAPRET */);
}
if (options.includeZapret2) {
checks.push("ZAPRET2" /* ZAPRET2 */);
}
if (options.includeByedpi) {
checks.push("BYEDPI" /* BYEDPI */);
}
@@ -3085,6 +3107,8 @@ var initialDiagnosticStore = {
sing_box_extended: 0,
zapret_version: "loading",
zapret_installed: 0,
zapret2_version: "loading",
zapret2_installed: 0,
byedpi_version: "loading",
byedpi_installed: 0,
server_inbounds_enabled_count: -1,
@@ -3129,6 +3153,9 @@ var initialDiagnosticStore = {
zapretCheck: { loading: false },
zapretInstall: { loading: false },
zapretRemove: { loading: false },
zapret2Check: { loading: false },
zapret2Install: { loading: false },
zapret2Remove: { loading: false },
byedpiCheck: { loading: false },
byedpiInstall: { loading: false },
byedpiRemove: { loading: false }
@@ -3137,6 +3164,7 @@ var initialDiagnosticStore = {
podkop: { status: null, latest_version: "", release_url: "" },
sing_box: { status: null, latest_version: "", release_url: "" },
zapret: { status: null, latest_version: "", release_url: "" },
zapret2: { status: null, latest_version: "", release_url: "" },
byedpi: { status: null, latest_version: "", release_url: "" }
}
};
@@ -5264,6 +5292,118 @@ async function runZapretCheck() {
});
}
// src/podkop/tabs/diagnostic/checks/runZapret2Check.ts
async function runZapret2Check() {
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.ZAPRET2;
updateCheckStore({
order,
code,
title,
description: _("Checking, please wait"),
state: "loading",
items: []
});
const zapret2Status = await PodkopShellMethods.getZapret2Status();
if (!zapret2Status.success) {
updateCheckStore({
order,
code,
title,
description: _("Cannot receive checks result"),
state: "error",
items: []
});
throw new Error("Zapret2 checks failed");
}
const data = zapret2Status.data;
const providerAvailable = Boolean(data.provider_available ?? data.installed);
const packageInstalled = Boolean(data.package_installed);
const hasZapret2Rules = Number(data.enabled_rule_count || 0) > 0;
const ready = Boolean(data.ready);
const queueOverlap = Boolean(data.queue_overlap);
const expectedProcesses = Number(data.expected_process_count || 0);
const runningProcesses = Number(data.running_process_count || 0);
const supervisorProcesses = Number(data.supervisor_process_count || 0);
const podkopRuntimeReady = !hasZapret2Rules || runningProcesses === expectedProcesses && supervisorProcesses === expectedProcesses;
const unexpectedRuntime = !hasZapret2Rules && (runningProcesses > 0 || supervisorProcesses > 0);
const outboundsConfigured = Boolean(data.outbounds_configured);
const routesConfigured = Boolean(data.routes_configured);
const standaloneServiceEnabled = Boolean(data.standalone_service_enabled);
const standaloneServiceRunning = Boolean(data.standalone_service_running);
const standaloneConflict = hasZapret2Rules && standaloneServiceRunning;
const standaloneAutostartRisk = hasZapret2Rules && standaloneServiceEnabled && !standaloneServiceRunning;
let state = "success";
let description = _("Checks passed");
if (hasZapret2Rules && !providerAvailable) {
state = "error";
description = _("Checks failed");
} else if (hasZapret2Rules && !ready) {
state = "error";
description = _("Checks failed");
} else if (queueOverlap) {
state = "error";
description = _("Checks failed");
} else if (standaloneConflict) {
state = "error";
description = _("Checks failed");
} else if (standaloneAutostartRisk || !packageInstalled) {
state = "warning";
description = _("Issues detected");
}
const items = [
{
state: providerAvailable ? "success" : hasZapret2Rules ? "error" : "warning",
key: providerAvailable ? _("Zapret2 provider binary is available") : _("Zapret2 provider binary is not available"),
value: ""
},
{
state: packageInstalled ? "success" : hasZapret2Rules ? "error" : "warning",
key: packageInstalled ? _("Zapret2 package is installed") : _("Zapret2 package is not installed"),
value: ""
},
{
state: hasZapret2Rules && !providerAvailable ? "error" : "success",
key: hasZapret2Rules ? _("There are rules using Zapret2") : _("No rules use Zapret2"),
value: ""
},
{
state: unexpectedRuntime || !podkopRuntimeReady ? "error" : "success",
key: hasZapret2Rules ? podkopRuntimeReady ? _("Podkop Plus-managed nfqws2 runtime is ready") : _("Podkop Plus-managed nfqws2 runtime is not ready") : unexpectedRuntime ? _("Unexpected Podkop Plus-managed nfqws2 runtime is running") : _("Podkop Plus-managed nfqws2 runtime is not running"),
value: hasZapret2Rules ? `${runningProcesses}/${expectedProcesses}` : ""
},
{
state: queueOverlap ? "error" : "success",
key: queueOverlap ? _("NFQUEUE range overlaps with another rule") : _("NFQUEUE range is available"),
value: `${Number(data.queue_base || 0)}-${Number(data.queue_range_end || 0)}`
},
{
state: !hasZapret2Rules || outboundsConfigured ? "success" : "error",
key: outboundsConfigured ? _("Zapret2 sing-box outbound is configured") : _("Zapret2 sing-box outbound is not configured"),
value: ""
},
{
state: !hasZapret2Rules || routesConfigured ? "success" : "error",
key: routesConfigured ? _("Zapret2 route rules are configured") : _("Zapret2 route rules are not configured"),
value: ""
},
{
state: standaloneConflict ? "error" : standaloneAutostartRisk ? "warning" : "success",
key: standaloneServiceRunning ? hasZapret2Rules ? _(
"Standalone Zapret2 is active together with Podkop Plus Zapret2 rules"
) : _("Standalone Zapret2 service is active") : standaloneAutostartRisk ? _("Standalone Zapret2 autostart is enabled") : _("Standalone Zapret2 service is inactive"),
value: ""
}
];
updateCheckStore({
order,
code,
title,
description,
state,
items
});
}
// src/podkop/tabs/diagnostic/checks/runByedpiCheck.ts
async function runByedpiCheck() {
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.BYEDPI;
@@ -5387,6 +5527,8 @@ var UNKNOWN_SYSTEM_INFO = {
sing_box_extended: 0,
zapret_version: _("unknown"),
zapret_installed: 0,
zapret2_version: _("unknown"),
zapret2_installed: 0,
byedpi_version: _("unknown"),
byedpi_installed: 0,
server_inbounds_enabled_count: -1,
@@ -5450,6 +5592,7 @@ async function ensureSystemInfo({
loaded: false,
providerInfoLoaded: latestSystemInfo.providerInfoLoaded,
zapret_installed: latestSystemInfo.zapret_installed,
zapret2_installed: latestSystemInfo.zapret2_installed,
byedpi_installed: latestSystemInfo.byedpi_installed,
server_inbounds_enabled_count: latestSystemInfo.server_inbounds_enabled_count
};
@@ -6268,6 +6411,7 @@ function sleep2(ms) {
function getDiagnosticsProviderOptions(systemInfo = store.get().diagnosticsSystemInfo) {
return {
includeZapret: Boolean(systemInfo.zapret_installed),
includeZapret2: Boolean(systemInfo.zapret2_installed),
includeByedpi: Boolean(systemInfo.byedpi_installed),
includeInbounds: systemInfo.server_inbounds_enabled_count !== 0
};
@@ -6391,8 +6535,9 @@ async function fetchDiagnosticsProviderInfo({
} = {}) {
const requestId = ++latestProviderInfoRequestId;
try {
const [zapretRuntime, byedpiRuntime, inboundsConfig] = await Promise.all([
const [zapretRuntime, zapret2Runtime, byedpiRuntime, inboundsConfig] = await Promise.all([
PodkopShellMethods.checkZapretRuntime(),
PodkopShellMethods.checkZapret2Runtime(),
PodkopShellMethods.checkByedpiRuntime(),
PodkopShellMethods.checkInboundsConfig()
]);
@@ -6404,12 +6549,20 @@ async function fetchDiagnosticsProviderInfo({
...currentSystemInfo,
providerInfoLoaded: true,
zapret_installed: zapretRuntime.success ? zapretRuntime.data.zapret_installed : currentSystemInfo.zapret_installed,
zapret2_installed: zapret2Runtime.success ? zapret2Runtime.data.zapret2_installed : currentSystemInfo.zapret2_installed,
byedpi_installed: byedpiRuntime.success ? byedpiRuntime.data.byedpi_installed : currentSystemInfo.byedpi_installed,
server_inbounds_enabled_count: inboundsConfig.success ? inboundsConfig.data.enabled_count : -1
};
if (!zapretRuntime.success) {
logger.error("[DIAGNOSTIC]", "fetchZapretRuntime failed", zapretRuntime);
}
if (!zapret2Runtime.success) {
logger.error(
"[DIAGNOSTIC]",
"fetchZapret2Runtime failed",
zapret2Runtime
);
}
if (!byedpiRuntime.success) {
logger.error("[DIAGNOSTIC]", "fetchByedpiRuntime failed", byedpiRuntime);
}
@@ -6423,6 +6576,9 @@ async function fetchDiagnosticsProviderInfo({
if (!nextSystemInfo.zapret_installed) {
nextSystemInfo.zapret_version = "not installed";
}
if (!nextSystemInfo.zapret2_installed) {
nextSystemInfo.zapret2_version = "not installed";
}
if (!nextSystemInfo.byedpi_installed) {
nextSystemInfo.byedpi_version = "not installed";
}
@@ -6727,6 +6883,12 @@ function renderDiagnosticSystemInfoWidget() {
value: diagnosticsSystemInfo.zapret_version
});
}
if (diagnosticsSystemInfo.zapret2_installed) {
items.push({
key: "Zapret2",
value: diagnosticsSystemInfo.zapret2_version
});
}
if (diagnosticsSystemInfo.byedpi_installed) {
items.push({
key: "ByeDPI",
@@ -6781,6 +6943,7 @@ async function runChecks() {
...providerOptions.includeInbounds ? [runInboundsCheck] : [],
runNftCheck,
...providerOptions.includeZapret ? [runZapretCheck] : [],
...providerOptions.includeZapret2 ? [runZapret2Check] : [],
...providerOptions.includeByedpi ? [runByedpiCheck] : [],
runSectionsCheck,
runFakeIPCheck
@@ -8914,6 +9077,7 @@ function notifyActionProvidersAvailabilityChanged(systemInfo) {
new CustomEvent(PODKOP_ACTION_PROVIDERS_AVAILABILITY_EVENT, {
detail: {
zapretInstalled: Boolean(systemInfo.zapret_installed),
zapret2Installed: Boolean(systemInfo.zapret2_installed),
byedpiInstalled: Boolean(systemInfo.byedpi_installed)
}
})
@@ -8950,6 +9114,16 @@ function patchSystemInfoAfterMutation(result) {
nextSystemInfo.zapret_version = version;
}
}
if (result.component === "zapret2") {
nextSystemInfo.providerInfoLoaded = true;
if (result.action === "remove") {
nextSystemInfo.zapret2_installed = 0;
nextSystemInfo.zapret2_version = "not installed";
} else {
nextSystemInfo.zapret2_installed = 1;
nextSystemInfo.zapret2_version = version;
}
}
if (result.component === "byedpi") {
nextSystemInfo.providerInfoLoaded = true;
if (result.action === "remove") {
@@ -8963,7 +9137,7 @@ function patchSystemInfoAfterMutation(result) {
store.set({
diagnosticsSystemInfo: nextSystemInfo
});
if (result.component === "zapret" || result.component === "byedpi") {
if (result.component === "zapret" || result.component === "zapret2" || result.component === "byedpi") {
notifyActionProvidersAvailabilityChanged(nextSystemInfo);
}
}
@@ -9044,6 +9218,7 @@ function getComponentCards() {
const systemInfo = store.get().diagnosticsSystemInfo;
const systemInfoLoading = isSystemInfoLoading();
const zapretInstalled = Boolean(systemInfo.zapret_installed);
const zapret2Installed = Boolean(systemInfo.zapret2_installed);
const byedpiInstalled = Boolean(systemInfo.byedpi_installed);
const singBoxInstallAction = systemInfo.sing_box_extended ? {
key: "singBoxInstallStable",
@@ -9074,11 +9249,7 @@ function getComponentCards() {
releaseUrl: getGitHubReleaseUrl("sing_box"),
tag: getCheckTag("sing_box"),
actions: [
getPrimaryUpdateAction(
"sing_box",
"singBoxCheck",
"singBoxInstall"
),
getPrimaryUpdateAction("sing_box", "singBoxCheck", "singBoxInstall"),
singBoxInstallAction
]
},
@@ -9106,6 +9277,30 @@ function getComponentCards() {
}
]
},
{
title: "Zapret2",
version: systemInfoLoading ? "loading" : zapret2Installed ? systemInfo.zapret2_version : _("Not installed"),
releaseUrl: getGitHubReleaseUrl("zapret2"),
tag: zapret2Installed ? getCheckTag("zapret2") : void 0,
actions: zapret2Installed ? [
getPrimaryUpdateAction("zapret2", "zapret2Check", "zapret2Install"),
{
key: "zapret2Remove",
text: _("Remove"),
icon: renderXIcon24,
component: "zapret2",
action: "remove"
}
] : [
{
key: "zapret2Install",
text: _("Install"),
icon: renderRotateCcwIcon24,
component: "zapret2",
action: "install"
}
]
},
{
title: "ByeDPI",
version: systemInfoLoading ? "loading" : byedpiInstalled ? systemInfo.byedpi_version : _("Not installed"),
@@ -9184,11 +9379,7 @@ function renderComponentCard(card) {
);
}
return E("div", { class: "pdk_updates-page__component" }, [
E(
"div",
{ class: "pdk_updates-page__component__header" },
headerChildren
),
E("div", { class: "pdk_updates-page__component__header" }, headerChildren),
E("div", { class: "pdk_updates-page__component__version" }, [
E(
"span",
@@ -105,6 +105,7 @@ const EntryPoint = {
loaded: false,
singBoxExtended: false,
zapretInstalled: false,
zapret2Installed: false,
byedpiInstalled: false,
serverInboundsEnabledCount: -1,
};
@@ -121,6 +122,7 @@ const EntryPoint = {
new CustomEvent(main.PODKOP_ACTION_PROVIDERS_AVAILABILITY_EVENT, {
detail: {
zapretInstalled: uiCapabilities.zapretInstalled,
zapret2Installed: uiCapabilities.zapret2Installed,
byedpiInstalled: uiCapabilities.byedpiInstalled,
},
}),
@@ -135,12 +137,16 @@ const EntryPoint = {
providerInfoLoaded: true,
sing_box_extended: uiCapabilities.singBoxExtended ? 1 : 0,
zapret_installed: uiCapabilities.zapretInstalled ? 1 : 0,
zapret2_installed: uiCapabilities.zapret2Installed ? 1 : 0,
byedpi_installed: uiCapabilities.byedpiInstalled ? 1 : 0,
server_inbounds_enabled_count:
uiCapabilities.serverInboundsEnabledCount,
zapret_version: uiCapabilities.zapretInstalled
? currentSystemInfo.zapret_version
: "not installed",
zapret2_version: uiCapabilities.zapret2Installed
? currentSystemInfo.zapret2_version
: "not installed",
byedpi_version: uiCapabilities.byedpiInstalled
? currentSystemInfo.byedpi_version
: "not installed",
@@ -157,6 +163,9 @@ const EntryPoint = {
uiCapabilities.zapretInstalled = Boolean(
Number(data?.zapret_installed) === 1,
);
uiCapabilities.zapret2Installed = Boolean(
Number(data?.zapret2_installed) === 1,
);
uiCapabilities.byedpiInstalled = Boolean(
Number(data?.byedpi_installed) === 1,
);
@@ -179,12 +188,14 @@ const EntryPoint = {
return Promise.allSettled([
main.PodkopShellMethods.getServerCapabilities(),
main.PodkopShellMethods.checkZapretRuntime(),
main.PodkopShellMethods.checkZapret2Runtime(),
main.PodkopShellMethods.checkByedpiRuntime(),
main.PodkopShellMethods.checkInboundsConfig(),
]).then(
([
serverCapabilitiesResult,
zapretRuntimeResult,
zapret2RuntimeResult,
byedpiRuntimeResult,
inboundsConfigResult,
]) => {
@@ -196,6 +207,10 @@ const EntryPoint = {
zapretRuntimeResult.status === "fulfilled"
? zapretRuntimeResult.value
: null;
const zapret2Runtime =
zapret2RuntimeResult.status === "fulfilled"
? zapret2RuntimeResult.value
: null;
const byedpiRuntime =
byedpiRuntimeResult.status === "fulfilled"
? byedpiRuntimeResult.value
@@ -216,6 +231,11 @@ const EntryPoint = {
Number(zapretRuntime.data?.zapret_installed) === 1
? 1
: 0,
zapret2_installed:
zapret2Runtime?.success &&
Number(zapret2Runtime.data?.zapret2_installed) === 1
? 1
: 0,
byedpi_installed:
byedpiRuntime?.success &&
Number(byedpiRuntime.data?.byedpi_installed) === 1
@@ -49,12 +49,17 @@ const ZAPRET_LEGACY_DEFAULT_NFQWS_OPT =
const ZAPRET_DEFAULT_NFQWS_OPT =
"--filter-tcp=80 --dpi-desync=fake,fakedsplit --dpi-desync-autottl=2 --dpi-desync-fooling=badsum --new --filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-repeats=11 --dpi-desync-fooling=badsum --dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com --new --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=11 --dpi-desync-fake-quic=/opt/zapret/files/fake/quic_initial_www_google_com.bin";
const ZAPRET2_DEFAULT_NFQWS2_OPT =
"--filter-tcp=80 --filter-l7=http --payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=multisplit:pos=method+2 --new --filter-tcp=443 --filter-l7=tls --payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=multidisorder:pos=1,midsld --new --filter-udp=443 --filter-l7=quic --payload=quic_initial --lua-desync=fake:blob=fake_default_quic:repeats=6";
const BYEDPI_DEFAULT_CMD_OPTS = "-o 2 --auto=t,r,a,s -d 2";
const ANNOTATED_TEXTAREA_STYLE_ID = "pdk-annotated-textarea-styles";
const NFQWS_REMOTE_VALIDATION_DEBOUNCE_MS = 500;
const NFQWS_VALIDATION_COMMAND = "/usr/bin/podkop-plus";
const nfqwsRemoteValidationCache = new Map();
const nfqwsRemoteValidationInflight = new Map();
const nfqws2RemoteValidationCache = new Map();
const nfqws2RemoteValidationInflight = new Map();
const byedpiRemoteValidationCache = new Map();
const byedpiRemoteValidationInflight = new Map();
const BYEDPI_LONG_VALUE_OPTIONS = new Set([
@@ -245,9 +250,70 @@ const NFQWS_REQUIRED_ARG_OPTIONS = new Set([
"--wssize-cutoff",
"--wssize-forced-cutoff",
]);
const NFQWS2_OPTIONAL_ARG_OPTIONS = new Set([
"--chdir",
"--comment",
"--ctrack-disable",
"--debug",
"--hostlist-auto-retrans-reset",
"--intercept",
"--ipcache-hostname",
"--new",
"--payload-disable",
"--reasm-disable",
"--server",
"--template",
"--writeable",
]);
const NFQWS2_NO_ARG_OPTIONS = new Set([
"--bind-fix4",
"--bind-fix6",
"--daemon",
"--dry-run",
"--skip",
"--version",
]);
const NFQWS2_REQUIRED_ARG_OPTIONS = new Set([
"--blob",
"--cookie",
"--ctrack-timeouts",
"--filter-l3",
"--filter-l7",
"--filter-tcp",
"--filter-udp",
"--fwmark",
"--fuzz",
"--hostlist",
"--hostlist-auto",
"--hostlist-auto-debug",
"--hostlist-auto-fail-threshold",
"--hostlist-auto-fail-time",
"--hostlist-auto-retrans-threshold",
"--hostlist-domains",
"--hostlist-exclude",
"--hostlist-exclude-domains",
"--import",
"--in-range",
"--ipcache-lifetime",
"--ipset",
"--ipset-exclude",
"--ipset-exclude-ip",
"--ipset-ip",
"--lua-gc",
"--lua-init",
"--lua-desync",
"--name",
"--out-range",
"--payload",
"--pidfile",
"--qnum",
"--uid",
"--user",
]);
const actionProvidersAvailabilityState = {
loaded: false,
zapretInstalled: false,
zapret2Installed: false,
byedpiInstalled: false,
};
let actionProvidersAvailabilityPromise = null;
@@ -275,6 +341,12 @@ function updateActionProvidersAvailabilityState(nextState) {
);
}
if (typeof nextState.zapret2Installed !== "undefined") {
actionProvidersAvailabilityState.zapret2Installed = Boolean(
nextState.zapret2Installed,
);
}
if (typeof nextState.byedpiInstalled !== "undefined") {
actionProvidersAvailabilityState.byedpiInstalled = Boolean(
nextState.byedpiInstalled,
@@ -291,6 +363,7 @@ function updateActionProvidersAvailabilityFromSystemInfo(systemInfo) {
updateActionProvidersAvailabilityState({
zapretInstalled: Boolean(systemInfo.zapret_installed),
zapret2Installed: Boolean(systemInfo.zapret2_installed),
byedpiInstalled: Boolean(systemInfo.byedpi_installed),
});
}
@@ -468,6 +541,7 @@ function ensureActionProvidersAvailabilityLoaded() {
.then((capabilities) => {
updateActionProvidersAvailabilityState({
zapretInstalled: Boolean(capabilities?.zapretInstalled),
zapret2Installed: Boolean(capabilities?.zapret2Installed),
byedpiInstalled: Boolean(capabilities?.byedpiInstalled),
});
return actionProvidersAvailabilityState;
@@ -486,13 +560,18 @@ function ensureActionProvidersAvailabilityLoaded() {
actionProvidersAvailabilityPromise = Promise.allSettled([
main.PodkopShellMethods.checkZapretRuntime(),
main.PodkopShellMethods.checkZapret2Runtime(),
main.PodkopShellMethods.checkByedpiRuntime(),
])
.then(([zapretResult, byedpiResult]) => {
.then(([zapretResult, zapret2Result, byedpiResult]) => {
const zapret =
zapretResult && zapretResult.status === "fulfilled"
? zapretResult.value
: null;
const zapret2 =
zapret2Result && zapret2Result.status === "fulfilled"
? zapret2Result.value
: null;
const byedpi =
byedpiResult && byedpiResult.status === "fulfilled"
? byedpiResult.value
@@ -500,22 +579,23 @@ function ensureActionProvidersAvailabilityLoaded() {
actionProvidersAvailabilityState.loaded = true;
actionProvidersAvailabilityState.zapretInstalled = Boolean(
zapret &&
zapret.success &&
zapret.data &&
zapret.data.zapret_installed,
zapret && zapret.success && zapret.data && zapret.data.zapret_installed,
);
actionProvidersAvailabilityState.zapret2Installed = Boolean(
zapret2 &&
zapret2.success &&
zapret2.data &&
zapret2.data.zapret2_installed,
);
actionProvidersAvailabilityState.byedpiInstalled = Boolean(
byedpi &&
byedpi.success &&
byedpi.data &&
byedpi.data.byedpi_installed,
byedpi && byedpi.success && byedpi.data && byedpi.data.byedpi_installed,
);
return actionProvidersAvailabilityState;
})
.catch(() => {
actionProvidersAvailabilityState.loaded = true;
actionProvidersAvailabilityState.zapretInstalled = false;
actionProvidersAvailabilityState.zapret2Installed = false;
actionProvidersAvailabilityState.byedpiInstalled = false;
return actionProvidersAvailabilityState;
})
@@ -530,6 +610,10 @@ function isZapretInstalledForUi() {
return actionProvidersAvailabilityState.zapretInstalled;
}
function isZapret2InstalledForUi() {
return actionProvidersAvailabilityState.zapret2Installed;
}
function isByedpiInstalledForUi() {
return actionProvidersAvailabilityState.byedpiInstalled;
}
@@ -553,6 +637,8 @@ function getActionOptionLabel(action) {
return "VPN";
case "zapret":
return "Zapret";
case "zapret2":
return "Zapret2";
case "byedpi":
return "ByeDPI";
case "outbound":
@@ -570,6 +656,10 @@ function getRuleActionDisplayValue(section_id) {
return "Zapret";
}
if (action === "zapret2") {
return "Zapret2";
}
if (action === "byedpi") {
return "ByeDPI";
}
@@ -592,6 +682,9 @@ function populateActionOptionValues(option) {
if (isZapretInstalledForUi()) {
option.value("zapret", getActionOptionLabel("zapret"));
}
if (isZapret2InstalledForUi()) {
option.value("zapret2", getActionOptionLabel("zapret2"));
}
if (isByedpiInstalledForUi()) {
option.value("byedpi", getActionOptionLabel("byedpi"));
}
@@ -1237,6 +1330,54 @@ function attachNfqwsRemoteValidation(option, section_id, textarea) {
scheduleValidation(0);
}
function attachNfqws2RemoteValidation(option, section_id, textarea) {
if (!textarea || textarea.__podkopNfqws2RemoteValidationAttached) {
return;
}
textarea.__podkopNfqws2RemoteValidationAttached = true;
textarea.__podkopNfqws2RemoteValidationRequestId = 0;
textarea.__podkopNfqws2RemoteValidationTimer = null;
const runValidation = () => {
const value = textarea.value;
const localAnalysis = buildNfqws2LocalAnalysis(value);
if (!localAnalysis.valid) {
refreshAnnotatedTextareaValidation(option, section_id, textarea);
return;
}
const requestId =
(textarea.__podkopNfqws2RemoteValidationRequestId || 0) + 1;
textarea.__podkopNfqws2RemoteValidationRequestId = requestId;
validateNfqws2StrategyRemotely(value).then(() => {
if (textarea.__podkopNfqws2RemoteValidationRequestId !== requestId) {
return;
}
refreshAnnotatedTextareaValidation(option, section_id, textarea);
});
};
const scheduleValidation = (delay = NFQWS_REMOTE_VALIDATION_DEBOUNCE_MS) => {
if (textarea.__podkopNfqws2RemoteValidationTimer) {
window.clearTimeout(textarea.__podkopNfqws2RemoteValidationTimer);
}
textarea.__podkopNfqws2RemoteValidationTimer = window.setTimeout(() => {
textarea.__podkopNfqws2RemoteValidationTimer = null;
runValidation();
}, delay);
};
textarea.addEventListener("input", () => scheduleValidation());
textarea.addEventListener("change", () => scheduleValidation(0));
textarea.addEventListener("blur", () => scheduleValidation(0));
scheduleValidation(0);
}
function parseCommentAwareListTokens(value) {
const text = value ? `${value}` : "";
const tokens = [];
@@ -1392,6 +1533,22 @@ function getNfqwsOptionArgumentMode(option) {
return "unknown";
}
function getNfqws2OptionArgumentMode(option) {
if (NFQWS2_REQUIRED_ARG_OPTIONS.has(option)) {
return "required";
}
if (NFQWS2_OPTIONAL_ARG_OPTIONS.has(option)) {
return "optional";
}
if (NFQWS2_NO_ARG_OPTIONS.has(option)) {
return "none";
}
return "unknown";
}
function normalizeNfqwsStrategyWhitespace(value) {
return value ? `${value}`.replace(/\s+/g, " ").trim() : "";
}
@@ -1863,6 +2020,414 @@ function analyzeNfqwsStrategy(value) {
};
}
function normalizeNfqws2StrategyValue(value) {
const normalized = normalizeNfqwsStrategyWhitespace(value);
return normalized.length ? normalized : ZAPRET2_DEFAULT_NFQWS2_OPT;
}
function getCachedNfqws2RemoteValidation(value) {
const normalized = normalizeNfqws2StrategyValue(value);
return normalized.length
? nfqws2RemoteValidationCache.get(normalized) || null
: null;
}
function cacheNfqws2RemoteValidation(value, result) {
const normalized = normalizeNfqws2StrategyValue(value);
if (!normalized.length) {
return result;
}
const cached = {
valid: result && result.valid === true,
message: result && result.message ? `${result.message}` : "",
needle: result && result.needle ? `${result.needle}` : "",
needles:
result && Array.isArray(result.needles)
? result.needles.filter(Boolean).map((item) => `${item}`)
: result && result.needle
? [`${result.needle}`]
: [],
};
nfqws2RemoteValidationCache.set(normalized, cached);
return cached;
}
function buildNfqws2RemoteValidationFallback(error) {
const message =
error && error.message
? `${error.message}`
: _("Unable to validate the NFQWS2 strategy through the backend parser.");
return {
valid: false,
message: _("Backend validation failed: %s").format(message),
needle: "",
needles: [],
};
}
function validateNfqws2StrategyRemotely(value) {
const normalized = normalizeNfqws2StrategyValue(value);
if (!normalized.length) {
return Promise.resolve({
valid: true,
message: "",
needle: "",
needles: [],
});
}
if (nfqws2RemoteValidationCache.has(normalized)) {
return Promise.resolve(nfqws2RemoteValidationCache.get(normalized));
}
if (nfqws2RemoteValidationInflight.has(normalized)) {
return nfqws2RemoteValidationInflight.get(normalized);
}
const validationTask = fs
.exec(NFQWS_VALIDATION_COMMAND, [
"validate_nfqws2_strategy_json",
normalized,
])
.then((result) => {
const payload = JSON.parse(
(result && result.stdout ? result.stdout : "{}").trim() || "{}",
);
return cacheNfqws2RemoteValidation(normalized, {
valid: payload.valid === true,
message: payload.message || "",
needle: payload.needle || "",
needles: Array.isArray(payload.needles)
? payload.needles.filter(Boolean)
: payload.needle
? [payload.needle]
: [],
});
})
.catch((error) =>
cacheNfqws2RemoteValidation(
normalized,
buildNfqws2RemoteValidationFallback(error),
),
)
.finally(() => {
nfqws2RemoteValidationInflight.delete(normalized);
});
nfqws2RemoteValidationInflight.set(normalized, validationTask);
return validationTask;
}
function getNfqws2ForbiddenTokenInfo(token, index, nextToken) {
const configFileMessage = _(
"External nfqws2 config files bypass Podkop Plus queue management and explicit validation.",
);
const hostSelectionMessage = _(
"Resource selection by hostname inside nfqws2 is not supported here; sing-box selects resources before NFQUEUE.",
);
const ipSelectionMessage = _(
"Resource selection by IP or CIDR inside nfqws2 is not supported here; sing-box selects resources before NFQUEUE.",
);
const placeholderMessage = _(
"Zapret2 hostlist templates are not supported here because Podkop Plus does not expand them for per-rule NFQWS2 strategies.",
);
const queueMessage = _(
"The NFQUEUE number is assigned by Podkop Plus for each rule and must not be overridden here.",
);
const fwmarkMessage = _(
"The desync fwmark is managed by Podkop Plus for loop prevention and must not be overridden here.",
);
const fuzzMessage = _(
"Fuzzing is not supported here because Podkop Plus needs deterministic runtime validation.",
);
const interceptMessage = _(
"Disabling interception is incompatible with action=zapret2 because Podkop Plus sends matched traffic through NFQUEUE.",
);
const daemonMessage = _(
"Podkop Plus manages the nfqws2 process lifecycle itself, so daemon mode is not allowed here.",
);
const dryRunMessage = _(
"This field must start a working nfqws2 strategy; --dry-run exits immediately and is not allowed here.",
);
const versionMessage = _(
"This field must start a working nfqws2 strategy; --version exits immediately and is not allowed here.",
);
if (index === 0 && (token.startsWith("@") || token.startsWith("$"))) {
return {
reason: configFileMessage,
captureNextValue: false,
};
}
if (token === "<HOSTLIST>" || token === "<HOSTLIST_NOAUTO>") {
return {
reason: placeholderMessage,
captureNextValue: false,
};
}
if (
token === "--hostlist" ||
token.startsWith("--hostlist=") ||
token === "--hostlist-domains" ||
token.startsWith("--hostlist-domains=") ||
token === "--hostlist-exclude" ||
token.startsWith("--hostlist-exclude=") ||
token === "--hostlist-exclude-domains" ||
token.startsWith("--hostlist-exclude-domains=") ||
token === "--hostlist-auto" ||
token.startsWith("--hostlist-auto=") ||
token === "--hostlist-auto-fail-threshold" ||
token.startsWith("--hostlist-auto-fail-threshold=") ||
token === "--hostlist-auto-fail-time" ||
token.startsWith("--hostlist-auto-fail-time=") ||
token === "--hostlist-auto-retrans-threshold" ||
token.startsWith("--hostlist-auto-retrans-threshold=") ||
token === "--hostlist-auto-debug" ||
token.startsWith("--hostlist-auto-debug=") ||
token === "--hostlist-auto-retrans-reset" ||
token.startsWith("--hostlist-auto-retrans-reset=")
) {
return {
reason: hostSelectionMessage,
captureNextValue: !token.includes("="),
};
}
if (
token === "--ipset" ||
token.startsWith("--ipset=") ||
token === "--ipset-ip" ||
token.startsWith("--ipset-ip=") ||
token === "--ipset-exclude" ||
token.startsWith("--ipset-exclude=") ||
token === "--ipset-exclude-ip" ||
token.startsWith("--ipset-exclude-ip=")
) {
return {
reason: ipSelectionMessage,
captureNextValue: !token.includes("="),
};
}
if (token === "--qnum" || token.startsWith("--qnum=")) {
return {
reason: queueMessage,
captureNextValue: !token.includes("="),
};
}
if (
token === "--fwmark" ||
token.startsWith("--fwmark=") ||
token === "--dpi-desync-fwmark" ||
token.startsWith("--dpi-desync-fwmark=")
) {
return {
reason: fwmarkMessage,
captureNextValue: !token.includes("="),
};
}
if (token === "--fuzz" || token.startsWith("--fuzz=")) {
return {
reason: fuzzMessage,
captureNextValue: !token.includes("="),
};
}
if (
token === "--intercept=0" ||
token === "--intercept=false" ||
token === "--intercept=no" ||
(token === "--intercept" && nextToken && nextToken.value === "0")
) {
return {
reason: interceptMessage,
captureNextValue: token === "--intercept",
};
}
if (token === "--daemon") {
return {
reason: daemonMessage,
captureNextValue: false,
};
}
if (token === "--dry-run") {
return {
reason: dryRunMessage,
captureNextValue: false,
};
}
if (token === "--version") {
return {
reason: versionMessage,
captureNextValue: false,
};
}
return null;
}
function buildNfqws2LocalAnalysis(value) {
const text = value ? `${value}` : "";
if (!text.trim().length) {
return {
valid: false,
message: _("NFQWS2 strategy cannot be empty"),
annotations: [],
};
}
const tokens = parseNfqwsRuntimeTokens(text);
const annotationMap = new Map();
const errors = [];
for (let index = 0; index < tokens.length; ) {
const token = tokens[index];
const bareToken = token.value.includes("=")
? token.value.slice(0, token.value.indexOf("="))
: token.value;
const nextToken = tokens[index + 1] || null;
const forbidden = getNfqws2ForbiddenTokenInfo(
token.value,
index,
nextToken,
);
if (forbidden) {
addAnnotationIssue(annotationMap, token, forbidden.reason);
let displayToken = token.value;
if (
forbidden.captureNextValue &&
nextToken &&
!nextToken.value.startsWith("--")
) {
addAnnotationIssue(annotationMap, nextToken, forbidden.reason);
displayToken = `${displayToken} ${nextToken.value}`;
index += 2;
} else {
index += 1;
}
errors.push(`${displayToken}: ${forbidden.reason}`);
continue;
}
if (!token.value.startsWith("--")) {
const reason = _(
"Unexpected standalone token. Use explicit flags such as --name or --name=value.",
);
addAnnotationIssue(annotationMap, token, reason);
errors.push(`${token.value}: ${reason}`);
index += 1;
continue;
}
const mode = getNfqws2OptionArgumentMode(bareToken);
if (mode === "unknown") {
const reason = _("Unknown NFQWS2 flag.");
addAnnotationIssue(annotationMap, token, reason);
errors.push(`${token.value}: ${reason}`);
index += 1;
continue;
}
if (mode === "none") {
if (token.value.includes("=")) {
const reason = _("This flag does not accept a value.");
addAnnotationIssue(annotationMap, token, reason);
errors.push(`${token.value}: ${reason}`);
}
index += 1;
continue;
}
if (mode === "optional") {
if (
nextToken &&
!token.value.includes("=") &&
!nextToken.value.startsWith("--")
) {
const reason = _(
"Optional values must be attached with '=' here; a separate token would be ignored by nfqws2.",
);
addAnnotationIssue(annotationMap, token, reason);
addAnnotationIssue(annotationMap, nextToken, reason);
errors.push(`${token.value} ${nextToken.value}: ${reason}`);
index += 2;
} else {
index += 1;
}
continue;
}
if (!token.value.includes("=")) {
if (!nextToken || nextToken.value.startsWith("--")) {
const reason = _("This option requires a value.");
addAnnotationIssue(annotationMap, token, reason);
errors.push(`${token.value}: ${reason}`);
index += 1;
continue;
}
index += 2;
continue;
}
index += 1;
}
if (!errors.length) {
return { valid: true, message: "", annotations: [] };
}
return {
valid: false,
message: [getValidationHeaderText(), ...errors].join("\n"),
annotations: finalizeAnnotations(annotationMap),
};
}
function analyzeNfqws2Strategy(value) {
const localAnalysis = buildNfqws2LocalAnalysis(value);
if (!localAnalysis.valid) {
return localAnalysis;
}
const remoteValidation = getCachedNfqws2RemoteValidation(value);
if (!remoteValidation || remoteValidation.valid) {
return localAnalysis;
}
const text = value ? `${value}` : "";
const tokens = parseNfqwsRuntimeTokens(text);
const annotationMap = new Map();
localAnalysis.annotations.forEach((annotation) =>
addAnnotationIssue(annotationMap, annotation, annotation.message),
);
addNfqwsRemoteValidationAnnotations(annotationMap, tokens, remoteValidation);
return {
valid: false,
message: [getValidationHeaderText(), remoteValidation.message].join("\n"),
annotations: finalizeAnnotations(annotationMap),
};
}
function normalizeByedpiStrategyWhitespace(value) {
return value ? `${value}`.replace(/\s+/g, " ").trim() : "";
}
@@ -2259,7 +2824,7 @@ function analyzeByedpiStrategy(value) {
};
}
function configureTextareaOption(option, analyzer) {
function configureTextareaOption(option, analyzer, remoteValidationAttacher) {
const originalRenderWidget = option.renderWidget;
option.renderWidget = function (section_id, option_index, cfgvalue) {
@@ -2279,12 +2844,127 @@ function configureTextareaOption(option, analyzer) {
if (typeof analyzer === "function") {
attachAnnotatedTextarea(textarea, analyzer);
}
if (typeof remoteValidationAttacher === "function") {
remoteValidationAttacher(this, section_id, textarea);
}
}
return node;
};
}
function getOptionTextarea(option, section_id) {
const field =
typeof option.map.findElement === "function"
? option.map.findElement("data-field", option.cbid(section_id))
: null;
if (field && typeof field.querySelector === "function") {
return field.querySelector("textarea");
}
const elem =
typeof option.getUIElement === "function"
? option.getUIElement(section_id)
: null;
const node = elem && elem.node ? elem.node : null;
if (node && node.nodeName === "TEXTAREA") {
return node;
}
return node && typeof node.querySelector === "function"
? node.querySelector("textarea")
: null;
}
function rejectStrategyValidation(option, section_id, message) {
const title = option.stripTags(option.title).trim();
const error = message || option.getValidationError(section_id) || "";
return Promise.reject(
new TypeError(
`${_('Option "%s" contains an invalid input value.').format(title || option.option)} ${error}`,
),
);
}
function parseStrategyWithRemoteValidation(section_id, config) {
const active = this.isActive(section_id);
if (active) {
if (typeof this.triggerValidation === "function") {
this.triggerValidation(section_id);
}
if (!this.isValid(section_id)) {
return rejectStrategyValidation(
this,
section_id,
this.getValidationError(section_id),
);
}
const cval = this.cfgvalue(section_id);
const fval = this.formvalue(section_id);
const cvalString = cval == null ? "" : `${cval}`;
const fvalString = fval == null ? "" : `${fval}`;
const shouldWrite = this.forcewrite || cvalString !== fvalString;
if (!shouldWrite) {
return Promise.resolve();
}
return config.remoteValidate(fvalString).then((result) => {
const textarea = getOptionTextarea(this, section_id);
if (textarea) {
refreshAnnotatedTextareaValidation(this, section_id, textarea);
}
if (typeof this.triggerValidation === "function") {
this.triggerValidation(section_id);
}
if (!result || result.valid !== true) {
return rejectStrategyValidation(
this,
section_id,
result && result.message
? result.message
: config.invalidMessage,
);
}
return Promise.resolve(this.write(section_id, fvalString));
});
}
if (!this.retain) {
return Promise.resolve(this.remove(section_id));
}
return Promise.resolve();
}
function parseNfqwsStrategyOnSave(section_id) {
return parseStrategyWithRemoteValidation.call(this, section_id, {
remoteValidate: validateNfqwsStrategyRemotely,
invalidMessage: _(
"Unable to validate the NFQWS strategy through the backend parser.",
),
});
}
function parseNfqws2StrategyOnSave(section_id) {
return parseStrategyWithRemoteValidation.call(this, section_id, {
remoteValidate: validateNfqws2StrategyRemotely,
invalidMessage: _(
"Unable to validate the NFQWS2 strategy through the backend parser.",
),
});
}
function addDynamicConditionField(section, config) {
const o = section.taboption(
"conditions",
@@ -2593,13 +3273,69 @@ function createSectionContent(section) {
? ZAPRET_DEFAULT_NFQWS_OPT
: normalized;
uci.set(UCI_PACKAGE, section_id, "nfqws_opt", nextValue);
return validateNfqwsStrategyRemotely(nextValue).then((result) => {
if (!result || result.valid !== true) {
throw new TypeError(
result && result.message
? result.message
: _(
"Unable to validate the NFQWS strategy through the backend parser.",
),
);
}
uci.set(UCI_PACKAGE, section_id, "nfqws_opt", nextValue);
});
};
o.validate = function (_section_id, value) {
const analysis = analyzeNfqwsStrategy(value);
return analysis.valid ? true : analysis.message;
};
configureTextareaOption(o, analyzeNfqwsStrategy);
o.parse = parseNfqwsStrategyOnSave;
configureTextareaOption(o, analyzeNfqwsStrategy, attachNfqwsRemoteValidation);
o = section.taboption(
"settings",
form.TextValue,
"nfqws2_opt",
_("NFQWS2 Strategy"),
);
o.depends("action", "zapret2");
o.rows = 6;
o.wrap = "soft";
o.textarea = true;
o.modalonly = true;
o.load = function (section_id) {
return (
uci.get(UCI_PACKAGE, section_id, "nfqws2_opt") ||
ZAPRET2_DEFAULT_NFQWS2_OPT
);
};
o.write = function (section_id, value) {
const normalized = normalizeNfqws2StrategyValue(value);
return validateNfqws2StrategyRemotely(normalized).then((result) => {
if (!result || result.valid !== true) {
throw new TypeError(
result && result.message
? result.message
: _("Invalid NFQWS2 strategy"),
);
}
uci.set(UCI_PACKAGE, section_id, "nfqws2_opt", normalized);
});
};
o.validate = function (_section_id, value) {
const analysis = analyzeNfqws2Strategy(value);
return analysis.valid ? true : analysis.message;
};
o.parse = parseNfqws2StrategyOnSave;
configureTextareaOption(
o,
analyzeNfqws2Strategy,
attachNfqws2RemoteValidation,
);
o = section.taboption(
"settings",
@@ -3130,6 +3866,7 @@ function createSectionContent(section) {
o.depends("action", "vpn");
o.depends("action", "byedpi");
o.depends("action", "zapret");
o.depends("action", "zapret2");
o.modalonly = true;
o = section.taboption(
@@ -3145,6 +3882,7 @@ function createSectionContent(section) {
o.depends({ action: "vpn", mixed_proxy_enabled: "1" });
o.depends({ action: "byedpi", mixed_proxy_enabled: "1" });
o.depends({ action: "zapret", mixed_proxy_enabled: "1" });
o.depends({ action: "zapret2", mixed_proxy_enabled: "1" });
o.modalonly = true;
o.validate = function (_section_id, value) {
if (!value || value.length === 0) {
@@ -3173,6 +3911,7 @@ function createSectionContent(section) {
o.depends({ action: "vpn", mixed_proxy_enabled: "1" });
o.depends({ action: "byedpi", mixed_proxy_enabled: "1" });
o.depends({ action: "zapret", mixed_proxy_enabled: "1" });
o.depends({ action: "zapret2", mixed_proxy_enabled: "1" });
o.modalonly = true;
o = section.taboption(
@@ -3207,6 +3946,11 @@ function createSectionContent(section) {
mixed_proxy_enabled: "1",
mixed_proxy_auth_enabled: "1",
});
o.depends({
action: "zapret2",
mixed_proxy_enabled: "1",
mixed_proxy_auth_enabled: "1",
});
o.modalonly = true;
o.validate = function (_section_id, value) {
if (!value || value.length === 0) {
@@ -3248,6 +3992,11 @@ function createSectionContent(section) {
mixed_proxy_enabled: "1",
mixed_proxy_auth_enabled: "1",
});
o.depends({
action: "zapret2",
mixed_proxy_enabled: "1",
mixed_proxy_auth_enabled: "1",
});
o.modalonly = true;
o.validate = function (_section_id, value) {
if (!value || value.length === 0) {
@@ -15,6 +15,7 @@ const ROUTING_SECTION_ACTIONS = [
"vpn",
"byedpi",
"zapret",
"zapret2",
"direct",
"block",
];
+101 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-06-02 17:11+0300\n"
"PO-Revision-Date: 2026-06-02 17:11+0300\n"
"POT-Creation-Date: 2026-06-02 18:22+0300\n"
"PO-Revision-Date: 2026-06-02 18:22+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -299,6 +299,9 @@ msgstr "Отключить QUIC"
msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming"
msgstr "Отключить QUIC протокол для улучшения совместимости или исправления видео стриминга"
msgid "Disabling interception is incompatible with action=zapret2 because Podkop Plus sends matched traffic through NFQUEUE."
msgstr "Отключение перехвата несовместимо с action=zapret2, потому что Podkop Plus отправляет совпавший трафик через NFQUEUE."
msgid "DNS on router"
msgstr "DNS на роутере"
@@ -443,6 +446,9 @@ msgstr "Открыть эту секцию как локальный HTTP+SOCKS
msgid "External nfqws config files bypass Podkop Plus queue management and explicit validation."
msgstr "Внешние конфиги nfqws обходят управление очередями и явную валидацию Podkop Plus."
msgid "External nfqws2 config files bypass Podkop Plus queue management and explicit validation."
msgstr "Внешние конфиги nfqws2 обходят управление очередями и явную валидацию Podkop Plus."
msgid "Failed to close connection"
msgstr "Не удалось закрыть подключение"
@@ -485,6 +491,9 @@ msgstr "Proxy protocol для фронтинга"
msgid "Fully Routed IPs"
msgstr "Полностью маршрутизированные IP-адреса"
msgid "Fuzzing is not supported here because Podkop Plus needs deterministic runtime validation."
msgstr "Fuzzing здесь не поддерживается, потому что Podkop Plus нужна предсказуемая runtime-валидация."
msgid "Generated inbound"
msgstr "Созданный inbound"
@@ -608,6 +617,9 @@ msgstr "Неверный IP-адрес"
msgid "Invalid JSON format"
msgstr "Неверный формат JSON"
msgid "Invalid NFQWS2 strategy"
msgstr "Некорректная стратегия NFQWS2"
msgid "Invalid path format. Path must start with \"/\" and contain valid characters"
msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы"
@@ -803,6 +815,12 @@ msgstr "Стратегия NFQWS"
msgid "NFQWS strategy cannot be empty"
msgstr "Стратегия NFQWS не может быть пустой"
msgid "NFQWS2 Strategy"
msgstr "Стратегия NFQWS2"
msgid "NFQWS2 strategy cannot be empty"
msgstr "Стратегия NFQWS2 не может быть пустой"
msgid "No active connections"
msgstr "Нет активных подключений"
@@ -827,6 +845,9 @@ msgstr "Нет правил, использующих ByeDPI"
msgid "No rules use Zapret"
msgstr "Нет правил, использующих Zapret"
msgid "No rules use Zapret2"
msgstr "Нет правил, использующих Zapret2"
msgid "None"
msgstr "Нет"
@@ -884,6 +905,9 @@ msgstr "Параметр \"%s\" содержит некорректное зна
msgid "Optional values must be attached with '=' here; a separate token would be ignored by nfqws."
msgstr "Необязательное значение здесь нужно передавать только через '='; отдельный токен nfqws проигнорирует."
msgid "Optional values must be attached with '=' here; a separate token would be ignored by nfqws2."
msgstr "Необязательные значения здесь должны задаваться через '='; отдельный токен будет проигнорирован nfqws2."
msgid "Outbound JSON"
msgstr "JSON outbound"
@@ -938,6 +962,9 @@ msgstr "Podkop Plus сам управляет жизненным циклом ci
msgid "Podkop Plus manages the nfqws process lifecycle itself, so daemon mode is not allowed here."
msgstr "Podkop Plus сам управляет жизненным циклом nfqws, поэтому режим daemon здесь запрещён."
msgid "Podkop Plus manages the nfqws2 process lifecycle itself, so daemon mode is not allowed here."
msgstr "Podkop Plus сам управляет жизненным циклом nfqws2, поэтому режим daemon здесь запрещён."
msgid "Podkop Plus rules"
msgstr "Правила Podkop Plus"
@@ -968,6 +995,15 @@ msgstr "NFQWS Podkop Plus не запущен"
msgid "Podkop Plus-managed nfqws runtime is ready"
msgstr "NFQWS Podkop Plus готов"
msgid "Podkop Plus-managed nfqws2 runtime is not ready"
msgstr "NFQWS2 Podkop Plus не готов"
msgid "Podkop Plus-managed nfqws2 runtime is not running"
msgstr "NFQWS2 Podkop Plus не запущен"
msgid "Podkop Plus-managed nfqws2 runtime is ready"
msgstr "NFQWS2 Podkop Plus готов"
msgid "Port cannot be empty"
msgstr "Порт не может быть пустым"
@@ -1052,9 +1088,15 @@ msgstr "Определять страны серверов через country.is
msgid "Resource selection by hostname inside nfqws is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по hostname внутри nfqws здесь не поддерживается; ресурсы выбирает sing-box до NFQUEUE."
msgid "Resource selection by hostname inside nfqws2 is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по hostname внутри nfqws2 здесь не поддерживается; sing-box выбирает ресурсы до NFQUEUE."
msgid "Resource selection by IP or CIDR inside nfqws is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по IP или CIDR внутри nfqws здесь не поддерживается; ресурсы выбирает sing-box до NFQUEUE."
msgid "Resource selection by IP or CIDR inside nfqws2 is not supported here; sing-box selects resources before NFQUEUE."
msgstr "Выбор ресурсов по IP или CIDR внутри nfqws2 здесь не поддерживается; sing-box выбирает ресурсы до NFQUEUE."
msgid "Restart Podkop Plus"
msgstr "Перезапустить Podkop Plus"
@@ -1277,6 +1319,18 @@ msgstr "Отдельная служба Zapret активна"
msgid "Standalone Zapret service is inactive"
msgstr "Отдельная служба Zapret неактивна"
msgid "Standalone Zapret2 autostart is enabled"
msgstr "Автозапуск отдельной службы Zapret2 включён"
msgid "Standalone Zapret2 is active together with Podkop Plus Zapret2 rules"
msgstr "Отдельная служба Zapret2 активна вместе с правилами Zapret2 в Podkop Plus"
msgid "Standalone Zapret2 service is active"
msgstr "Отдельная служба Zapret2 активна"
msgid "Standalone Zapret2 service is inactive"
msgstr "Отдельная служба Zapret2 неактивна"
msgid "Start Podkop Plus"
msgstr "Запустить Podkop Plus"
@@ -1346,6 +1400,9 @@ msgstr "Есть правила, использующие ByeDPI"
msgid "There are rules using Zapret"
msgstr "Есть правила использующие Zapret"
msgid "There are rules using Zapret2"
msgstr "Есть правила, использующие Zapret2"
msgid "This field is required"
msgstr "Заполните это поле"
@@ -1358,6 +1415,12 @@ msgstr "Это поле должно запускать рабочую стра
msgid "This field must start a working nfqws strategy; --version exits immediately and is not allowed here."
msgstr "Это поле должно запускать рабочую стратегию nfqws; --version сразу завершает процесс и здесь запрещён."
msgid "This field must start a working nfqws2 strategy; --dry-run exits immediately and is not allowed here."
msgstr "Это поле должно запускать рабочую стратегию nfqws2; --dry-run сразу завершает работу и здесь запрещён."
msgid "This field must start a working nfqws2 strategy; --version exits immediately and is not allowed here."
msgstr "Это поле должно запускать рабочую стратегию nfqws2; --version сразу завершает работу и здесь запрещён."
msgid "This flag does not accept a value."
msgstr "Этот флаг не принимает значение."
@@ -1418,6 +1481,9 @@ msgstr "Не удалось проверить стратегию ByeDPI чер
msgid "Unable to validate the NFQWS strategy through the backend parser."
msgstr "Не удалось проверить стратегию NFQWS через backend-парсер."
msgid "Unable to validate the NFQWS2 strategy through the backend parser."
msgstr "Не удалось проверить стратегию NFQWS2 через backend-парсер."
msgid "Unexpected ByeDPI strategy argument: %s"
msgstr "Неожиданный аргумент стратегии ByeDPI: %s"
@@ -1427,6 +1493,9 @@ msgstr "Обнаружен лишний ciadpi Podkop Plus"
msgid "Unexpected Podkop Plus-managed nfqws runtime is running"
msgstr "Обнаружен лишний NFQWS Podkop Plus"
msgid "Unexpected Podkop Plus-managed nfqws2 runtime is running"
msgstr "Обнаружен лишний NFQWS2 Podkop Plus"
msgid "Unexpected standalone token. Use explicit flags such as --name or --name=value."
msgstr "Неожиданный отдельный токен. Используйте явные флаги вида --имя или --имя=значение."
@@ -1448,6 +1517,9 @@ msgstr "Неизвестная ошибка"
msgid "Unknown NFQWS flag."
msgstr "Неизвестный флаг NFQWS."
msgid "Unknown NFQWS2 flag."
msgstr "Неизвестный флаг NFQWS2."
msgid "Unsupported security mode for this protocol"
msgstr "Этот протокол не поддерживает выбранный режим защиты"
@@ -1591,3 +1663,30 @@ msgstr "Бинарник провайдера Zapret доступен"
msgid "Zapret provider binary is not available"
msgstr "Бинарник провайдера Zapret недоступен"
msgid "Zapret2 hostlist templates are not supported here because Podkop Plus does not expand them for per-rule NFQWS2 strategies."
msgstr "Шаблоны hostlist Zapret2 здесь не поддерживаются, потому что Podkop Plus не разворачивает их для per-rule стратегий NFQWS2."
msgid "Zapret2 package is installed"
msgstr "Пакет Zapret2 установлен"
msgid "Zapret2 package is not installed"
msgstr "Пакет Zapret2 не установлен"
msgid "Zapret2 provider binary is available"
msgstr "Бинарник провайдера Zapret2 доступен"
msgid "Zapret2 provider binary is not available"
msgstr "Бинарник провайдера Zapret2 недоступен"
msgid "Zapret2 route rules are configured"
msgstr "Правила маршрутизации Zapret2 настроены"
msgid "Zapret2 route rules are not configured"
msgstr "Правила маршрутизации Zapret2 не настроены"
msgid "Zapret2 sing-box outbound is configured"
msgstr "sing-box outbound для Zapret2 настроен"
msgid "Zapret2 sing-box outbound is not configured"
msgstr "sing-box outbound для Zapret2 не настроен"
File diff suppressed because it is too large Load Diff
+242 -19
View File
@@ -124,7 +124,7 @@ podkop_fast_ui_capabilities_server_handler() {
}
podkop_fast_get_ui_capabilities() {
local sing_box_version sing_box_extended=0 zapret_installed=0 byedpi_installed=0
local sing_box_version sing_box_extended=0 zapret_installed=0 zapret2_installed=0 byedpi_installed=0
if command -v sing-box >/dev/null 2>&1; then
sing_box_version="$(sing-box version 2>/dev/null | head -1 | awk '{print $NF}')"
@@ -134,15 +134,17 @@ podkop_fast_get_ui_capabilities() {
fi
[ -x "$ZAPRET_PROVIDER_NFQWS_BIN" ] && zapret_installed=1
[ -x "$ZAPRET2_PROVIDER_NFQWS2_BIN" ] && zapret2_installed=1
[ -x "$BYEDPI_BIN" ] && byedpi_installed=1
PODKOP_FAST_SERVER_INBOUNDS_ENABLED_COUNT=0
config_load "$PODKOP_CONFIG_NAME"
config_foreach podkop_fast_ui_capabilities_server_handler "server"
printf '{ "sing_box_extended": %s, "zapret_installed": %s, "byedpi_installed": %s, "server_inbounds_enabled_count": %s }\n' \
printf '{ "sing_box_extended": %s, "zapret_installed": %s, "zapret2_installed": %s, "byedpi_installed": %s, "server_inbounds_enabled_count": %s }\n' \
"$sing_box_extended" \
"$zapret_installed" \
"$zapret2_installed" \
"$byedpi_installed" \
"${PODKOP_FAST_SERVER_INBOUNDS_ENABLED_COUNT:-0}"
}
@@ -160,6 +162,7 @@ check_required_file "$PODKOP_LIB/helpers.sh"
check_required_file "$PODKOP_LIB/json_utils.uc"
check_required_file "$PODKOP_LIB/provider_status.uc"
check_required_file "$PODKOP_LIB/zapret.sh"
check_required_file "$PODKOP_LIB/zapret2.sh"
check_required_file "$PODKOP_LIB/byedpi.sh"
check_required_file "$PODKOP_LIB/sing_box_config_manager.sh"
check_required_file "$PODKOP_LIB/sing_box_config_manager.uc"
@@ -176,6 +179,7 @@ check_required_file "$PODKOP_LIB/updater.sh"
. "$PODKOP_LIB/nft.sh"
. "$PODKOP_LIB/helpers.sh"
. "$PODKOP_LIB/zapret.sh"
. "$PODKOP_LIB/zapret2.sh"
. "$PODKOP_LIB/byedpi.sh"
. "$PODKOP_LIB/sing_box_config_manager.sh"
. "$PODKOP_LIB/sing_box_config_facade.sh"
@@ -214,6 +218,7 @@ PODKOP_SUBSCRIPTION_UPDATE_JOB_FINISHED_TTL_MINUTES=60
PODKOP_SUBSCRIPTION_UPDATE_JOB_ORPHAN_OUTPUT_TTL_MINUTES=60
PODKOP_NFT_POPULATE_ENABLED=1
PODKOP_ZAPRET_MISSING_WARNING_EMITTED=0
PODKOP_ZAPRET2_MISSING_WARNING_EMITTED=0
PODKOP_BYEDPI_MISSING_WARNING_EMITTED=0
PODKOP_URLTEST_NEW_ENABLED_SECTIONS=""
PODKOP_URLTEST_SELECTOR_SWITCHES=""
@@ -1745,7 +1750,7 @@ append_sing_box_rule_signature() {
signature_add "rule.$section.resolve_real_ip_for_routing" "1"
;;
zapret)
zapret | zapret2)
config_get_bool mixed_proxy_enabled "$section" "mixed_proxy_enabled" 0
signature_add "rule.$section.mixed_proxy_enabled" "$mixed_proxy_enabled"
if [ "$mixed_proxy_enabled" -eq 1 ]; then
@@ -2105,6 +2110,63 @@ build_zapret_runtime_signature() {
signature_finish
}
append_zapret2_queue_signature() {
local section="$1"
rule_is_enabled "$section" || return 0
[ "$(get_rule_action "$section")" = "zapret2" ] || return 0
signature_add "zapret2_queue.section" "$section"
}
build_zapret2_queue_signature() {
signature_begin
config_foreach append_zapret2_queue_signature "section"
signature_finish
}
append_zapret2_runtime_signature() {
local section="$1"
local domain domain_suffix community_lists rule_set rule_set_with_subnets domain_ip_lists user_domain_list_type items
rule_is_enabled "$section" || return 0
[ "$(get_rule_action "$section")" = "zapret2" ] || return 0
domain="$(get_rule_condition_commas_string "$section" "domain" "domains")"
domain_suffix="$(get_rule_condition_commas_string "$section" "domain_suffix" "domains")"
config_get community_lists "$section" "community_lists"
config_get rule_set "$section" "rule_set"
config_get rule_set_with_subnets "$section" "rule_set_with_subnets"
config_get domain_ip_lists "$section" "domain_ip_lists"
config_get user_domain_list_type "$section" "user_domain_list_type" "disabled"
signature_add "zapret2.$section.nfqws2_opt" "$(get_rule_nfqws2_opt "$section")"
signature_add "zapret2.$section.domain" "$domain"
signature_add "zapret2.$section.domain_suffix" "$domain_suffix"
signature_add "zapret2.$section.community_lists" "$community_lists"
signature_add "zapret2.$section.rule_set" "$rule_set"
signature_add "zapret2.$section.rule_set_with_subnets" "$rule_set_with_subnets"
signature_add "zapret2.$section.domain_ip_lists" "$domain_ip_lists"
signature_add "zapret2.$section.user_domain_list_type" "$user_domain_list_type"
config_get items "$section" "local_domain_lists"
signature_add "zapret2.$section.local_domain_lists" "$items"
config_get items "$section" "remote_domain_lists"
signature_add "zapret2.$section.remote_domain_lists" "$items"
case "$user_domain_list_type" in
dynamic) config_get items "$section" "user_domains" ;;
text) config_get items "$section" "user_domains_text" ;;
*) items="" ;;
esac
signature_add "zapret2.$section.user_domains" "$items"
}
build_zapret2_runtime_signature() {
signature_begin
config_foreach append_zapret2_runtime_signature "section"
signature_finish
}
append_byedpi_runtime_signature() {
local section="$1"
local byedpi_index
@@ -2394,6 +2456,8 @@ capture_reload_state() {
RELOAD_STATE_NFT_SIGNATURE="$(build_nft_signature)"
RELOAD_STATE_ZAPRET_QUEUE_SIGNATURE="$(build_zapret_queue_signature)"
RELOAD_STATE_ZAPRET_RUNTIME_SIGNATURE="$(build_zapret_runtime_signature)"
RELOAD_STATE_ZAPRET2_QUEUE_SIGNATURE="$(build_zapret2_queue_signature)"
RELOAD_STATE_ZAPRET2_RUNTIME_SIGNATURE="$(build_zapret2_runtime_signature)"
RELOAD_STATE_BYEDPI_RUNTIME_SIGNATURE="$(build_byedpi_runtime_signature)"
RELOAD_STATE_LIST_SIGNATURE="$(build_list_update_signature)"
RELOAD_STATE_CRON_SIGNATURE="$(build_cron_signature)"
@@ -2411,6 +2475,8 @@ sing_box_signature=$RELOAD_STATE_SING_BOX_SIGNATURE
nft_signature=$RELOAD_STATE_NFT_SIGNATURE
zapret_queue_signature=$RELOAD_STATE_ZAPRET_QUEUE_SIGNATURE
zapret_runtime_signature=$RELOAD_STATE_ZAPRET_RUNTIME_SIGNATURE
zapret2_queue_signature=$RELOAD_STATE_ZAPRET2_QUEUE_SIGNATURE
zapret2_runtime_signature=$RELOAD_STATE_ZAPRET2_RUNTIME_SIGNATURE
byedpi_runtime_signature=$RELOAD_STATE_BYEDPI_RUNTIME_SIGNATURE
list_signature=$RELOAD_STATE_LIST_SIGNATURE
cron_signature=$RELOAD_STATE_CRON_SIGNATURE
@@ -3055,7 +3121,7 @@ migrate_0_7_17_8_rule() {
migrate_0_7_17_8_delete_subscription_cache "$section"
fi
;;
vpn | outbound | block | direct | zapret | byedpi)
vpn | outbound | block | direct | zapret | zapret2 | byedpi)
podkop_uci_delete_option "$section" "proxy_config_type"
podkop_uci_delete_option "$section" "proxy_string"
podkop_uci_delete_option "$section" "urltest_proxy_links"
@@ -3172,6 +3238,7 @@ check_requirements() {
fi
check_zapret_requirements
check_zapret2_requirements
check_byedpi_requirements
}
@@ -3192,6 +3259,16 @@ _check_outbound_node() {
return 0
fi
if [ "$action" = "zapret" ] && is_zapret_installed; then
section_exists=0
return 0
fi
if [ "$action" = "zapret2" ] && is_zapret2_installed; then
section_exists=0
return 0
fi
if [ -n "$selector_proxy_links" ] || [ -n "$subscription_urls" ] ||
[ -n "$outbound_json" ] || [ -n "$interface" ]; then
section_exists=0
@@ -3266,6 +3343,7 @@ start_main() {
apply_pending_urltest_selector_switches
run_deferred_subscription_bootstrap
start_zapret_runtime
start_zapret2_runtime
(close_inherited_service_lock_fd; list_update) &
}
@@ -3291,6 +3369,7 @@ stop_main() {
rm -f "$TMP_RULESET_FOLDER"/*
stop_zapret_runtime
stop_zapret2_runtime
stop_byedpi_runtime
log "Flush nft"
@@ -3419,10 +3498,10 @@ reload() {
local status
local force_runtime_reload=0
local previous_service_trigger_signature previous_dnsmasq_signature previous_sing_box_signature previous_nft_signature
local previous_zapret_queue_signature previous_zapret_runtime_signature previous_byedpi_runtime_signature previous_list_signature previous_cron_signature
local previous_urltest_enabled_sections previous_urltest_enabled_sections_known previous_dont_touch_dhcp needs_sing_box_reload needs_nft_rebuild needs_zapret_restart needs_byedpi_restart
local previous_zapret_queue_signature previous_zapret_runtime_signature previous_zapret2_queue_signature previous_zapret2_runtime_signature previous_byedpi_runtime_signature previous_list_signature previous_cron_signature
local previous_urltest_enabled_sections previous_urltest_enabled_sections_known previous_dont_touch_dhcp needs_sing_box_reload needs_nft_rebuild needs_zapret_restart needs_zapret2_restart needs_byedpi_restart
local needs_dnsmasq_configure needs_dnsmasq_restore needs_cron_refresh needs_list_update
local changed_service_triggers changed_dnsmasq changed_sing_box changed_nft changed_zapret_queue changed_zapret_runtime changed_byedpi_runtime changed_cron changed_list
local changed_service_triggers changed_dnsmasq changed_sing_box changed_nft changed_zapret_queue changed_zapret_runtime changed_zapret2_queue changed_zapret2_runtime changed_byedpi_runtime changed_cron changed_list
log "Podkop Plus reload"
@@ -3458,6 +3537,8 @@ reload() {
previous_nft_signature="$(read_reload_state_value "nft_signature")"
previous_zapret_queue_signature="$(read_reload_state_value "zapret_queue_signature")"
previous_zapret_runtime_signature="$(read_reload_state_value "zapret_runtime_signature")"
previous_zapret2_queue_signature="$(read_reload_state_value "zapret2_queue_signature")"
previous_zapret2_runtime_signature="$(read_reload_state_value "zapret2_runtime_signature")"
previous_byedpi_runtime_signature="$(read_reload_state_value "byedpi_runtime_signature")"
previous_list_signature="$(read_reload_state_value "list_signature")"
previous_cron_signature="$(read_reload_state_value "cron_signature")"
@@ -3481,6 +3562,7 @@ reload() {
needs_sing_box_reload=0
needs_nft_rebuild=0
needs_zapret_restart=0
needs_zapret2_restart=0
needs_byedpi_restart=0
needs_dnsmasq_configure=0
needs_dnsmasq_restore=0
@@ -3492,6 +3574,8 @@ reload() {
changed_nft=0
changed_zapret_queue=0
changed_zapret_runtime=0
changed_zapret2_queue=0
changed_zapret2_runtime=0
changed_byedpi_runtime=0
changed_cron=0
changed_list=0
@@ -3520,6 +3604,18 @@ reload() {
needs_zapret_restart=1
fi
if [ "$RELOAD_STATE_ZAPRET2_QUEUE_SIGNATURE" != "$previous_zapret2_queue_signature" ]; then
changed_zapret2_queue=1
needs_zapret2_restart=1
needs_nft_rebuild=1
needs_sing_box_reload=1
fi
if [ "$RELOAD_STATE_ZAPRET2_RUNTIME_SIGNATURE" != "$previous_zapret2_runtime_signature" ]; then
changed_zapret2_runtime=1
needs_zapret2_restart=1
fi
if [ "$RELOAD_STATE_BYEDPI_RUNTIME_SIGNATURE" != "$previous_byedpi_runtime_signature" ]; then
changed_byedpi_runtime=1
needs_byedpi_restart=1
@@ -3552,18 +3648,20 @@ reload() {
[ "$needs_sing_box_reload" -eq 0 ] &&
[ "$needs_nft_rebuild" -eq 0 ] &&
[ "$needs_zapret_restart" -eq 0 ] &&
[ "$needs_zapret2_restart" -eq 0 ] &&
[ "$needs_byedpi_restart" -eq 0 ] &&
[ "$needs_dnsmasq_configure" -eq 0 ] &&
[ "$needs_dnsmasq_restore" -eq 0 ]; then
needs_sing_box_reload=1
fi
log "Reload signature changes: service_triggers=$changed_service_triggers sing-box=$changed_sing_box nft=$changed_nft zapret_queue=$changed_zapret_queue zapret_runtime=$changed_zapret_runtime byedpi_runtime=$changed_byedpi_runtime dnsmasq=$changed_dnsmasq cron=$changed_cron list=$changed_list forced=$force_runtime_reload" "debug"
log "Reload plan: sing-box=$needs_sing_box_reload nft=$needs_nft_rebuild zapret=$needs_zapret_restart byedpi=$needs_byedpi_restart dnsmasq_configure=$needs_dnsmasq_configure dnsmasq_restore=$needs_dnsmasq_restore cron=$needs_cron_refresh list_update=$needs_list_update" "debug"
log "Reload signature changes: service_triggers=$changed_service_triggers sing-box=$changed_sing_box nft=$changed_nft zapret_queue=$changed_zapret_queue zapret_runtime=$changed_zapret_runtime zapret2_queue=$changed_zapret2_queue zapret2_runtime=$changed_zapret2_runtime byedpi_runtime=$changed_byedpi_runtime dnsmasq=$changed_dnsmasq cron=$changed_cron list=$changed_list forced=$force_runtime_reload" "debug"
log "Reload plan: sing-box=$needs_sing_box_reload nft=$needs_nft_rebuild zapret=$needs_zapret_restart zapret2=$needs_zapret2_restart byedpi=$needs_byedpi_restart dnsmasq_configure=$needs_dnsmasq_configure dnsmasq_restore=$needs_dnsmasq_restore cron=$needs_cron_refresh list_update=$needs_list_update" "debug"
if [ "$needs_sing_box_reload" -eq 0 ] &&
[ "$needs_nft_rebuild" -eq 0 ] &&
[ "$needs_zapret_restart" -eq 0 ] &&
[ "$needs_zapret2_restart" -eq 0 ] &&
[ "$needs_byedpi_restart" -eq 0 ] &&
[ "$needs_dnsmasq_configure" -eq 0 ] &&
[ "$needs_dnsmasq_restore" -eq 0 ] &&
@@ -3582,6 +3680,10 @@ reload() {
stop_zapret_runtime
fi
if [ "$needs_zapret2_restart" -eq 1 ]; then
stop_zapret2_runtime
fi
if [ "$needs_byedpi_restart" -eq 1 ]; then
stop_byedpi_runtime
fi
@@ -3609,6 +3711,10 @@ reload() {
start_zapret_runtime
fi
if [ "$needs_zapret2_restart" -eq 1 ]; then
start_zapret2_runtime
fi
if [ "$needs_byedpi_restart" -eq 1 ]; then
start_byedpi_runtime
fi
@@ -4034,6 +4140,22 @@ process_validate_rule() {
validate_rule_nfqws_opt "$section"
fi
if [ "$action" = "zapret2" ]; then
if ! is_zapret2_installed; then
if [ "$PODKOP_ZAPRET2_MISSING_WARNING_EMITTED" -eq 0 ]; then
log "There are enabled rules with action 'zapret2', but the zapret2 package is not installed. These rules will be skipped." "error"
PODKOP_ZAPRET2_MISSING_WARNING_EMITTED=1
fi
config_list_foreach "$section" "community_lists" validate_service
config_list_foreach "$section" "rule_set" validate_ruleset_reference
config_list_foreach "$section" "rule_set_with_subnets" validate_ruleset_reference
config_list_foreach "$section" "domain_ip_lists" validate_plain_domain_ip_list_reference
return 0
fi
validate_rule_nfqws2_opt "$section"
fi
if [ "$action" = "byedpi" ]; then
if ! is_byedpi_installed; then
if [ "$PODKOP_BYEDPI_MISSING_WARNING_EMITTED" -eq 0 ]; then
@@ -4523,6 +4645,7 @@ create_nft_rules() {
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "@$NFT_LOCALV4_SET_NAME" return
nft add rule inet "$NFT_TABLE_NAME" mangle_output meta mark "$NFT_OUTBOUND_MARK" counter return
create_zapret_nft_rules
create_zapret2_nft_rules
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto tcp meta mark set "$NFT_FAKEIP_MARK" counter
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto udp meta mark set "$NFT_FAKEIP_MARK" counter
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr . tcp dport "@$NFT_IP_PORT_SET_NAME" meta mark set "$NFT_FAKEIP_MARK" counter
@@ -7895,6 +8018,26 @@ configure_outbound_handler() {
return 0
fi
if [ "$action" = "zapret2" ]; then
write_subscription_metadata_json "$section" ""
if ! is_zapret2_installed; then
return 0
fi
local routing_mark outbound_tag zapret2_index
zapret2_index="$(get_zapret2_rule_index "$section")"
if [ "${zapret2_index:-0}" -le 0 ]; then
log "Unable to resolve Zapret2 index for rule '$section'. Aborted." "fatal"
exit 1
fi
routing_mark="$(get_zapret2_rule_mark_value "$zapret2_index")"
outbound_tag="$(get_outbound_tag_by_section "$section")"
config=$(sing_box_cm_add_direct_outbound "$config" "$outbound_tag" "$routing_mark")
return 0
fi
if [ "$action" = "byedpi" ]; then
if ! is_byedpi_installed; then
return 0
@@ -8282,10 +8425,10 @@ create_route_rule_for_action() {
outbound_tag="$(get_outbound_tag_by_section "$section")"
config=$(sing_box_cm_add_route_rule "$config" "$rule_tag" "$inbound_tag" "$outbound_tag")
;;
zapret)
local zapret_outbound_tag
zapret_outbound_tag="$(get_outbound_tag_by_section "$section")"
config=$(sing_box_cm_add_route_rule "$config" "$rule_tag" "$inbound_tag" "$zapret_outbound_tag")
zapret | zapret2)
local provider_outbound_tag
provider_outbound_tag="$(get_outbound_tag_by_section "$section")"
config=$(sing_box_cm_add_route_rule "$config" "$rule_tag" "$inbound_tag" "$provider_outbound_tag")
;;
direct)
local direct_outbound_tag
@@ -8570,6 +8713,10 @@ configure_route_rule_handler() {
return 0
fi
if [ "$action" = "zapret2" ] && ! is_zapret2_installed; then
return 0
fi
if [ "$action" = "byedpi" ] && ! is_byedpi_installed; then
return 0
fi
@@ -8681,6 +8828,10 @@ configure_fully_routed_ips_for_section() {
return 0
fi
if [ "$action" = "zapret2" ] && ! is_zapret2_installed; then
return 0
fi
if [ "$action" = "byedpi" ] && ! is_byedpi_installed; then
return 0
fi
@@ -8800,13 +8951,16 @@ configure_section_mixed_proxy() {
subscription_section_is_deferred "$section" && return 0
action="$(get_rule_action "$section")"
[ "$action" = "proxy" ] || [ "$action" = "outbound" ] || [ "$action" = "vpn" ] || [ "$action" = "byedpi" ] || [ "$action" = "zapret" ] || return 0
[ "$action" = "proxy" ] || [ "$action" = "outbound" ] || [ "$action" = "vpn" ] || [ "$action" = "byedpi" ] || [ "$action" = "zapret" ] || [ "$action" = "zapret2" ] || return 0
if [ "$action" = "byedpi" ] && ! is_byedpi_installed; then
return 0
fi
if [ "$action" = "zapret" ] && ! is_zapret_installed; then
return 0
fi
if [ "$action" = "zapret2" ] && ! is_zapret2_installed; then
return 0
fi
local mixed_inbound_enabled mixed_proxy_port mixed_inbound_tag mixed_outbound_tag mixed_proxy_address \
mixed_proxy_auth_enabled mixed_proxy_username mixed_proxy_password
@@ -9332,7 +9486,10 @@ _determine_first_outbound_section() {
subscription_section_is_deferred "$section" && return 0
action="$(get_rule_action "$section")"
if [ "$action" = "proxy" ] || [ "$action" = "outbound" ] || [ "$action" = "vpn" ] || { [ "$action" = "byedpi" ] && is_byedpi_installed; }; then
if [ "$action" = "proxy" ] || [ "$action" = "outbound" ] || [ "$action" = "vpn" ] ||
{ [ "$action" = "byedpi" ] && is_byedpi_installed; } ||
{ [ "$action" = "zapret" ] && is_zapret_installed; } ||
{ [ "$action" = "zapret2" ] && is_zapret2_installed; }; then
[ -z "$first_section" ] && first_section="$1"
fi
}
@@ -10299,7 +10456,7 @@ write_system_info_cache() {
}
build_system_info() {
local podkop_version podkop_latest_version luci_app_version sing_box_version sing_box_extended zapret_version zapret_installed byedpi_version byedpi_installed openwrt_version device_model
local podkop_version podkop_latest_version luci_app_version sing_box_version sing_box_extended zapret_version zapret_installed zapret2_version zapret2_installed byedpi_version byedpi_installed openwrt_version device_model
local generated_at
podkop_version="$PODKOP_VERSION"
@@ -10327,6 +10484,15 @@ build_system_info() {
zapret_version="not installed"
fi
zapret2_installed=0
if is_zapret2_installed; then
zapret2_installed=1
zapret2_version="$(get_zapret2_package_version)"
[ -z "$zapret2_version" ] && zapret2_version="unknown"
else
zapret2_version="not installed"
fi
byedpi_installed=0
if is_byedpi_installed; then
byedpi_installed=1
@@ -10359,6 +10525,7 @@ build_system_info() {
"$podkop_version" "$podkop_latest_version" "$luci_app_version" \
"$sing_box_version" "$sing_box_extended" \
"$zapret_version" "$zapret_installed" \
"$zapret2_version" "$zapret2_installed" \
"$byedpi_version" "$byedpi_installed" \
"$openwrt_version" "$device_model" "$generated_at"
}
@@ -10397,11 +10564,12 @@ get_server_capabilities() {
}
get_ui_capabilities() {
local sing_box_version sing_box_extended=0 zapret_installed=0 byedpi_installed=0
local sing_box_version sing_box_extended=0 zapret_installed=0 zapret2_installed=0 byedpi_installed=0
sing_box_version="$(get_sing_box_version)"
is_sing_box_extended "$sing_box_version" && sing_box_extended=1
is_zapret_installed && zapret_installed=1
is_zapret2_installed && zapret2_installed=1
is_byedpi_installed && byedpi_installed=1
INBOUNDS_CONFIG_ENABLED_COUNT=0
@@ -10410,6 +10578,7 @@ get_ui_capabilities() {
json_utils_ucode object-json \
sing_box_extended n "$sing_box_extended" \
zapret_installed n "$zapret_installed" \
zapret2_installed n "$zapret2_installed" \
byedpi_installed n "$byedpi_installed" \
server_inbounds_enabled_count n "${INBOUNDS_CONFIG_ENABLED_COUNT:-0}"
}
@@ -10418,6 +10587,10 @@ get_zapret_status() {
get_zapret_status_json
}
get_zapret2_status() {
get_zapret2_status_json
}
neutralize_zapret_defaults() {
log "Standalone zapret is not neutralized automatically; Podkop Plus uses /opt/zapret/nfq/nfqws as an external provider and manages only its own NFQUEUE range."
}
@@ -10426,6 +10599,10 @@ check_zapret_runtime() {
check_zapret_runtime_json
}
check_zapret2_runtime() {
check_zapret2_runtime_json
}
get_byedpi_status() {
get_byedpi_status_json
}
@@ -10593,6 +10770,17 @@ validate_nfqws_strategy_json() {
json_utils_ucode nfqws-strategy-validation false "$NFQWS_VALIDATE_ERROR" "$NFQWS_VALIDATE_NEEDLE" "$NFQWS_VALIDATE_NEEDLES"
}
validate_nfqws2_strategy_json() {
local raw_opt="${1:-}"
if check_nfqws2_strategy "$raw_opt"; then
json_utils_ucode nfqws-strategy-validation true "" "" ""
return 0
fi
json_utils_ucode nfqws-strategy-validation false "$NFQWS2_VALIDATE_ERROR" "$NFQWS2_VALIDATE_NEEDLE" "$NFQWS2_VALIDATE_NEEDLES"
}
dns_check_resolve_host() {
local host="$1"
local resolver="$2"
@@ -11212,7 +11400,7 @@ global_check() {
system_info_json=$(get_system_info)
if [ -n "$system_info_json" ]; then
local podkop_version podkop_latest_version luci_app_version sing_box_version sing_box_extended zapret_version zapret_installed byedpi_version byedpi_installed openwrt_version device_model
local podkop_version podkop_latest_version luci_app_version sing_box_version sing_box_extended zapret_version zapret_installed zapret2_version zapret2_installed byedpi_version byedpi_installed openwrt_version device_model
podkop_version=$(json_get_default "$system_info_json" podkop_version "unknown")
podkop_latest_version=$(json_get_default "$system_info_json" podkop_latest_version "unknown")
@@ -11221,6 +11409,8 @@ global_check() {
sing_box_extended=$(json_get_default "$system_info_json" sing_box_extended 0)
zapret_version=$(json_get_default "$system_info_json" zapret_version "unknown")
zapret_installed=$(json_get_default "$system_info_json" zapret_installed 0)
zapret2_version=$(json_get_default "$system_info_json" zapret2_version "unknown")
zapret2_installed=$(json_get_default "$system_info_json" zapret2_installed 0)
byedpi_version=$(json_get_default "$system_info_json" byedpi_version "unknown")
byedpi_installed=$(json_get_default "$system_info_json" byedpi_installed 0)
openwrt_version=$(json_get_default "$system_info_json" openwrt_version "unknown")
@@ -11237,6 +11427,9 @@ global_check() {
if [ "$zapret_installed" -eq 1 ]; then
print_global "🧵 Zapret: $zapret_version"
fi
if [ "$zapret2_installed" -eq 1 ]; then
print_global "🧵 Zapret2: $zapret2_version"
fi
if [ "$byedpi_installed" -eq 1 ]; then
print_global "🧵 ByeDPI: $byedpi_version"
fi
@@ -11488,6 +11681,15 @@ global_check() {
print_global "✅ Zapret provider available"
fi
local zapret2_check_json
zapret2_check_json=$(check_zapret2_runtime)
if [ -n "$zapret2_check_json" ] && [ "$(json_get_default "$zapret2_check_json" zapret2_installed 0)" = "1" ]; then
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "🧵 Zapret2 status"
print_global "✅ Zapret2 provider available"
fi
local byedpi_check_json
byedpi_check_json=$(check_byedpi_runtime)
@@ -11646,6 +11848,14 @@ global_check() {
print_global "⚠️ Standalone zapret autostart is enabled. Podkop Plus will not modify /etc/config/zapret."
fi
if [ -x /etc/init.d/zapret2 ] && /etc/init.d/zapret2 status >/dev/null 2>&1; then
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "⚠️ Standalone zapret2 service is active. Podkop Plus uses separate queues, but packet-level policy overlap is possible."
elif [ -x /etc/init.d/zapret2 ] && /etc/init.d/zapret2 enabled >/dev/null 2>&1; then
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "⚠️ Standalone zapret2 autostart is enabled. Podkop Plus will not modify /etc/config/zapret2."
fi
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "🥸 FakeIP status"
@@ -11701,6 +11911,7 @@ Available commands:
check_sing_box_logs Show sing-box logs
check_fakeip Test sing-box FakeIP DNS
check_zapret_runtime Check external zapret provider state
check_zapret2_runtime Check external zapret2 provider state
check_byedpi_runtime Check external ByeDPI provider state
neutralize_zapret_defaults
Compatibility no-op; Podkop Plus does not manage standalone zapret
@@ -11716,19 +11927,22 @@ Available commands:
Get UI-only metadata for a subscription rule
get_sing_box_status Get sing-box service status
get_zapret_status Get zapret provider and Podkop Plus-managed nfqws status
get_zapret2_status Get zapret2 provider and Podkop Plus-managed nfqws2 status
get_byedpi_status Get ByeDPI provider and Podkop Plus-managed ciadpi status
get_system_info Get system information in JSON format
get_server_capabilities Get server UI capabilities in JSON format
get_ui_capabilities Get lightweight UI capabilities in JSON format
generate_reality_keypair
Generate a sing-box Reality key pair in JSON format
component_action Manage Podkop Plus, sing-box, zapret, and ByeDPI components
component_action Manage Podkop Plus, sing-box, zapret, zapret2, and ByeDPI components
component_action_async Start a component action in the background
component_action_status Get background component action status
check_dns_available Check DNS server availability
global_check Run global system check
validate_nfqws_strategy_json
Validate an NFQWS strategy and print JSON result
validate_nfqws2_strategy_json
Validate an NFQWS2 strategy and print JSON result
validate_byedpi_strategy_json
Validate a ByeDPI ciadpi strategy and print JSON result
EOF
@@ -11810,6 +12024,9 @@ check_fakeip)
check_zapret_runtime)
check_zapret_runtime
;;
check_zapret2_runtime)
check_zapret2_runtime
;;
check_byedpi_runtime)
check_byedpi_runtime
;;
@@ -11852,6 +12069,9 @@ get_sing_box_status)
get_zapret_status)
get_zapret_status
;;
get_zapret2_status)
get_zapret2_status
;;
get_byedpi_status)
get_byedpi_status
;;
@@ -11885,6 +12105,9 @@ global_check)
validate_nfqws_strategy_json)
validate_nfqws_strategy_json "${2:-}"
;;
validate_nfqws2_strategy_json)
validate_nfqws2_strategy_json "${2:-}"
;;
validate_byedpi_strategy_json)
validate_byedpi_strategy_json "${2:-}"
;;
+20
View File
@@ -97,6 +97,26 @@ ZAPRET_DESYNC_MARK_POSTNAT="0x20000000"
ZAPRET_LEGACY_DEFAULT_NFQWS_OPT="--filter-tcp=80 <HOSTLIST> --dpi-desync=fake,fakedsplit --dpi-desync-autottl=2 --dpi-desync-fooling=badsum --new --filter-tcp=443 --hostlist=/opt/zapret/ipset/zapret-hosts-google.txt --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-repeats=11 --dpi-desync-fooling=badsum --dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com --new --filter-udp=443 --hostlist=/opt/zapret/ipset/zapret-hosts-google.txt --dpi-desync=fake --dpi-desync-repeats=11 --dpi-desync-fake-quic=/opt/zapret/files/fake/quic_initial_www_google_com.bin --new --filter-udp=443 <HOSTLIST_NOAUTO> --dpi-desync=fake --dpi-desync-repeats=11 --new --filter-tcp=443 <HOSTLIST> --dpi-desync=multidisorder --dpi-desync-split-pos=1,sniext+1,host+1,midsld-2,midsld,midsld+2,endhost-1"
ZAPRET_DEFAULT_NFQWS_OPT="--filter-tcp=80 --dpi-desync=fake,fakedsplit --dpi-desync-autottl=2 --dpi-desync-fooling=badsum --new --filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-repeats=11 --dpi-desync-fooling=badsum --dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com --new --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=11 --dpi-desync-fake-quic=/opt/zapret/files/fake/quic_initial_www_google_com.bin"
## Zapret2
ZAPRET2_PROVIDER_BASE_DIR="/opt/zapret2"
ZAPRET2_PROVIDER_NFQWS2_BIN="$ZAPRET2_PROVIDER_BASE_DIR/nfq2/nfqws2"
ZAPRET2_PROVIDER_FILES_DIR="$ZAPRET2_PROVIDER_BASE_DIR/files"
ZAPRET2_PROVIDER_IPSET_DIR="$ZAPRET2_PROVIDER_BASE_DIR/ipset"
ZAPRET2_PROVIDER_LUA_DIR="$ZAPRET2_PROVIDER_BASE_DIR/lua"
ZAPRET2_NFQWS2_BIN="$ZAPRET2_PROVIDER_NFQWS2_BIN"
ZAPRET2_STATE_DIR="/var/run/podkop-plus/zapret2"
ZAPRET2_PID_DIR="$ZAPRET2_STATE_DIR/pid"
ZAPRET2_CHILD_PID_DIR="$ZAPRET2_STATE_DIR/child-pid"
ZAPRET2_LOG_DIR="$ZAPRET2_STATE_DIR/log"
ZAPRET2_ROUTE_MARK_BASE="0x01100000"
# Keep zapret2 queues outside the zapret provider range (4000-4255).
ZAPRET2_QUEUE_BASE=4300
ZAPRET2_QUEUE_RANGE_SIZE=256
ZAPRET2_NFQWS2_RESPAWN_DELAY=5
ZAPRET2_DESYNC_MARK="0x40000000"
ZAPRET2_DESYNC_MARK_POSTNAT="0x20000000"
ZAPRET2_DEFAULT_NFQWS2_OPT="--filter-tcp=80 --filter-l7=http --payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=multisplit:pos=method+2 --new --filter-tcp=443 --filter-l7=tls --payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=multidisorder:pos=1,midsld --new --filter-udp=443 --filter-l7=quic --payload=quic_initial --lua-desync=fake:blob=fake_default_quic:repeats=6"
## ByeDPI
BYEDPI_BIN="/usr/bin/ciadpi"
BYEDPI_SERVICE_INIT="/etc/init.d/byedpi"
+48 -5
View File
@@ -345,6 +345,45 @@ function byedpi_asset_matches(name, arch, ext) {
(str_contains(name, "_" + arch + "." + ext) || str_contains(name, "-" + arch + "." + ext));
}
function release_asset_matches_arch(name, prefix, arch, ext) {
return (str_startswith(name, prefix + "_") || str_startswith(name, prefix + "-")) &&
str_endswith(name, "." + ext) &&
(str_contains(name, "_" + arch + "." + ext) || str_contains(name, "-" + arch + "." + ext));
}
function named_release_select_asset(release_prefix, asset_prefix, asset_ext, arch_candidates) {
let releases = array_or_empty(read_stdin_json());
for (let release in releases) {
if (type(release) != "object")
continue;
if (release.draft === true)
continue;
let release_name = as_string(release.name || "");
if (!str_startswith(release_name, release_prefix))
continue;
for (let arch in split(as_string(arch_candidates), " ")) {
if (arch == "")
continue;
for (let asset in array_or_empty(release.assets)) {
if (type(asset) != "object")
continue;
let name = as_string(asset.name || "");
let url = as_string(asset.browser_download_url || "");
if (url != "" && release_asset_matches_arch(name, asset_prefix, arch, asset_ext)) {
print(arch, "\t", name, "\t", url, "\t",
as_string(release.html_url || ""), "\t", as_string(release.tag_name || ""), "\n");
return;
}
}
}
}
}
function select_byedpi_asset_from_release(release, asset_ext, arch_candidates) {
for (let arch in split(as_string(arch_candidates), " ")) {
if (arch == "")
@@ -427,11 +466,13 @@ function system_info_json() {
sing_box_extended: arg_number(ARGV[5]),
zapret_version: as_string(ARGV[6]),
zapret_installed: arg_number(ARGV[7]),
byedpi_version: as_string(ARGV[8]),
byedpi_installed: arg_number(ARGV[9]),
openwrt_version: as_string(ARGV[10]),
device_model: as_string(ARGV[11]),
generated_at: arg_number(ARGV[12])
zapret2_version: as_string(ARGV[8]),
zapret2_installed: arg_number(ARGV[9]),
byedpi_version: as_string(ARGV[10]),
byedpi_installed: arg_number(ARGV[11]),
openwrt_version: as_string(ARGV[12]),
device_model: as_string(ARGV[13]),
generated_at: arg_number(ARGV[14])
});
}
@@ -932,6 +973,8 @@ else if (mode == "release-asset-name-by-suffix")
release_asset_name_by_suffix(ARGV[1]);
else if (mode == "release-asset-url-by-suffix")
release_asset_url_by_suffix(ARGV[1]);
else if (mode == "named-release-select-asset")
named_release_select_asset(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
else if (mode == "byedpi-select-asset")
byedpi_select_asset(ARGV[1], ARGV[2], ARGV[3]);
else if (mode == "sing-box-extended-release-tag")
+42
View File
@@ -225,6 +225,44 @@ function zapret_check(installed, package_installed, provider_path) {
});
}
function zapret2_status(args) {
write_json({
installed: bool_arg(args[0]),
package_installed: bool_arg(args[1]),
provider_available: bool_arg(args[2]),
provider_path: as_string(args[3]),
files_available: bool_arg(args[4]),
ipset_available: bool_arg(args[5]),
version: as_string(args[6]),
configured: bool_arg(args[7]),
enabled_rule_count: int_arg(args[8]),
expected_process_count: int_arg(args[9]),
running_process_count: int_arg(args[10]),
supervisor_process_count: int_arg(args[11]),
standalone_service_enabled: bool_arg(args[12]),
standalone_service_running: bool_arg(args[13]),
standalone_config_present: bool_arg(args[14]),
standalone_conflict: bool_arg(args[15]),
luci_app_installed: bool_arg(args[16]),
queue_base: int_arg(args[17]),
queue_range_end: int_arg(args[18]),
queue_overlap: bool_arg(args[19]),
ready: bool_arg(args[20]),
conflict: bool_arg(args[21]),
outbounds_configured: bool_arg(args[22]),
routes_configured: bool_arg(args[23]),
status_message: as_string(args[24])
});
}
function zapret2_check(installed, package_installed, provider_path) {
write_json({
zapret2_installed: bool_arg(installed),
zapret2_package_installed: bool_arg(package_installed),
zapret2_provider_path: as_string(provider_path)
});
}
let mode = ARGV[0] || "";
if (mode == "byedpi-validation")
@@ -249,6 +287,10 @@ else if (mode == "zapret-status")
zapret_status(slice(ARGV, 1));
else if (mode == "zapret-check")
zapret_check(ARGV[1], ARGV[2], ARGV[3]);
else if (mode == "zapret2-status")
zapret2_status(slice(ARGV, 1));
else if (mode == "zapret2-check")
zapret2_check(ARGV[1], ARGV[2], ARGV[3]);
else {
warn("Usage: provider_status.uc <operation> ...\n");
exit(1);
+130 -1
View File
@@ -10,6 +10,13 @@ UPDATES_ZAPRET_PACKAGE_FILE=""
UPDATES_ZAPRET_PACKAGE_NAME=""
UPDATES_ZAPRET_PACKAGE_VERSION=""
UPDATES_ZAPRET_RELEASE_URL=""
UPDATES_ZAPRET2_ARCH=""
UPDATES_ZAPRET2_BUNDLE_URL=""
UPDATES_ZAPRET2_BUNDLE_NAME=""
UPDATES_ZAPRET2_PACKAGE_FILE=""
UPDATES_ZAPRET2_PACKAGE_NAME=""
UPDATES_ZAPRET2_PACKAGE_VERSION=""
UPDATES_ZAPRET2_RELEASE_URL=""
UPDATES_BYEDPI_ARCH=""
UPDATES_BYEDPI_PACKAGE_URL=""
UPDATES_BYEDPI_PACKAGE_NAME=""
@@ -146,9 +153,11 @@ updates_fail() {
local message="$3"
local current_version="${4:-}"
local latest_version="${5:-}"
local status="${6:-}"
local release_url="${7:-}"
updates_log "$message" "error"
updates_json_response false "$component" "$action" "$message" "$current_version" "$latest_version" 0
updates_json_response false "$component" "$action" "$message" "$current_version" "$latest_version" 0 "$status" "$release_url"
exit 1
}
@@ -1020,6 +1029,8 @@ updates_extract_arch_package_version() {
version="$(printf '%s\n' "$package_name" | sed 's/\.ipk$//;s/\.apk$//')"
case "$version" in
zapret2_*) version="${version#zapret2_}" ;;
zapret2-*) version="${version#zapret2-}" ;;
zapret_*) version="${version#zapret_}" ;;
zapret-*) version="${version#zapret-}" ;;
byedpi_*) version="${version#byedpi_}" ;;
@@ -1184,6 +1195,63 @@ updates_download_and_extract_zapret_package() {
[ -n "$UPDATES_ZAPRET_PACKAGE_VERSION" ] || UPDATES_ZAPRET_PACKAGE_VERSION="$(updates_extract_arch_package_version "$UPDATES_ZAPRET_PACKAGE_NAME" "$UPDATES_ZAPRET_ARCH")"
}
updates_resolve_zapret2_release() {
local releases_json resolved
UPDATES_ZAPRET2_ARCH=""
UPDATES_ZAPRET2_BUNDLE_URL=""
UPDATES_ZAPRET2_BUNDLE_NAME=""
UPDATES_ZAPRET2_PACKAGE_VERSION=""
UPDATES_ZAPRET2_RELEASE_URL=""
releases_json="$(updates_fetch_github_releases_json "remittor" "zapret-openwrt" 30)" || return 1
resolved="$(
printf '%s' "$releases_json" |
json_utils_ucode named-release-select-asset "zapret2 " "zapret2" "zip" "$UPDATES_ARCH_CANDIDATES" 2>/dev/null
)"
[ -n "$resolved" ] || return 1
UPDATES_ZAPRET2_ARCH="$(printf '%s\n' "$resolved" | cut -f1)"
UPDATES_ZAPRET2_BUNDLE_NAME="$(printf '%s\n' "$resolved" | cut -f2)"
UPDATES_ZAPRET2_BUNDLE_URL="$(printf '%s\n' "$resolved" | cut -f3)"
UPDATES_ZAPRET2_RELEASE_URL="$(printf '%s\n' "$resolved" | cut -f4)"
[ -n "$UPDATES_ZAPRET2_ARCH" ] || return 1
[ -n "$UPDATES_ZAPRET2_BUNDLE_NAME" ] || return 1
[ -n "$UPDATES_ZAPRET2_BUNDLE_URL" ] || return 1
UPDATES_ZAPRET2_PACKAGE_VERSION="$(updates_extract_zapret2_bundle_version "$UPDATES_ZAPRET2_BUNDLE_NAME")"
[ -n "$UPDATES_ZAPRET2_PACKAGE_VERSION" ] || UPDATES_ZAPRET2_PACKAGE_VERSION="$(printf '%s\n' "$UPDATES_ZAPRET2_BUNDLE_NAME" | sed 's/\.zip$//')"
}
updates_download_and_extract_zapret2_package() {
local bundle_file inner_package_path
UPDATES_ZAPRET2_PACKAGE_FILE=""
UPDATES_ZAPRET2_PACKAGE_NAME=""
UPDATES_ZAPRET2_PACKAGE_VERSION=""
bundle_file="$UPDATES_TMP_DIR/$UPDATES_ZAPRET2_BUNDLE_NAME"
updates_download_with_retry "$UPDATES_ZAPRET2_BUNDLE_URL" "$bundle_file" "$UPDATES_ZAPRET2_BUNDLE_NAME" || return 1
if updates_is_apk; then
inner_package_path="$(unzip -l "$bundle_file" | awk '{print $4}' | grep -E '^apk/zapret2-.*\.apk$' | sed -n '1p')"
else
inner_package_path="$(unzip -l "$bundle_file" | awk '{print $4}' | grep -E "^zapret2_.*_${UPDATES_ZAPRET2_ARCH}\.ipk$" | sed -n '1p')"
[ -n "$inner_package_path" ] || inner_package_path="$(unzip -l "$bundle_file" | awk '{print $4}' | grep -E '^zapret2_.*\.ipk$' | sed -n '1p')"
fi
[ -n "$inner_package_path" ] || return 1
UPDATES_ZAPRET2_PACKAGE_NAME="$(basename "$inner_package_path")"
UPDATES_ZAPRET2_PACKAGE_FILE="$UPDATES_TMP_DIR/$UPDATES_ZAPRET2_PACKAGE_NAME"
unzip -p "$bundle_file" "$inner_package_path" >"$UPDATES_ZAPRET2_PACKAGE_FILE" || return 1
[ -s "$UPDATES_ZAPRET2_PACKAGE_FILE" ] || return 1
[ -n "$UPDATES_ZAPRET2_PACKAGE_VERSION" ] || UPDATES_ZAPRET2_PACKAGE_VERSION="$(updates_extract_zapret2_bundle_version "$UPDATES_ZAPRET2_BUNDLE_NAME")"
[ -n "$UPDATES_ZAPRET2_PACKAGE_VERSION" ] || UPDATES_ZAPRET2_PACKAGE_VERSION="$(updates_extract_arch_package_version "$UPDATES_ZAPRET2_PACKAGE_NAME" "$UPDATES_ZAPRET2_ARCH")"
}
updates_resolve_byedpi_release() {
local response release_series asset_ext resolved
@@ -1230,6 +1298,13 @@ updates_disable_standalone_zapret_service() {
updates_log_command "Disabling standalone zapret autostart" /etc/init.d/zapret disable || true
}
updates_disable_standalone_zapret2_service() {
[ -x /etc/init.d/zapret2 ] || return 0
updates_log_command "Stopping standalone zapret2 service" /etc/init.d/zapret2 stop || true
updates_log_command "Disabling standalone zapret2 autostart" /etc/init.d/zapret2 disable || true
}
updates_disable_standalone_byedpi_service() {
[ -x /etc/init.d/byedpi ] || return 0
@@ -1272,6 +1347,43 @@ updates_install_zapret() {
updates_success "zapret" "$action" "zapret package has been installed" "$current_version" "$UPDATES_ZAPRET_PACKAGE_VERSION" 1 "latest"
}
updates_install_zapret2() {
local action="$1"
local current_version installed normalized_current normalized_latest
updates_init_tmp_dir || updates_fail "zapret2" "$action" "Failed to create temporary directory"
updates_resolve_arch_candidates || updates_fail "zapret2" "$action" "Failed to detect package architecture"
updates_retry_resolve "Resolving zapret2 package" updates_resolve_zapret2_release ||
updates_fail "zapret2" "$action" "Failed to resolve zapret2 package for this router architecture"
installed=0
is_zapret2_installed && installed=1
current_version="$(get_zapret2_package_version)"
if [ "$action" = "check_update" ]; then
[ "$installed" -eq 1 ] ||
updates_fail "zapret2" "$action" "zapret2 is not installed" "$current_version" "$UPDATES_ZAPRET2_PACKAGE_VERSION" "" "$UPDATES_ZAPRET2_RELEASE_URL"
normalized_current="$(updates_normalize_zapret_version "$current_version")"
normalized_latest="$(updates_normalize_zapret_version "$UPDATES_ZAPRET2_PACKAGE_VERSION")"
updates_check_success_compared "zapret2" "$current_version" "$UPDATES_ZAPRET2_PACKAGE_VERSION" "$normalized_current" "$normalized_latest" "$UPDATES_ZAPRET2_RELEASE_URL"
fi
updates_ensure_package_tool "unzip" "unzip" || updates_fail "zapret2" "$action" "Failed to install unzip"
updates_download_and_extract_zapret2_package ||
updates_fail "zapret2" "$action" "Failed to download zapret2 package" "$current_version" "$UPDATES_ZAPRET2_PACKAGE_VERSION" "" "$UPDATES_ZAPRET2_RELEASE_URL"
if ! updates_log_command "Installing zapret2 package $UPDATES_ZAPRET2_PACKAGE_NAME" updates_pkg_install_files "$UPDATES_ZAPRET2_PACKAGE_FILE"; then
updates_fail "zapret2" "$action" "Failed to install zapret2 package" "$current_version" "$UPDATES_ZAPRET2_PACKAGE_VERSION" "" "$UPDATES_ZAPRET2_RELEASE_URL"
fi
updates_disable_standalone_zapret2_service
updates_restart_podkop_after_successful_change
updates_clear_version_caches
current_version="$(get_zapret2_package_version)"
updates_success "zapret2" "$action" "zapret2 package has been installed" "$current_version" "$UPDATES_ZAPRET2_PACKAGE_VERSION" 1 "latest" "$UPDATES_ZAPRET2_RELEASE_URL"
}
updates_install_byedpi() {
local action="$1"
local current_version installed
@@ -1436,6 +1548,17 @@ updates_extract_zapret_bundle_version() {
printf '%s\n' "$version" | sed 's/^v//'
}
updates_extract_zapret2_bundle_version() {
local bundle_name="$1"
local version
version="$(basename "$bundle_name" | sed -n 's/^zapret2_v\([^_][^_]*\)_.*/\1/p')"
[ -n "$version" ] || version="$(basename "$bundle_name" | sed -n 's/^zapret2_\([^_][^_]*\)_.*/\1/p')"
[ -n "$version" ] || version="$(updates_extract_zapret_bundle_version "$bundle_name")"
printf '%s\n' "$version" | sed 's/^v//'
}
updates_normalize_zapret_version() {
printf '%s\n' "$1" |
sed 's/^v//;s/-r[0-9][0-9]*$//;s/+.*$//;s/[[:space:]].*$//'
@@ -1771,6 +1894,12 @@ component_action() {
zapret:remove)
updates_remove_optional_component "zapret" "zapret" "zapret" is_zapret_installed get_zapret_package_version
;;
zapret2:check_update | zapret2:install)
updates_install_zapret2 "$action"
;;
zapret2:remove)
updates_remove_optional_component "zapret2" "zapret2" "zapret2" is_zapret2_installed get_zapret2_package_version
;;
byedpi:check_update | byedpi:install)
updates_install_byedpi "$action"
;;
File diff suppressed because it is too large Load Diff