Compare commits

...

71 Commits

Author SHA1 Message Date
itdoginfo
63dc86fca4 v0.3.45 Update checker domain 2025-05-07 22:39:23 +03:00
itdoginfo
4d9cedaf4c Return upgrade command 2025-05-07 17:58:19 +03:00
itdoginfo
14e7cbae01 v0.3.44 2025-05-07 17:26:57 +03:00
itdoginfo
c9f610bb1e Change to ip.podkop.net. Fix log. Added restart 2025-05-07 17:24:32 +03:00
itdoginfo
19671c7f67 Stop btn. Change to ip.podkop.net 2025-05-07 17:23:20 +03:00
itdoginfo
6d1e4091e5 Detour 2025-05-07 00:22:04 +03:00
itdoginfo
96d661c49f Fixed default values ttl in comment 2025-05-03 18:52:57 +03:00
itdoginfo
da8dd06b34 Move doc to wiki 2025-05-03 18:12:00 +03:00
itdoginfo
2c1bcffb6d fix iptables 2025-05-03 18:11:49 +03:00
itdoginfo
3040ce7286 v0.3.43 2025-05-02 14:53:11 +03:00
itdoginfo
e025271a14 Added to global check: DNS check and proxy check. From VizzleTF 2025-05-02 14:50:06 +03:00
itdoginfo
2b8208186d Fix global check text 2025-05-02 13:55:07 +03:00
itdoginfo
17fb11baf0 Fixed diagnostics from VizzleTF 2025-05-02 13:34:11 +03:00
itdoginfo
3c1b041b52 Edited text from #96 2025-05-01 22:52:41 +03:00
itdoginfo
38acac1a31 Merge pull request #96 from itdoginfo/chore/sing-box-status
Issue #91 , Issue #94
2025-05-01 22:16:38 +03:00
Ivan K
2939229df3 back to the future 2025-05-01 19:30:05 +03:00
Ivan K
26c3d0bc7e ♻️ refactor(podkop): simplify DoH URL determination logic 2025-05-01 19:26:21 +03:00
Ivan K
b364363b1b feat(dns): add DoH URL resolution function 2025-05-01 19:20:36 +03:00
itdoginfo
d85caf0c0c Fix https-dns-proxy i18 2025-05-01 19:10:18 +03:00
Ivan K
65f72e1e04 ♻️ refactor(podkop): update WARP detection logic 2025-05-01 18:29:42 +03:00
Ivan K
e59ef6dd6f 💄 style(podkop): remove unnecessary sed commands in global_check 2025-05-01 17:57:51 +03:00
Ivan K
05272de650 💄 style(podkop): update formatting and messages 2025-05-01 17:48:25 +03:00
Ivan K
48716e7156 Enhance Podkop functionality with global check feature and improved diagnostics. Added support for FakeIP tests in both browser and router contexts. Updated UI elements for better status reporting and added localization for new messages. 2025-05-01 17:18:07 +03:00
itdoginfo
f29b97e495 v0.3.42 2025-05-01 14:17:18 +03:00
itdoginfo
41c21cebcd Fixed validation for ws 2025-04-30 23:43:36 +03:00
itdoginfo
238e99a547 Update 2025-04-30 19:02:31 +03:00
itdoginfo
4f44fcfe99 Update 2025-04-30 14:48:12 +03:00
itdoginfo
9fd2fb9b6e Update 2025-04-30 00:19:42 +03:00
itdoginfo
c0591b25b9 Fix 2025-04-30 00:16:09 +03:00
itdoginfo
97fd392334 Fixed read. Added upgrade flag 2025-04-30 00:11:55 +03:00
itdoginfo
848c784cc0 Fix 2025-04-29 23:49:28 +03:00
itdoginfo
ab971dcd36 Update 2025-04-29 23:48:49 +03:00
itdoginfo
b8d96f28cd Added CF. Fixed https-dns-proxy warning. Masked for static wan 2025-04-29 18:54:50 +03:00
itdoginfo
f2268fd494 v0.3.41. Improved Diagnotics: WAN, WARP, versions, etc 2025-04-29 12:53:29 +03:00
itdoginfo
19897afcdd v0.3.40. Improved Diagnotics 2025-04-28 00:33:07 +03:00
itdoginfo
0e2ea60f01 v0.3.39. Added global check button 2025-04-27 19:29:34 +03:00
itdoginfo
2dc5944961 Fix https-dns-proxy --force-depends 2025-04-27 18:07:58 +03:00
itdoginfo
f65de36804 Detect https-dns-proxy 2025-04-27 15:50:37 +03:00
itdoginfo
19541f8bb3 v0.3.38. fix reload config luci 2025-04-26 22:35:11 +03:00
itdoginfo
aa42c707fe v0.3.37 2025-04-26 17:49:28 +03:00
itdoginfo
bf96f93987 Fix kill stderr. Return if 127.0.0.42 exists 2025-04-26 17:49:04 +03:00
itdoginfo
ff9aad8947 Option enable iface mon 2025-04-26 17:47:52 +03:00
itdoginfo
d9718617bd Option enable iface mon 2025-04-26 17:47:42 +03:00
itdoginfo
e865c9f324 Validate raw network. Path for DoH. Bool for iface monitoring 2025-04-26 17:47:08 +03:00
itdoginfo
7df8bb5826 rmempty proxy url string 2025-04-25 19:29:31 +03:00
itdoginfo
f960358eb6 0.3.36 2025-04-25 10:57:59 +03:00
itdoginfo
ba44966c02 Interface trigger. Disable sing-box autostart. dont touch dhcp. reload without dnsmasq restart 2025-04-24 19:25:08 +03:00
itdoginfo
615241aa37 Merge pull request #88 from Davoyan/patch-1
Update localisation
2025-04-22 11:36:38 +03:00
Davoyan
9a3220d226 Update localisation 2025-04-22 11:24:54 +03:00
itdoginfo
ec8d28857e #82 and #83 2025-04-15 00:42:16 +03:00
itdoginfo
26b49f5bbb Check fix 2025-04-15 00:15:28 +03:00
itdoginfo
0a7efb3169 Fix 2025-04-03 17:53:21 +03:00
itdoginfo
468e51ee8e v0.3.35 2025-04-03 17:42:45 +03:00
itdoginfo
3b93a914de v0.3.34 2025-04-03 17:27:35 +03:00
itdoginfo
76c5baf1e2 Fix tailscale smartdns in resolve.conf 2025-04-03 17:27:13 +03:00
itdoginfo
c752c46abf Fix resolv_conf value 2025-04-03 17:24:57 +03:00
itdoginfo
1df1defa5e Check curl 2025-04-03 17:24:31 +03:00
itdoginfo
3cb4be6427 v0.3.33 2025-04-03 16:47:47 +03:00
itdoginfo
25bfdce5ce Added critical log. Rm friendlywrt check. Added iptables check 2025-04-03 16:47:26 +03:00
itdoginfo
6d0f097a07 Merge pull request #75 from itdoginfo/feature/error-notification (#47)
Feature/error notification
2025-04-03 14:52:00 +03:00
Ivan K
5f780955eb 💄 style(podkop): update error log filtering criteria 2025-04-03 13:42:55 +03:00
Ivan K
389def9056 ♻️ refactor(podkop): remove unused createErrorModal function 2025-04-03 13:40:41 +03:00
Ivan K
e816da5133 feat(podkop): add error logging and notification system 2025-04-03 13:36:22 +03:00
Ivan K
e57adbe042 🔒 refactor(config): Mask NextDNS server address in config output 2025-03-30 20:36:49 +03:00
itdoginfo
d78c51360d Merge pull request #73 from itdoginfo/feature/no-more-cache
🐛 fix(doh): Improve DoH server compatibility detection for quad9
2025-03-30 18:44:26 +03:00
Ivan K
c2357337fc 🐛 fix(dns): improve DoH server compatibility and error handling 2025-03-30 17:20:06 +03:00
Ivan K
bc6490b56e 🐛 fix(doh): Improve DoH server compatibility detection for quad9 2025-03-30 17:04:30 +03:00
itdoginfo
2f645d9151 v0.3.32 2025-03-30 16:03:49 +03:00
itdoginfo
94cc65001b Merge pull request #72 from itdoginfo/feature/no-more-cache
🐛 fix(podkop): Handle DNS check errors and timeouts properly
2025-03-30 16:02:44 +03:00
Ivan K
87caa70e97 feat(dns): Mask NextDNS ID in DNS availability check output 2025-03-30 14:53:01 +03:00
Ivan K
90d7c60fcb 🐛 fix(podkop): Handle DNS check errors and timeouts properly 2025-03-30 14:46:03 +03:00
10 changed files with 952 additions and 442 deletions

141
README.md
View File

