Compare commits

..

33 Commits

Author SHA1 Message Date
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
itdoginfo
3f114b4710 v0.3.31 2025-03-30 12:41:40 +03:00
itdoginfo
b821abe82c Merge pull request #67 from itdoginfo/feature/no-more-cache
Feature/add DNS and bypass status checks to diagnostics
2025-03-30 12:37:58 +03:00
Ivan K
732cab2ef3 🐛 fix(podkop): fix typo in translation and dns check timeout 2025-03-30 09:54:31 +03:00
Ivan K
3b4ce9e7a3 feat: add visibility change event listener for diagnostics updates 2025-03-21 15:06:08 +03:00
Ivan K
69c4445c85 refactor: move buttons for NFT and DNSMasq checks 2025-03-21 14:54:52 +03:00
Ivan K
dcebc3d67d docs: update Russian translations for proxy configuration and add new translation strings 2025-03-21 14:53:24 +03:00
Ivan K
1be31eaf59 feat: add local DNS availability check and display in UI 2025-03-21 14:47:20 +03:00
Ivan K
023210e0f0 style: update text for Bypass Status to Main config 2025-03-21 14:21:35 +03:00
Ivan K
5ff832533e feat: add DNS and bypass status checks to diagnostics 2025-03-21 13:03:29 +03:00
Ivan K
5d2163515e refactor: improve caching prevention logic 2025-03-20 21:47:55 +03:00
Ivan K
5865706d0c feat: add timestamp to URL to prevent caching 2025-03-20 21:45:08 +03:00
itdoginfo
aabe1c53dc Update 2025-03-18 00:32:06 +03:00
itdoginfo
8e91b582ad #36 2025-03-18 00:31:56 +03:00
9 changed files with 631 additions and 59 deletions

View File

