feat(network-modes): Prepare v0.3.5 implementation foundation
- Version bump to 0.3.5 in Makefile and README - Add helpers.js utility module for common UI operations - Expand API with new RPC method declarations - Enhance view files with improved functionality: - accesspoint.js: Enhanced WiFi configuration options - relay.js: WireGuard setup improvements - router.js: Expanded proxy and vhost settings - sniffer.js: Enhanced capture configuration - wizard.js: Improved mode selection flow - RPCD backend enhancements (+176 lines) - Add deployment script for easier testing Claude settings: Update permissions for network-modes work Preparation for implementing features documented in CODEX-v0.3.5.md
This commit is contained in:
parent
94bc005ec0
commit
562ac55fe1
@ -130,7 +130,21 @@
|
||||
"Bash(./secubox-tools/add-pkg-file-modes.sh:*)",
|
||||
"Bash(luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js )",
|
||||
"Bash(luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js )",
|
||||
"Bash(git checkout:*)"
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(./scripts/setup-github-pages.sh:*)",
|
||||
"Bash(mkdocs:*)",
|
||||
"Bash(apt search:*)",
|
||||
"Bash(source .venv/bin/activate)",
|
||||
"Bash(pip install:*)",
|
||||
"Bash(./scripts/setup-wiki.sh:*)",
|
||||
"Bash(gh repo edit --help:*)",
|
||||
"Bash(gh repo edit:*)",
|
||||
"Bash(gh auth status:*)",
|
||||
"Bash(git ls-remote:*)",
|
||||
"Bash(for module in luci-app-ksm-manager luci-app-media-flow luci-app-netdata-dashboard luci-app-netifyd-dashboard luci-app-network-modes luci-app-secubox luci-app-system-hub luci-app-traffic-shaper luci-app-vhost-manager luci-app-wireguard-dashboard)",
|
||||
"Bash(do echo \"=== $module ===\" find \"$module/htdocs/luci-static/resources/view\" -name \"*.js\")",
|
||||
"Bash(gh run view:*)",
|
||||
"Bash(/tmp/deploy-system-hub-overview-fix.sh)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-network-modes
|
||||
PKG_VERSION:=0.3.1
|
||||
PKG_VERSION:=0.3.5
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# LuCI Network Modes Dashboard
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Version:** 0.3.5
|
||||
**Last Updated:** 2025-12-28
|
||||
**Status:** Active
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@ -15,6 +15,11 @@ Configure your OpenWrt router for different network operation modes with a moder
|
||||
|
||||
## 🎯 Network Modes
|
||||
|
||||
### 🚀 What's New in v0.3.5
|
||||
- **WireGuard automation:** generate key pairs, deploy `wg0` interfaces, and push MTU/MSS/BBR optimizations directly from the Relay panel.
|
||||
- **Optimization RPCs:** new backend methods expose MTU clamping, TCP BBR, and WireGuard deployment to both UI and automation agents.
|
||||
- **UI action buttons:** Relay mode now includes one-click buttons for key generation, interface deployment, and optimization runs.
|
||||
|
||||
### 🔍 Sniffer Bridge Mode (Inline / Passthrough)
|
||||
Transparent Ethernet bridge without IP address for in-line traffic analysis. All traffic passes through the device.
|
||||
|
||||
|
||||
@ -34,6 +34,30 @@ var callSetMode = rpc.declare({
|
||||
params: ['mode']
|
||||
});
|
||||
|
||||
var callPreviewChanges = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'preview_changes',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callApplyMode = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'apply_mode',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callConfirmMode = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'confirm_mode',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callRollback = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'rollback',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callGetInterfaces = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'get_interfaces',
|
||||
@ -47,6 +71,71 @@ var callValidateConfig = rpc.declare({
|
||||
expect: { valid: false, errors: [] }
|
||||
});
|
||||
|
||||
var callSnifferConfig = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'sniffer_config',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callApConfig = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'ap_config',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callRelayConfig = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'relay_config',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callRouterConfig = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'router_config',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callUpdateSettings = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'update_settings'
|
||||
});
|
||||
|
||||
var callAddVhost = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'add_vhost'
|
||||
});
|
||||
|
||||
var callGenerateConfig = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'generate_config',
|
||||
params: ['mode'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callGenerateWireguardKeys = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'generate_wireguard_keys',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callApplyWireguardConfig = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'apply_wireguard_config',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callApplyMtuClamping = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'apply_mtu_clamping',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callEnableTcpBbr = rpc.declare({
|
||||
object: 'luci.network-modes',
|
||||
method: 'enable_tcp_bbr',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
return baseclass.extend({
|
||||
getStatus: callStatus,
|
||||
getCurrentMode: callGetCurrentMode,
|
||||
@ -54,6 +143,9 @@ return baseclass.extend({
|
||||
setMode: callSetMode,
|
||||
getInterfaces: callGetInterfaces,
|
||||
validateConfig: callValidateConfig,
|
||||
previewChanges: callPreviewChanges,
|
||||
confirmMode: callConfirmMode,
|
||||
rollbackMode: callRollback,
|
||||
|
||||
// Aggregate function for overview page
|
||||
getAllData: function() {
|
||||
@ -77,6 +169,23 @@ return baseclass.extend({
|
||||
});
|
||||
},
|
||||
|
||||
applyMode: function(targetMode) {
|
||||
var chain = Promise.resolve();
|
||||
|
||||
if (targetMode) {
|
||||
chain = callSetMode({ mode: targetMode }).then(function(result) {
|
||||
if (!result || result.success === false) {
|
||||
return Promise.reject(new Error((result && result.error) || 'Unable to prepare mode'));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
return chain.then(function() {
|
||||
return callApplyMode();
|
||||
});
|
||||
},
|
||||
|
||||
// Get static information about a mode
|
||||
getModeInfo: function(mode) {
|
||||
var modeInfo = {
|
||||
@ -160,5 +269,39 @@ return baseclass.extend({
|
||||
var minutes = Math.floor((seconds % 3600) / 60);
|
||||
|
||||
return days + 'd ' + hours + 'h ' + minutes + 'm';
|
||||
},
|
||||
|
||||
getSnifferConfig: callSnifferConfig,
|
||||
getApConfig: callApConfig,
|
||||
getRelayConfig: callRelayConfig,
|
||||
getRouterConfig: callRouterConfig,
|
||||
|
||||
updateSettings: function(mode, settings) {
|
||||
var payload = Object.assign({}, settings || {}, { mode: mode });
|
||||
return callUpdateSettings(payload);
|
||||
},
|
||||
|
||||
addVirtualHost: function(vhost) {
|
||||
return callAddVhost(vhost);
|
||||
},
|
||||
|
||||
generateConfig: function(mode) {
|
||||
return callGenerateConfig({ mode: mode });
|
||||
},
|
||||
|
||||
generateWireguardKeys: function() {
|
||||
return callGenerateWireguardKeys();
|
||||
},
|
||||
|
||||
applyWireguardConfig: function() {
|
||||
return callApplyWireguardConfig();
|
||||
},
|
||||
|
||||
applyMtuClamping: function() {
|
||||
return callApplyMtuClamping();
|
||||
},
|
||||
|
||||
enableTcpBbr: function() {
|
||||
return callEnableTcpBbr();
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
'require ui';
|
||||
'require network-modes.api as api';
|
||||
|
||||
function isToggleActive(node) {
|
||||
return !!(node && node.classList.contains('active'));
|
||||
}
|
||||
|
||||
function persistSettings(mode, payload) {
|
||||
ui.showModal(_('Saving settings...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Applying configuration changes...'))
|
||||
]);
|
||||
|
||||
return api.updateSettings(mode, payload).then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null, E('p', {}, result.message || _('Settings updated')), 'info');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, (result && result.error) || _('Failed to update settings')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function showGeneratedConfig(mode) {
|
||||
ui.showModal(_('Generating configuration...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Building configuration preview...'))
|
||||
]);
|
||||
|
||||
return api.generateConfig(mode).then(function(result) {
|
||||
ui.hideModal();
|
||||
|
||||
if (!result || !result.config) {
|
||||
ui.addNotification(null, E('p', {}, _('No configuration data returned')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ui.showModal(_('Configuration Preview'), [
|
||||
E('pre', { 'class': 'nm-config-preview' }, result.config),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 16px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-positive',
|
||||
'click': ui.hideModal
|
||||
}, _('Close'))
|
||||
])
|
||||
]);
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
isToggleActive: isToggleActive,
|
||||
persistSettings: persistSettings,
|
||||
showGeneratedConfig: showGeneratedConfig
|
||||
};
|
||||
@ -3,6 +3,7 @@
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require network-modes.api as api';
|
||||
'require network-modes.helpers as helpers';
|
||||
|
||||
return view.extend({
|
||||
title: _('Access Point Mode'),
|
||||
@ -210,26 +211,17 @@ return view.extend({
|
||||
|
||||
// Actions
|
||||
E('div', { 'class': 'nm-btn-group' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary', 'data-action': 'ap-save', 'type': 'button' }, [
|
||||
E('span', {}, '💾'),
|
||||
'Save Settings'
|
||||
]),
|
||||
E('button', { 'class': 'nm-btn' }, [
|
||||
E('span', {}, '🔄'),
|
||||
'Restart WiFi'
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'ap-config', 'type': 'button' }, [
|
||||
E('span', {}, '📝'),
|
||||
'Generate Config'
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
// TX Power slider handler
|
||||
var slider = view.querySelector('#wifi-txpower');
|
||||
var valueDisplay = view.querySelector('#txpower-value');
|
||||
if (slider && valueDisplay) {
|
||||
slider.addEventListener('input', function() {
|
||||
valueDisplay.textContent = this.value + ' dBm';
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle handlers
|
||||
view.querySelectorAll('.nm-toggle-switch').forEach(function(toggle) {
|
||||
toggle.addEventListener('click', function() {
|
||||
@ -241,10 +233,48 @@ return view.extend({
|
||||
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') });
|
||||
document.head.appendChild(cssLink);
|
||||
|
||||
this.bindAccessPointActions(view);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
bindAccessPointActions: function(container) {
|
||||
var slider = container.querySelector('#wifi-txpower');
|
||||
var valueDisplay = container.querySelector('#txpower-value');
|
||||
if (slider && valueDisplay) {
|
||||
slider.addEventListener('input', function() {
|
||||
valueDisplay.textContent = this.value + ' dBm';
|
||||
});
|
||||
}
|
||||
|
||||
var saveBtn = container.querySelector('[data-action="ap-save"]');
|
||||
var configBtn = container.querySelector('[data-action="ap-config"]');
|
||||
|
||||
if (saveBtn)
|
||||
saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveAccessPointSettings', container));
|
||||
if (configBtn)
|
||||
configBtn.addEventListener('click', ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'accesspoint'));
|
||||
},
|
||||
|
||||
saveAccessPointSettings: function(container) {
|
||||
var toggles = {};
|
||||
container.querySelectorAll('.nm-toggle-switch[data-setting]').forEach(function(toggle) {
|
||||
var key = toggle.getAttribute('data-setting');
|
||||
toggles[key] = helpers.isToggleActive(toggle);
|
||||
});
|
||||
|
||||
var payload = {
|
||||
wifi_channel: container.querySelector('#wifi-channel') ? container.querySelector('#wifi-channel').value : 'auto',
|
||||
wifi_htmode: container.querySelector('#wifi-htmode') ? container.querySelector('#wifi-htmode').value : 'VHT80',
|
||||
wifi_txpower: container.querySelector('#wifi-txpower') ? parseInt(container.querySelector('#wifi-txpower').value, 10) : 20,
|
||||
roaming_enabled: toggles.roaming_80211r ? 1 : 0,
|
||||
band_steering: toggles.band_steering ? 1 : 0,
|
||||
rrm_enabled: toggles.rrm_80211k ? 1 : 0,
|
||||
wnm_enabled: toggles.wnm_80211v ? 1 : 0,
|
||||
airtime_fairness: toggles.airtime_fairness ? 1 : 0,
|
||||
beamforming: toggles.beamforming ? 1 : 0
|
||||
};
|
||||
|
||||
return helpers.persistSettings('accesspoint', payload);
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require network-modes.api as api';
|
||||
'require network-modes.helpers as helpers';
|
||||
|
||||
return view.extend({
|
||||
title: _('Relay Mode'),
|
||||
@ -45,6 +46,10 @@ return view.extend({
|
||||
]),
|
||||
E('div', { 'class': 'nm-card-badge' }, config.relayd_available ? 'Relayd Available' : 'Relayd Not Installed')
|
||||
]),
|
||||
E('div', { 'class': 'nm-btn-group', 'style': 'margin-top: 16px' }, [
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'relay-generate-keys', 'type': 'button' }, '🔑 Generate Keys'),
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'relay-apply-wireguard', 'type': 'button' }, '🚀 Deploy Interface')
|
||||
])
|
||||
E('div', { 'class': 'nm-card-body' }, [
|
||||
E('div', { 'class': 'nm-form-group' }, [
|
||||
E('label', { 'class': 'nm-form-label' }, 'Relay Interface (Upstream)'),
|
||||
@ -79,6 +84,9 @@ return view.extend({
|
||||
]),
|
||||
E('div', { 'class': 'nm-card-badge' }, (config.wg_interfaces || []).length + ' tunnels')
|
||||
]),
|
||||
E('div', { 'class': 'nm-btn-group', 'style': 'margin-top: 12px' }, [
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'relay-apply-optimizations', 'type': 'button' }, '⚙️ Apply Optimizations')
|
||||
])
|
||||
E('div', { 'class': 'nm-card-body' }, [
|
||||
E('div', { 'class': 'nm-toggle' }, [
|
||||
E('div', { 'class': 'nm-toggle-info' }, [
|
||||
@ -191,13 +199,13 @@ return view.extend({
|
||||
|
||||
// Actions
|
||||
E('div', { 'class': 'nm-btn-group' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary', 'data-action': 'relay-save', 'type': 'button' }, [
|
||||
E('span', {}, '💾'),
|
||||
'Save Settings'
|
||||
]),
|
||||
E('button', { 'class': 'nm-btn' }, [
|
||||
E('span', {}, '🔄'),
|
||||
'Apply & Restart'
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'relay-config', 'type': 'button' }, [
|
||||
E('span', {}, '📝'),
|
||||
'Generate Config'
|
||||
])
|
||||
])
|
||||
]);
|
||||
@ -213,10 +221,111 @@ return view.extend({
|
||||
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') });
|
||||
document.head.appendChild(cssLink);
|
||||
|
||||
this.bindRelayActions(view);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
bindRelayActions: function(container) {
|
||||
var saveBtn = container.querySelector('[data-action="relay-save"]');
|
||||
var configBtn = container.querySelector('[data-action="relay-config"]');
|
||||
|
||||
if (saveBtn)
|
||||
saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveRelaySettings', container));
|
||||
if (configBtn)
|
||||
configBtn.addEventListener('click', ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'relay'));
|
||||
var generateBtn = container.querySelector('[data-action="relay-generate-keys"]');
|
||||
var deployBtn = container.querySelector('[data-action="relay-apply-wireguard"]');
|
||||
var optimizeBtn = container.querySelector('[data-action="relay-apply-optimizations"]');
|
||||
|
||||
if (generateBtn)
|
||||
generateBtn.addEventListener('click', ui.createHandlerFn(this, 'generateWireguardKeys'));
|
||||
if (deployBtn)
|
||||
deployBtn.addEventListener('click', ui.createHandlerFn(this, 'deployWireguardInterface'));
|
||||
if (optimizeBtn)
|
||||
optimizeBtn.addEventListener('click', ui.createHandlerFn(this, 'applyOptimizations'));
|
||||
},
|
||||
|
||||
saveRelaySettings: function(container) {
|
||||
var toggles = {};
|
||||
container.querySelectorAll('.nm-toggle-switch[data-opt]').forEach(function(toggle) {
|
||||
var key = toggle.getAttribute('data-opt');
|
||||
toggles[key] = helpers.isToggleActive(toggle);
|
||||
});
|
||||
|
||||
var mtuValue = container.querySelector('#wg-mtu') ? parseInt(container.querySelector('#wg-mtu').value, 10) : 1420;
|
||||
var conntrackValue = container.querySelector('#conntrack-max') ? parseInt(container.querySelector('#conntrack-max').value, 10) : 16384;
|
||||
|
||||
var payload = {
|
||||
wireguard_enabled: helpers.isToggleActive(container.querySelector('#toggle-wg')) ? 1 : 0,
|
||||
wireguard_interface: container.querySelector('#wg-interface') ? container.querySelector('#wg-interface').value : '',
|
||||
relay_interface: container.querySelector('#relay-interface') ? container.querySelector('#relay-interface').value : '',
|
||||
lan_interface: container.querySelector('#lan-interface') ? container.querySelector('#lan-interface').value : '',
|
||||
wireguard_mtu: isNaN(mtuValue) ? 1420 : mtuValue,
|
||||
mtu_optimization: toggles.mtu_optimization ? 1 : 0,
|
||||
mss_clamping: toggles.mss_clamping ? 1 : 0,
|
||||
tcp_optimization: toggles.tcp_optimization ? 1 : 0,
|
||||
conntrack_max: isNaN(conntrackValue) ? 16384 : conntrackValue
|
||||
};
|
||||
|
||||
return helpers.persistSettings('relay', payload);
|
||||
},
|
||||
|
||||
generateWireguardKeys: function() {
|
||||
ui.showModal(_('Generating WireGuard keys...'), [
|
||||
E('p', { 'class': 'spinning' }, _('wg genkey / wg pubkey'))
|
||||
]);
|
||||
|
||||
return api.generateWireguardKeys().then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('New keys generated')), 'info');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, (result && result.error) || _('Key generation failed')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
deployWireguardInterface: function() {
|
||||
ui.showModal(_('Deploying WireGuard interface...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Writing /etc/config/network and reloading interfaces'))
|
||||
]);
|
||||
|
||||
return api.applyWireguardConfig().then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('WireGuard interface deployed')), 'info');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, (result && result.error) || _('Deployment failed')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
applyOptimizations: function() {
|
||||
ui.showModal(_('Applying optimizations...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Configuring firewall MSS clamping and TCP BBR'))
|
||||
]);
|
||||
|
||||
return Promise.all([
|
||||
api.applyMtuClamping(),
|
||||
api.enableTcpBbr()
|
||||
]).then(function(responses) {
|
||||
ui.hideModal();
|
||||
var errors = responses.filter(function(r) { return r && r.success === 0; });
|
||||
if (errors.length > 0) {
|
||||
ui.addNotification(null, E('p', {}, (errors[0].error || _('Optimization failed'))), 'error');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, _('Optimizations applied')), 'info');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require network-modes.api as api';
|
||||
'require network-modes.helpers as helpers';
|
||||
|
||||
return view.extend({
|
||||
title: _('Router Mode'),
|
||||
@ -300,14 +301,18 @@ return view.extend({
|
||||
// Add Virtual Host
|
||||
E('div', { 'style': 'margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--nm-border)' }, [
|
||||
E('h4', { 'style': 'margin: 0 0 12px 0; font-size: 14px' }, 'Add Virtual Host'),
|
||||
E('div', { 'style': 'display: grid; grid-template-columns: 2fr 2fr 1fr auto; gap: 12px; align-items: end' }, [
|
||||
E('div', { 'style': 'display: grid; grid-template-columns: 2fr 2fr 1fr 1fr auto; gap: 12px; align-items: end' }, [
|
||||
E('div', { 'class': 'nm-form-group', 'style': 'margin: 0' }, [
|
||||
E('label', { 'class': 'nm-form-label' }, 'Domain'),
|
||||
E('input', { 'class': 'nm-input', 'type': 'text', 'placeholder': 'example.com', 'id': 'new-domain' })
|
||||
]),
|
||||
E('div', { 'class': 'nm-form-group', 'style': 'margin: 0' }, [
|
||||
E('label', { 'class': 'nm-form-label' }, 'Backend'),
|
||||
E('input', { 'class': 'nm-input', 'type': 'text', 'placeholder': '127.0.0.1:8080', 'id': 'new-backend' })
|
||||
E('input', { 'class': 'nm-input', 'type': 'text', 'placeholder': '127.0.0.1', 'id': 'new-backend' })
|
||||
]),
|
||||
E('div', { 'class': 'nm-form-group', 'style': 'margin: 0' }, [
|
||||
E('label', { 'class': 'nm-form-label' }, 'Port'),
|
||||
E('input', { 'class': 'nm-input', 'type': 'number', 'min': '1', 'max': '65535', 'value': '443', 'id': 'new-port' })
|
||||
]),
|
||||
E('div', { 'class': 'nm-form-group', 'style': 'margin: 0' }, [
|
||||
E('label', { 'class': 'nm-form-label' }, 'SSL'),
|
||||
@ -316,7 +321,7 @@ return view.extend({
|
||||
E('option', { 'value': '0' }, 'No')
|
||||
])
|
||||
]),
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary', 'style': 'height: 46px' }, '➕ Add')
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary', 'style': 'height: 46px', 'type': 'button', 'data-action': 'router-add-vhost' }, '➕ Add')
|
||||
])
|
||||
])
|
||||
])
|
||||
@ -324,15 +329,15 @@ return view.extend({
|
||||
|
||||
// Actions
|
||||
E('div', { 'class': 'nm-btn-group' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary', 'data-action': 'router-save', 'type': 'button' }, [
|
||||
E('span', {}, '💾'),
|
||||
'Save Settings'
|
||||
]),
|
||||
E('button', { 'class': 'nm-btn' }, [
|
||||
E('span', {}, '🔄'),
|
||||
'Apply & Restart'
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'router-wizard', 'type': 'button' }, [
|
||||
E('span', {}, '🧭'),
|
||||
'Open Mode Wizard'
|
||||
]),
|
||||
E('button', { 'class': 'nm-btn' }, [
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'router-config', 'type': 'button' }, [
|
||||
E('span', {}, '📝'),
|
||||
'Generate Config'
|
||||
])
|
||||
@ -350,10 +355,81 @@ return view.extend({
|
||||
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') });
|
||||
document.head.appendChild(cssLink);
|
||||
|
||||
this.bindRouterActions(view);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
bindRouterActions: function(container) {
|
||||
var saveBtn = container.querySelector('[data-action="router-save"]');
|
||||
var wizardBtn = container.querySelector('[data-action="router-wizard"]');
|
||||
var configBtn = container.querySelector('[data-action="router-config"]');
|
||||
var addVhostBtn = container.querySelector('[data-action="router-add-vhost"]');
|
||||
|
||||
if (saveBtn)
|
||||
saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveRouterSettings', container));
|
||||
if (wizardBtn)
|
||||
wizardBtn.addEventListener('click', ui.createHandlerFn(this, 'openWizard'));
|
||||
if (configBtn)
|
||||
configBtn.addEventListener('click', ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'router'));
|
||||
if (addVhostBtn)
|
||||
addVhostBtn.addEventListener('click', ui.createHandlerFn(this, 'addVirtualHost', container));
|
||||
},
|
||||
|
||||
saveRouterSettings: function(container) {
|
||||
var payload = {
|
||||
wan_interface: container.querySelector('#wan-interface') ? container.querySelector('#wan-interface').value : 'eth1',
|
||||
wan_protocol: container.querySelector('#wan-protocol') ? container.querySelector('#wan-protocol').value : 'dhcp',
|
||||
nat_enabled: helpers.isToggleActive(container.querySelector('#toggle-nat')) ? 1 : 0,
|
||||
firewall_enabled: helpers.isToggleActive(container.querySelector('#toggle-firewall')) ? 1 : 0,
|
||||
proxy_enabled: helpers.isToggleActive(container.querySelector('#toggle-proxy')) ? 1 : 0,
|
||||
proxy_type: container.querySelector('#proxy-type') ? container.querySelector('#proxy-type').value : 'squid',
|
||||
proxy_port: container.querySelector('#proxy-port') ? parseInt(container.querySelector('#proxy-port').value, 10) || 3128 : 3128,
|
||||
transparent_proxy: helpers.isToggleActive(container.querySelector('#toggle-transparent')) ? 1 : 0,
|
||||
dns_over_https: helpers.isToggleActive(container.querySelector('#toggle-doh')) ? 1 : 0,
|
||||
https_frontend: helpers.isToggleActive(container.querySelector('#toggle-frontend')) ? 1 : 0,
|
||||
frontend_type: container.querySelector('#frontend-type') ? container.querySelector('#frontend-type').value : 'nginx',
|
||||
letsencrypt: helpers.isToggleActive(container.querySelector('#toggle-letsencrypt')) ? 1 : 0
|
||||
};
|
||||
|
||||
return helpers.persistSettings('router', payload);
|
||||
},
|
||||
|
||||
openWizard: function() {
|
||||
window.location.hash = '#admin/secubox/network/network-modes/wizard';
|
||||
},
|
||||
|
||||
addVirtualHost: function(container) {
|
||||
var domain = container.querySelector('#new-domain').value.trim();
|
||||
var backend = container.querySelector('#new-backend').value.trim();
|
||||
var portValue = parseInt(container.querySelector('#new-port').value, 10);
|
||||
var sslValue = container.querySelector('#new-ssl').value === '1' ? 1 : 0;
|
||||
|
||||
if (!domain || !backend) {
|
||||
ui.addNotification(null, E('p', {}, _('Domain and backend are required')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ui.showModal(_('Adding virtual host...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Saving virtual host entry'))
|
||||
]);
|
||||
|
||||
return api.addVirtualHost({
|
||||
domain: domain,
|
||||
backend: backend,
|
||||
port: isNaN(portValue) ? 80 : portValue,
|
||||
ssl: sslValue
|
||||
}).then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null, E('p', {}, result.message || _('Virtual host added')), 'info');
|
||||
window.location.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, (result && result.error) || _('Failed to add virtual host')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require network-modes.api as api';
|
||||
'require network-modes.helpers as helpers';
|
||||
|
||||
return view.extend({
|
||||
title: _('Sniffer Mode'),
|
||||
@ -130,11 +131,11 @@ return view.extend({
|
||||
|
||||
// Actions
|
||||
E('div', { 'class': 'nm-btn-group' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary' }, [
|
||||
E('button', { 'class': 'nm-btn nm-btn-primary', 'data-action': 'sniffer-save', 'type': 'button' }, [
|
||||
E('span', {}, '💾'),
|
||||
'Save Settings'
|
||||
]),
|
||||
E('button', { 'class': 'nm-btn' }, [
|
||||
E('button', { 'class': 'nm-btn', 'data-action': 'sniffer-config', 'type': 'button' }, [
|
||||
E('span', {}, '🔄'),
|
||||
'Apply & Restart'
|
||||
])
|
||||
@ -152,10 +153,28 @@ return view.extend({
|
||||
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') });
|
||||
document.head.appendChild(cssLink);
|
||||
|
||||
this.bindSnifferActions(view);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
bindSnifferActions: function(container) {
|
||||
var saveBtn = container.querySelector('[data-action="sniffer-save"]');
|
||||
var applyBtn = container.querySelector('[data-action="sniffer-config"]');
|
||||
|
||||
if (saveBtn)
|
||||
saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveSnifferSettings', container));
|
||||
if (applyBtn)
|
||||
applyBtn.addEventListener('click', ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'sniffer'));
|
||||
},
|
||||
|
||||
saveSnifferSettings: function(container) {
|
||||
var payload = {
|
||||
bridge_interface: container.querySelector('#bridge-interface') ? container.querySelector('#bridge-interface').value : '',
|
||||
netifyd_enabled: helpers.isToggleActive(container.querySelector('#toggle-netifyd')) ? 1 : 0,
|
||||
promiscuous: helpers.isToggleActive(container.querySelector('#toggle-promisc')) ? 1 : 0
|
||||
};
|
||||
|
||||
return helpers.persistSettings('sniffer', payload);
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,44 +6,44 @@
|
||||
'require poll';
|
||||
|
||||
var callGetAvailableModes = rpc.declare({
|
||||
object: 'luci.network_modes',
|
||||
object: 'luci.network-modes',
|
||||
method: 'get_available_modes',
|
||||
expect: { modes: [] }
|
||||
});
|
||||
|
||||
var callGetCurrentMode = rpc.declare({
|
||||
object: 'luci.network_modes',
|
||||
object: 'luci.network-modes',
|
||||
method: 'get_current_mode',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callSetMode = rpc.declare({
|
||||
object: 'luci.network_modes',
|
||||
object: 'luci.network-modes',
|
||||
method: 'set_mode',
|
||||
params: ['mode'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callPreviewChanges = rpc.declare({
|
||||
object: 'luci.network_modes',
|
||||
object: 'luci.network-modes',
|
||||
method: 'preview_changes',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callApplyMode = rpc.declare({
|
||||
object: 'luci.network_modes',
|
||||
object: 'luci.network-modes',
|
||||
method: 'apply_mode',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callConfirmMode = rpc.declare({
|
||||
object: 'luci.network_modes',
|
||||
object: 'luci.network-modes',
|
||||
method: 'confirm_mode',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callRollback = rpc.declare({
|
||||
object: 'luci.network_modes',
|
||||
object: 'luci.network-modes',
|
||||
method: 'rollback',
|
||||
expect: { }
|
||||
});
|
||||
@ -228,7 +228,7 @@ return view.extend({
|
||||
E('p', { 'class': 'spinning' }, _('Préparation du changement de mode...'))
|
||||
]);
|
||||
|
||||
return callSetMode(mode.id).then(L.bind(function(result) {
|
||||
return callSetMode({ mode: mode.id }).then(L.bind(function(result) {
|
||||
if (!result.success) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', result.error || _('Erreur')), 'error');
|
||||
|
||||
@ -455,6 +455,10 @@ apply_mode() {
|
||||
uci set network.stabridge=interface
|
||||
uci set network.stabridge.proto='relay'
|
||||
uci set network.stabridge.network='lan wwan'
|
||||
|
||||
apply_wireguard_config
|
||||
apply_mtu_clamping
|
||||
enable_tcp_bbr
|
||||
;;
|
||||
|
||||
bridge)
|
||||
@ -542,10 +546,12 @@ update_settings() {
|
||||
case "$mode" in
|
||||
sniffer)
|
||||
json_get_var bridge_interface bridge_interface
|
||||
json_get_var capture_interface capture_interface
|
||||
json_get_var netifyd_enabled netifyd_enabled
|
||||
json_get_var promiscuous promiscuous
|
||||
|
||||
[ -n "$bridge_interface" ] && uci set network-modes.sniffer.bridge_interface="$bridge_interface"
|
||||
[ -n "$capture_interface" ] && uci set network-modes.sniffer.capture_interface="$capture_interface"
|
||||
[ -n "$netifyd_enabled" ] && uci set network-modes.sniffer.netifyd_enabled="$netifyd_enabled"
|
||||
[ -n "$promiscuous" ] && uci set network-modes.sniffer.promiscuous="$promiscuous"
|
||||
;;
|
||||
@ -555,40 +561,68 @@ update_settings() {
|
||||
json_get_var wifi_txpower wifi_txpower
|
||||
json_get_var roaming_enabled roaming_enabled
|
||||
json_get_var band_steering band_steering
|
||||
json_get_var rrm_enabled rrm_enabled
|
||||
json_get_var wnm_enabled wnm_enabled
|
||||
json_get_var airtime_fairness airtime_fairness
|
||||
json_get_var beamforming beamforming
|
||||
|
||||
[ -n "$wifi_channel" ] && uci set network-modes.accesspoint.wifi_channel="$wifi_channel"
|
||||
[ -n "$wifi_htmode" ] && uci set network-modes.accesspoint.wifi_htmode="$wifi_htmode"
|
||||
[ -n "$wifi_txpower" ] && uci set network-modes.accesspoint.wifi_txpower="$wifi_txpower"
|
||||
[ -n "$roaming_enabled" ] && uci set network-modes.accesspoint.roaming_enabled="$roaming_enabled"
|
||||
[ -n "$band_steering" ] && uci set network-modes.accesspoint.band_steering="$band_steering"
|
||||
[ -n "$rrm_enabled" ] && uci set network-modes.accesspoint.rrm_enabled="$rrm_enabled"
|
||||
[ -n "$wnm_enabled" ] && uci set network-modes.accesspoint.wnm_enabled="$wnm_enabled"
|
||||
[ -n "$airtime_fairness" ] && uci set network-modes.accesspoint.airtime_fairness="$airtime_fairness"
|
||||
[ -n "$beamforming" ] && uci set network-modes.accesspoint.beamforming="$beamforming"
|
||||
;;
|
||||
relay)
|
||||
json_get_var wireguard_enabled wireguard_enabled
|
||||
json_get_var wireguard_interface wireguard_interface
|
||||
json_get_var wireguard_mtu wireguard_mtu
|
||||
json_get_var mtu_optimization mtu_optimization
|
||||
json_get_var mss_clamping mss_clamping
|
||||
json_get_var relay_interface relay_interface
|
||||
json_get_var lan_interface lan_interface
|
||||
json_get_var tcp_optimization tcp_optimization
|
||||
json_get_var conntrack_max conntrack_max
|
||||
|
||||
[ -n "$wireguard_enabled" ] && uci set network-modes.relay.wireguard_enabled="$wireguard_enabled"
|
||||
[ -n "$wireguard_interface" ] && uci set network-modes.relay.wireguard_interface="$wireguard_interface"
|
||||
[ -n "$wireguard_mtu" ] && uci set network-modes.relay.wireguard_mtu="$wireguard_mtu"
|
||||
[ -n "$relay_interface" ] && uci set network-modes.relay.relay_interface="$relay_interface"
|
||||
[ -n "$lan_interface" ] && uci set network-modes.relay.lan_interface="$lan_interface"
|
||||
[ -n "$mtu_optimization" ] && uci set network-modes.relay.mtu_optimization="$mtu_optimization"
|
||||
[ -n "$mss_clamping" ] && uci set network-modes.relay.mss_clamping="$mss_clamping"
|
||||
[ -n "$tcp_optimization" ] && uci set network-modes.relay.tcp_optimization="$tcp_optimization"
|
||||
[ -n "$conntrack_max" ] && uci set network-modes.relay.conntrack_max="$conntrack_max"
|
||||
;;
|
||||
router)
|
||||
json_get_var wan_interface wan_interface
|
||||
json_get_var wan_protocol wan_protocol
|
||||
json_get_var nat_enabled nat_enabled
|
||||
json_get_var firewall_enabled firewall_enabled
|
||||
json_get_var proxy_enabled proxy_enabled
|
||||
json_get_var proxy_type proxy_type
|
||||
json_get_var proxy_port proxy_port
|
||||
json_get_var https_frontend https_frontend
|
||||
json_get_var frontend_type frontend_type
|
||||
json_get_var transparent_proxy transparent_proxy
|
||||
json_get_var dns_over_https dns_over_https
|
||||
json_get_var letsencrypt letsencrypt
|
||||
|
||||
[ -n "$wan_interface" ] && uci set network-modes.router.wan_interface="$wan_interface"
|
||||
[ -n "$wan_protocol" ] && uci set network-modes.router.wan_protocol="$wan_protocol"
|
||||
[ -n "$nat_enabled" ] && uci set network-modes.router.nat_enabled="$nat_enabled"
|
||||
[ -n "$firewall_enabled" ] && uci set network-modes.router.firewall_enabled="$firewall_enabled"
|
||||
[ -n "$proxy_enabled" ] && uci set network-modes.router.proxy_enabled="$proxy_enabled"
|
||||
[ -n "$proxy_type" ] && uci set network-modes.router.proxy_type="$proxy_type"
|
||||
[ -n "$proxy_port" ] && uci set network-modes.router.proxy_port="$proxy_port"
|
||||
[ -n "$https_frontend" ] && uci set network-modes.router.https_frontend="$https_frontend"
|
||||
[ -n "$frontend_type" ] && uci set network-modes.router.frontend_type="$frontend_type"
|
||||
[ -n "$transparent_proxy" ] && uci set network-modes.router.proxy_transparent="$transparent_proxy"
|
||||
[ -n "$dns_over_https" ] && uci set network-modes.router.dns_over_https="$dns_over_https"
|
||||
[ -n "$letsencrypt" ] && uci set network-modes.router.letsencrypt="$letsencrypt"
|
||||
;;
|
||||
*)
|
||||
json_add_boolean "success" 0
|
||||
@ -605,6 +639,122 @@ update_settings() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Generate WireGuard key pair and store in UCI
|
||||
generate_wireguard_keys() {
|
||||
json_init
|
||||
|
||||
if ! command -v wg >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "wireguard-tools not installed"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local privkey="$(wg genkey 2>/dev/null)"
|
||||
if [ -z "$privkey" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "failed to generate private key"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local pubkey
|
||||
pubkey="$(printf '%s' "$privkey" | wg pubkey 2>/dev/null)"
|
||||
|
||||
if [ -z "$pubkey" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "failed to derive public key"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
uci set network-modes.relay.wg_private_key="$privkey"
|
||||
uci set network-modes.relay.wg_public_key="$pubkey"
|
||||
uci commit network-modes
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "private_key" "$privkey"
|
||||
json_add_string "public_key" "$pubkey"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Deploy WireGuard interface/peer config to /etc/config/network
|
||||
apply_wireguard_config() {
|
||||
local wg_enabled=$(uci -q get network-modes.relay.wireguard_enabled || echo "0")
|
||||
[ "$wg_enabled" = "1" ] || return 0
|
||||
|
||||
local privkey=$(uci -q get network-modes.relay.wg_private_key)
|
||||
local peer_pubkey=$(uci -q get network-modes.relay.wg_peer_pubkey)
|
||||
local peer_endpoint=$(uci -q get network-modes.relay.wg_peer_endpoint)
|
||||
local wg_port=$(uci -q get network-modes.relay.wg_port || echo "51820")
|
||||
local wg_ip=$(uci -q get network-modes.relay.wg_ip || echo "10.200.200.2/24")
|
||||
|
||||
[ -n "$privkey" ] || return 1
|
||||
[ -n "$peer_pubkey" ] || return 1
|
||||
[ -n "$peer_endpoint" ] || return 1
|
||||
|
||||
uci -q delete network.wg0
|
||||
uci -q delete network.wg0_peer
|
||||
|
||||
uci set network.wg0=interface
|
||||
uci set network.wg0.proto='wireguard'
|
||||
uci set network.wg0.private_key="$privkey"
|
||||
uci set network.wg0.listen_port="$wg_port"
|
||||
uci add_list network.wg0.addresses="$wg_ip"
|
||||
|
||||
uci set network.wg0_peer=wireguard_wg0
|
||||
uci set network.wg0_peer.public_key="$peer_pubkey"
|
||||
uci set network.wg0_peer.endpoint_host="$(echo "$peer_endpoint" | cut -d: -f1)"
|
||||
uci set network.wg0_peer.endpoint_port="$(echo "$peer_endpoint" | cut -d: -f2-)"
|
||||
uci set network.wg0_peer.persistent_keepalive='25'
|
||||
uci add_list network.wg0_peer.allowed_ips='0.0.0.0/0'
|
||||
|
||||
uci commit network
|
||||
/etc/init.d/network reload >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Configure firewall MSS clamping based on configured MTU
|
||||
apply_mtu_clamping() {
|
||||
local mtu_optimization=$(uci -q get network-modes.relay.mtu_optimization || echo "0")
|
||||
[ "$mtu_optimization" = "1" ] || return 0
|
||||
|
||||
local wg_mtu=$(uci -q get network-modes.relay.wg_mtu || echo "1420")
|
||||
local mss_value=$((wg_mtu - 40))
|
||||
if [ "$mss_value" -lt 500 ]; then
|
||||
mss_value=500
|
||||
fi
|
||||
|
||||
uci -q delete firewall.mss_clamping
|
||||
uci set firewall.mss_clamping=rule
|
||||
uci set firewall.mss_clamping.name='WireGuard MSS Clamping'
|
||||
uci set firewall.mss_clamping.src='lan'
|
||||
uci set firewall.mss_clamping.dest='wan'
|
||||
uci set firewall.mss_clamping.proto='tcp'
|
||||
uci set firewall.mss_clamping.tcp_flags='SYN'
|
||||
uci set firewall.mss_clamping.target='TCPMSS'
|
||||
uci set firewall.mss_clamping.set_mss="$mss_value"
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Enable TCP BBR congestion control if requested
|
||||
enable_tcp_bbr() {
|
||||
local tcp_optimize=$(uci -q get network-modes.relay.tcp_optimization || echo "0")
|
||||
[ "$tcp_optimize" = "1" ] || return 0
|
||||
|
||||
if ! modprobe tcp_bbr 2>/dev/null; then
|
||||
logger -t network-modes "tcp_bbr module unavailable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cat > /etc/sysctl.d/90-tcp-bbr.conf <<EOF
|
||||
net.core.default_qdisc=fq
|
||||
net.ipv4.tcp_congestion_control=bbr
|
||||
EOF
|
||||
|
||||
sysctl -p /etc/sysctl.d/90-tcp-bbr.conf >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Add virtual host
|
||||
add_vhost() {
|
||||
read input
|
||||
@ -1100,7 +1250,7 @@ rollback() {
|
||||
# Main dispatcher
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"status":{},"modes":{},"get_current_mode":{},"get_available_modes":{},"set_mode":{"mode":"str"},"preview_changes":{},"apply_mode":{},"confirm_mode":{},"rollback":{},"sniffer_config":{},"ap_config":{},"relay_config":{},"router_config":{},"update_settings":{"mode":"str"},"add_vhost":{"domain":"str","backend":"str","port":"int","ssl":"bool"},"generate_config":{"mode":"str"}}'
|
||||
echo '{"status":{},"modes":{},"get_current_mode":{},"get_available_modes":{},"set_mode":{"mode":"str"},"preview_changes":{},"apply_mode":{},"confirm_mode":{},"rollback":{},"sniffer_config":{},"ap_config":{},"relay_config":{},"router_config":{},"update_settings":{"mode":"str"},"generate_wireguard_keys":{},"apply_wireguard_config":{},"apply_mtu_clamping":{},"enable_tcp_bbr":{},"add_vhost":{"domain":"str","backend":"str","port":"int","ssl":"bool"},"generate_config":{"mode":"str"}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
@ -1146,6 +1296,30 @@ case "$1" in
|
||||
update_settings)
|
||||
update_settings
|
||||
;;
|
||||
generate_wireguard_keys)
|
||||
generate_wireguard_keys
|
||||
;;
|
||||
apply_wireguard_config)
|
||||
if apply_wireguard_config; then
|
||||
json_init; json_add_boolean "success" 1; json_dump
|
||||
else
|
||||
json_init; json_add_boolean "success" 0; json_add_string "error" "WireGuard deployment failed"; json_dump
|
||||
fi
|
||||
;;
|
||||
apply_mtu_clamping)
|
||||
if apply_mtu_clamping; then
|
||||
json_init; json_add_boolean "success" 1; json_dump
|
||||
else
|
||||
json_init; json_add_boolean "success" 0; json_add_string "error" "Unable to update firewall rule"; json_dump
|
||||
fi
|
||||
;;
|
||||
enable_tcp_bbr)
|
||||
if enable_tcp_bbr; then
|
||||
json_init; json_add_boolean "success" 1; json_dump
|
||||
else
|
||||
json_init; json_add_boolean "success" 0; json_add_string "error" "TCP BBR not applied"; json_dump
|
||||
fi
|
||||
;;
|
||||
add_vhost)
|
||||
add_vhost
|
||||
;;
|
||||
|
||||
59
secubox-tools/deploy-network-modes.sh
Executable file
59
secubox-tools/deploy-network-modes.sh
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy luci-app-network-modes to an OpenWrt router.
|
||||
# Usage: ./secubox-tools/deploy-network-modes.sh [root@192.168.1.1] [package.ipk]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
ROUTER_HOST="${1:-root@192.168.1.1}"
|
||||
PACKAGE_PATH="${2:-}"
|
||||
|
||||
if [[ -z "$PACKAGE_PATH" ]]; then
|
||||
echo "[1/4] Building luci-app-network-modes…" >&2
|
||||
(cd "$REPO_ROOT" && ./secubox-tools/local-build.sh build luci-app-network-modes)
|
||||
|
||||
echo "[2/4] Locating IPK artifact…" >&2
|
||||
mapfile -t PKGS < <(cd "$REPO_ROOT" && find bin -type f -name 'luci-app-network-modes_*_all.ipk' -print 2>/dev/null | sort)
|
||||
if [[ "${#PKGS[@]}" -eq 0 ]]; then
|
||||
echo "ERROR: No luci-app-network-modes IPK found under bin/. Build step may have failed." >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_PATH="${PKGS[-1]}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$PACKAGE_PATH" ]]; then
|
||||
echo "ERROR: Package file not found: $PACKAGE_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PACKAGE_PATH="$(cd "$(dirname "$PACKAGE_PATH")" && pwd)/$(basename "$PACKAGE_PATH")"
|
||||
PKG_NAME="$(basename "$PACKAGE_PATH")"
|
||||
|
||||
echo "[3/4] Uploading $PKG_NAME to $ROUTER_HOST:/tmp/" >&2
|
||||
scp "$PACKAGE_PATH" "${ROUTER_HOST}:/tmp/$PKG_NAME"
|
||||
|
||||
echo "[4/4] Installing on router and restarting services…" >&2
|
||||
ssh "$ROUTER_HOST" "sh -s" <<EOF
|
||||
set -e
|
||||
PKG="/tmp/$PKG_NAME"
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
echo "[router] Detected apk – ensuring package database…" >&2
|
||||
apk add --allow-untrusted "\$PKG"
|
||||
else
|
||||
echo "[router] Using opkg…" >&2
|
||||
opkg remove luci-app-network-modes --force-depends >/dev/null 2>&1 || true
|
||||
opkg install "\$PKG"
|
||||
fi
|
||||
|
||||
chmod 755 /usr/libexec/rpcd/luci.network-modes || true
|
||||
chmod 644 /www/luci-static/resources/network-modes/* 2>/dev/null || true
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null || true
|
||||
/etc/init.d/rpcd restart
|
||||
/etc/init.d/uhttpd restart
|
||||
|
||||
echo "[router] Deployment complete."
|
||||
EOF
|
||||
|
||||
echo "Deployment completed successfully."
|
||||
Loading…
Reference in New Issue
Block a user