Compare commits

...

30 Commits

Author SHA1 Message Date
itdoginfo
f90ab7f468 v0.4.0 beta 2025-05-15 13:26:57 +03:00
itdoginfo
e4bfd447ce Merge pull request #108 from itdoginfo/fix/comments
fix(podkop): add dont touch my dhcp logic to fake IP check functions
2025-05-15 12:07:08 +03:00
Ivan K
fbdd759b83 ♻️ refactor(podkop): remove redundant DNS/sing-box checks in fakeip status 2025-05-15 11:53:30 +03:00
Ivan K
2488bc30b1 fix(podkop): add dont touch my dhcp logic to fake IP check functions 2025-05-15 11:31:33 +03:00
itdoginfo
dcc12cf920 Merge pull request #107 from itdoginfo/fix/comments
💄 style(podkop): update modal button titles and clipboard content
2025-05-14 14:33:53 +03:00
Ivan K
c99cef9f27 💄 style(podkop): update modal button titles and clipboard content 2025-05-14 14:30:52 +03:00
itdoginfo
8a68f3fcc2 Update 2025-05-13 14:26:50 +03:00
itdoginfo
ed2994be3a v0.3.50 Adde Google AI, Play, HTZ, OVH of list 2025-05-12 23:31:06 +03:00
itdoginfo
77ff5ab781 Merge pull request #105 from itdoginfo/fix/comments
️ perf(dns): improve DNS query performance and error handling
2025-05-12 18:17:50 +03:00
Ivan K
1c80bc5a5e ️ perf(dns): improve DNS query performance and error handling 2025-05-12 17:50:50 +03:00
itdoginfo
f688d74c32 v0.3.49 Improved diagnostics 2025-05-12 17:32:51 +03:00
itdoginfo
7bc50d58d3 Merge pull request #104 from itdoginfo/fix/comments
Fix/comments
2025-05-12 17:25:59 +03:00
Ivan K
77ce0c380b 🐛 fix(dns): improve DNS availability check logic 2025-05-12 17:21:38 +03:00
Ivan K
47d1b349c7 ♻️ refactor(podkop): add local var 2025-05-12 16:51:59 +03:00
Ivan K
e9face1f4a ♻️ refactor(podkop): simplify logging function 2025-05-12 16:48:59 +03:00
itdoginfo
e5bf7d9bed Merge pull request #103 from itdoginfo/fix/comments
♻️ refactor(podkop): improve diagnostics and error handling
2025-05-12 16:48:15 +03:00
Ivan K
dd4722f3e1 ♻️ refactor(podkop): simplify logging functions 2025-05-12 16:40:44 +03:00
Ivan K
1e945dafe7 feat(logging): add colored logging to stdout and syslog 2025-05-12 15:54:12 +03:00
itdoginfo
b080521a58 Update 2025-05-12 00:57:52 +03:00
itdoginfo
6a96a85773 Update 2025-05-12 00:49:19 +03:00
itdoginfo
6fb3a36974 Update 2025-05-12 00:46:30 +03:00
Ivan K
b3dbee1dbe 💄 style(podkop): adjust margin styles in status panel 2025-05-11 20:30:16 +03:00
Ivan K
916321578d ️ feat(dns): add random DNS query ID generation 2025-05-11 19:33:08 +03:00
Ivan K
c74d733717 ♻️ refactor(podkop): improve diagnostics and error handling 2025-05-11 19:26:29 +03:00
itdoginfo
433724f762 v0.3.48 Custom URL CRLF 2025-05-10 18:55:35 +03:00
itdoginfo
6378aa9910 Update 2025-05-10 16:15:53 +03:00
itdoginfo
68f5f123ca v0.3.47 Fix noresolv 1 2025-05-10 12:50:01 +03:00
itdoginfo
fae43d0471 v0.3.46 2025-05-08 19:24:08 +03:00
itdoginfo
9d6dc45fdb #99 Block mode 2025-05-08 19:23:45 +03:00
itdoginfo
9aa5a2d242 Fix site. #100 added ntpd 2025-05-08 10:14:58 +03:00
7 changed files with 496 additions and 223 deletions

View File