@@ -80,11 +80,7 @@ Luci: Services/podkop
# ToDo # ToDo
Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме. Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме.
- [x] Проверка, что версия в makefile совпадает с тегом - [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn (нужно wiki)
- [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn
- [x] Обработка ошибки `sing-box[9345]: FATAL[0000] start service: initialize DNS rule[2]: rule-set not found: main`. Когда не задана строка\интерфейс
- [x] Проверка `/etc/resolv.conf` на наличие DNS-серверов
- [x] Отслеживание интерфейса wan в sing-box
- [ ] Рестарт сервиса без рестарта dnsmasq - [ ] Рестарт сервиса без рестарта dnsmasq
- [ ] `ash: can't kill pid 9848: No such process` при обновлении - [ ] `ash: can't kill pid 9848: No such process` при обновлении

View File

@@ -42,6 +42,15 @@ main() {
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")
if echo "$check_response" | grep -q 'API rate limit '; then
echo "You've reached rate limit from GitHub. Repeat in five minutes."
exit 1
fi
fi
download_success=0 download_success=0
while read -r url; do while read -r url; do

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.30 PKG_VERSION:=0.3.35
PKG_RELEASE:=1 PKG_RELEASE:=1
LUCI_TITLE:=LuCI podkop app LUCI_TITLE:=LuCI podkop app

View File

@@ -4,6 +4,7 @@
'require ui'; 'require ui';
'require network'; 'require network';
'require fs'; 'require fs';
'require uci';
const STATUS_COLORS = { const STATUS_COLORS = {
SUCCESS: '#4caf50', SUCCESS: '#4caf50',
@@ -11,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();
@@ -90,20 +93,29 @@ function createConfigSection(section, map, network) {
if (cfgvalue) { if (cfgvalue) {
try { try {
// Extract only the active configuration (first non-comment line)
const activeConfig = cfgvalue.split('\n') const activeConfig = cfgvalue.split('\n')
.map(line => line.trim()) .map(line => line.trim())
.find(line => line && !line.startsWith('//')); .find(line => line && !line.startsWith('//'));
if (activeConfig) { if (activeConfig) {
const label = activeConfig.split('#').pop() || 'unnamed'; if (activeConfig.includes('#')) {
const decodedLabel = decodeURIComponent(label); const label = activeConfig.split('#').pop();
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + decodedLabel); if (label && label.trim()) {
container.appendChild(descDiv); const decodedLabel = decodeURIComponent(label);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + decodedLabel);
container.appendChild(descDiv);
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} }
} catch (e) { } catch (e) {
console.error('Error parsing config label:', e); console.error('Error parsing config label:', e);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + (cfgvalue.split('#').pop() || 'unnamed')); const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv); container.appendChild(descDiv);
} }
} else { } else {
@@ -121,7 +133,6 @@ function createConfigSection(section, map, network) {
} }
try { try {
// Get the first non-comment line as the active configuration
const activeConfig = value.split('\n') const activeConfig = value.split('\n')
.map(line => line.trim()) .map(line => line.trim())
.find(line => line && !line.startsWith('//')); .find(line => line && !line.startsWith('//'));
@@ -663,9 +674,8 @@ const createStatusPanel = (title, status, buttons) => {
}; };
// Update the status section creation // Update the status section creation
let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus) { let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus, dnsStatus, bypassStatus, configName) {
return E('div', { 'class': 'cbi-section' }, [ return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Service Status')),
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, [
@@ -696,6 +706,11 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
label: 'View Logs', label: 'View Logs',
command: 'check_logs', command: 'check_logs',
title: 'Podkop Logs' title: 'Podkop Logs'
}),
ButtonFactory.createModalButton({
label: _('Update Lists'),
command: 'list_update',
title: _('Lists Update Results')
}) })
]), ]),
@@ -715,6 +730,16 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
label: 'Check Connections', label: 'Check Connections',
command: 'check_sing_box_connections', command: 'check_sing_box_connections',
title: 'Active Connections' title: 'Active Connections'
}),
ButtonFactory.createModalButton({
label: _('Check NFT Rules'),
command: 'check_nft',
title: _('NFT Rules')
}),
ButtonFactory.createModalButton({
label: _('Check DNSMasq'),
command: 'check_dnsmasq',
title: _('DNSMasq Configuration')
}) })
]), ]),
@@ -736,26 +761,34 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
]) ])
]) ])
]), ]),
ButtonFactory.createModalButton({ E('div', { style: 'margin-bottom: 10px;' }, [
label: _('Check NFT Rules'), E('div', { style: 'margin-bottom: 5px;' }, [
command: 'check_nft', E('strong', {}, _('DNS Status')),
title: _('NFT Rules') E('br'),
}), E('span', { style: `color: ${dnsStatus.remote.color}` }, [
ButtonFactory.createModalButton({ dnsStatus.remote.state === 'available' ? '✔' : dnsStatus.remote.state === 'unavailable' ? '✘' : '!',
label: _('Check DNSMasq'), ' ',
command: 'check_dnsmasq', dnsStatus.remote.message
title: _('DNSMasq Configuration') ]),
}), E('br'),
ButtonFactory.createModalButton({ E('span', { style: `color: ${dnsStatus.local.color}` }, [
label: _('Update Lists'), dnsStatus.local.state === 'available' ? '✔' : dnsStatus.local.state === 'unavailable' ? '✘' : '!',
command: 'list_update', ' ',
title: _('Lists Update Results') dnsStatus.local.message
}), ])
ButtonFactory.createModalButton({ ])
label: _('Check Router FakeIP'), ]),
command: 'check_fakeip', E('div', { style: 'margin-bottom: 10px;' }, [
title: _('FakeIP Router Check') 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
@@ -772,6 +805,131 @@ let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, s
]); ]);
}; };
function checkDNSAvailability() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
const dnsStatusResult = await safeExec('/usr/bin/podkop', ['check_dns_available']);
if (!dnsStatusResult || !dnsStatusResult.stdout) {
return resolve({
remote: createStatus('error', 'DNS check timeout', 'WARNING'),
local: createStatus('error', 'DNS check timeout', 'WARNING')
});
}
try {
const dnsStatus = JSON.parse(dnsStatusResult.stdout);
const remoteStatus = dnsStatus.is_available ?
createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') :
createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
const localStatus = dnsStatus.local_dns_working ?
createStatus('available', 'Router DNS working', 'SUCCESS') :
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) {
return resolve({
remote: createStatus('error', 'DNS check error', 'WARNING'),
local: createStatus('error', 'DNS check error', 'WARNING')
});
}
});
}
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', `
@@ -1071,6 +1229,82 @@ return view.extend({
}); });
} }
function checkBypass() {
const createStatus = (state, message, color) => ({
state,
message: _(message),
color: STATUS_COLORS[color]
});
return new Promise(async (resolve) => {
try {
let configMode = 'proxy'; // Default fallback
try {
const formData = document.querySelector('form.map-podkop');
if (formData) {
const modeSelect = formData.querySelector('select[name="cbid.podkop.main.mode"]');
if (modeSelect && modeSelect.value) {
configMode = modeSelect.value;
}
}
} catch (formError) {
console.error('Error getting mode from form:', formError);
}
// Check if sing-box is running
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', `${configMode} not running`, 'ERROR'));
}
// Fetch IP from first endpoint
let ip1 = null;
try {
const controller1 = new AbortController();
const timeoutId1 = setTimeout(() => controller1.abort(), 10000);
const response1 = await fetch('https://fakeip.tech-domain.club/check', { signal: controller1.signal });
const data1 = await response1.json();
clearTimeout(timeoutId1);
ip1 = data1.IP;
} catch (error) {
return resolve(createStatus('error', 'First endpoint check failed', 'WARNING'));
}
// Fetch IP from second endpoint
let ip2 = null;
try {
const controller2 = new AbortController();
const timeoutId2 = setTimeout(() => controller2.abort(), 10000);
const response2 = await fetch('https://ip.tech-domain.club/check', { signal: controller2.signal });
const data2 = await response2.json();
clearTimeout(timeoutId2);
ip2 = data2.IP;
} catch (error) {
return resolve(createStatus('not_working', `${configMode} not working`, 'ERROR'));
}
// Compare IPs
if (ip1 && ip2) {
if (ip1 !== ip2) {
return resolve(createStatus('working', `${configMode} working correctly`, 'SUCCESS'));
} else {
return resolve(createStatus('not_working', `${configMode} routing incorrect`, 'ERROR'));
}
} else {
return resolve(createStatus('error', 'IP comparison failed', 'WARNING'));
}
} catch (error) {
return resolve(createStatus('error', 'Bypass check error', 'WARNING'));
}
});
}
async function updateDiagnostics() { async function updateDiagnostics() {
try { try {
const [ const [
@@ -1081,7 +1315,9 @@ return view.extend({
singbox, singbox,
system, system,
fakeipStatus, fakeipStatus,
fakeipCLIStatus fakeipCLIStatus,
dnsStatus,
bypassStatus
] = await Promise.all([ ] = await Promise.all([
safeExec('/usr/bin/podkop', ['get_status']), safeExec('/usr/bin/podkop', ['get_status']),
safeExec('/usr/bin/podkop', ['get_sing_box_status']), safeExec('/usr/bin/podkop', ['get_sing_box_status']),
@@ -1090,7 +1326,9 @@ return view.extend({
safeExec('/usr/bin/podkop', ['show_sing_box_version']), safeExec('/usr/bin/podkop', ['show_sing_box_version']),
safeExec('/usr/bin/podkop', ['show_system_info']), safeExec('/usr/bin/podkop', ['show_system_info']),
checkFakeIP(), checkFakeIP(),
checkFakeIPCLI() checkFakeIPCLI(),
checkDNSAvailability(),
checkBypass()
]); ]);
const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}'); const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}');
@@ -1099,7 +1337,35 @@ return view.extend({
const container = document.getElementById('diagnostics-status'); const container = document.getElementById('diagnostics-status');
if (!container) return; if (!container) return;
const statusSection = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus); let configName = _('Main config');
try {
const data = await uci.load('podkop');
const proxyString = uci.get('podkop', 'main', 'proxy_string');
if (proxyString) {
const activeConfig = proxyString.split('\n')
.map(line => line.trim())
.find(line => line && !line.startsWith('//'));
if (activeConfig) {
if (activeConfig.includes('#')) {
const label = activeConfig.split('#').pop();
if (label && label.trim()) {
configName = _('Config: ') + decodeURIComponent(label);
} else {
configName = _('Main config');
}
} else {
configName = _('Main config');
}
}
}
} catch (e) {
console.error('Error getting config name from UCI:', e);
}
// Create a modified statusSection function with the configName
const statusSection = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system, fakeipStatus, fakeipCLIStatus, dnsStatus, bypassStatus, configName);
container.innerHTML = ''; container.innerHTML = '';
container.appendChild(statusSection); container.appendChild(statusSection);
@@ -1118,6 +1384,22 @@ return view.extend({
fakeipCLIStatus.message fakeipCLIStatus.message
]).outerHTML; ]).outerHTML;
} }
const dnsRemoteElement = document.getElementById('dns-remote-status');
if (dnsRemoteElement) {
dnsRemoteElement.innerHTML = E('span', { 'style': `color: ${dnsStatus.remote.color}` }, [
dnsStatus.remote.state === 'available' ? '✔ ' : dnsStatus.remote.state === 'unavailable' ? '✘ ' : '! ',
dnsStatus.remote.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) {
@@ -1142,6 +1424,17 @@ return view.extend({
const titleDiv = E('h2', { 'class': 'cbi-map-title' }, _('Podkop')); const titleDiv = E('h2', { 'class': 'cbi-map-title' }, _('Podkop'));
node.insertBefore(titleDiv, node.firstChild); node.insertBefore(titleDiv, node.firstChild);
document.addEventListener('visibilitychange', function () {
const diagnosticsContainer = document.getElementById('diagnostics-status');
if (document.hidden) {
stopDiagnosticsUpdates();
stopErrorPolling();
} else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) {
startDiagnosticsUpdates();
startErrorPolling();
}
});
setTimeout(() => { setTimeout(() => {
const diagnosticsContainer = document.getElementById('diagnostics-status'); const diagnosticsContainer = document.getElementById('diagnostics-status');
if (diagnosticsContainer) { if (diagnosticsContainer) {
@@ -1149,6 +1442,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();
} }
}); });
} }
@@ -1164,9 +1458,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();
} }
} }
}); });
@@ -1177,6 +1473,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

