From 000d2f8e18d89b63484aae68dc0b810a670af3cf Mon Sep 17 00:00:00 2001 From: Ivan K Date: Fri, 21 Feb 2025 15:37:30 +0300 Subject: [PATCH 1/7] fix: add missing URL decoding for semicolon --- podkop/files/etc/init.d/podkop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index a5584a1..37e87ed 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -981,7 +981,7 @@ sing_box_config_vless() { get_param() { local param="$1" local value=$(echo "$STRING" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p") - value=$(echo "$value" | sed 's/%2F/\//g; s/%2C/,/g; s/%3D/=/g; s/%2B/+/g; s/%20/ /g' | tr -d '\n' | tr -d '\r') + value=$(echo "$value" | sed 's/%2F/\//g; s/%2C/,/g; s/%3D/=/g; s/%2B/+/g; s/%20/ /g; s/%3B/;/g' | tr -d '\n' | tr -d '\r') echo "$value" } From 1a6ee45612faeed353582098c14d8f16b1d4ef94 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Fri, 21 Feb 2025 17:34:31 +0300 Subject: [PATCH 2/7] fix: add function to dynamically fetch network interfaces for VPN configuration --- .../resources/view/podkop/podkop.js | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js index 5048098..8d9cf35 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js @@ -13,6 +13,23 @@ function formatDiagnosticOutput(output) { .replace(/\r/g, '\n'); } +function getNetworkInterfaces(o) { + const excludeInterfaces = ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan']; + + return network.getDevices().then(devices => { + devices.forEach(device => { + if (device.dev && device.dev.name) { + const deviceName = device.dev.name; + if (!excludeInterfaces.includes(deviceName) && !/^lan\d+$/.test(deviceName)) { + o.value(deviceName, deviceName); + } + } + }); + }).catch(error => { + console.error('Failed to get network devices:', error); + }); +} + // Общая функция для создания конфигурационных секций function createConfigSection(section, map, network) { const s = section; @@ -168,21 +185,7 @@ function createConfigSection(section, map, network) { o = s.taboption('basic', form.ListValue, 'interface', _('Network Interface'), _('Select network interface for VPN connection')); o.depends('mode', 'vpn'); o.ucisection = s.section; - - try { - const devices = network.getDevicesSync ? network.getDevicesSync() : network.getDevices(); - const excludeInterfaces = ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0']; - devices.forEach(device => { - if (device.dev && device.dev.name) { - const deviceName = device.dev.name; - if (!excludeInterfaces.includes(deviceName) && !/^lan\d+$/.test(deviceName)) { - o.value(deviceName, deviceName); - } - } - }); - } catch (error) { - console.error('Error fetching devices:', error); - } + getNetworkInterfaces(o); o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Lists')); o.default = '0'; @@ -584,10 +587,6 @@ return view.extend({ 'class': 'btn cbi-button-apply', 'click': () => fs.exec('/etc/init.d/podkop', ['restart']).then(() => location.reload()) }, _('Restart Podkop')), - E('button', { - 'class': 'btn cbi-button-' + (podkopStatus.enabled ? 'remove' : 'apply'), - 'click': () => fs.exec('/etc/init.d/podkop', [podkopStatus.enabled ? 'disable' : 'enable']).then(() => location.reload()) - }, podkopStatus.enabled ? _('Disable Podkop') : _('Enable Podkop')), E('button', { 'class': 'btn', 'click': () => fs.exec('/etc/init.d/podkop', ['show_config']).then(res => { From febb69d0befd25a66827d65d1f769fd7a18e27e7 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Fri, 21 Feb 2025 17:38:57 +0300 Subject: [PATCH 3/7] fix: add enable/disable button for Podkop service --- .../htdocs/luci-static/resources/view/podkop/podkop.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js index 8d9cf35..3a71052 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js @@ -587,6 +587,10 @@ return view.extend({ 'class': 'btn cbi-button-apply', 'click': () => fs.exec('/etc/init.d/podkop', ['restart']).then(() => location.reload()) }, _('Restart Podkop')), + E('button', { + 'class': 'btn cbi-button-' + (podkopStatus.enabled ? 'remove' : 'apply'), + 'click': () => fs.exec('/etc/init.d/podkop', [podkopStatus.enabled ? 'disable' : 'enable']).then(() => location.reload()) + }, podkopStatus.enabled ? _('Disable Podkop') : _('Enable Podkop')), E('button', { 'class': 'btn', 'click': () => fs.exec('/etc/init.d/podkop', ['show_config']).then(res => { From a278918e77e77843de577eae392550c57be72170 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Fri, 21 Feb 2025 17:55:39 +0300 Subject: [PATCH 4/7] feat: add timeout and chunking to proxy label fetching --- .../resources/view/podkop/podkop.js | 41 ++++++++++++++----- podkop/files/etc/init.d/podkop | 10 ++++- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js index 3a71052..0952646 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js @@ -53,17 +53,36 @@ function createConfigSection(section, map, network) { o.rows = 5; o.ucisection = s.section; o.load = function (section_id) { - return fs.exec('/etc/init.d/podkop', ['get_proxy_label', section_id]).then(res => { - if (res.stdout) { - try { - const decodedLabel = decodeURIComponent(res.stdout.trim()); - this.description = _('Current config: ') + decodedLabel; - } catch (e) { - console.error('Error decoding label:', e); - this.description = _('Current config: ') + res.stdout.trim(); - } - } - return this.super('load', section_id); + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + console.error('Label fetch timeout'); + resolve(this.super('load', section_id)); + }, 5000); // 5 second timeout + + fs.exec('/etc/init.d/podkop', ['get_proxy_label', section_id]) + .then(res => { + clearTimeout(timeout); + if (res.stdout) { + try { + const chunks = res.stdout.trim().split('\n'); + const fullLabel = chunks.join(''); + const decodedLabel = decodeURIComponent(fullLabel); + this.description = _('Current config: ') + decodedLabel; + } catch (e) { + console.error('Error processing label:', e); + // If decoding fails, try to display the raw chunks + const chunks = res.stdout.trim().split('\n'); + const fullLabel = chunks.join(''); + this.description = _('Current config: ') + fullLabel; + } + } + resolve(this.super('load', section_id)); + }) + .catch(error => { + clearTimeout(timeout); + console.error('Error fetching label:', error); + resolve(this.super('load', section_id)); + }); }); }; o.validate = function (section_id, value) { diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index 37e87ed..f2edc9b 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -1840,11 +1840,19 @@ get_proxy_label() { local section="$1" local proxy_string local label="" + local chunk_size=50 + local start=0 config_get proxy_string "$section" "proxy_string" if [ -n "$proxy_string" ]; then label=$(echo "$proxy_string" | sed -n 's/.*#\(.*\)$/\1/p') - echo "$label" + if [ -n "$label" ]; then + # Split label into chunks and output one at a time + while [ $start -lt ${#label} ]; do + echo "${label:$start:$chunk_size}" + start=$((start + chunk_size)) + done + fi fi } From 25107a04813b122a86041056baee4a679d528d6d Mon Sep 17 00:00:00 2001 From: Ivan K Date: Sat, 22 Feb 2025 09:52:04 +0300 Subject: [PATCH 5/7] refactor: simplify label fetching and decoding in podkop.js --- .../resources/view/podkop/podkop.js | 41 +++++-------------- podkop/files/etc/init.d/podkop | 10 +---- 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js index 0952646..3a71052 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js @@ -53,36 +53,17 @@ function createConfigSection(section, map, network) { o.rows = 5; o.ucisection = s.section; o.load = function (section_id) { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - console.error('Label fetch timeout'); - resolve(this.super('load', section_id)); - }, 5000); // 5 second timeout - - fs.exec('/etc/init.d/podkop', ['get_proxy_label', section_id]) - .then(res => { - clearTimeout(timeout); - if (res.stdout) { - try { - const chunks = res.stdout.trim().split('\n'); - const fullLabel = chunks.join(''); - const decodedLabel = decodeURIComponent(fullLabel); - this.description = _('Current config: ') + decodedLabel; - } catch (e) { - console.error('Error processing label:', e); - // If decoding fails, try to display the raw chunks - const chunks = res.stdout.trim().split('\n'); - const fullLabel = chunks.join(''); - this.description = _('Current config: ') + fullLabel; - } - } - resolve(this.super('load', section_id)); - }) - .catch(error => { - clearTimeout(timeout); - console.error('Error fetching label:', error); - resolve(this.super('load', section_id)); - }); + return fs.exec('/etc/init.d/podkop', ['get_proxy_label', section_id]).then(res => { + if (res.stdout) { + try { + const decodedLabel = decodeURIComponent(res.stdout.trim()); + this.description = _('Current config: ') + decodedLabel; + } catch (e) { + console.error('Error decoding label:', e); + this.description = _('Current config: ') + res.stdout.trim(); + } + } + return this.super('load', section_id); }); }; o.validate = function (section_id, value) { diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index f2edc9b..37e87ed 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -1840,19 +1840,11 @@ get_proxy_label() { local section="$1" local proxy_string local label="" - local chunk_size=50 - local start=0 config_get proxy_string "$section" "proxy_string" if [ -n "$proxy_string" ]; then label=$(echo "$proxy_string" | sed -n 's/.*#\(.*\)$/\1/p') - if [ -n "$label" ]; then - # Split label into chunks and output one at a time - while [ $start -lt ${#label} ]; do - echo "${label:$start:$chunk_size}" - start=$((start + chunk_size)) - done - fi + echo "$label" fi } From 7697754a73ae847d1ab68b7f52389d6790d8eb5c Mon Sep 17 00:00:00 2001 From: Ivan K Date: Sat, 22 Feb 2025 12:45:25 +0300 Subject: [PATCH 6/7] refactor: replace fs.exec with safeExec for command execution with timeout --- .../resources/view/podkop/podkop.js | 63 +++++++++++++------ podkop/files/etc/init.d/podkop | 2 +- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js index 3a71052..96f359f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js @@ -5,6 +5,29 @@ 'require network'; 'require fs'; +// Add helper function for safe command execution with timeout +async function safeExec(command, args = [], timeout = 3000) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + const result = await Promise.race([ + fs.exec(command, args), + new Promise((_, reject) => { + controller.signal.addEventListener('abort', () => { + reject(new Error('Command execution timed out')); + }); + }) + ]); + + clearTimeout(timeoutId); + return result; + } catch (error) { + console.warn(`Command execution failed or timed out: ${command} ${args.join(' ')}`); + return { stdout: '', stderr: error.message }; + } +} + function formatDiagnosticOutput(output) { if (typeof output !== 'string') return ''; return output.trim() @@ -53,7 +76,7 @@ function createConfigSection(section, map, network) { o.rows = 5; o.ucisection = s.section; o.load = function (section_id) { - return fs.exec('/etc/init.d/podkop', ['get_proxy_label', section_id]).then(res => { + return safeExec('/etc/init.d/podkop', ['get_proxy_label', section_id]).then(res => { if (res.stdout) { try { const decodedLabel = decodeURIComponent(res.stdout.trim()); @@ -457,7 +480,7 @@ return view.extend({ `); const m = new form.Map('podkop', _('Podkop configuration'), null, ['main', 'extra']); - fs.exec('/etc/init.d/podkop', ['show_version']).then(res => { + safeExec('/etc/init.d/podkop', ['show_version']).then(res => { if (res.stdout) m.title = _('Podkop') + ' v' + res.stdout.trim(); }); @@ -577,23 +600,23 @@ return view.extend({ podkopStatus.running ? E('button', { 'class': 'btn cbi-button-remove', - 'click': () => fs.exec('/etc/init.d/podkop', ['stop']).then(() => location.reload()) + 'click': () => safeExec('/etc/init.d/podkop', ['stop']).then(() => location.reload()) }, _('Stop Podkop')) : E('button', { 'class': 'btn cbi-button-apply', - 'click': () => fs.exec('/etc/init.d/podkop', ['start']).then(() => location.reload()) + 'click': () => safeExec('/etc/init.d/podkop', ['start']).then(() => location.reload()) }, _('Start Podkop')), E('button', { 'class': 'btn cbi-button-apply', - 'click': () => fs.exec('/etc/init.d/podkop', ['restart']).then(() => location.reload()) + 'click': () => safeExec('/etc/init.d/podkop', ['restart']).then(() => location.reload()) }, _('Restart Podkop')), E('button', { 'class': 'btn cbi-button-' + (podkopStatus.enabled ? 'remove' : 'apply'), - 'click': () => fs.exec('/etc/init.d/podkop', [podkopStatus.enabled ? 'disable' : 'enable']).then(() => location.reload()) + 'click': () => safeExec('/etc/init.d/podkop', [podkopStatus.enabled ? 'disable' : 'enable']).then(() => location.reload()) }, podkopStatus.enabled ? _('Disable Podkop') : _('Enable Podkop')), E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['show_config']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['show_config']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('Podkop Configuration'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -624,7 +647,7 @@ return view.extend({ }, _('Show Config')), E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['check_logs']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['check_logs']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('Podkop Logs'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -668,7 +691,7 @@ return view.extend({ E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [ E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['show_sing_box_config']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['show_sing_box_config']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('Sing-box Configuration'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -699,7 +722,7 @@ return view.extend({ }, _('Show Config')), E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['check_sing_box_logs']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['check_sing_box_logs']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('Sing-box Logs'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -730,7 +753,7 @@ return view.extend({ }, _('View Logs')), E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['check_sing_box_connections']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['check_sing_box_connections']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('Active Connections'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -769,7 +792,7 @@ return view.extend({ E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [ E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['check_nft']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['check_nft']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('NFT Rules'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -800,7 +823,7 @@ return view.extend({ }, _('Check NFT Rules')), E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['check_dnsmasq']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['check_dnsmasq']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('DNSMasq Configuration'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -831,7 +854,7 @@ return view.extend({ }, _('Check DNSMasq')), E('button', { 'class': 'btn', - 'click': () => fs.exec('/etc/init.d/podkop', ['list_update']).then(res => { + 'click': () => safeExec('/etc/init.d/podkop', ['list_update']).then(res => { const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); ui.showModal(_('Lists Update Results'), [ E('div', { style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; font-size: 14px;' }, [ @@ -934,12 +957,12 @@ return view.extend({ system, fakeipStatus ] = await Promise.all([ - fs.exec('/etc/init.d/podkop', ['get_status']), - fs.exec('/etc/init.d/podkop', ['get_sing_box_status']), - fs.exec('/etc/init.d/podkop', ['show_version']), - fs.exec('/etc/init.d/podkop', ['show_luci_version']), - fs.exec('/etc/init.d/podkop', ['show_sing_box_version']), - fs.exec('/etc/init.d/podkop', ['show_system_info']), + safeExec('/etc/init.d/podkop', ['get_status']), + safeExec('/etc/init.d/podkop', ['get_sing_box_status']), + safeExec('/etc/init.d/podkop', ['show_version']), + safeExec('/etc/init.d/podkop', ['show_luci_version']), + safeExec('/etc/init.d/podkop', ['show_sing_box_version']), + safeExec('/etc/init.d/podkop', ['show_system_info']), checkFakeIP() ]); diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index 37e87ed..e9b61cd 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -1814,7 +1814,7 @@ get_status() { fi # Check if service is running - if pgrep -f "sing-box" >/dev/null; then + if pgrep -f "podkop" >/dev/null; then running=1 fi From d8d8d79d68dd1f92cd119b6fa3319f4be78358e9 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Sat, 22 Feb 2025 14:15:27 +0300 Subject: [PATCH 7/7] feat: add support for outbound JSON configuration in sing-box --- .../resources/view/podkop/podkop.js | 18 ------ podkop/files/etc/init.d/podkop | 60 +++++++++---------- 2 files changed, 29 insertions(+), 49 deletions(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js index ed1ce92..6bc649f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js @@ -53,23 +53,6 @@ function getNetworkInterfaces(o) { }); } -function getNetworkInterfaces(o) { - const excludeInterfaces = ['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan']; - - return network.getDevices().then(devices => { - devices.forEach(device => { - if (device.dev && device.dev.name) { - const deviceName = device.dev.name; - if (!excludeInterfaces.includes(deviceName) && !/^lan\d+$/.test(deviceName)) { - o.value(deviceName, deviceName); - } - } - }); - }).catch(error => { - console.error('Failed to get network devices:', error); - }); -} - // Общая функция для создания конфигурационных секций function createConfigSection(section, map, network) { const s = section; @@ -226,7 +209,6 @@ function createConfigSection(section, map, network) { o.depends('mode', 'vpn'); o.ucisection = s.section; getNetworkInterfaces(o); - getNetworkInterfaces(o); o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Lists')); o.default = '0'; diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index 61f4f40..21cd8ce 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -212,8 +212,9 @@ main() { config_get proxy_string "main" "proxy_string" config_get interface "main" "interface" + config_get outbound_json "main" "outbound_json" - if [ -n "$proxy_string" ] || [ -n "$interface" ]; then + if [ -n "$proxy_string" ] || [ -n "$interface" ] || [ -n "$outbound_json" ]; then config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0" if [ "$dont_touch_dhcp" -eq 0 ]; then dnsmasq_add_resolver @@ -798,7 +799,7 @@ sing_box_outdound() { config_get outbound_json $section "outbound_json" if [ -n "$outbound_json" ]; then log "Using JSON outbound configuration" - sing_box_config_outbound_json "$outbound_json" + sing_box_config_outbound_json "$outbound_json" "$section" else log "Missing outbound JSON configuration" return @@ -880,37 +881,34 @@ sing_box_config_check() { sing_box_config_outbound_json() { local json_config="$1" - local listen_port="$2" + local section="$2" - cat > /tmp/base_config.json << EOF -{ - "log": { - "level": "warn" - }, - "inbounds": [ - { - "type": "tproxy", - "listen": "::", - "listen_port": $listen_port, - "tcp_fast_open": true, - "udp_fragment": true - }, - { - "tag": "dns-in", - "type": "direct", - "listen": "127.0.0.42", - "listen_port": 53 - } - ], - "outbounds": [], - "route": { - "auto_detect_interface": true - } -} -EOF + # Create new object with tag first, then merge with the rest of the config + local modified_config=$(echo "$json_config" | jq --arg section "$section" \ + 'del(.tag) | {"tag": $section} + .') + + jq --argjson outbound "$modified_config" \ + --arg section "$section" \ + '. | + .outbounds |= ( + map( + if .tag == $section then + $outbound + else . end + ) + + ( + if (map(select(.tag == $section)) | length) == 0 then + [$outbound] + else [] end + ) + )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG - jq --argjson outbound "$json_config" '.outbounds += [$outbound]' /tmp/base_config.json > $SING_BOX_CONFIG - rm -f /tmp/base_config.json + if [ $? -eq 0 ]; then + log "Outbound config updated successfully" + else + log "Error: Invalid JSON config generated" + return 1 + fi }