🍓 Sync 2026-09-15 02:13:06

This commit is contained in:
github-actions[bot] committed 2026-09-15 02:13:06 +08:00
1 parent 0e5393594c
commit 1dd96ac2bb
146 files changed
+26541 -508

No files matched your search

@@ -0,0 +1,15 @@
config global 'settings'
option mode 'router'
config backup 'router_state'
option lan_proto 'static'
option lan_ipaddr '192.168.1.1'
option lan_netmask '255.255.255.0'
option lan_gateway ''
list lan_dns ''
option dhcp_ignore '0'
option wan_device ''
option wan_disabled '0'
option wan6_disabled '0'
@@ -0,0 +1,5 @@
#!/bin/sh
[ "$1" = "bound" ] || exit 0
echo "$ip" > /tmp/ap_switch_probed_ip
@@ -0,0 +1,13 @@
{
"admin/services/ap_switch": {
"title": "AP Switch",
"action": {
"type": "view",
"path": "ap-switch/ap-switch"
},
"order": 90,
"depends": {
"acl": [ "luci-app-ap-switch" ]
}
}
}
@@ -0,0 +1,19 @@
{
"luci-app-ap-switch": {
"description": "ACL for AP Switch",
"read": {
"uci": [ "ap-switch", "network", "dhcp" ],
"ubus": {
"luci.ap-switch": [ "get_status", "probe_ip" ],
"network.interface": [ "dump" ],
"network.device": [ "status" ]
}
},
"write": {
"uci": [ "ap-switch", "network", "dhcp" ],
"ubus": {
"luci.ap-switch": [ "set_mode" ]
}
}
}
}
@@ -0,0 +1,226 @@
'use strict';
import { cursor } from 'uci';
import { connect } from 'ubus';
import { readfile, unlink } from 'fs';
const UCI_CONFIG = 'ap-switch';
const PROBE_FILE = '/tmp/ap_switch_probed_ip';
const methods = {
get_status: {
call: function() {
let uci_cursor = cursor();
let mode = uci_cursor.get(UCI_CONFIG, 'settings', 'mode') || 'router';
let lan_proto = uci_cursor.get('network', 'lan', 'proto') || 'static';
let lan_ip = ''; // We will get the real-time IP
let ubus_conn = connect();
let lan_mac = '';
let wan_mac = '';
let wan_ip = '';
if (ubus_conn) {
// 1. Get real-time LAN IP and MAC
let lan_status = ubus_conn.call('network.interface.lan', 'status');
if (lan_status && type(lan_status['ipv4-address']) === 'array' && length(lan_status['ipv4-address']) > 0) {
lan_ip = lan_status['ipv4-address'][0].address;
}
let lan_dev = ubus_conn.call('network.device', 'status', { name: 'br-lan' });
lan_mac = lan_dev?.macaddr || '';
// 2. Get current WAN interface status and its physical device MAC
let wan_status = ubus_conn.call('network.interface.wan', 'status');
let wan_device_name = wan_status?.device || '';
if (wan_device_name) {
let wan_dev = ubus_conn.call('network.device', 'status', { name: wan_device_name });
wan_mac = wan_dev?.macaddr || '';
}
// 3. Get current WAN IP
if (wan_status && type(wan_status['ipv4-address']) === 'array' && length(wan_status['ipv4-address']) > 0) {
wan_ip = wan_status['ipv4-address'][0].address;
}
ubus_conn.disconnect();
}
let probed_ip = rtrim(readfile(PROBE_FILE) || '');
return {
mode: mode,
lan_proto: lan_proto,
lan_ip: lan_ip,
lan_mac: lan_mac,
wan_mac: wan_mac,
wan_ip: wan_ip,
probed_ip: probed_ip
};
}
},
probe_ip: {
call: function() {
unlink(PROBE_FILE);
// Run udhcpc on br-lan with a short timeout and our probe script
// -n: exit if no lease
// -q: quit after obtaining lease
// -t 3: send 3 discover packets
// -T 2: pause 2 seconds between packets
let rc = system('udhcpc -i br-lan -n -q -t 3 -T 2 -s /usr/share/luci-app-ap-switch/dhcp-probe.sh >/dev/null 2>&1');
let ip = rtrim(readfile(PROBE_FILE) || '');
return { success: (rc === 0), ip: ip };
}
},
set_mode: {
args: { mode: 'string' },
call: function(request) {
let target_mode = request.args?.mode;
if (target_mode !== 'router' && target_mode !== 'ap') {
return { error: 'Invalid mode' };
}
let uci_cursor = cursor();
let current_mode = uci_cursor.get(UCI_CONFIG, 'settings', 'mode') || 'router';
if (target_mode === current_mode) {
return { result: 'Already in target mode' };
}
if (target_mode === 'ap') {
// --- Switch to AP Mode (Full Lossless Snapshot) ---
// 1. Identify WAN device
let wan_device = uci_cursor.get('network', 'wan', 'device');
if (!wan_device) {
wan_device = uci_cursor.get('network', 'wan', 'ifname');
}
// 2. Full Snapshot Backup of Router Mode state
let lan_proto = uci_cursor.get('network', 'lan', 'proto') || 'static';
let lan_ipaddr = uci_cursor.get('network', 'lan', 'ipaddr') || '';
let lan_netmask = uci_cursor.get('network', 'lan', 'netmask') || '';
let lan_gateway = uci_cursor.get('network', 'lan', 'gateway') || '';
let lan_dns = uci_cursor.get('network', 'lan', 'dns') || [];
let dhcp_ignore = uci_cursor.get('dhcp', 'lan', 'ignore') || '0';
let wan_disabled = uci_cursor.get('network', 'wan', 'disabled') || '0';
let wan6_disabled = uci_cursor.get('network', 'wan6', 'disabled') || '0';
uci_cursor.set(UCI_CONFIG, 'router_state', 'lan_proto', lan_proto);
uci_cursor.set(UCI_CONFIG, 'router_state', 'lan_ipaddr', lan_ipaddr);
uci_cursor.set(UCI_CONFIG, 'router_state', 'lan_netmask', lan_netmask);
uci_cursor.set(UCI_CONFIG, 'router_state', 'lan_gateway', lan_gateway);
uci_cursor.set(UCI_CONFIG, 'router_state', 'lan_dns', lan_dns);
uci_cursor.set(UCI_CONFIG, 'router_state', 'dhcp_ignore', dhcp_ignore);
uci_cursor.set(UCI_CONFIG, 'router_state', 'wan_device', wan_device || '');
uci_cursor.set(UCI_CONFIG, 'router_state', 'wan_disabled', wan_disabled);
uci_cursor.set(UCI_CONFIG, 'router_state', 'wan6_disabled', wan6_disabled);
uci_cursor.set(UCI_CONFIG, 'settings', 'mode', 'ap');
// 3. Bridge WAN device to LAN bridge
if (wan_device) {
uci_cursor.foreach('network', 'device', (s) => {
if (s.name === 'br-lan') {
let ports = s.ports || [];
if (type(ports) === 'string') ports = [ports];
let exists = false;
for (let p in ports) if (p === wan_device) exists = true;
if (!exists) {
push(ports, wan_device);
uci_cursor.set('network', s['.name'], 'ports', ports);
}
}
});
}
// 4. Set LAN to DHCP client
uci_cursor.set('network', 'lan', 'proto', 'dhcp');
uci_cursor.delete('network', 'lan', 'ipaddr');
uci_cursor.delete('network', 'lan', 'netmask');
uci_cursor.delete('network', 'lan', 'gateway');
uci_cursor.delete('network', 'lan', 'dns');
// 5. Disable WAN interface
uci_cursor.set('network', 'wan', 'disabled', '1');
uci_cursor.set('network', 'wan6', 'disabled', '1');
// 6. Disable DHCP Server
uci_cursor.set('dhcp', 'lan', 'ignore', '1');
} else {
// --- Switch to Router Mode (Lossless State Restoration) ---
// 1. Read Snapshot
let wan_device = uci_cursor.get(UCI_CONFIG, 'router_state', 'wan_device');
let lan_proto = uci_cursor.get(UCI_CONFIG, 'router_state', 'lan_proto') || 'static';
let lan_ipaddr = uci_cursor.get(UCI_CONFIG, 'router_state', 'lan_ipaddr') || '192.168.1.1';
let lan_netmask = uci_cursor.get(UCI_CONFIG, 'router_state', 'lan_netmask') || '';
let lan_gateway = uci_cursor.get(UCI_CONFIG, 'router_state', 'lan_gateway') || '';
let lan_dns = uci_cursor.get(UCI_CONFIG, 'router_state', 'lan_dns') || [];
let dhcp_ignore = uci_cursor.get(UCI_CONFIG, 'router_state', 'dhcp_ignore') || '0';
let wan_disabled = uci_cursor.get(UCI_CONFIG, 'router_state', 'wan_disabled') || '0';
let wan6_disabled = uci_cursor.get(UCI_CONFIG, 'router_state', 'wan6_disabled') || '0';
uci_cursor.set(UCI_CONFIG, 'settings', 'mode', 'router');
// 2. Remove WAN device from LAN bridge
if (wan_device) {
uci_cursor.foreach('network', 'device', (s) => {
if (s.name === 'br-lan') {
let ports = s.ports || [];
if (type(ports) === 'string') ports = [ports];
let new_ports = [];
for (let p in ports) if (p !== wan_device) push(new_ports, p);
uci_cursor.set('network', s['.name'], 'ports', new_ports);
}
});
}
// 3. Restore LAN settings accurately without hardcoding
uci_cursor.set('network', 'lan', 'proto', lan_proto);
if (lan_ipaddr) uci_cursor.set('network', 'lan', 'ipaddr', lan_ipaddr);
if (lan_netmask) uci_cursor.set('network', 'lan', 'netmask', lan_netmask);
if (lan_gateway) uci_cursor.set('network', 'lan', 'gateway', lan_gateway);
if (type(lan_dns) === 'array' && length(lan_dns) > 0) {
uci_cursor.set('network', 'lan', 'dns', lan_dns);
} else if (type(lan_dns) === 'string' && lan_dns !== '') {
uci_cursor.set('network', 'lan', 'dns', lan_dns);
}
// 4. Restore WAN / WAN6 states
if (wan_disabled === '1') {
uci_cursor.set('network', 'wan', 'disabled', '1');
} else {
uci_cursor.delete('network', 'wan', 'disabled');
}
if (wan6_disabled === '1') {
uci_cursor.set('network', 'wan6', 'disabled', '1');
} else {
uci_cursor.delete('network', 'wan6', 'disabled');
}
// 5. Restore DHCP Server state
if (dhcp_ignore === '1') {
uci_cursor.set('dhcp', 'lan', 'ignore', '1');
} else {
uci_cursor.delete('dhcp', 'lan', 'ignore');
}
}
uci_cursor.commit('ap-switch');
uci_cursor.commit('network');
uci_cursor.commit('dhcp');
// Apply changes in background
system('/etc/init.d/network restart &');
system('/etc/init.d/dnsmasq restart &');
return { success: true, result: 'success' };
}
}
};
return { 'luci.ap-switch': methods };