@@ -1,15 +1,15 @@
# Вещи, которые вам нужно знать перед установкой
- Это альфа версия, которая находится в активной разработке. Из версии в версию что-то может меняться.
- Основной функционал работает, но побочные штуки сейчас могут сбоить.
- При обновлении **обязательно** сбрасывайте кэш LuCI.
- Это бета-версия, которая находится в активной разработке. Из версии в версию что-то может меняться.
- При возникновении проблем, нужен технически грамотный фидбэк в чат.
- При обновлении **обязательно** [сбрасывайте кэш LuCI](https://podkop.net/docs/clearbrowsercache/).
- Также при обновлении всегда заходите в конфигурацию и проверяйте свои настройки. Конфигурация может измениться.
- Необходимо минимум 15МБ свободного места на роутере. Роутерами с флешками на 16МБ сразу мимо.
- Необходимо минимум 15МБ свободного места на роутере. Роутеры с флешками на 16МБ сразу мимо.
- При старте программы редактируется конфиг Dnsmasq.
- Podkop редактирует конфиг sing-box. Обязательно сохраните ваш конфиг sing-box перед установкой, если он вам нужен.
- Информация здесь может быть устаревшей. Все изменения фиксируются в телеграм-чате https://t.me/itdogchat - топик **Podkop**.
- Если у вас не что-то не работает, то следуюет сходить в телеграм чат, прочитать закрепы и выполнить что там написано..
- Если у вас установлен Getdomains, его следует удалить.
- Информация здесь может быть устаревшей. Все изменения фиксируются в [телеграм-чате](https://t.me/itdogchat/81758/420321).
- [Если у вас не что-то не работает.](https://podkop.net/docs/diagnostics/)
- Если у вас установлен Getdomains, [его следует удалить](https://github.com/itdoginfo/domain-routing-openwrt?tab=readme-ov-file#%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82-%D0%B4%D0%BB%D1%8F-%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F).
# Документация
https://podkop.net/
@@ -17,12 +17,12 @@ https://podkop.net/
# Установка Podkop
Полная информация в [документации](https://podkop.net/docs/install/)
Вкратце, достаточно одного скрипта:
Вкратце, достаточно одного скрипта для установки:
```
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
```
@@ -32,12 +32,28 @@ sh <(wget -qO- https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/mai
Основные задачи в issues.
Низкий приоритет
- [ ] Галочка, которая режет доступ к doh серверам
- [ ] IPv6. Только после наполнения Wiki
## Рефактор
- [ ] Очевидные повторения в `/usr/bin/podkop` загнать в переменые
- [ ] Возможно поменять структуру
Рефактор
- [ ] Handle для sing-box
- [ ] Handle для dnsmasq
## Списки
- [ ] Speedtest
- [x] Google AI
- [x] Google PlayMarket. Здесь уточнить, что точно не работает через корректную настройку FakeIP, а не dnsmasq+nft.
- [x] Hetzner ASN (AS24940)
- [x] OVH ASN (AS16276)
## Будущее
- [ ] После наполнения вики про туннели, убрать всё что связано с их установкой из скрипта. Только с AWG что-то решить, лучше чтоб был скрипт в сторонем репозитории.
- [ ] Подписка. Здесь нужна реализация, чтоб для каждой секции помимо ручного выбора, был выбор фильтрации по тегу. Например, для main выбираем ключевые слова NL, DE, FI. А для extra секции фильтруем по RU. И создаётся outbound c urltest в которых перечислены outbound из фильтров.
- [ ] Опция, когда все запросы (с роутера в первую очередь), а не только br-lan идут в прокси. С этим связана #95. Требуется много переделать для nftables.
- [ ] Весь трафик в Proxy\VPN. Вопрос, что делать с экстрасекциями в этом случае. FakeIP здесь скорее не нужен, а значит только main секция остаётся. Всё что касается fakeip проверок, придётся выключать в этом режиме.
- [ ] Поддержка Source format. Нужна расшифровка в json и если присуствуют подсети, заносить их в custom subnet nftset.
- [ ] Переделывание функции формирования кастомных списков в JSON. Обрабатывать сразу скопом, а не по одному.
- [ ] При успешном запуске переходит в фоновый режим и следит за состоянием sing-box. Если вдруг идёт exit 1, выполняется dnsmasq restore и снова следит за состоянием. Вопрос в том, как это искусcтвенно провернуть. Попробовать положить прокси и посмотреть, останется ли работать DNS в этом случае. И здесь, вероятно, можно обойтись триггером в init.d.
- [ ] Галочка, которая режет доступ к doh серверам.
- [ ] IPv6. Только после наполнения Wiki.
## Тесты
- [ ] Unit тесты (BATS)
- [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS)
- [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS)

View File

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

View File

@@ -12,9 +12,14 @@ const STATUS_COLORS = {
WARNING: '#ff9800'
};
const ERROR_POLL_INTERVAL = 5000; // 5 seconds
const DIAGNOSTICS_UPDATE_INTERVAL = 10000; // 10 seconds
const ERROR_POLL_INTERVAL = 10000; // 10 seconds
const COMMAND_TIMEOUT = 10000; // 10 seconds
const FETCH_TIMEOUT = 10000; // 10 seconds
const BUTTON_FEEDBACK_TIMEOUT = 1000; // 1 second
const DIAGNOSTICS_INITIAL_DELAY = 100; // 100 milliseconds
async function safeExec(command, args = [], timeout = 7000) {
async function safeExec(command, args = [], timeout = COMMAND_TIMEOUT) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
@@ -87,6 +92,7 @@ function createConfigSection(section, map, network) {
o = s.taboption('basic', form.ListValue, 'mode', _('Connection Type'), _('Select between VPN and Proxy connection methods for traffic routing'));
o.value('proxy', ('Proxy'));
o.value('vpn', ('VPN'));
o.value('block', ('Block'));
o.ucisection = s.section;
o = s.taboption('basic', form.ListValue, 'proxy_config_type', _('Configuration Type'), _('Select how to configure the proxy'));
@@ -294,7 +300,7 @@ function createConfigSection(section, map, network) {
o.value('russia_inside', 'Russia inside');
o.value('russia_outside', 'Russia outside');
o.value('ukraine_inside', 'Ukraine');
o.value('geoblock', 'GEO Block');
o.value('geoblock', 'Geo Block');
o.value('block', 'Block');
o.value('porn', 'Porn');
o.value('news', 'News');
@@ -307,6 +313,11 @@ function createConfigSection(section, map, network) {
o.value('tiktok', 'Tik-Tok');
o.value('telegram', 'Telegram');
o.value('cloudflare', 'Cloudflare');
o.value('google_ai', 'Google AI');
o.value('google_play', 'Google Play');
o.value('hetzner', 'Hetzner ASN');
o.value('ovh', 'OVH ASN');
o.depends('domain_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
@@ -338,13 +349,13 @@ function createConfigSection(section, map, network) {
}
if (newValues.includes('russia_inside')) {
const allowedWithRussiaInside = ['russia_inside', 'meta', 'twitter', 'discord', 'telegram', 'cloudflare'];
const allowedWithRussiaInside = ['russia_inside', 'meta', 'twitter', 'discord', 'telegram', 'cloudflare', 'google_ai', 'google_play', 'hetzner', 'ovh'];
const removedServices = newValues.filter(v => !allowedWithRussiaInside.includes(v));
if (removedServices.length > 0) {
newValues = newValues.filter(v => allowedWithRussiaInside.includes(v));
notifications.push(E('p', { class: 'alert-message warning' }, [
E('strong', {}, _('Russia inside restrictions')), E('br'),
_('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.')
_('Warning: Russia inside can only be used with Meta, Twitter, Discord, Cloudflare, Google AI, Google Play, Hetzner, OVH and Telegram. %s already in Russia inside and have been removed from selection.')
.format(removedServices.join(', '))
]));
}
@@ -396,14 +407,18 @@ function createConfigSection(section, map, network) {
const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*(\.[A-Za-z]{2,})?$/;
const lines = value.split(/\n/).map(line => line.trim());
let hasValidDomain = false;
for (const line of lines) {
// Skip empty lines or lines that start with //
if (!line || line.startsWith('//')) continue;
// Skip empty lines
if (!line) continue;
// Extract domain part (before any //)
const domainPart = line.split('//')[0].trim();
// Skip if line is empty after removing comments
if (!domainPart) continue;
// Process each domain in the line (separated by comma or space)
const domains = domainPart.split(/[,\s]+/).map(d => d.trim()).filter(d => d.length > 0);
@@ -411,8 +426,14 @@ function createConfigSection(section, map, network) {
if (!domainRegex.test(domain)) {
return _('Invalid domain format: %s. Enter domain without protocol').format(domain);
}
hasValidDomain = true;
}
}
if (!hasValidDomain) {
return _('At least one valid domain must be specified. Comments-only content is not allowed.');
}
return true;
};
@@ -491,14 +512,18 @@ function createConfigSection(section, map, network) {
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
const lines = value.split(/\n/).map(line => line.trim());
let hasValidSubnet = false;
for (const line of lines) {
// Skip empty lines or lines that start with //
if (!line || line.startsWith('//')) continue;
// Skip empty lines
if (!line) continue;
// Extract subnet part (before any //)
const subnetPart = line.split('//')[0].trim();
// Skip if line is empty after removing comments
if (!subnetPart) continue;
// Process each subnet in the line (separated by comma or space)
const subnets = subnetPart.split(/[,\s]+/).map(s => s.trim()).filter(s => s.length > 0);
@@ -522,8 +547,14 @@ function createConfigSection(section, map, network) {
return _('CIDR must be between 0 and 32 in: %s').format(subnet);
}
}
hasValidSubnet = true;
}
}
if (!hasValidSubnet) {
return _('At least one valid subnet or IP must be specified. Comments-only content is not allowed.');
}
return true;
};
@@ -575,7 +606,7 @@ const copyToClipboard = (text, button) => {
document.execCommand('copy');
const originalText = button.textContent;
button.textContent = _('Copied!');
setTimeout(() => button.textContent = originalText, 1000);
setTimeout(() => button.textContent = originalText, BUTTON_FEEDBACK_TIMEOUT);
} catch (err) {
ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message));
}
@@ -630,53 +661,100 @@ const maskIP = (ip) => {
};
const showConfigModal = async (command, title) => {
const res = await safeExec('/usr/bin/podkop', [command]);
let formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
// Create and show modal immediately with loading state
const modalContent = E('div', { 'class': 'panel-body' }, [
E('div', {
'class': 'panel-body',
style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; ' +
'font-family: monospace; white-space: pre-wrap; word-wrap: break-word; ' +
'line-height: 1.5; font-size: 14px;'
}, [
E('pre', {
'id': 'modal-content-pre',
style: 'margin: 0;'
}, _('Loading...'))
]),
E('div', {
'class': 'right',
style: 'margin-top: 1em;'
}, [
E('button', {
'class': 'btn',
'id': 'copy-button',
'click': ev => copyToClipboard('```txt\n' + document.getElementById('modal-content-pre').innerText + '\n```', ev.target)
}, _('Copy to Clipboard')),
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
])
]);
if (command === 'global_check') {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
ui.showModal(_(title), modalContent);
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';
// Function to update modal content
const updateModalContent = (content) => {
const pre = document.getElementById('modal-content-pre');
if (pre) {
pre.textContent = content;
}
}
};
ui.showModal(_(title), createModalContent(_(title), formattedOutput));
try {
let formattedOutput = '';
if (command === 'global_check') {
const res = await safeExec('/usr/bin/podkop', [command]);
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
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';
}
updateModalContent(formattedOutput);
} catch (error) {
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
updateModalContent(formattedOutput);
}
} else {
const res = await safeExec('/usr/bin/podkop', [command]);
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
updateModalContent(formattedOutput);
}
} catch (error) {
updateModalContent(_('Error: ') + error.message);
}
};
// Button Factory
@@ -769,7 +847,7 @@ const createStatusPanel = (title, status, buttons, extraData = {}) => {
ButtonFactory.createModalButton({
label: E('strong', _('Global check')),
command: 'global_check',
title: _('Click here for all the info')
title: _('Global check')
}),
ButtonFactory.createModalButton({
label: 'View Logs',
@@ -782,8 +860,8 @@ const createStatusPanel = (title, status, buttons, extraData = {}) => {
title: _('Lists Update Results')
})
] : title === _('FakeIP Status') ? [
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', {}, [
E('span', { style: `color: ${extraData.fakeipStatus?.color}` }, [
extraData.fakeipStatus?.state === 'working' ? '✔' : extraData.fakeipStatus?.state === 'not_working' ? '✘' : '!',
' ',
@@ -798,8 +876,8 @@ const createStatusPanel = (title, status, buttons, extraData = {}) => {
])
])
]),
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', {}, [
E('strong', {}, _('DNS Status')),
E('br'),
E('span', { style: `color: ${extraData.dnsStatus?.remote?.color}` }, [
@@ -815,8 +893,8 @@ const createStatusPanel = (title, status, buttons, extraData = {}) => {
])
])
]),
E('div', { style: 'margin-bottom: 10px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', {}, [
E('strong', {}, extraData.configName),
E('br'),
E('span', { style: `color: ${extraData.bypassStatus?.color}` }, [
@@ -842,8 +920,14 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
action: 'restart',
reload: true
}),
ButtonFactory.createActionButton({
label: 'Stop Podkop',
type: 'apply',
action: 'stop',
reload: true
}),
ButtonFactory.createInitActionButton({
label: podkopStatus.enabled ? 'Disable Podkop' : 'Enable Podkop',
label: podkopStatus.enabled ? 'Disable Autostart' : 'Enable Autostart',
type: podkopStatus.enabled ? 'remove' : 'apply',
action: podkopStatus.enabled ? 'disable' : 'enable',
reload: true
@@ -1042,6 +1126,64 @@ function stopErrorPolling() {
}
}
async function checkFakeIP() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
try {
const response = await fetch('https://fakeip.podkop.fyi/check', { signal: controller.signal });
const data = await response.json();
clearTimeout(timeoutId);
if (data.fakeip === true) {
return createStatus('working', 'working', 'SUCCESS');
} else {
return createStatus('not_working', 'not working', 'ERROR');
}
} catch (fetchError) {
clearTimeout(timeoutId);
const message = fetchError.name === 'AbortError' ? 'timeout' : 'check error';
return createStatus('error', message, 'WARNING');
}
} catch (error) {
return createStatus('error', 'check error', 'WARNING');
}
}
async function checkFakeIPCLI() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
try {
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
if (!singboxStatus.running) {
return createStatus('not_working', 'sing-box not running', 'ERROR');
}
const result = await safeExec('nslookup', ['-timeout=2', 'fakeip.podkop.fyi', '127.0.0.42']);
if (result.stdout && result.stdout.includes('198.18')) {
return createStatus('working', 'working on router', 'SUCCESS');
} else {
return createStatus('not_working', 'not working on router', 'ERROR');
}
} catch (error) {
return createStatus('error', 'CLI check error', 'WARNING');
}
}
return view.extend({
async render() {
document.head.insertAdjacentHTML('beforeend', `
@@ -1274,7 +1416,7 @@ return view.extend({
}
updateDiagnostics();
diagnosticsUpdateTimer = setInterval(updateDiagnostics, 10000);
diagnosticsUpdateTimer = setInterval(updateDiagnostics, DIAGNOSTICS_UPDATE_INTERVAL);
}
function stopDiagnosticsUpdates() {
@@ -1290,81 +1432,6 @@ return view.extend({
}
}
function checkFakeIP() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
if (!singboxStatus.running) {
return resolve(createStatus('not_working', 'sing-box not running', 'ERROR'));
}
if (!singboxStatus.dns_configured) {
return resolve(createStatus('not_working', 'DNS not configured', 'ERROR'));
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch('https://fakeip.podkop.fyi/check', { signal: controller.signal });
const data = await response.json();
clearTimeout(timeoutId);
if (data.fakeip === true) {
return resolve(createStatus('working', 'working', 'SUCCESS'));
} else {
return resolve(createStatus('not_working', 'not working', 'ERROR'));
}
} catch (fetchError) {
clearTimeout(timeoutId);
const message = fetchError.name === 'AbortError' ? 'timeout' : 'check error';
return resolve(createStatus('error', message, 'WARNING'));
}
} catch (error) {
return resolve(createStatus('error', 'check error', 'WARNING'));
}
});
}
function checkFakeIPCLI() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
if (!singboxStatus.running) {
return resolve(createStatus('not_working', 'sing-box not running', 'ERROR'));
}
if (!singboxStatus.dns_configured) {
return resolve(createStatus('not_working', 'DNS not configured', 'ERROR'));
}
const result = await safeExec('nslookup', ['-timeout=2', 'fakeip.podkop.fyi', '127.0.0.42']);
if (result.stdout && result.stdout.includes('198.18')) {
return resolve(createStatus('working', 'working on router', 'SUCCESS'));
} else {
return resolve(createStatus('not_working', 'not working on router', 'ERROR'));
}
} catch (error) {
return resolve(createStatus('error', 'CLI check error', 'WARNING'));
}
});
}
function checkBypass() {
const createStatus = (state, message, color) => ({
state,
@@ -1399,7 +1466,7 @@ return view.extend({
let ip1 = null;
try {
const controller1 = new AbortController();
const timeoutId1 = setTimeout(() => controller1.abort(), 10000);
const timeoutId1 = setTimeout(() => controller1.abort(), FETCH_TIMEOUT);
const response1 = await fetch('https://fakeip.podkop.fyi/check', { signal: controller1.signal });
const data1 = await response1.json();
@@ -1414,7 +1481,7 @@ return view.extend({
let ip2 = null;
try {
const controller2 = new AbortController();
const timeoutId2 = setTimeout(() => controller2.abort(), 10000);
const timeoutId2 = setTimeout(() => controller2.abort(), FETCH_TIMEOUT);
const response2 = await fetch('https://ip.podkop.fyi/check', { signal: controller2.signal });
const data2 = await response2.json();
@@ -1493,8 +1560,8 @@ return view.extend({
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 }
remote: createStatus('error', 'DNS check error', 'WARNING'),
local: createStatus('error', 'DNS check error', 'WARNING')
}),
checkBypass()
@@ -1664,7 +1731,7 @@ return view.extend({
}
}
}
}, 100);
}, DIAGNOSTICS_INITIAL_DELAY);
node.classList.add('fade-in');
return node;

View File

@@ -842,4 +842,34 @@ msgid "Its must be router!"
msgstr "Это должен быть роутер!"
msgid "Global check"
msgstr "Глобальная проверка"
msgstr "Глобальная проверка"
msgid "Starting lists update..."
msgstr "Начало обновления списков..."
msgid "DNS check passed"
msgstr "Проверка DNS пройдена"
msgid "DNS check failed after 60 attempts"
msgstr "Проверка DNS не удалась после 60 попыток"
msgid "GitHub connection check passed"
msgstr "Проверка подключения к GitHub пройдена"
msgid "GitHub connection check passed (via proxy)"
msgstr "Проверка подключения к GitHub пройдена (через прокси)"
msgid "GitHub connection check failed after 60 attempts"
msgstr "Проверка подключения к GitHub не удалась после 60 попыток"
msgid "Downloading and processing lists..."
msgstr "Загрузка и обработка списков..."
msgid "Lists update completed successfully"
msgstr "Обновление списков успешно завершено"
msgid "Lists update failed"
msgstr "Обновление списков не удалось"
msgid "Error: "
msgstr "Ошибка: "

View File

@@ -1193,4 +1193,37 @@ msgid "Its must be router!"
msgstr ""
msgid "Global check"
msgstr ""
msgid "Starting lists update..."
msgstr ""
msgid "DNS check passed"
msgstr ""
msgid "DNS check failed after 60 attempts"
msgstr ""
msgid "GitHub connection check passed"
msgstr ""
msgid "GitHub connection check passed (via proxy)"
msgstr ""
msgid "GitHub connection check failed after 60 attempts"
msgstr ""
msgid "Downloading and processing lists..."
msgstr ""
msgid "Lists update completed successfully"
msgstr ""
msgid "Lists update failed"
msgstr ""
msgid "Loading..."
msgstr ""
msgid "Error: "
msgstr ""

View File

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

View File

@@ -16,9 +16,11 @@ SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/meta.lst"
SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/discord.lst"
SUBNETS_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst"
SUBNETS_CLOUDFLARE="${GITHUB_RAW_URL}/Subnets/IPv4/cloudflare.lst"
SUBNETS_HETZNER="${GITHUB_RAW_URL}/Subnets/IPv4/hetzner.lst"
SUBNETS_OVH="${GITHUB_RAW_URL}/Subnets/IPv4/ovh.lst"
SING_BOX_CONFIG="/etc/sing-box/config.json"
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 cloudflare"
VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram cloudflare google_ai google_play hetzner ovh"
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.podkop.fyi"
INTERFACES_LIST=""
@@ -26,6 +28,11 @@ 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"
# Color constants
COLOR_CYAN="\033[0;36m"
COLOR_GREEN="\033[0;32m"
COLOR_RESET="\033[0m"
log() {
local message="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
@@ -36,11 +43,14 @@ log() {
nolog() {
local message="$1"
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}"
echo -e "${COLOR_CYAN}[$timestamp]${COLOR_RESET} ${COLOR_GREEN}$message${COLOR_RESET}"
}
echolog() {
local message="$1"
log "$message"
nolog "$message"
}
start_main() {
@@ -67,7 +77,10 @@ start_main() {
config_foreach process_validate_service
sleep 3
# Sync time for DoH/DoT
/usr/sbin/ntpd -q -p 194.190.168.1 -p 216.239.35.0 -p 216.239.35.4 -p 162.159.200.1 -p 162.159.200.123
sleep 2
mkdir -p /tmp/podkop
@@ -160,7 +173,7 @@ stop_main() {
if [ -f /var/run/podkop_list_update.pid ]; then
pid=$(cat /var/run/podkop_list_update.pid)
if kill -0 "$pid"; then
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null
log "Stopped list_update"
fi
@@ -375,8 +388,6 @@ save_dnsmasq_config() {
dnsmasq_add_resolver() {
log "Save dnsmasq config"
save_dnsmasq_config "dhcp.@dnsmasq[0].noresolv" "dhcp.@dnsmasq[0].podkop_noresolv"
save_dnsmasq_config "dhcp.@dnsmasq[0].cachesize" "dhcp.@dnsmasq[0].podkop_cachesize"
uci -q delete dhcp.@dnsmasq[0].podkop_server
for server in $(uci get dhcp.@dnsmasq[0].server 2>/dev/null); do
@@ -388,6 +399,9 @@ dnsmasq_add_resolver() {
fi
done
save_dnsmasq_config "dhcp.@dnsmasq[0].noresolv" "dhcp.@dnsmasq[0].podkop_noresolv"
save_dnsmasq_config "dhcp.@dnsmasq[0].cachesize" "dhcp.@dnsmasq[0].podkop_cachesize"
log "Configure dnsmasq for sing-box"
uci set dhcp.@dnsmasq[0].noresolv="1"
uci set dhcp.@dnsmasq[0].cachesize="0"
@@ -418,11 +432,11 @@ dnsmasq_restore() {
local server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
if [[ "$server" == "127.0.0.42" ]]; then
uci -q delete dhcp.@dnsmasq[0].server
uci -q delete dhcp.@dnsmasq[0].server 2>/dev/null
for server in $(uci get dhcp.@dnsmasq[0].podkop_server 2>/dev/null); do
uci add_list dhcp.@dnsmasq[0].server="$server"
done
uci delete dhcp.@dnsmasq[0].podkop_server
uci delete dhcp.@dnsmasq[0].podkop_server 2>/dev/null
fi
uci delete dhcp.@dnsmasq[0].podkop_cachesize
@@ -553,13 +567,13 @@ prepare_custom_ruleset() {
}
list_update() {
log "Update remote lists"
echolog "🔄 Starting lists update..."
local i
for i in $(seq 1 60); do
if nslookup -timeout=1 openwrt.org >/dev/null 2>&1; then
log "DNS is available"
echolog "DNS check passed"
break
fi
log "DNS is unavailable [$i/60]"
@@ -567,7 +581,7 @@ list_update() {
done
if [ "$i" -eq 60 ]; then
log "Error: DNS check failed after 10 attempts"
echolog " DNS check failed after 60 attempts"
return 1
fi
@@ -575,28 +589,36 @@ list_update() {
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
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"
echolog "GitHub connection check passed (via proxy)"
break
fi
else
if curl -s -m 3 https://github.com >/dev/null; then
log "GitHub is available"
echolog "GitHub connection check passed"
break
fi
fi
log "GitHub is unavailable [$i/60]"
echolog "GitHub is unavailable [$i/60]"
sleep 3
done
if [ "$i" -eq 60 ]; then
log "Error: Cannot connect to GitHub after 10 attempts"
echolog "❌ GitHub connection check failed after 60 attempts"
return 1
fi
echolog "📥 Downloading and processing lists..."
config_foreach process_remote_ruleset_subnet
config_foreach process_domains_list_url
config_foreach process_subnet_for_section_remote
if [ $? -eq 0 ]; then
echolog "✅ Lists update completed successfully"
else
echolog "❌ Lists update failed"
fi
}
find_working_resolver() {
@@ -939,6 +961,9 @@ sing_box_outdound() {
fi
fi
;;
"block")
log "Block mode"
;;
*)
log "Requires *vpn* or *proxy* value"
return
@@ -1219,8 +1244,8 @@ sing_box_config_vless() {
if [ $? -eq 0 ]; then
log "Config VLESS created successfully"
else
log "Error: VLESS invalid JSON config generated"
return 1
log "[critical] Error: VLESS invalid JSON config generated"
exit 1
fi
}
@@ -1425,6 +1450,12 @@ list_subnets_download() {
"cloudflare")
URL=$SUBNETS_CLOUDFLARE
;;
"hetzner")
URL=$SUBNETS_HETZNER
;;
"ovh")
URL=$SUBNETS_OVH
;;
"discord")
URL=$SUBNETS_DISCORD
nft add set inet $table podkop_discord_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
@@ -1458,27 +1489,54 @@ sing_box_rules() {
local rule_set="$1"
local outbound="$2"
# Check if there is an outbound rule for "tproxy-in"
local rule_exists=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .inbound == ["tproxy-in"])' "$SING_BOX_CONFIG")
config_get mode "$section" "mode"
if [[ -n "$rule_exists" ]]; then
# If a rule for tproxy-in exists, add a new rule_set to the existing rule
jq \
--arg rule_set "$rule_set" \
--arg outbound "$outbound" \
'(.route.rules[] | select(.outbound == $outbound and .inbound == ["tproxy-in"]) .rule_set) += [$rule_set]' \
"$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
if [[ "$mode" == "block" ]]; then
# Action reject
# Check if there is an rule with reject"
local rule_exists=$(jq -r '.route.rules[] | select(.inbound == ["tproxy-in"] and .action == "reject")' "$SING_BOX_CONFIG")
if [[ -n "$rule_exists" ]]; then
# If a rule for rejectexists, add a new rule_set to the existing rule
jq \
--arg rule_set "$rule_set" \
'(.route.rules[] | select(.inbound == ["tproxy-in"] and .action == "reject") .rule_set) += [$rule_set]' \
"$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
else
# If there is no rule for reject, create a new one with rule_set
jq \
--arg rule_set "$rule_set" \
'.route.rules += [{
"inbound": ["tproxy-in"],
"rule_set": [$rule_set],
"action": "reject"
}]' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
fi
return
else
# If there is no rule for tproxy-in, create a new one with rule_set
jq \
--arg rule_set "$rule_set" \
--arg outbound "$outbound" \
'.route.rules += [{
"inbound": ["tproxy-in"],
"rule_set": [$rule_set],
"outbound": $outbound,
"action": "route"
}]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
# Action route
# Check if there is an outbound rule for "tproxy-in"
local rule_exists=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .inbound == ["tproxy-in"])' "$SING_BOX_CONFIG")
if [[ -n "$rule_exists" ]]; then
# If a rule for tproxy-in exists, add a new rule_set to the existing rule
jq \
--arg rule_set "$rule_set" \
--arg outbound "$outbound" \
'(.route.rules[] | select(.outbound == $outbound and .inbound == ["tproxy-in"]) .rule_set) += [$rule_set]' \
"$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
else
# If there is no rule for tproxy-in, create a new one with rule_set
jq \
--arg rule_set "$rule_set" \
--arg outbound "$outbound" \
'.route.rules += [{
"inbound": ["tproxy-in"],
"rule_set": [$rule_set],
"outbound": $outbound,
"action": "route"
}]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
fi
fi
}
@@ -1572,18 +1630,24 @@ list_custom_url_domains_create() {
local section="$2"
local URL="$1"
local filename=$(basename "$URL")
local filepath="/tmp/podkop/${filename}"
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"
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "$filepath" "$URL"
else
wget -O "/tmp/podkop/${filename}" "$URL"
wget -O "$filepath" "$URL"
fi
if grep -q $'\r' "$filepath"; then
log "$filename has Windows line endings (CRLF). Converting to Unix (LF)"
sed -i 's/\r$//' "$filepath"
fi
while IFS= read -r domain; do
log "From downloaded file: $domain"
sing_box_ruleset_domains_json $domain $section
done <"/tmp/podkop/$filename"
done <"$filepath"
}
process_domains_list_url() {
@@ -1617,19 +1681,25 @@ list_custom_url_subnets_create() {
local section="$2"
local URL="$1"
local filename=$(basename "$URL")
local filepath="/tmp/podkop/${filename}"
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"
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "$filepath" "$URL"
else
wget -O "/tmp/podkop/${filename}" "$URL"
wget -O "$filepath" "$URL"
fi
if grep -q $'\r' "$filepath"; then
log "$filename has Windows line endings (CRLF). Converting to Unix (LF)"
sed -i 's/\r$//' "$filepath"
fi
while IFS= read -r subnet; do
log "From local file: $subnet"
sing_box_ruleset_subnets_json $subnet $section
nft add element inet PodkopTable podkop_subnets { $subnet }
done <"/tmp/podkop/$filename"
done <"$filepath"
}
process_subnet_for_section_remote() {
@@ -2149,25 +2219,48 @@ check_dns_available() {
fi
if [ "$dns_type" = "doh" ]; then
local result=""
if echo "$dns_server" | grep -q "quad9.net" || \
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:5053/dns-query?name=itdog.info&type=A")
# Generate random DNS query ID (2 bytes)
local random_id=$(head -c2 /dev/urandom | hexdump -ve '1/1 "%.2x"' 2>/dev/null)
if [ $? -ne 0 ]; then
error_message="Failed to generate random ID"
status="internal error"
else
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/dns-query?name=itdog.info&type=A")
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1
status="available"
# Create DNS wire format query for google.com A record with random ID
local dns_query=$(printf "\x${random_id:0:2}\x${random_id:2:2}\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01" | base64 2>/dev/null)
if [ $? -ne 0 ]; then
error_message="Failed to generate DNS query"
status="internal error"
else
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
# Try POST method first (RFC 8484 compliant) with shorter timeout
local result=$(echo "$dns_query" | base64 -d 2>/dev/null | curl -H "Content-Type: application/dns-message" \
-H "Accept: application/dns-message" \
--data-binary @- \
--max-time 2 \
--connect-timeout 1 \
-s \
"https://$dns_server/dns-query" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then
is_available=1
status="available"
else
# Try GET method as fallback with shorter timeout
local dns_query_no_padding=$(echo "$dns_query" | tr -d '=' 2>/dev/null)
result=$(curl -H "accept: application/dns-message" \
--max-time 2 \
--connect-timeout 1 \
-s \
"https://$dns_server/dns-query?dns=$dns_query_no_padding" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then
is_available=1
status="available"
else
error_message="DoH server not responding"
fi
fi
fi
fi
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
is_available=1
status="available"
fi
elif [ "$dns_type" = "dot" ]; then
(nc "$dns_server" 853 </dev/null >/dev/null 2>&1) & pid=$!
sleep 2
@@ -2371,6 +2464,40 @@ global_check() {
fi
}
show_help() {
cat << EOF
Usage: $0 COMMAND
Available commands:
start Start podkop service
stop Stop podkop service
reload Reload podkop configuration
restart Restart podkop service
enable Enable podkop autostart
disable Disable podkop autostart
main Run main podkop process
list_update Update domain lists
check_proxy Check proxy connectivity
check_nft Check NFT rules
check_github Check GitHub connectivity
check_logs Show podkop logs from system journal
check_sing_box_connections Show active sing-box connections
check_sing_box_logs Show sing-box logs
check_fakeip Check FakeIP DNS functionality
check_dnsmasq Check DNSMasq configuration
show_config Display current podkop configuration
show_version Show podkop version
show_sing_box_config Show sing-box configuration
show_luci_version Show LuCI app version
show_sing_box_version Show sing-box version
show_system_info Show system information
get_status Get podkop service status
get_sing_box_status Get sing-box service status
check_dns_available Check DNS server availability
global_check Run global system check
EOF
}
case "$1" in
start)
start
@@ -2445,7 +2572,7 @@ case "$1" in
global_check
;;
*)
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}"
show_help
exit 1
;;
esac