feat: add simplified windows x86 support
This commit is contained in:
+82
-38
@@ -1,9 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const { execSync, spawnSync, spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const log = require('./logger');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const log = require('./logger');
|
||||
const { IS_WINDOWS } = require('./platform-paths');
|
||||
|
||||
const AP_SSID_PREFIX = 'ClawBox-';
|
||||
const AP_IP = '10.42.0.1';
|
||||
@@ -68,8 +69,13 @@ function _firstScanWiredIfaceWithCarrier() {
|
||||
* 返回当前可用于「有线 ping / 路由」的网卡名。
|
||||
* 优先级:CLAWD_ETH_IFACE → 存在 end0 则只用 end0 → 否则扫描 sysfs。
|
||||
*/
|
||||
function getWiredIfaceWithCarrier() {
|
||||
const explicit = process.env.CLAWD_ETH_IFACE;
|
||||
function getWiredIfaceWithCarrier() {
|
||||
if (IS_WINDOWS) {
|
||||
const entry = _localNetworkEntries()[0];
|
||||
return entry ? entry.iface : null;
|
||||
}
|
||||
|
||||
const explicit = process.env.CLAWD_ETH_IFACE;
|
||||
if (explicit) {
|
||||
return _netIfaceExists(explicit) && _sysfsCarrierUp(explicit) ? explicit : null;
|
||||
}
|
||||
@@ -79,16 +85,18 @@ function getWiredIfaceWithCarrier() {
|
||||
return _firstScanWiredIfaceWithCarrier();
|
||||
}
|
||||
|
||||
function hasWiredCarrier() {
|
||||
return getWiredIfaceWithCarrier() !== null;
|
||||
}
|
||||
function hasWiredCarrier() {
|
||||
if (IS_WINDOWS) return _localNetworkEntries().length > 0;
|
||||
return getWiredIfaceWithCarrier() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* LAN 面板灯:只反映 RJ45 对应口,与 `cat /sys/class/net/end0/carrier 2>/dev/null` 同源(仅读 carrier)。
|
||||
* 若配置的接口在 sysfs 中不存在(常见为开发机无 end0),则退回与 hasWiredCarrier() 一致,避免灯永远灭。
|
||||
*/
|
||||
function hasLanCableCarrier() {
|
||||
const iface = _ethIfaceEnvOrDefault();
|
||||
function hasLanCableCarrier() {
|
||||
if (IS_WINDOWS) return hasWiredCarrier();
|
||||
const iface = _ethIfaceEnvOrDefault();
|
||||
if (_netIfaceExists(iface)) return _sysfsCarrierUp(iface);
|
||||
return hasWiredCarrier();
|
||||
}
|
||||
@@ -114,16 +122,19 @@ function _tryPingWiredInternet() {
|
||||
/**
|
||||
* 仅经有线口 ping 公网(不依赖默认路由)。
|
||||
*/
|
||||
function hasWiredInternetProbe() {
|
||||
return _tryPingWiredInternet();
|
||||
}
|
||||
function hasWiredInternetProbe() {
|
||||
if (IS_WINDOWS) return hasInternet();
|
||||
return _tryPingWiredInternet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否有真实互联网连接。
|
||||
* 注意:NetworkManager 的 limited/local 可能只是 AP 本地网络或 captive 状态,不能当公网可用。
|
||||
*/
|
||||
function hasInternet() {
|
||||
const wifiSta = isWifiStaConnected();
|
||||
function hasInternet() {
|
||||
if (IS_WINDOWS) return _localNetworkEntries().length > 0;
|
||||
|
||||
const wifiSta = isWifiStaConnected();
|
||||
const wired = getWiredIfaceWithCarrier();
|
||||
|
||||
// 物理层快检:无 WiFi STA 且无任何有线 carrier → 立即 false(nmcli 有缓存,不可信)
|
||||
@@ -143,8 +154,9 @@ function hasInternet() {
|
||||
* 获取默认 WiFi 接口名(wlan0 等)。
|
||||
* 必须 TYPE 精确为 wifi,不能用 grep wifi(会误匹配 wifi-p2p,导致选到 p2p-dev-wlan0,STA/热点均失败)。
|
||||
*/
|
||||
function getWifiIface() {
|
||||
if (AP_IFACE) return AP_IFACE;
|
||||
function getWifiIface() {
|
||||
if (IS_WINDOWS) return '';
|
||||
if (AP_IFACE) return AP_IFACE;
|
||||
try {
|
||||
const out = run('nmcli -t -f DEVICE,TYPE device');
|
||||
let fallback = '';
|
||||
@@ -173,8 +185,9 @@ function getWifiIface() {
|
||||
/**
|
||||
* 扫描周围 WiFi,返回 [{ ssid, signal, security }]
|
||||
*/
|
||||
function scanWifi() {
|
||||
const iface = getWifiIface();
|
||||
function scanWifi() {
|
||||
if (IS_WINDOWS) return [];
|
||||
const iface = getWifiIface();
|
||||
try {
|
||||
// 先触发一次扫描
|
||||
try { run(`nmcli device wifi rescan ifname ${iface}`); } catch (_) {}
|
||||
@@ -265,7 +278,11 @@ function nmcliAsync(args, timeoutMs = 60000) {
|
||||
* 必须异步:同步 spawnSync + execSync(sleep) 会卡住主线程,导致 systemd WatchdogSec 内收不到 WATCHDOG=1。
|
||||
* @returns {Promise<{ success: boolean, error?: string }>}
|
||||
*/
|
||||
async function connectWifi(ssid, password) {
|
||||
async function connectWifi(ssid, password) {
|
||||
if (IS_WINDOWS) {
|
||||
return { success: false, error: 'Windows/x86 clawd does not manage WiFi credentials' };
|
||||
}
|
||||
|
||||
cancelHotspotRadioRetry(`准备连接 WiFi: ${ssid}`);
|
||||
const iface = getWifiIface();
|
||||
log.info('network', `尝试连接 WiFi: ${ssid}(ifname=${iface})`);
|
||||
@@ -521,7 +538,11 @@ function _activateHotspot(ssid, iface, timeoutMs = 8000) {
|
||||
/**
|
||||
* 启动 WiFi AP 热点
|
||||
*/
|
||||
function startAP(clawId) {
|
||||
function startAP(clawId) {
|
||||
if (IS_WINDOWS) {
|
||||
throw new Error('Windows/x86 clawd does not support AP provisioning');
|
||||
}
|
||||
|
||||
const iface = getWifiIface();
|
||||
const ssid = `${AP_SSID_PREFIX}${clawId || 'Setup'}`;
|
||||
|
||||
@@ -608,7 +629,9 @@ function _parseNmcliTerseLine(line) {
|
||||
/**
|
||||
* 列出已保存的 WiFi STA 连接(排除自身热点),按 autoconnect-priority 从高到低排序。
|
||||
*/
|
||||
function listSavedWifiConnections() {
|
||||
function listSavedWifiConnections() {
|
||||
if (IS_WINDOWS) return [];
|
||||
|
||||
const profiles = [];
|
||||
try {
|
||||
const out = run('nmcli -t -f NAME,UUID,TYPE,AUTOCONNECT,AUTOCONNECT-PRIORITY connection show');
|
||||
@@ -663,7 +686,11 @@ async function _ensureActiveWifiAutoconnect() {
|
||||
* 主动让 NetworkManager 尝试已保存 WiFi。
|
||||
* clawd 只做调度;真正的认证、DHCP、重连细节仍交给 NM。
|
||||
*/
|
||||
async function connectSavedWifiConnections() {
|
||||
async function connectSavedWifiConnections() {
|
||||
if (IS_WINDOWS) {
|
||||
return { success: false, error: 'Windows/x86 clawd does not manage saved WiFi profiles' };
|
||||
}
|
||||
|
||||
cancelHotspotRadioRetry('准备连接已保存 WiFi');
|
||||
const iface = getWifiIface();
|
||||
const profiles = listSavedWifiConnections();
|
||||
@@ -701,7 +728,9 @@ async function connectSavedWifiConnections() {
|
||||
* 是否已以 STA 连上某 WiFi(排除自身热点)。
|
||||
* 不用 device 列表按 `:` 拆字段(连接名含冒号会错;state 含 connecting 勿误匹配 connected)。
|
||||
*/
|
||||
function isWifiStaConnected() {
|
||||
function isWifiStaConnected() {
|
||||
if (IS_WINDOWS) return false;
|
||||
|
||||
const iface = getWifiIface();
|
||||
let state;
|
||||
let conn;
|
||||
@@ -718,22 +747,37 @@ function isWifiStaConnected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function _ifaceNetworkType(name) {
|
||||
function _ifaceNetworkType(name) {
|
||||
if (IS_WINDOWS) {
|
||||
return /wi-?fi|wlan|wireless/i.test(name) ? 'wifi' : 'lan';
|
||||
}
|
||||
|
||||
const wifi = getWifiIface();
|
||||
if (name === wifi || name.startsWith('wl')) return 'wifi';
|
||||
if (name === DEFAULT_ETH_IFACE || name.startsWith('en') || name.startsWith('eth')) return 'lan';
|
||||
return null;
|
||||
}
|
||||
|
||||
function _localNetworkEntries() {
|
||||
const ifaces = os.networkInterfaces();
|
||||
const entries = [];
|
||||
for (const [name, addrs] of Object.entries(ifaces)) {
|
||||
if (!addrs) continue;
|
||||
const type = _ifaceNetworkType(name);
|
||||
if (!type) continue;
|
||||
for (const addr of addrs) {
|
||||
if (addr.family !== 'IPv4' || addr.internal) continue;
|
||||
if (name === DEFAULT_ETH_IFACE || name.startsWith('en') || name.startsWith('eth')) return 'lan';
|
||||
return null;
|
||||
}
|
||||
|
||||
function _isWindowsExcludedIface(name) {
|
||||
return /tailscale|zerotier|vethernet|virtual|vmware|virtualbox|docker|wsl|hyper-v|loopback|npcap|meta/i.test(name);
|
||||
}
|
||||
|
||||
function _isWindowsExcludedAddress(ip) {
|
||||
return /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(ip)
|
||||
|| /^198\.(18|19)\./.test(ip);
|
||||
}
|
||||
|
||||
function _localNetworkEntries() {
|
||||
const ifaces = os.networkInterfaces();
|
||||
const entries = [];
|
||||
for (const [name, addrs] of Object.entries(ifaces)) {
|
||||
if (!addrs) continue;
|
||||
if (IS_WINDOWS && _isWindowsExcludedIface(name)) continue;
|
||||
const type = _ifaceNetworkType(name);
|
||||
if (!type) continue;
|
||||
for (const addr of addrs) {
|
||||
if (addr.family !== 'IPv4' || addr.internal) continue;
|
||||
if (IS_WINDOWS && _isWindowsExcludedAddress(addr.address)) continue;
|
||||
// clawd-hotspot 的 AP 管理网段只用于配网,不上报为 BOX 可访问地址。
|
||||
if (addr.address.startsWith('10.42.')) continue;
|
||||
entries.push({ ip: addr.address, type, iface: name });
|
||||
|
||||
Reference in New Issue
Block a user