@@ -40,8 +40,8 @@ msgstr "Конфигурация Outbound"
msgid "Proxy Configuration URL" msgid "Proxy Configuration URL"
msgstr "URL конфигурации прокси" msgstr "URL конфигурации прокси"
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration" msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for saving other configs"
msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси" msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для сохранения других конфигураций"
msgid "Outbound Configuration" msgid "Outbound Configuration"
msgstr "Конфигурация исходящего соединения" msgstr "Конфигурация исходящего соединения"
@@ -749,4 +749,67 @@ msgid "not works on router"
msgstr "не работает на роутере" msgstr "не работает на роутере"
msgid "Diagnostics" msgid "Diagnostics"
msgstr "Диагностика" msgstr "Диагностика"
msgid "DNS Status"
msgstr "Статус DNS"
msgid "Bypass Status"
msgstr "Статус обхода"
msgid "proxy working correctly"
msgstr "прокси работает корректно"
msgid "vpn working correctly"
msgstr "vpn работает корректно"
msgid "proxy not working"
msgstr "прокси не работает"
msgid "vpn not working"
msgstr "vpn не работает"
msgid "proxy not running"
msgstr "прокси не запущен"
msgid "vpn not running"
msgstr "vpn не запущен"
msgid "proxy routing incorrect"
msgstr "маршрутизация прокси некорректна"
msgid "vpn routing incorrect"
msgstr "маршрутизация vpn некорректна"
msgid "First endpoint check failed"
msgstr "Проверка первой конечной точки не удалась"
msgid "IP comparison failed"
msgstr "Сравнение IP-адресов не удалось"
msgid "Bypass check error"
msgstr "Ошибка проверки обхода"
msgid "Main config"
msgstr "Основная конфигурация"
msgid "Config without description"
msgstr "Конфигурация без описания"
msgid "DNS working"
msgstr "DNS работает"
msgid "Router DNS working"
msgstr "DNS роутера работает"
msgid "Router DNS not working"
msgstr "DNS роутера не работает"
msgid "DNS check error"
msgstr "Ошибка проверки DNS"
msgid "available"
msgstr "доступен"
msgid "unavailable"
msgstr "недоступен"