@@ -11,78 +11,26 @@
- Если у вас не что-то не работает, то следуюет сходить в телеграм чат, прочитать закрепы и выполнить что там написано.. - Если у вас не что-то не работает, то следуюет сходить в телеграм чат, прочитать закрепы и выполнить что там написано..
- Если у вас установлен Getdomains, его следует удалить. - Если у вас установлен Getdomains, его следует удалить.
# Удаление GetDomains скриптом # Документация
``` https://podkop.net/
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/domain-routing-openwrt/refs/heads/master/getdomains-uninstall.sh)
```
Оставляет туннели, зоны, forwarding. А также stubby и dnscrypt. Они не помешают. Конфиг sing-box будет перезаписан в podkop.
# Установка Podkop # Установка Podkop
Пакет работает на всех архитектурах. Полная информация в [документации](https://podkop.net/docs/install/)
Тестировался на **ванильной** OpenWrt 23.05 и OpenWrt 24.10.
На FriendlyWrt 23.05 присуствуют зависимости от iptables, которые ломают tproxy. Если у вас появляется warning про это в логах, следуйте инструкции по приведённой там ссылке.
Поддержки APK на данный момент нет. APK будет сделан после того как разгребу основное. Вкратце, достаточно одного скрипта:
## Автоматическая
``` ```
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh) sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh)
``` ```
Скрипт также предложит выбрать, какой туннель будет использоваться. Для выбранного туннеля будут установлены нужные пакеты, а для Wireguard и AmneziaWG также будет предложена автоматическая настройка - прямо в консоли скрипт запросит данные конфига. Для AmneziaWG можно также выбрать вариант с использованием конфига обычного Wireguard и автоматической обфускацией до AmneziaWG. ## Обновление
Для AmneziaWG скрипт проверяет наличие пакетов под вашу платформу в [стороннем репозитории](https://github.com/Slava-Shchipunov/awg-openwrt/releases), так как в официальном репозитории OpenWRT они отсутствуют, и автоматически их устанавливает.
## Вручную
Сделать `opkg update`, чтоб установились зависимости.
Скачать пакеты `podkop_*.ipk` и `luci-app-podkop_*.ipk` из релиза. `opkg install` сначала первый, потом второй.
# Обновление
Та же самая команда, что для установки. Скрипт обнаружит уже установленный podkop и предложит обновиться.
``` ```
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh) sh <(wget -qO- https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh) --upgrade
``` ```
# Удаление
```
opkg remove luci-i18n-podkop-ru luci-app-podkop podkop
```
# Использование
Конфиг: /etc/config/podkop
Luci: Services/podkop
## Режимы
### Proxy
Для VLESS и Shadowsocks. Другие протоколы тоже будут, кидайте в чат примеры строк без чувствительных данных.
В этом режиме просто копируйте строку в **Proxy String** и из неё автоматически настроится sing-box.
### VPN
Здесь у вас должен быть уже настроен WG/OpenVPN/OpenConnect etc, зона Zone и Forwarding не обязательны.
Просто выбрать интерфейс из списка.
## Настройка доменов и подсетей
**Community Lists** - Включить списки комьюнити
**Custom domains enable** - Добавить свои домены
**Custom subnets enable** - Добавить подсети или IP-адреса. Для подсетей задать маску.
# Известные баги
- [x] Не отрабатывает service podkop stop, если podkop запущен и не может, к пример, зарезолвить домен с сломанным DNS
- [x] Update list из remote url domain не удаляет старые домены. А добавляет новые. Для подсетей тоже самое скорее всего. Пересоздавать ruleset?
# ToDo # ToDo
Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме. Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме.
- [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn (нужно wiki) Основные задачи в issues.
- [ ] Рестарт сервиса без рестарта dnsmasq
- [ ] `ash: can't kill pid 9848: No such process` при обновлении
Низкий приоритет Низкий приоритет
- [ ] Галочка, которая режет доступ к doh серверам - [ ] Галочка, которая режет доступ к doh серверам
@@ -93,78 +41,3 @@ Luci: Services/podkop
- [ ] Handle для dnsmasq - [ ] Handle для dnsmasq
- [ ] Unit тесты (BATS) - [ ] Unit тесты (BATS)
- [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS) - [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS)
# Разработка
Есть два варианта:
- Просто поставить пакет на роутер или виртуалку и прям редактировать через SFTP (opkg install openssh-sftp-server)
- SDK, чтоб собирать пакеты
Для сборки пакетов нужен SDK, один из вариантов скачать прям файл и разархивировать
https://downloads.openwrt.org/releases/23.05.5/targets/x86/64/
Нужен файл с SDK в имени
```
wget https://downloads.openwrt.org/releases/23.05.5/targets/x86/64/openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64.tar.xz
tar xf openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64.tar.xz
mv openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64 SDK
```
Последнее для удобства.
Создаём директорию для пакета
```
mkdir package/utilites
```
Симлинк из репозитория
```
ln -s ~/podkop/podkop package/utilites/podkop
ln -s ~/podkop/luci-app-podkop package/luci-app-podkop
```
В первый раз для сборки luci-app необходимо обновить пакеты
```
./scripts/feeds update -a
```
Для make можно добавить флаг -j N, где N - количество ядер для сборки. Первый раз пройдёт быстрее.
При первом make выводится менюшка, можно просто save, exit и всё. Первый раз долго грузит зависимости.
Сборка пакета. Сами пакеты собираются быстро.
```
make package/podkop/{clean,compile} V=s
```
Также для luci
```
make package/luci-app-podkop/{clean,compile} V=s
```
.ipk лежат в `bin/packages/x86_64/base/`
## Примеры строк
https://github.com/itdoginfo/podkop/blob/main/String-example.md
## Ошибки
```
Makefile:17: /SDK/feeds/luci/luci.mk: No such file or directory
make[2]: *** No rule to make target '/SDK/feeds/luci/luci.mk'. Stop.
time: package/luci/luci-app-podkop/clean#0.00#0.00#0.00
ERROR: package/luci/luci-app-podkop failed to build.
make[1]: *** [package/Makefile:129: package/luci/luci-app-podkop/clean] Error 1
make[1]: Leaving directory '/SDK'
make: *** [/SDK/include/toplevel.mk:226: package/luci-app-podkop/clean] Error 2
```
Не загружены пакеты для luci
## make зависимости
https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
Ubuntu
```
sudo apt update
sudo apt install build-essential clang flex bison g++ gawk \
gcc-multilib g++-multilib gettext git libncurses-dev libssl-dev \
python3-distutils rsync unzip zlib1g-dev file wget
```

View File

@@ -5,10 +5,17 @@ REPO="https://api.github.com/repos/itdoginfo/podkop/releases/latest"
IS_SHOULD_RESTART_NETWORK= IS_SHOULD_RESTART_NETWORK=
DOWNLOAD_DIR="/tmp/podkop" DOWNLOAD_DIR="/tmp/podkop"
COUNT=3 COUNT=3
UPGRADE=0
rm -rf "$DOWNLOAD_DIR" rm -rf "$DOWNLOAD_DIR"
mkdir -p "$DOWNLOAD_DIR" mkdir -p "$DOWNLOAD_DIR"
for arg in "$@"; do
if [ "$arg" = "--upgrade" ]; then
UPGRADE=1
fi
done
main() { main() {
check_system check_system
sing_box sing_box
@@ -16,38 +23,46 @@ main() {
opkg update opkg update
if [ -f "/etc/init.d/podkop" ]; then if [ -f "/etc/init.d/podkop" ]; then
printf "\033[32;1mPodkop is already installed. Just upgrade it? (y/n)\033[0m\n" if [ "$UPGRADE" -eq 1 ]; then
printf "\033[32;1my - Only upgrade podkop\033[0m\n" echo "Upgraded podkop with flag..."
printf "\033[32;1mn - Upgrade and install tunnels (WG, AWG, OpenVPN, OC)\033[0m\n" break
else
printf "\033[32;1mPodkop is already installed. Just upgrade it?\033[0m\n"
printf "\033[32;1my - Only upgrade podkop\033[0m\n"
printf "\033[32;1mn - Upgrade and install tunnels (WG, AWG, OpenVPN, OC)\033[0m\n"
while true; do while true; do
read -r -p '' UPDATE printf "\033[32;1mEnter (y/n): \033[0m"
case $UPDATE in read -r -p '' UPDATE
y) case $UPDATE in
echo "Upgraded podkop..." y)
break echo "Upgraded podkop..."
;; break
;;
n) n)
add_tunnel add_tunnel
break break
;; ;;
*) *)
echo "Please enter y or n" echo "Please enter y or n"
;; ;;
esac esac
done done
fi
else else
echo "Installed podkop..." echo "Installed podkop..."
add_tunnel add_tunnel
fi fi
if command -v curl &> /dev/null; then
check_response=$(curl -s "https://api.github.com/repos/itdoginfo/podkop/releases/latest")
check_response=$(curl -s "https://api.github.com/repos/itdoginfo/podkop/releases/latest") if echo "$check_response" | grep -q 'API rate limit '; then
echo "You've reached rate limit from GitHub. Repeat in five minutes."
if echo "$check_response" | grep -q 'API rate limit '; then exit 1
echo "You've reached rate limit from GitHub. Repeat in five minutes." fi
exit 1
fi fi
download_success=0 download_success=0
@@ -158,13 +173,13 @@ add_tunnel() {
;; ;;
3) 3)
opkg install opkg install openvpn-openssl luci-app-openvpn opkg install openvpn-openssl luci-app-openvpn
printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openvpn-na-openwrt/\e[0m\n" printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openvpn-na-openwrt/\e[0m\n"
break break
;; ;;
4) 4)
opkg install opkg install openconnect luci-proto-openconnect opkg install openconnect luci-proto-openconnect
printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openconnect-na-openwrt/\e[0m\n" printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openconnect-na-openwrt/\e[0m\n"
break break
;; ;;
@@ -246,8 +261,8 @@ install_awg_packages() {
fi fi
fi fi
if opkg list-installed | grep -q luci-app-amneziawg; then if opkg list-installed | grep -qE 'luci-app-amneziawg|luci-proto-amneziawg'; then
echo "luci-app-amneziawg already installed" echo "luci-app-amneziawg or luci-proto-amneziawg already installed"
else else
LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}" LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}"
DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}" DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}"
@@ -423,7 +438,26 @@ check_system() {
exit 1 exit 1
fi fi
if opkg list-installed | grep -qE "iptables|kmod-iptab"; then if opkg list-installed | grep -q https-dns-proxy; then
printf "\033[31;1mСonflicting package detected: https-dns-proxy. Remove? yes/no\033[0m\n"
while true; do
read -r -p '' DNSPROXY
case $DNSPROXY in
yes|y|Y|yes)
opkg remove --force-depends luci-app-https-dns-proxy https-dns-proxy luci-i18n-https-dns-proxy*
break
;;
*)
echo "Exit"
exit 1
;;
esac
done
fi
if opkg list-installed | grep -q "iptables-mod-extra"; then
printf "\033[31;1mFound incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082\033[0m\n" printf "\033[31;1mFound incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082\033[0m\n"
fi fi
} }

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-podkop PKG_NAME:=luci-app-podkop
PKG_VERSION:=0.3.31 PKG_VERSION:=0.3.45
PKG_RELEASE:=1 PKG_RELEASE:=1
LUCI_TITLE:=LuCI podkop app LUCI_TITLE:=LuCI podkop app

View File