View File

@@ -1103,4 +1103,70 @@ msgid "not works on router"
msgstr "" msgstr ""
msgid "Diagnostics" msgid "Diagnostics"
msgstr ""
msgid "DNS Status"
msgstr ""
msgid "Bypass Status"
msgstr ""
msgid "proxy working correctly"
msgstr ""
msgid "vpn working correctly"
msgstr ""
msgid "proxy not working"
msgstr ""
msgid "vpn not working"
msgstr ""
msgid "proxy not running"
msgstr ""
msgid "vpn not running"
msgstr ""
msgid "proxy routing incorrect"
msgstr ""
msgid "vpn routing incorrect"
msgstr ""
msgid "First endpoint check failed"
msgstr ""
msgid "IP comparison failed"
msgstr ""
msgid "Bypass check error"
msgstr ""
msgid "Main config"
msgstr ""
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs"
msgstr ""
msgid "Config without description"
msgstr ""
msgid "DNS working"
msgstr ""
msgid "Router DNS working"
msgstr ""
msgid "Router DNS not working"
msgstr ""
msgid "DNS check error"
msgstr ""
msgid "available"
msgstr ""
msgid "unavailable"
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.30 PKG_VERSION:=0.3.35
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog <podkop@itdog.info> PKG_MAINTAINER:=ITDog <podkop@itdog.info>