@@ -12,6 +12,8 @@ const STATUS_COLORS = {
WARNING: '#ff9800' WARNING: '#ff9800'
}; };
const ERROR_POLL_INTERVAL = 5000; // 5 seconds
async function safeExec(command, args = [], timeout = 7000) { async function safeExec(command, args = [], timeout = 7000) {
try { try {
const controller = new AbortController(); const controller = new AbortController();
@@ -60,6 +62,23 @@ function getNetworkInterfaces(o, section_id, excludeInterfaces = []) {
}); });
} }
function getNetworkNetworks(o, section_id, excludeInterfaces = []) {
return network.getNetworks().then(networks => {
o.keylist = [];
o.vallist = [];
networks.forEach(net => {
const name = net.getName();
const ifname = net.getIfname();
if (name && !excludeInterfaces.includes(name)) {
o.value(name, ifname ? `${name} (${ifname})` : name);
}
});
}).catch(error => {
console.error('Failed to get networks:', error);
});
}
function createConfigSection(section, map, network) { function createConfigSection(section, map, network) {
const s = section; const s = section;
@@ -80,6 +99,7 @@ function createConfigSection(section, map, network) {
o = s.taboption('basic', form.TextValue, 'proxy_string', _('Proxy Configuration URL'), _('')); o = s.taboption('basic', form.TextValue, 'proxy_string', _('Proxy Configuration URL'), _(''));
o.depends('proxy_config_type', 'url'); o.depends('proxy_config_type', 'url');
o.rows = 5; o.rows = 5;
o.rmempty = false;
o.ucisection = s.section; o.ucisection = s.section;
o.sectionDescriptions = new Map(); o.sectionDescriptions = new Map();
o.placeholder = 'vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt'; o.placeholder = 'vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt';
@@ -204,9 +224,9 @@ function createConfigSection(section, map, network) {
let params = new URLSearchParams(queryString.split('#')[0]); let params = new URLSearchParams(queryString.split('#')[0]);
let type = params.get('type'); let type = params.get('type');
const validTypes = ['tcp', 'udp', 'grpc', 'http']; const validTypes = ['tcp', 'raw', 'udp', 'grpc', 'http', 'ws'];
if (!type || !validTypes.includes(type)) { if (!type || !validTypes.includes(type)) {
return _('Invalid VLESS URL: type must be one of tcp, udp, grpc, http'); return _('Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws');
} }
let security = params.get('security'); let security = params.get('security');
@@ -259,7 +279,7 @@ function createConfigSection(section, map, network) {
o.depends('mode', 'vpn'); o.depends('mode', 'vpn');
o.ucisection = s.section; o.ucisection = s.section;
o.load = function (section_id) { o.load = function (section_id) {
return getNetworkInterfaces(this, section_id, ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan']).then(() => { return getNetworkInterfaces(this, section_id, ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan', 'lan']).then(() => {
return this.super('load', section_id); return this.super('load', section_id);
}); });
}; };
@@ -286,6 +306,7 @@ function createConfigSection(section, map, network) {
o.value('hdrezka', 'HDRezka'); o.value('hdrezka', 'HDRezka');
o.value('tiktok', 'Tik-Tok'); o.value('tiktok', 'Tik-Tok');
o.value('telegram', 'Telegram'); o.value('telegram', 'Telegram');
o.value('cloudflare', 'Cloudflare');
o.depends('domain_list_enabled', '1'); o.depends('domain_list_enabled', '1');
o.rmempty = false; o.rmempty = false;
o.ucisection = s.section; o.ucisection = s.section;
@@ -317,13 +338,13 @@ function createConfigSection(section, map, network) {
} }
if (newValues.includes('russia_inside')) { if (newValues.includes('russia_inside')) {
const allowedWithRussiaInside = ['russia_inside', 'meta', 'twitter', 'discord', 'telegram']; const allowedWithRussiaInside = ['russia_inside', 'meta', 'twitter', 'discord', 'telegram', 'cloudflare'];
const removedServices = newValues.filter(v => !allowedWithRussiaInside.includes(v)); const removedServices = newValues.filter(v => !allowedWithRussiaInside.includes(v));
if (removedServices.length > 0) { if (removedServices.length > 0) {
newValues = newValues.filter(v => allowedWithRussiaInside.includes(v)); newValues = newValues.filter(v => allowedWithRussiaInside.includes(v));
notifications.push(E('p', { class: 'alert-message warning' }, [ notifications.push(E('p', { class: 'alert-message warning' }, [
E('strong', {}, _('Russia inside restrictions')), E('br'), E('strong', {}, _('Russia inside restrictions')), E('br'),
_('Warning: Russia inside can only be used with Meta, Twitter, Discord, and Telegram. %s already in Russia inside and have been removed from selection.') _('Warning: Russia inside can only be used with Meta, Twitter, Discord, Cloudflare and Telegram. %s already in Russia inside and have been removed from selection.')
.format(removedServices.join(', ')) .format(removedServices.join(', '))
])); ]));
} }
@@ -600,9 +621,61 @@ const createModalContent = (title, content) => {
]; ];
}; };
// Add IP masking function before showConfigModal
const maskIP = (ip) => {
if (!ip) return '';
const parts = ip.split('.');
if (parts.length !== 4) return ip;
return ['XX', 'XX', 'XX', parts[3]].join('.');
};
const showConfigModal = async (command, title) => { const showConfigModal = async (command, title) => {
const res = await safeExec('/usr/bin/podkop', [command]); const res = await safeExec('/usr/bin/podkop', [command]);
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); let formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
if (command === 'global_check') {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch('https://fakeip.podkop.fyi/check', { signal: controller.signal });
const data = await response.json();
clearTimeout(timeoutId);
if (data.fakeip === true) {
formattedOutput += '\n✅ ' + _('FakeIP is working in browser!') + '\n';
} else {
formattedOutput += '\n❌ ' + _('FakeIP is not working in browser') + '\n';
formattedOutput += _('Check DNS server on current device (PC, phone)') + '\n';
formattedOutput += _('Its must be router!') + '\n';
}
// Bypass check
const bypassResponse = await fetch('https://fakeip.podkop.fyi/check', { signal: controller.signal });
const bypassData = await bypassResponse.json();
const bypassResponse2 = await fetch('https://ip.podkop.fyi/check', { signal: controller.signal });
const bypassData2 = await bypassResponse2.json();
formattedOutput += '━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
if (bypassData.IP && bypassData2.IP && bypassData.IP !== bypassData2.IP) {
formattedOutput += '✅ ' + _('Proxy working correctly') + '\n';
formattedOutput += _('Direct IP: ') + maskIP(bypassData.IP) + '\n';
formattedOutput += _('Proxy IP: ') + maskIP(bypassData2.IP) + '\n';
} else if (bypassData.IP === bypassData2.IP) {
formattedOutput += '❌ ' + _('Proxy is not working - same IP for both domains') + '\n';
formattedOutput += _('IP: ') + maskIP(bypassData.IP) + '\n';
} else {
formattedOutput += '❌ ' + _('Proxy check failed') + '\n';
}
} catch (error) {
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
}
}
ui.showModal(_(title), createModalContent(_(title), formattedOutput)); ui.showModal(_(title), createModalContent(_(title), formattedOutput));
}; };
@@ -646,16 +719,23 @@ const ButtonFactory = {
}; };
// Status Panel Factory // Status Panel Factory
const createStatusPanel = (title, status, buttons) => { const createStatusPanel = (title, status, buttons, extraData = {}) => {
const headerContent = [ const headerContent = [
E('strong', {}, _(title)), E('strong', {}, _(title)),
status && E('br'), status && E('br'),
status && E('span', { status && E('span', {
'style': `color: ${status.running ? STATUS_COLORS.SUCCESS : STATUS_COLORS.ERROR}` 'style': `color: ${title === 'Sing-box Status' ?
(status.running && !status.enabled ? STATUS_COLORS.SUCCESS : STATUS_COLORS.ERROR) :
title === 'Podkop Status' ?
(status.enabled ? STATUS_COLORS.SUCCESS : STATUS_COLORS.ERROR) :
(status.running ? STATUS_COLORS.SUCCESS : STATUS_COLORS.ERROR)
}`
}, [ }, [
status.running ? '✔' : '✘', title === 'Sing-box Status' ?
' ', (status.running && !status.enabled ? '✔ running' : '✘ ' + status.status) :
status.status title === 'Podkop Status' ?
(status.enabled ? '✔ Autostart enabled' : '✘ Autostart disabled') :
(status.running ? '✔' : '✘') + ' ' + status.status
]) ])
].filter(Boolean); ].filter(Boolean);
@@ -667,7 +747,86 @@ const createStatusPanel = (title, status, buttons) => {
E('div', { E('div', {
'class': 'panel-body', 'class': 'panel-body',
'style': 'display: flex; flex-direction: column; gap: 8px;' 'style': 'display: flex; flex-direction: column; gap: 8px;'
}, buttons) }, title === 'Podkop Status' ? [
ButtonFactory.createActionButton({
label: 'Restart Podkop',
type: 'apply',
action: 'restart',
reload: true
}),
ButtonFactory.createActionButton({
label: 'Stop Podkop',
type: 'apply',
action: 'stop',
reload: true
}),
ButtonFactory.createInitActionButton({
label: status.enabled ? 'Disable Autostart' : 'Enable Autostart',
type: status.enabled ? 'remove' : 'apply',
action: status.enabled ? 'disable' : 'enable',
reload: true
}),
ButtonFactory.createModalButton({
label: E('strong', _('Global check')),
command: 'global_check',
title: _('Click here for all the info')
}),
ButtonFactory.createModalButton({
label: 'View Logs',
command: 'check_logs',
title: 'Podkop Logs'
}),
ButtonFactory.createModalButton({
label: _('Update Lists'),
command: 'list_update',
title: _('Lists Update Results')
})
] : title === _('FakeIP Status') ? [
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('span', { style: `color: ${extraData.fakeipStatus?.color}` }, [
extraData.fakeipStatus?.state === 'working' ? '✔' : extraData.fakeipStatus?.state === 'not_working' ? '✘' : '!',
' ',
extraData.fakeipStatus?.state === 'working' ? _('works in browser') : _('not works in browser')
])
]),
E('div', {}, [
E('span', { style: `color: ${extraData.fakeipCLIStatus?.color}` }, [
extraData.fakeipCLIStatus?.state === 'working' ? '✔' : extraData.fakeipCLIStatus?.state === 'not_working' ? '✘' : '!',
' ',
extraData.fakeipCLIStatus?.state === 'working' ? _('works on router') : _('not works on router')
])
])
]),
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('strong', {}, _('DNS Status')),
E('br'),
E('span', { style: `color: ${extraData.dnsStatus?.remote?.color}` }, [
extraData.dnsStatus?.remote?.state === 'available' ? '✔' : extraData.dnsStatus?.remote?.state === 'unavailable' ? '✘' : '!',
' ',
extraData.dnsStatus?.remote?.message
]),
E('br'),
E('span', { style: `color: ${extraData.dnsStatus?.local?.color}` }, [
extraData.dnsStatus?.local?.state === 'available' ? '✔' : extraData.dnsStatus?.local?.state === 'unavailable' ? '✘' : '!',
' ',
extraData.dnsStatus?.local?.message
])
])
]),
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('strong', {}, extraData.configName),
E('br'),
E('span', { style: `color: ${extraData.bypassStatus?.color}` }, [
extraData.bypassStatus?.state === 'working' ? '✔' : extraData.bypassStatus?.state === 'not_working' ? '✘' : '!',
' ',
extraData.bypassStatus?.message
])
])
])
] : buttons)
]); ]);
}; };
@@ -677,12 +836,6 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [ E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [
// Podkop Status Panel // Podkop Status Panel
createStatusPanel('Podkop Status', podkopStatus, [ createStatusPanel('Podkop Status', podkopStatus, [
ButtonFactory.createActionButton({
label: podkopStatus.running ? 'Stop Podkop' : 'Start Podkop',
type: podkopStatus.running ? 'remove' : 'apply',
action: podkopStatus.running ? 'stop' : 'start',
reload: true
}),
ButtonFactory.createActionButton({ ButtonFactory.createActionButton({
label: 'Restart Podkop', label: 'Restart Podkop',
type: 'apply', type: 'apply',
@@ -696,9 +849,9 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
reload: true reload: true
}), }),
ButtonFactory.createModalButton({ ButtonFactory.createModalButton({
label: 'Show Config', label: _('Global check'),
command: 'show_config', command: 'global_check',
title: 'Podkop Configuration' title: _('Click here for all the info')
}), }),
ButtonFactory.createModalButton({ ButtonFactory.createModalButton({
label: 'View Logs', label: 'View Logs',
@@ -741,53 +894,14 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
}) })
]), ]),
// FakeIP Status Panel with both browser and router checks // FakeIP Status Panel
createStatusPanel(_('FakeIP Status'), null, [ createStatusPanel(_('FakeIP Status'), null, null, {
E('div', { style: 'margin-bottom: 10px;' }, [ fakeipStatus,
E('div', { style: 'margin-bottom: 5px;' }, [ fakeipCLIStatus,
E('span', { style: `color: ${fakeipStatus.color}` }, [ dnsStatus,
fakeipStatus.state === 'working' ? '✔' : fakeipStatus.state === 'not_working' ? '✘' : '!', bypassStatus,
' ', configName
fakeipStatus.state === 'working' ? _('works in browser') : _('not works in browser') }),
])
]),
E('div', {}, [
E('span', { style: `color: ${fakeipCLIStatus.color}` }, [
fakeipCLIStatus.state === 'working' ? '✔' : fakeipCLIStatus.state === 'not_working' ? '✘' : '!',
' ',
fakeipCLIStatus.state === 'working' ? _('works on router') : _('not works on router')
])
])
]),
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('strong', {}, _('DNS Status')),
E('br'),
E('span', { style: `color: ${dnsStatus.remote.color}` }, [
dnsStatus.remote.state === 'available' ? '✔' : dnsStatus.remote.state === 'unavailable' ? '✘' : '!',
' ',
dnsStatus.remote.message
]),
E('br'),
E('span', { style: `color: ${dnsStatus.local.color}` }, [
dnsStatus.local.state === 'available' ? '✔' : dnsStatus.local.state === 'unavailable' ? '✘' : '!',
' ',
dnsStatus.local.message
])
])
]),
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('strong', {}, configName),
E('br'),
E('span', { style: `color: ${bypassStatus.color}` }, [
bypassStatus.state === 'working' ? '✔' : bypassStatus.state === 'not_working' ? '✘' : '!',
' ',
bypassStatus.message
])
])
])
]),
// Version Information Panel // Version Information Panel
createStatusPanel(_('Version Information'), null, [ createStatusPanel(_('Version Information'), null, [
@@ -813,20 +927,34 @@ function checkDNSAvailability() {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
try { try {
const dnsStatusResult = await safeExec('/usr/bin/podkop', ['check_dns_available']); const dnsStatusResult = await safeExec('/usr/bin/podkop', ['check_dns_available']);
const dnsStatus = JSON.parse(dnsStatusResult.stdout || '{"dns_type":"unknown","dns_server":"unknown","is_available":0,"status":"unknown","local_dns_working":0,"local_dns_status":"unknown"}'); if (!dnsStatusResult || !dnsStatusResult.stdout) {
return resolve({
remote: createStatus('error', 'DNS check timeout', 'WARNING'),
local: createStatus('error', 'DNS check timeout', 'WARNING')
});
}
const remoteStatus = dnsStatus.is_available ? try {
createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') : const dnsStatus = JSON.parse(dnsStatusResult.stdout);
createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
const localStatus = dnsStatus.local_dns_working ? const remoteStatus = dnsStatus.is_available ?
createStatus('available', 'Router DNS working', 'SUCCESS') : createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') :
createStatus('unavailable', 'Router DNS not working', 'ERROR'); createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
return resolve({ const localStatus = dnsStatus.local_dns_working ?
remote: remoteStatus, createStatus('available', 'Router DNS working', 'SUCCESS') :
local: localStatus createStatus('unavailable', 'Router DNS not working', 'ERROR');
});
return resolve({
remote: remoteStatus,
local: localStatus
});
} catch (parseError) {
return resolve({
remote: createStatus('error', 'DNS check parse error', 'WARNING'),
local: createStatus('error', 'DNS check parse error', 'WARNING')
});
}
} catch (error) { } catch (error) {
return resolve({ return resolve({
remote: createStatus('error', 'DNS check error', 'WARNING'), remote: createStatus('error', 'DNS check error', 'WARNING'),
@@ -836,6 +964,84 @@ function checkDNSAvailability() {
}); });
} }
async function getPodkopErrors() {
try {
const result = await safeExec('/usr/bin/podkop', ['check_logs']);
if (!result || !result.stdout) return [];
const logs = result.stdout.split('\n');
const errors = logs.filter(log =>
// log.includes('saved for future filters') ||
log.includes('[critical]')
);
console.log('Found errors:', errors);
return errors;
} catch (error) {
console.error('Error getting podkop logs:', error);
return [];
}
}
let errorPollTimer = null;
let lastErrorsSet = new Set();
let isInitialCheck = true;
function showErrorNotification(error, isMultiple = false) {
const notificationContent = E('div', { 'class': 'alert-message error' }, [
E('pre', { 'class': 'error-log' }, error)
]);
ui.addNotification(null, notificationContent);
}
function startErrorPolling() {
if (errorPollTimer) {
clearInterval(errorPollTimer);
}
async function checkErrors() {
const result = await safeExec('/usr/bin/podkop', ['check_logs']);
if (!result || !result.stdout) return;
const logs = result.stdout;
const errorLines = logs.split('\n').filter(line =>
// line.includes('saved for future filters') ||
line.includes('[critical]')
);
if (errorLines.length > 0) {
const currentErrors = new Set(errorLines);
if (isInitialCheck) {
if (errorLines.length > 0) {
showErrorNotification(errorLines.join('\n'), true);
}
isInitialCheck = false;
} else {
const newErrors = [...currentErrors].filter(error => !lastErrorsSet.has(error));
newErrors.forEach(error => {
showErrorNotification(error, false);
});
}
lastErrorsSet = currentErrors;
}
}
checkErrors();
errorPollTimer = setInterval(checkErrors, ERROR_POLL_INTERVAL);
}
function stopErrorPolling() {
if (errorPollTimer) {
clearInterval(errorPollTimer);
errorPollTimer = null;
}
}
return view.extend({ return view.extend({
async render() { async render() {
document.head.insertAdjacentHTML('beforeend', ` document.head.insertAdjacentHTML('beforeend', `
@@ -932,15 +1138,15 @@ return view.extend({
return true; return true;
} }
const domainRegex = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$/; const domainRegex = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/;
if (!domainRegex.test(value)) { if (!domainRegex.test(value)) {
return _('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com'); return _('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH');
} }
return true; return true;
}; };
o = mainSection.taboption('additional', form.Value, 'dns_rewrite_ttl', _('DNS Rewrite TTL'), _('Time in seconds for DNS record caching (default: 600)')); o = mainSection.taboption('additional', form.Value, 'dns_rewrite_ttl', _('DNS Rewrite TTL'), _('Time in seconds for DNS record caching (default: 60)'));
o.default = '60'; o.default = '60';
o.rmempty = false; o.rmempty = false;
o.ucisection = 'main'; o.ucisection = 'main';
@@ -993,6 +1199,30 @@ return view.extend({
}); });
}; };
o = mainSection.taboption('additional', form.Flag, 'mon_restart_ifaces', _('Interface monitoring'), _('Interface monitoring for bad WAN'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.MultiValue, 'restart_ifaces', _('Interface for monitoring'), _('Select the WAN interfaces to be monitored'));
o.ucisection = 'main';
o.depends('mon_restart_ifaces', '1');
o.load = function (section_id) {
return getNetworkNetworks(this, section_id, ['lan', 'loopback']).then(() => {
return this.super('load', section_id);
});
};
o = mainSection.taboption('additional', form.Flag, 'dont_touch_dhcp', _('Dont touch my DHCP!'), _('Podkop will not change the DHCP config'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.Flag, 'detour', _('Proxy download of lists'), _('Downloading all lists via main Proxy/VPN'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
// Extra IPs and exclusions (main section) // Extra IPs and exclusions (main section)
o = mainSection.taboption('basic', form.Flag, 'exclude_from_ip_enabled', _('IP for exclusion'), _('Specify local IP addresses that will never use the configured route')); o = mainSection.taboption('basic', form.Flag, 'exclude_from_ip_enabled', _('IP for exclusion'), _('Specify local IP addresses that will never use the configured route'));
o.default = '0'; o.default = '0';
@@ -1083,7 +1313,7 @@ return view.extend({
const timeoutId = setTimeout(() => controller.abort(), 10000); const timeoutId = setTimeout(() => controller.abort(), 10000);
try { try {
const response = await fetch('https://fakeip.tech-domain.club/check', { signal: controller.signal }); const response = await fetch('https://fakeip.podkop.fyi/check', { signal: controller.signal });
const data = await response.json(); const data = await response.json();
clearTimeout(timeoutId); clearTimeout(timeoutId);
@@ -1122,7 +1352,7 @@ return view.extend({
return resolve(createStatus('not_working', 'DNS not configured', 'ERROR')); return resolve(createStatus('not_working', 'DNS not configured', 'ERROR'));
} }
const result = await safeExec('nslookup', ['-timeout=2', 'fakeip.tech-domain.club', '127.0.0.42']); const result = await safeExec('nslookup', ['-timeout=2', 'fakeip.podkop.fyi', '127.0.0.42']);
if (result.stdout && result.stdout.includes('198.18')) { if (result.stdout && result.stdout.includes('198.18')) {
return resolve(createStatus('working', 'working on router', 'SUCCESS')); return resolve(createStatus('working', 'working on router', 'SUCCESS'));
@@ -1171,7 +1401,7 @@ return view.extend({
const controller1 = new AbortController(); const controller1 = new AbortController();
const timeoutId1 = setTimeout(() => controller1.abort(), 10000); const timeoutId1 = setTimeout(() => controller1.abort(), 10000);
const response1 = await fetch('https://fakeip.tech-domain.club/check', { signal: controller1.signal }); const response1 = await fetch('https://fakeip.podkop.fyi/check', { signal: controller1.signal });
const data1 = await response1.json(); const data1 = await response1.json();
clearTimeout(timeoutId1); clearTimeout(timeoutId1);
@@ -1186,7 +1416,7 @@ return view.extend({
const controller2 = new AbortController(); const controller2 = new AbortController();
const timeoutId2 = setTimeout(() => controller2.abort(), 10000); const timeoutId2 = setTimeout(() => controller2.abort(), 10000);
const response2 = await fetch('https://ip.tech-domain.club/check', { signal: controller2.signal }); const response2 = await fetch('https://ip.podkop.fyi/check', { signal: controller2.signal });
const data2 = await response2.json(); const data2 = await response2.json();
clearTimeout(timeoutId2); clearTimeout(timeoutId2);
@@ -1213,32 +1443,67 @@ return view.extend({
async function updateDiagnostics() { async function updateDiagnostics() {
try { try {
const [ const results = {
podkopStatus, podkopStatus: null,
singboxStatus, singboxStatus: null,
podkop, podkop: null,
luci, luci: null,
singbox, singbox: null,
system, system: null,
fakeipStatus, fakeipStatus: null,
fakeipCLIStatus, fakeipCLIStatus: null,
dnsStatus, dnsStatus: null,
bypassStatus bypassStatus: null
] = await Promise.all([ };
safeExec('/usr/bin/podkop', ['get_status']),
safeExec('/usr/bin/podkop', ['get_sing_box_status']),
safeExec('/usr/bin/podkop', ['show_version']),
safeExec('/usr/bin/podkop', ['show_luci_version']),
safeExec('/usr/bin/podkop', ['show_sing_box_version']),
safeExec('/usr/bin/podkop', ['show_system_info']),
checkFakeIP(),
checkFakeIPCLI(),
checkDNSAvailability(),
checkBypass()
]);
const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}'); // Perform all checks independently of each other
const parsedSingboxStatus = JSON.parse(singboxStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}'); const checks = [
safeExec('/usr/bin/podkop', ['get_status'])
.then(result => results.podkopStatus = result)
.catch(() => results.podkopStatus = { stdout: '{"enabled":0,"status":"error"}' }),
safeExec('/usr/bin/podkop', ['get_sing_box_status'])
.then(result => results.singboxStatus = result)
.catch(() => results.singboxStatus = { stdout: '{"running":0,"enabled":0,"status":"error"}' }),
safeExec('/usr/bin/podkop', ['show_version'])
.then(result => results.podkop = result)
.catch(() => results.podkop = { stdout: 'error' }),
safeExec('/usr/bin/podkop', ['show_luci_version'])
.then(result => results.luci = result)
.catch(() => results.luci = { stdout: 'error' }),
safeExec('/usr/bin/podkop', ['show_sing_box_version'])
.then(result => results.singbox = result)
.catch(() => results.singbox = { stdout: 'error' }),
safeExec('/usr/bin/podkop', ['show_system_info'])
.then(result => results.system = result)
.catch(() => results.system = { stdout: 'error' }),
checkFakeIP()
.then(result => results.fakeipStatus = result)
.catch(() => results.fakeipStatus = { state: 'error', message: 'check error', color: STATUS_COLORS.WARNING }),
checkFakeIPCLI()
.then(result => results.fakeipCLIStatus = result)
.catch(() => results.fakeipCLIStatus = { state: 'error', message: 'check error', color: STATUS_COLORS.WARNING }),
checkDNSAvailability()
.then(result => results.dnsStatus = result)
.catch(() => results.dnsStatus = {
remote: { state: 'error', message: 'check error', color: STATUS_COLORS.WARNING },
local: { state: 'error', message: 'check error', color: STATUS_COLORS.WARNING }
}),
checkBypass()
.then(result => results.bypassStatus = result)
.catch(() => results.bypassStatus = { state: 'error', message: 'check error', color: STATUS_COLORS.WARNING })
];
// Waiting for all the checks to be completed
await Promise.allSettled(checks);
const container = document.getElementById('diagnostics-status'); const container = document.getElementById('diagnostics-status');
if (!container) return; if (!container) return;
@@ -1258,11 +1523,7 @@ return view.extend({
const label = activeConfig.split('#').pop(); const label = activeConfig.split('#').pop();
if (label && label.trim()) { if (label && label.trim()) {
configName = _('Config: ') + decodeURIComponent(label); configName = _('Config: ') + decodeURIComponent(label);
} else {
configName = _('Main config');
} }
} else {
configName = _('Main config');
} }
} }
} }
@@ -1270,42 +1531,62 @@ return view.extend({
console.error('Error getting config name from UCI:', e); console.error('Error getting config name from UCI:', e);
} }
// Create a modified statusSection function with the configName const parsedPodkopStatus = JSON.parse(results.podkopStatus.stdout || '{"enabled":0,"status":"error"}');
const statusSection = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus, dnsStatus, bypassStatus, configName); const parsedSingboxStatus = JSON.parse(results.singboxStatus.stdout || '{"running":0,"enabled":0,"status":"error"}');
const statusSection = createStatusSection(
parsedPodkopStatus,
parsedSingboxStatus,
results.podkop,
results.luci,
results.singbox,
results.system,
results.fakeipStatus,
results.fakeipCLIStatus,
results.dnsStatus,
results.bypassStatus,
configName
);
container.innerHTML = ''; container.innerHTML = '';
container.appendChild(statusSection); container.appendChild(statusSection);
const fakeipElement = document.getElementById('fakeip-status'); // Updating individual status items
if (fakeipElement) { const updateStatusElement = (elementId, status, template) => {
fakeipElement.innerHTML = E('span', { 'style': `color: ${fakeipStatus.color}` }, [ const element = document.getElementById(elementId);
fakeipStatus.state === 'working' ? '✔ ' : fakeipStatus.state === 'not_working' ? '✘ ' : '! ', if (element) {
fakeipStatus.message element.innerHTML = template(status);
]).outerHTML; }
} };
const fakeipCLIElement = document.getElementById('fakeip-cli-status'); updateStatusElement('fakeip-status', results.fakeipStatus,
if (fakeipCLIElement) { status => E('span', { 'style': `color: ${status.color}` }, [
fakeipCLIElement.innerHTML = E('span', { 'style': `color: ${fakeipCLIStatus.color}` }, [ status.state === 'working' ? '✔ ' : status.state === 'not_working' ? '✘ ' : '! ',
fakeipCLIStatus.state === 'working' ? '✔ ' : fakeipCLIStatus.state === 'not_working' ? '✘ ' : '! ', status.message
fakeipCLIStatus.message ]).outerHTML
]).outerHTML; );
}
const dnsRemoteElement = document.getElementById('dns-remote-status'); updateStatusElement('fakeip-cli-status', results.fakeipCLIStatus,
if (dnsRemoteElement) { status => E('span', { 'style': `color: ${status.color}` }, [
dnsRemoteElement.innerHTML = E('span', { 'style': `color: ${dnsStatus.remote.color}` }, [ status.state === 'working' ? '✔ ' : status.state === 'not_working' ? '✘ ' : '! ',
dnsStatus.remote.state === 'available' ? '✔ ' : dnsStatus.remote.state === 'unavailable' ? '✘ ' : '! ', status.message
dnsStatus.remote.message ]).outerHTML
]).outerHTML; );
}
updateStatusElement('dns-remote-status', results.dnsStatus.remote,
status => E('span', { 'style': `color: ${status.color}` }, [
status.state === 'available' ? '✔ ' : status.state === 'unavailable' ? '✘ ' : '! ',
status.message
]).outerHTML
);
updateStatusElement('dns-local-status', results.dnsStatus.local,
status => E('span', { 'style': `color: ${status.color}` }, [
status.state === 'available' ? '✔ ' : status.state === 'unavailable' ? '✘ ' : '! ',
status.message
]).outerHTML
);
const dnsLocalElement = document.getElementById('dns-local-status');
if (dnsLocalElement) {
dnsLocalElement.innerHTML = E('span', { 'style': `color: ${dnsStatus.local.color}` }, [
dnsStatus.local.state === 'available' ? '✔ ' : dnsStatus.local.state === 'unavailable' ? '✘ ' : '! ',
dnsStatus.local.message
]).outerHTML;
}
} catch (e) { } catch (e) {
const container = document.getElementById('diagnostics-status'); const container = document.getElementById('diagnostics-status');
if (container) { if (container) {
@@ -1334,8 +1615,10 @@ return view.extend({
const diagnosticsContainer = document.getElementById('diagnostics-status'); const diagnosticsContainer = document.getElementById('diagnostics-status');
if (document.hidden) { if (document.hidden) {
stopDiagnosticsUpdates(); stopDiagnosticsUpdates();
stopErrorPolling();
} else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) { } else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) {
startDiagnosticsUpdates(); startDiagnosticsUpdates();
startErrorPolling();
} }
}); });
@@ -1346,6 +1629,7 @@ return view.extend({
if (!this.hasAttribute('data-loading')) { if (!this.hasAttribute('data-loading')) {
this.setAttribute('data-loading', 'true'); this.setAttribute('data-loading', 'true');
startDiagnosticsUpdates(); startDiagnosticsUpdates();
startErrorPolling();
} }
}); });
} }
@@ -1361,9 +1645,11 @@ return view.extend({
if (container && !container.hasAttribute('data-loading')) { if (container && !container.hasAttribute('data-loading')) {
container.setAttribute('data-loading', 'true'); container.setAttribute('data-loading', 'true');
startDiagnosticsUpdates(); startDiagnosticsUpdates();
startErrorPolling();
} }
} else { } else {
stopDiagnosticsUpdates(); stopDiagnosticsUpdates();
stopErrorPolling();
} }
} }
}); });
@@ -1374,6 +1660,7 @@ return view.extend({
if (container && !container.hasAttribute('data-loading')) { if (container && !container.hasAttribute('data-loading')) {
container.setAttribute('data-loading', 'true'); container.setAttribute('data-loading', 'true');
startDiagnosticsUpdates(); startDiagnosticsUpdates();
startErrorPolling();
} }
} }
} }

View File

@@ -88,8 +88,8 @@ msgstr "Введите имена доменов без протоколов (п
msgid "User Domains List" msgid "User Domains List"
msgstr "Список пользовательских доменов" msgstr "Список пользовательских доменов"
msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)" msgid "Enter domain names separated by comma, space or newline. You can add comments after //"
msgstr "Введите имена доменов через запятую, пробел или новую строку (пример: sub.example.com, example.com или один домен на строку)" msgstr "Введите имена доменов, разделяя их запятой, пробелом или с новой строки. Вы можете добавлять комментарии после //"
msgid "Local Domain Lists" msgid "Local Domain Lists"
msgstr "Локальные списки доменов" msgstr "Локальные списки доменов"
@@ -556,6 +556,9 @@ msgstr "Путь должен содержать хотя бы одну дире
msgid "Invalid path format. Must be like /tmp/cache.db" msgid "Invalid path format. Must be like /tmp/cache.db"
msgstr "Неверный формат пути. Пример: /tmp/cache.db" msgstr "Неверный формат пути. Пример: /tmp/cache.db"
msgid "Select the network interface from which the traffic will originate"
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
msgid "Copy to Clipboard" msgid "Copy to Clipboard"
msgstr "Копировать в буфер обмена" msgstr "Копировать в буфер обмена"
@@ -812,4 +815,31 @@ msgid "available"
msgstr "доступен" msgstr "доступен"
msgid "unavailable" msgid "unavailable"
msgstr "недоступен" msgstr "недоступен"
msgid "Apply for SS2022"
msgstr "Применить для SS2022"
msgid "PODKOP CONFIGURATION"
msgstr "КОНФИГУРАЦИЯ PODKOP"
msgid "FAKEIP ROUTER TEST"
msgstr "ПРОВЕРКА FAKEIP НА РОУТЕРЕ"
msgid "FAKEIP BROWSER TEST"
msgstr "ПРОВЕРКА FAKEIP В БРАУЗЕРЕ"
msgid "FakeIP is working correctly on router (198.18.x.x)"
msgstr "FakeIP работает корректно на роутере (198.18.x.x)"
msgid "Click here for all the info"
msgstr "Нажмите для просмотра всей информации"
msgid "Check DNS server on current device (PC, phone)"
msgstr "Проверьте DNS сервер на текущем устройстве (ПК, телефон)"
msgid "Its must be router!"
msgstr "Это должен быть роутер!"
msgid "Global check"
msgstr "Глобальная проверка"

View File

@@ -1169,4 +1169,28 @@ msgid "available"
msgstr "" msgstr ""
msgid "unavailable" msgid "unavailable"
msgstr ""
msgid "PODKOP CONFIGURATION"
msgstr ""
msgid "FAKEIP ROUTER TEST"
msgstr ""
msgid "FAKEIP BROWSER TEST"
msgstr ""
msgid "FakeIP is working correctly on router (198.18.x.x)"
msgstr ""
msgid "Click here for all the info"
msgstr ""
msgid "Check DNS server on current device (PC, phone)"
msgstr ""
msgid "Its must be router!"
msgstr ""
msgid "Global check"
msgstr "" msgstr ""

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=podkop PKG_NAME:=podkop
PKG_VERSION:=0.3.31 PKG_VERSION:=0.3.45
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog <podkop@itdog.info> PKG_MAINTAINER:=ITDog <podkop@itdog.info>
@@ -13,6 +13,7 @@ define Package/podkop
SECTION:=net SECTION:=net
CATEGORY:=Network CATEGORY:=Network
DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64 DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64
CONFLICTS:=https-dns-proxy
TITLE:=Domain routing app TITLE:=Domain routing app
URL:=https://itdog.info URL:=https://itdog.info
PKGARCH:=all PKGARCH:=all

View File

@@ -36,4 +36,7 @@ config main 'main'
option dns_rewrite_ttl '60' option dns_rewrite_ttl '60'
option cache_file '/tmp/cache.db' option cache_file '/tmp/cache.db'
list iface 'br-lan' list iface 'br-lan'
option ss_uot '0' option mon_restart_ifaces '0'
#list restart_ifaces 'wan'
option ss_uot '0'
option detour '0'

View File

@@ -6,38 +6,16 @@ USE_PROCD=1
script=$(readlink "$initscript") script=$(readlink "$initscript")
NAME="$(basename ${script:-$initscript})" NAME="$(basename ${script:-$initscript})"
config_load "$NAME" config_load "$NAME"
resolv_conf="/etc/resolv.conf"
start_service() { start_service() {
echo "Start podkop" echo "Start podkop"
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}') config_get mon_restart_ifaces "main" "mon_restart_ifaces"
required_version="1.11.1" config_get restart_ifaces "main" "restart_ifaces"
if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
echo "The version of sing-box ($sing_box_version) is lower than the minimum version. Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box"
exit 1
fi
if grep -q FriendlyWrt /etc/banner; then
printf "\033[31;1mYou use FriendlyWrt. If you have problems, check out: https://t.me/itdogchat/44512/181082\033[0m\n"
fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
printf "\033[31;1mDetected https-dns-proxy. Disable or uninstall it for correct functionality.\033[0m\n"
fi
if ! ip addr | grep -q "br-lan"; then
echo "Interface br-lan not found"
exit 1
fi
if ! grep -q "search lan" "$resolv_conf" || ! grep -q "nameserver 127.0.0.1" "$resolv_conf"; then
echo "/etc/resolv.conf does not contain 'search lan' or 'nameserver 127.0.0.1' entries"
fi
procd_open_instance procd_open_instance
procd_set_param command /bin/sh -c "/usr/bin/podkop start" procd_set_param command /usr/bin/podkop start
[ "$mon_restart_ifaces" = "1" ] && [ -n "$restart_ifaces" ] && procd_set_param netdev $restart_ifaces
procd_set_param stdout 1 procd_set_param stdout 1
procd_set_param stderr 1 procd_set_param stderr 1
procd_close_instance procd_close_instance
@@ -47,17 +25,23 @@ stop_service() {
/usr/bin/podkop stop /usr/bin/podkop stop
} }
restart_service() {
stop
start
}
reload_service() { reload_service() {
stop /usr/bin/podkop reload > /dev/null 2>&1
start
} }
service_triggers() { service_triggers() {
echo "service_triggers start" echo "service_triggers start"
procd_add_config_trigger "config.change" "$NAME" "$initscript" reload 'on_config_change'
config_get mon_restart_ifaces "main" "mon_restart_ifaces"
config_get restart_ifaces "main" "restart_ifaces"
procd_open_trigger
procd_add_config_trigger "config.change" "$NAME" "$initscript" restart 'on_config_change'
if [ "$mon_restart_ifaces" = "1" ]; then
for iface in $restart_ifaces; do
procd_add_reload_interface_trigger $iface
done
fi
procd_close_trigger
} }

View File

@@ -15,22 +15,21 @@ SUBNETS_TWITTER="${GITHUB_RAW_URL}/Subnets/IPv4/twitter.lst"
SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/meta.lst" SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/meta.lst"
SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/discord.lst" SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/discord.lst"
SUBNETS_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst" SUBNETS_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst"
SUBNETS_CLOUDFLARE="${GITHUB_RAW_URL}/Subnets/IPv4/cloudflare.lst"
SING_BOX_CONFIG="/etc/sing-box/config.json" SING_BOX_CONFIG="/etc/sing-box/config.json"
FAKEIP="198.18.0.0/15" FAKEIP="198.18.0.0/15"
VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram" VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram cloudflare"
DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8" DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8"
TEST_DOMAIN="fakeip.tech-domain.club" TEST_DOMAIN="fakeip.podkop.fyi"
INTERFACES_LIST="" INTERFACES_LIST=""
SRC_INTERFACE="" SRC_INTERFACE=""
RESOLV_CONF="/etc/resolv.conf"
CLOUDFLARE_OCTETS="103.21 103.22 103.31 104.16 104.17 104.18 104.19 104.20 104.21 104.22 104.23 104.24 104.25 104.26 104.27 104.28 108.162 131.0 141.101 162.158 162.159 172.64 172.65 172.66 172.67 172.68 172.69 172.70 172.71 173.245 188.114 190.93 197.234 198.41"
log() { log() {
local message="$1" local message="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S") local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
local CYAN="\033[0;36m"
local GREEN="\033[0;32m"
local RESET="\033[0m"
echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
logger -t "podkop" "$timestamp $message" logger -t "podkop" "$timestamp $message"
} }
@@ -44,7 +43,26 @@ nolog() {
echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}" echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
} }
start() { start_main() {
log "Starting podkop"
# checking
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}')
required_version="1.11.1"
if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
log "[critical] The version of sing-box ($sing_box_version) is lower than the minimum version. Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box"
exit 1
fi
if opkg list-installed | grep -q iptables-mod-extra; then
log "[critical] Conflicting package detected: iptables-mod-extra"
fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
log "[critical] Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp"
fi
migration migration
config_foreach process_validate_service config_foreach process_validate_service
@@ -110,9 +128,20 @@ start() {
sing_box_quic_reject sing_box_quic_reject
fi fi
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
log "Detour mixed enable"
detour_mixed
fi
sing_box_config_check sing_box_config_check
/etc/init.d/sing-box start /etc/init.d/sing-box start
/etc/init.d/sing-box enable #/etc/init.d/sing-box enable
log "Nice"
}
start() {
start_main
config_get proxy_string "main" "proxy_string" config_get proxy_string "main" "proxy_string"
config_get interface "main" "interface" config_get interface "main" "interface"
@@ -126,13 +155,13 @@ start() {
fi fi
} }
stop() { stop_main() {
log "Stopping the podkop" log "Stopping the podkop"
if [ -f /var/run/podkop_list_update.pid ]; then if [ -f /var/run/podkop_list_update.pid ]; then
pid=$(cat /var/run/podkop_list_update.pid) pid=$(cat /var/run/podkop_list_update.pid)
if kill -0 "$pid"; then if kill -0 "$pid"; then
kill "$pid" kill "$pid" 2>/dev/null
log "Stopped list_update" log "Stopped list_update"
fi fi
rm -f /var/run/podkop_list_update.pid rm -f /var/run/podkop_list_update.pid
@@ -140,11 +169,6 @@ stop() {
remove_cron_job remove_cron_job
config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
if [ "$dont_touch_dhcp" -eq 0 ]; then
dnsmasq_restore
fi
rm -rf /tmp/podkop/*.lst rm -rf /tmp/podkop/*.lst
log "Flush nft" log "Flush nft"
@@ -164,8 +188,28 @@ stop() {
log "Stop sing-box" log "Stop sing-box"
/etc/init.d/sing-box stop /etc/init.d/sing-box stop
/etc/init.d/sing-box disable #/etc/init.d/sing-box disable
}
stop() {
config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
if [ "$dont_touch_dhcp" -eq 0 ]; then
dnsmasq_restore
fi
stop_main
}
reload() {
log "Podkop reload"
stop_main
start_main
}
restart() {
log "Podkop restart"
stop
start
} }
# Migrations and validation funcs # Migrations and validation funcs
@@ -337,7 +381,8 @@ dnsmasq_add_resolver() {
uci -q delete dhcp.@dnsmasq[0].podkop_server uci -q delete dhcp.@dnsmasq[0].podkop_server
for server in $(uci get dhcp.@dnsmasq[0].server 2>/dev/null); do for server in $(uci get dhcp.@dnsmasq[0].server 2>/dev/null); do
if [[ "$server" == "127.0.0.42" ]]; then if [[ "$server" == "127.0.0.42" ]]; then
log "Dnsmasq save config error: server=127.0.0.42" log "Dnsmasq save config error: server=127.0.0.42 is already configured. Skip editing DHCP"
return
else else
uci add_list dhcp.@dnsmasq[0].podkop_server="$server" uci add_list dhcp.@dnsmasq[0].podkop_server="$server"
fi fi
@@ -527,10 +572,19 @@ list_update() {
fi fi
for i in $(seq 1 60); do for i in $(seq 1 60); do
if curl -s -m 3 https://github.com >/dev/null; then config_get_bool detour "main" "detour" "0"
log "GitHub is available" if [ "$detour" -eq 1 ]; then
break if http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" curl -s -m 3 https://github.com >/dev/null; then
log "GitHub is available"
break
fi
else
if curl -s -m 3 https://github.com >/dev/null; then
log "GitHub is available"
break
fi
fi fi
log "GitHub is unavailable [$i/60]" log "GitHub is unavailable [$i/60]"
sleep 3 sleep 3
done done
@@ -568,10 +622,12 @@ sing_box_uci() {
log "Change sing-box UCI config" log "Change sing-box UCI config"
fi fi
if grep -q '#\s*list ifaces' "$config"; then [ -f /etc/rc.d/S99sing-box ] && log "Disable sing-box" && /etc/init.d/sing-box disable
sed -i '/ifaces/s/#//g' $config
log "Uncommented list ifaces" # if grep -q '#\s*list ifaces' "$config"; then
fi # sed -i '/ifaces/s/#//g' $config
# log "Uncommented list ifaces"
# fi
} }
add_socks5_for_section() { add_socks5_for_section() {
@@ -737,7 +793,7 @@ sing_box_create_bypass_ruleset() {
"rules": [ "rules": [
{ {
"domain_suffix": [ "domain_suffix": [
"ip.tech-domain.club" "ip.podkop.fyi"
] ]
} }
] ]
@@ -842,7 +898,7 @@ sing_box_outdound() {
config_get interface "$section" "interface" config_get interface "$section" "interface"
if [ -z "$interface" ]; then if [ -z "$interface" ]; then
log "VPN interface is not set. Exit" log "[critical] VPN interface is not set. Exit"
exit 1 exit 1
fi fi
@@ -868,7 +924,7 @@ sing_box_outdound() {
active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1) active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1)
if [ -z "$active_proxy_string" ]; then if [ -z "$active_proxy_string" ]; then
log "Proxy string is not set. Exit" log "[critical] Proxy string is not set. Exit"
exit 1 exit 1
fi fi
@@ -941,7 +997,7 @@ sing_box_rule_dns() {
sing_box_config_check() { sing_box_config_check() {
if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then
log "Sing-box configuration is invalid" log "[critical] Sing-box configuration is invalid"
exit 1 exit 1
fi fi
} }
@@ -973,7 +1029,7 @@ sing_box_config_outbound_json() {
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
log "Outbound config updated successfully" log "Outbound config updated successfully"
else else
log "Error: Invalid JSON config generated" log "Error: Outbound invalid JSON config generated"
return 1 return 1
fi fi
} }
@@ -1034,9 +1090,9 @@ sing_box_config_shadowsocks() {
)' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
log "Config updated successfully" log "Config Shadowsocks updated successfully"
else else
log "Error: Invalid JSON config generated" log "Error: Shadowsocks invalid JSON config generated"
return 1 return 1
fi fi
} }
@@ -1161,9 +1217,9 @@ sing_box_config_vless() {
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
log "Config created successfully" log "Config VLESS created successfully"
else else
log "Error: Invalid JSON config generated" log "Error: VLESS invalid JSON config generated"
return 1 return 1
fi fi
} }
@@ -1317,6 +1373,7 @@ sing_box_ruleset_remote() {
local tag=$1 local tag=$1
local type=$2 local type=$2
local update_interval=$3 local update_interval=$3
local detour=$4
url="$SRS_MAIN_URL/$tag.srs" url="$SRS_MAIN_URL/$tag.srs"
@@ -1332,15 +1389,19 @@ sing_box_ruleset_remote() {
--arg type "$type" \ --arg type "$type" \
--arg url "$url" \ --arg url "$url" \
--arg update_interval "$update_interval" \ --arg update_interval "$update_interval" \
--arg detour "$detour" \
' '
.route.rule_set += [ .route.rule_set += [
{ (
"tag": $tag, {
"type": $type, "tag": $tag,
"format": "binary", "type": $type,
"url": $url, "format": "binary",
"update_interval": $update_interval "url": $url,
} "update_interval": $update_interval
} +
(if $detour == "1" then {"download_detour": "main"} else {} end)
)
]' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG" ]' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
log "Added new ruleset with tag $tag" log "Added new ruleset with tag $tag"
@@ -1361,6 +1422,9 @@ list_subnets_download() {
"telegram") "telegram")
URL=$SUBNETS_TELERAM URL=$SUBNETS_TELERAM
;; ;;
"cloudflare")
URL=$SUBNETS_CLOUDFLARE
;;
"discord") "discord")
URL=$SUBNETS_DISCORD URL=$SUBNETS_DISCORD
nft add set inet $table podkop_discord_subnets { type ipv4_addr\; flags interval\; auto-merge\; } nft add set inet $table podkop_discord_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
@@ -1372,7 +1436,13 @@ list_subnets_download() {
esac esac
local filename=$(basename "$URL") local filename=$(basename "$URL")
wget -O "/tmp/podkop/$filename" "$URL"
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "/tmp/podkop/$filename" "$URL"
else
wget -O "/tmp/podkop/$filename" "$URL"
fi
while IFS= read -r subnet; do while IFS= read -r subnet; do
if [ "$service" = "discord" ]; then if [ "$service" = "discord" ]; then
@@ -1434,8 +1504,9 @@ sing_box_quic_reject() {
process_remote_ruleset_srs() { process_remote_ruleset_srs() {
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0" config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
if [ "$domain_list_enabled" -eq 1 ]; then if [ "$domain_list_enabled" -eq 1 ]; then
config_get_bool detour "main" "detour" "0"
log "Adding a srs list for $section" log "Adding a srs list for $section"
config_list_foreach "$section" domain_list "sing_box_ruleset_remote" "remote" "1d" config_list_foreach "$section" domain_list "sing_box_ruleset_remote" "remote" "1d" "$detour"
fi fi
} }
@@ -1502,7 +1573,12 @@ list_custom_url_domains_create() {
local URL="$1" local URL="$1"
local filename=$(basename "$URL") local filename=$(basename "$URL")
wget -q -O "/tmp/podkop/${filename}" "$URL" config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "/tmp/podkop/${filename}" "$URL"
else
wget -O "/tmp/podkop/${filename}" "$URL"
fi
while IFS= read -r domain; do while IFS= read -r domain; do
log "From downloaded file: $domain" log "From downloaded file: $domain"
@@ -1542,7 +1618,12 @@ list_custom_url_subnets_create() {
local URL="$1" local URL="$1"
local filename=$(basename "$URL") local filename=$(basename "$URL")
wget -q -O "/tmp/podkop/${filename}" "$URL" config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "/tmp/podkop/${filename}" "$URL"
else
wget -O "/tmp/podkop/${filename}" "$URL"
fi
while IFS= read -r subnet; do while IFS= read -r subnet; do
log "From local file: $subnet" log "From local file: $subnet"
@@ -1601,6 +1682,31 @@ sing_box_rules_source_ip_cidr() {
fi fi
} }
detour_mixed() {
local section="main"
local port="4534"
local tag="detour"
log "Adding detour Socks5 for $section on port $port"
jq \
--arg tag "$tag" \
--arg port "$port" \
--arg section "$section" \
'.inbounds += [{
"tag": $tag,
"type": "mixed",
"listen": "127.0.0.1",
"listen_port": ($port|tonumber),
"set_system_proxy": false
}] |
.route.rules += [{
"inbound": [$tag],
"outbound": $section,
"action": "route"
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
}
## nftables ## nftables
list_all_traffic_from_ip() { list_all_traffic_from_ip() {
local ip="$1" local ip="$1"
@@ -1735,7 +1841,14 @@ check_github() {
for url in "$DOMAINS_RU_INSIDE" "$DOMAINS_RU_OUTSIDE" "$DOMAINS_UA" "$DOMAINS_YOUTUBE" \ for url in "$DOMAINS_RU_INSIDE" "$DOMAINS_RU_OUTSIDE" "$DOMAINS_UA" "$DOMAINS_YOUTUBE" \
"$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do "$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do
local list_name=$(basename "$url") local list_name=$(basename "$url")
wget -q -O /dev/null "$url"
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -q -O /dev/null "$url"
else
wget -q -O /dev/null "$url"
fi
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
nolog "- $list_name: available" nolog "- $list_name: available"
else else
@@ -1790,6 +1903,7 @@ check_sing_box_logs() {
} }
check_fakeip() { check_fakeip() {
# Not used
nolog "Checking fakeip functionality..." nolog "Checking fakeip functionality..."
if ! command -v nslookup >/dev/null 2>&1; then if ! command -v nslookup >/dev/null 2>&1; then
@@ -1857,12 +1971,24 @@ check_fakeip() {
check_logs() { check_logs() {
nolog "Showing podkop logs from system journal..." nolog "Showing podkop logs from system journal..."
if command -v logread >/dev/null 2>&1; then if ! command -v logread >/dev/null 2>&1; then
logread -e podkop | tail -n 50
else
nolog "Error: logread command not found" nolog "Error: logread command not found"
return 1 return 1
fi fi
# Get all logs first
local all_logs=$(logread)
# Find the last occurrence of "Starting podkop"
local start_line=$(echo "$all_logs" | grep -n "podkop.*Starting podkop" | tail -n 1 | cut -d: -f1)
if [ -z "$start_line" ]; then
nolog "No 'Starting podkop' message found in logs"
return 1
fi
# Output all logs from the last start
echo "$all_logs" | tail -n +"$start_line"
} }
show_sing_box_config() { show_sing_box_config() {
@@ -1899,9 +2025,7 @@ show_sing_box_config() {
)' "$SING_BOX_CONFIG" )' "$SING_BOX_CONFIG"
} }
show_config() { show_config() {
nolog "Current podkop configuration:"
if [ ! -f /etc/config/podkop ]; then if [ ! -f /etc/config/podkop ]; then
nolog "Configuration file not found" nolog "Configuration file not found"
return 1 return 1
@@ -1918,6 +2042,8 @@ show_config() {
-e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \ -e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \
-e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \ -e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \
-e 's/\(sid=[^&]*\)/sid=MASKED/g' \ -e 's/\(sid=[^&]*\)/sid=MASKED/g' \
-e 's/\(option dns_server '\''[^'\'']*\.dns\.nextdns\.io'\''\)/option dns_server '\''MASKED.dns.nextdns.io'\''/g' \
-e "s|\(option dns_server 'dns\.nextdns\.io\)/[^']*|\1/MASKED|"
> "$tmp_config" > "$tmp_config"
cat "$tmp_config" cat "$tmp_config"
@@ -1925,17 +2051,17 @@ show_config() {
} }
show_version() { show_version() {
local version=$(opkg info podkop | grep -m 1 "Version:" | cut -d' ' -f2) local version=$(opkg list-installed podkop | awk '{print $3}')
echo "$version" echo "$version"
} }
show_luci_version() { show_luci_version() {
local version=$(opkg info luci-app-podkop | grep -m 1 "Version:" | cut -d' ' -f2) local version=$(opkg list-installed luci-app-podkop | awk '{print $3}')
echo "$version" echo "$version"
} }
show_sing_box_version() { show_sing_box_version() {
local version=$(opkg info sing-box | grep -m 1 "Version:" | cut -d' ' -f2) local version=$(sing-box version | head -n 1 | awk '{print $3}')
echo "$version" echo "$version"
} }
@@ -1990,36 +2116,18 @@ get_sing_box_status() {
} }
get_status() { get_status() {
local running=0
local enabled=0 local enabled=0
local status="" local status=""
# Check if service is enabled # Check if service is enabled
if [ -x /etc/rc.d/S99podkop ]; then if [ -x /etc/rc.d/S99podkop ]; then
enabled=1 enabled=1
fi status="enabled"
# Check if service is running
if pgrep -f "sing-box" >/dev/null; then
running=1
fi
# Format status message
if [ $running -eq 1 ]; then
if [ $enabled -eq 1 ]; then
status="running & enabled"
else
status="running but disabled"
fi
else else
if [ $enabled -eq 1 ]; then status="disabled"
status="stopped but enabled"
else
status="stopped & disabled"
fi
fi fi
echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}" echo "{\"enabled\":$enabled,\"status\":\"$status\"}"
} }
check_dns_available() { check_dns_available() {
@@ -2029,34 +2137,47 @@ check_dns_available() {
local status="unavailable" local status="unavailable"
local local_dns_working=0 local local_dns_working=0
local local_dns_status="unavailable" local local_dns_status="unavailable"
# Mask NextDNS ID if present
local display_dns_server="$dns_server"
if echo "$dns_server" | grep -q "\.dns\.nextdns\.io$"; then
local nextdns_id=$(echo "$dns_server" | cut -d'.' -f1)
display_dns_server="$(echo "$nextdns_id" | sed 's/./*/g').dns.nextdns.io"
elif echo "$dns_server" | grep -q "^dns\.nextdns\.io/"; then
local masked_path=$(echo "$dns_server" | cut -d'/' -f2- | sed 's/./*/g')
display_dns_server="dns.nextdns.io/$masked_path"
fi
if [ "$dns_type" = "doh" ]; then if [ "$dns_type" = "doh" ]; then
# Different DoH providers use different endpoints and formats
local result="" local result=""
# Try common DoH endpoints and check for valid responses if echo "$dns_server" | grep -q "quad9.net" || \
# First try /dns-query endpoint (Cloudflare, AdGuard DNS, etc.) echo "$dns_server" | grep -qE "^9\.9\.9\.(9|10|11)$|^149\.112\.112\.(112|10|11)$|^2620:fe::(fe|9|10|11)$|^2620:fe::fe:(10|11)$"; then
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/dns-query?name=itdog.info&type=A") result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server:5053/dns-query?name=itdog.info&type=A")
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1
status="available"
else else
# If that fails, try /resolve endpoint (Google DNS) result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/dns-query?name=itdog.info&type=A")
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1 is_available=1
status="available" status="available"
else
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
fi fi
fi fi
elif [ "$dns_type" = "dot" ]; then
nc $dns_server 853 </dev/null >/dev/null 2>&1 & pid=$! if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
(sleep 3; kill $pid 2>/dev/null) & killpid=$! is_available=1
wait $pid >/dev/null 2>&1 status="available"
if [ $? -eq 0 ]; then fi
elif [ "$dns_type" = "dot" ]; then
(nc "$dns_server" 853 </dev/null >/dev/null 2>&1) & pid=$!
sleep 2
if kill -0 $pid 2>/dev/null; then
kill $pid 2>/dev/null
wait $pid 2>/dev/null
else
is_available=1 is_available=1
status="available" status="available"
fi fi
kill $killpid 2>/dev/null
elif [ "$dns_type" = "udp" ]; then elif [ "$dns_type" = "udp" ]; then
if nslookup -timeout=2 itdog.info $dns_server >/dev/null 2>&1; then if nslookup -timeout=2 itdog.info $dns_server >/dev/null 2>&1; then
is_available=1 is_available=1
@@ -2070,7 +2191,7 @@ check_dns_available() {
local_dns_status="available" local_dns_status="available"
fi fi
echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$dns_server\",\"is_available\":$is_available,\"status\":\"$status\",\"local_dns_working\":$local_dns_working,\"local_dns_status\":\"$local_dns_status\"}" echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"is_available\":$is_available,\"status\":\"$status\",\"local_dns_working\":$local_dns_working,\"local_dns_status\":\"$local_dns_status\"}"
} }
sing_box_add_secure_dns_probe_domain() { sing_box_add_secure_dns_probe_domain() {
@@ -2102,6 +2223,154 @@ sing_box_add_secure_dns_probe_domain() {
log "DNS probe domain ${domain} configured with override to port ${override_port}" log "DNS probe domain ${domain} configured with override to port ${override_port}"
} }
print_global() {
local message="$1"
echo "$message"
}
global_check() {
print_global "📡 Global check run!"
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "🛠️ System info"
print_global "🕳️ Podkop: $(opkg list-installed podkop | awk '{print $3}')"
print_global "🕳️ LuCI App: $(opkg list-installed luci-app-podkop | awk '{print $3}')"
print_global "📦 Sing-box: $(sing-box version | head -n 1 | awk '{print $3}')"
print_global "🛜 OpenWrt: $(grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2)"
print_global "🛜 Device: $(cat /tmp/sysinfo/model)"
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "📄 Podkop config"
show_config
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "🔧 System check"
if grep -E "^nameserver\s+([0-9]{1,3}\.){3}[0-9]{1,3}" "$RESOLV_CONF" | grep -vqE "127\.0\.0\.1|0\.0\.0\.0"; then
print_global "❌ /etc/resolv.conf contains external nameserver:"
cat /etc/resolv.conf
echo ""
else
print_global "✅ /etc/resolv.conf"
fi
cachesize="$(uci get dhcp.@dnsmasq[0].cachesize 2>/dev/null)"
noresolv="$(uci get dhcp.@dnsmasq[0].noresolv 2>/dev/null)"
server="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)"
if [ "$cachesize" != "0" ] || [ "$noresolv" != "1" ] || [ "$server" != "127.0.0.42" ]; then
print_global "❌ DHCP configuration differs from template. 📄 DHCP config:"
awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp
elif [ "$(uci get podkop.main.dont_touch_dhcp 2>/dev/null)" = "1" ]; then
print_global "⚠️ dont_touch_dhcp is enabled. 📄 DHCP config:"
awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp
else
print_global "✅ /etc/config/dhcp"
fi
if ! pgrep -f "sing-box" >/dev/null; then
print_global "❌ sing-box is not running"
else
print_global "✅ sing-box is running"
fi
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "🧱 NFT table"
if ! nft list table inet PodkopTable >/dev/null 2>&1; then
print_global "❌ PodkopTable not found"
else
nft list table inet PodkopTable
fi
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "📄 WAN config"
if uci show network.wan >/dev/null 2>&1; then
awk '
/^config / {
p = ($2 == "interface" && $3 == "'\''wan'\''")
proto = ""
}
p {
if ($1 == "option" && $2 == "proto") {
proto = $3
print
} else if (proto == "'\''static'\''" && $1 == "option" && ($2 == "ipaddr" || $2 == "netmask" || $2 == "gateway")) {
print " option", $2, "'\''******'\''"
} else if (proto == "'\''pppoe'\''" && $1 == "option" && ($2 == "username" || $2 == "password")) {
print " option", $2, "'\''******'\''"
} else {
print
}
}
' /etc/config/network
else
print_global "❌ WAN configuration not found"
fi
if uci show network | grep -q endpoint_host; then
uci show network | grep endpoint_host | cut -d'=' -f2 | tr -d "'\" " | while read -r host; do
if [ "$host" = "engage.cloudflareclient.com" ]; then
print_global "⚠️ WARP detected: $host"
continue
fi
ip_prefix=$(echo "$host" | cut -d'.' -f1,2)
if echo "$CLOUDFLARE_OCTETS" | grep -wq "$ip_prefix"; then
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "⚠️ WARP detected: $host"
fi
done
fi
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "➡️ DNS status"
dns_info=$(check_dns_available)
dns_type=$(echo "$dns_info" | jq -r '.dns_type')
dns_server=$(echo "$dns_info" | jq -r '.dns_server')
status=$(echo "$dns_info" | jq -r '.status')
print_global "$dns_type ($dns_server) is $status"
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "🔁 FakeIP"
print_global "➡️ DNS resolution: system DNS server"
nslookup -timeout=2 $TEST_DOMAIN
local working_resolver=$(find_working_resolver)
if [ -z "$working_resolver" ]; then
print_global "❌ No working external resolver found"
else
print_global "➡️ DNS resolution: external resolver ($working_resolver)"
nslookup -timeout=2 $TEST_DOMAIN $working_resolver
fi
print_global "➡️ DNS resolution: sing-box DNS server (127.0.0.42)"
local result=$(nslookup -timeout=2 $TEST_DOMAIN 127.0.0.42 2>&1)
echo "$result"
if echo "$result" | grep -q "198.18"; then
print_global "✅ FakeIP is working correctly on router (198.18.x.x)"
else
print_global "❌ FakeIP test failed: Domain did not resolve to FakeIP range"
if ! pgrep -f "sing-box" >/dev/null; then
print_global " ❌ sing-box is not running"
else
print_global " 🤔 sing-box is running, checking configuration"
if [ -f "$SING_BOX_CONFIG" ]; then
local fakeip_enabled=$(jq -r '.dns.fakeip.enabled' "$SING_BOX_CONFIG")
local fakeip_range=$(jq -r '.dns.fakeip.inet4_range' "$SING_BOX_CONFIG")
local dns_rules=$(jq -r '.dns.rules[] | select(.server == "fakeip-server") | .domain' "$SING_BOX_CONFIG")
print_global " 📦 FakeIP enabled: $fakeip_enabled"
print_global " 📦 FakeIP range: $fakeip_range"
print_global " 📦 FakeIP domain: $dns_rules"
else
print_global " ⛔ sing-box config file not found"
fi
fi
fi
}
case "$1" in case "$1" in
start) start)
start start
@@ -2109,9 +2378,11 @@ case "$1" in
stop) stop)
stop stop
;; ;;
reload)
reload
;;
restart) restart)
stop restart
start
;; ;;
main) main)
main main
@@ -2170,8 +2441,11 @@ case "$1" in
check_dns_available) check_dns_available)
check_dns_available check_dns_available
;; ;;
global_check)
global_check
;;
*) *)
echo "Usage: $0 {start|stop|restart|reload|enable|disable|main|list_update|check_proxy|check_nft|check_github|check_logs|check_sing_box_connections|check_sing_box_logs|check_fakeip|check_dnsmasq|show_config|show_version|show_sing_box_config|show_luci_version|show_sing_box_version|show_system_info|get_status|get_sing_box_status|check_dns_available}" echo "Usage: $0 {start|stop|reload|restart|enable|disable|main|list_update|check_proxy|check_nft|check_github|check_logs|check_sing_box_connections|check_sing_box_logs|check_fakeip|check_dnsmasq|show_config|show_version|show_sing_box_config|show_luci_version|show_sing_box_version|show_system_info|get_status|get_sing_box_status|check_dns_available|global_check}"
exit 1 exit 1
;; ;;
esac esac