View File

@@ -6,7 +6,7 @@ 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" RESOLV_CONF="/etc/resolv.conf"
start_service() { start_service() {
echo "Start podkop" echo "Start podkop"
@@ -19,20 +19,19 @@ start_service() {
exit 1 exit 1
fi fi
if grep -q FriendlyWrt /etc/banner; then if opkg list-installed | grep -q iptables-mod-extra; then
printf "\033[31;1mYou use FriendlyWrt. If you have problems, check out: https://t.me/itdogchat/44512/181082\033[0m\n" echo "Conflicting package detected: iptables-mod-extra"
fi
if opkg list-installed | grep -q kmod-ipt-nat; then
echo "Conflicting package detected: kmod-ipt-nat"
fi fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then 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" printf "\033[31;1mDetected https-dns-proxy. Disable or uninstall it for correct functionality.\033[0m\n"
fi fi
if ! ip addr | grep -q "br-lan"; then if { ! grep -q "search lan" "$RESOLV_CONF" || ! grep -q "nameserver 127.0.0.1" "$RESOLV_CONF"; } && ! grep -q "search tail" "$RESOLV_CONF"; 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" echo "/etc/resolv.conf does not contain 'search lan' or 'nameserver 127.0.0.1' entries"
fi fi

View File

@@ -22,6 +22,7 @@ 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.
TEST_DOMAIN="fakeip.tech-domain.club" TEST_DOMAIN="fakeip.tech-domain.club"
INTERFACES_LIST="" INTERFACES_LIST=""
SRC_INTERFACE="" SRC_INTERFACE=""
RESOLV_CONF="/etc/resolv.conf"
log() { log() {
local message="$1" local message="$1"
@@ -45,6 +46,33 @@ nolog() {
} }
start() { start() {
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 opkg list-installed | grep -q kmod-ipt-nat; then
log "[critical] Conflicting package detected: kmod-ipt-nat"
fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
log "[critical] Detected https-dns-proxy. Disable or uninstall it for correct functionality."
fi
if { ! grep -q "search lan" "$RESOLV_CONF" || ! grep -q "nameserver 127.0.0.1" "$RESOLV_CONF"; } && ! grep -q "search tail" "$RESOLV_CONF"; then
log "[critical] /etc/resolv.conf does not contain 'search lan' or 'nameserver 127.0.0.1' entries"
fi
migration migration
config_foreach process_validate_service config_foreach process_validate_service
@@ -63,6 +91,7 @@ start() {
sing_box_dns sing_box_dns
sing_box_dns_rule_fakeip sing_box_dns_rule_fakeip
sing_box_rule_dns sing_box_rule_dns
sing_box_create_bypass_ruleset
sing_box_add_secure_dns_probe_domain sing_box_add_secure_dns_probe_domain
sing_box_cache_file sing_box_cache_file
process_socks5 process_socks5
@@ -110,7 +139,7 @@ start() {
fi fi
sing_box_config_check sing_box_config_check
/etc/init.d/sing-box restart /etc/init.d/sing-box start
/etc/init.d/sing-box enable /etc/init.d/sing-box enable
config_get proxy_string "main" "proxy_string" config_get proxy_string "main" "proxy_string"
@@ -726,6 +755,42 @@ sing_box_dns() {
}' $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
} }
sing_box_create_bypass_ruleset() {
log "Creating bypass ruleset for direct access"
jq '
.route.rule_set += [{
"tag": "bypass",
"type": "inline",
"rules": [
{
"domain_suffix": [
"ip.tech-domain.club"
]
}
]
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
# Add a rule to route bypass domains to direct-out outbound
jq '
.route.rules += [{
"inbound": ["tproxy-in"],
"rule_set": ["bypass"],
"outbound": "main",
"action": "route"
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
# Make sure the bypass ruleset is in the fakeip DNS rule
jq '
.dns.rules = (.dns.rules | map(
if .server == "fakeip-server" then
.rule_set += ["bypass"]
else
.
end
))' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
}
sing_box_dns_rule_fakeip() { sing_box_dns_rule_fakeip() {
local rewrite_ttl local rewrite_ttl
config_get rewrite_ttl "main" "dns_rewrite_ttl" "600" config_get rewrite_ttl "main" "dns_rewrite_ttl" "600"
@@ -805,7 +870,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
@@ -831,7 +896,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
@@ -904,7 +969,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
} }
@@ -1820,12 +1885,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() {
@@ -1881,6 +1958,7 @@ 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' \
> "$tmp_config" > "$tmp_config"
cat "$tmp_config" cat "$tmp_config"
@@ -1985,6 +2063,67 @@ get_status() {
echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}" echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}"
} }
check_dns_available() {
local dns_type=$(uci get podkop.main.dns_type 2>/dev/null)
local dns_server=$(uci get podkop.main.dns_server 2>/dev/null)
local is_available=0
local status="unavailable"
local local_dns_working=0
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"
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")
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"
else
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
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
if kill -0 $pid 2>/dev/null; then
kill $pid 2>/dev/null
wait $pid 2>/dev/null
else
is_available=1
status="available"
fi
elif [ "$dns_type" = "udp" ]; then
if nslookup -timeout=2 itdog.info $dns_server >/dev/null 2>&1; then
is_available=1
status="available"
fi
fi
# Check if local DNS resolver is working
if nslookup -timeout=2 $TEST_DOMAIN 127.0.0.1 >/dev/null 2>&1; then
local_dns_working=1
local_dns_status="available"
fi
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() {
local domain="$TEST_DOMAIN" local domain="$TEST_DOMAIN"
local override_port=8443 local override_port=8443
@@ -2079,8 +2218,11 @@ case "$1" in
get_sing_box_status) get_sing_box_status)
get_sing_box_status get_sing_box_status
;; ;;
check_dns_available)
check_dns_available
;;
*) *)
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}" 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}"
exit 1 exit 1
;; ;;
esac esac