feat: add simplified windows x86 support

This commit is contained in:
yankun
2026-06-27 18:33:59 +08:00
parent 7de0777748
commit 67c995db83
12 changed files with 400 additions and 84 deletions
+13 -8
View File
@@ -11,11 +11,12 @@ const { ClawClient } = require('../lib/client');
const config = require('../lib/config');
const log = require('../lib/logger');
const { pollSms } = require('../drivers/sim/sms-reader');
const IS_WINDOWS = process.platform === 'win32';
// 每次启动同步 3588s demo 到 /usr/bin/demoidempotent,失败不影响主流程)
const demoBin = path.join(__dirname, "..", "lib/resource/3588s/demo");
const demoDst = "/usr/bin/demo";
if (fs.existsSync(demoBin)) {
if (!IS_WINDOWS && fs.existsSync(demoBin)) {
exec(`install -m 0755 "${demoBin}" "${demoDst}"`, (err) => {
if (err) log.warn("clawd", `demo sync failed: ${err.message}`);
else log.info("clawd", "demo synced to /usr/bin/demo");
@@ -24,14 +25,16 @@ if (fs.existsSync(demoBin)) {
// 每次启动绑定 Quectel 串口驱动(失败不影响主流程)
const bindScript = path.join(__dirname, '..', 'tools', 'bind-quectel-serial.sh');
exec(`bash "${bindScript}"`, (err, stdout, stderr) => {
if (err) log.warn('clawd', `bind-quectel-serial: ${stderr || err.message}`);
else log.info('clawd', `bind-quectel-serial: ok`);
});
if (!IS_WINDOWS) {
exec(`bash "${bindScript}"`, (err, stdout, stderr) => {
if (err) log.warn('clawd', `bind-quectel-serial: ${stderr || err.message}`);
else log.info('clawd', `bind-quectel-serial: ok`);
});
}
// 同步 Samba 共享密码(idempotent,失败不影响主流程)
const cfg = config.load();
if (cfg.share_key) {
if (!IS_WINDOWS && cfg.share_key) {
const shareKey = cfg.share_key.replace(/'/g, "'\\''");
exec(`printf '%s\\n%s\\n' '${shareKey}' '${shareKey}' | smbpasswd -a sts -s 2>/dev/null`, (err) => {
if (err) log.warn('clawd', `smbpasswd sync failed (samba not installed?): ${err.message}`);
@@ -58,8 +61,10 @@ async function pollSmsSafe() {
const client = new ClawClient();
client.start();
pollSmsSafe();
smsTimer = setInterval(pollSmsSafe, 15_000);
if (!IS_WINDOWS) {
pollSmsSafe();
smsTimer = setInterval(pollSmsSafe, 15_000);
}
let stopping = false;
+24 -2
View File
@@ -19,6 +19,7 @@ const { hasInternet, hasWiredInternetProbe, getLocalIps, getLocalNetworks } = re
const { applyFullProviderFromVps, removeProviderByName, refreshModelsIfChanged, isFullProvider } = require('./openclaw-provider');
const sysCall = require('./sys-call');
const led = require('./led');
const { IS_WINDOWS } = require('./platform-paths');
const MAX_BACKOFF_MS = 60_000;
/** 连续若干轮 ping 后仍无 pong 才判定死链(单轮易因调度/弱网误判) */
@@ -125,7 +126,7 @@ class ClawClient {
led.lan.start();
// 蓝牙状态监控(bluetoothctl);默认不启用,见 btMonitorEnabled()
if (btMonitorEnabled()) {
if (!IS_WINDOWS && btMonitorEnabled()) {
this._btMonitor = new BtMonitor();
this._btMonitor.start();
} else {
@@ -317,7 +318,8 @@ class ClawClient {
this._wsFailCount++;
log.warn('clawd', `连接断开 (${code}),失败次数=${this._wsFailCount}${this._backoff / 1000}s 后重连...`);
if (this._hasEverConnected && this._wsFailCount >= 3) {
led.display.showAP();
if (IS_WINDOWS) led.display.showErr0();
else led.display.showAP();
}
if (this._certTimeError) {
// NTP 未同步:固定 5s 重试,等时钟校正
@@ -388,6 +390,16 @@ class ClawClient {
local_networks: getLocalNetworks(),
external_ip: this._externalIp ?? null,
location: this._location ?? null,
platform: process.platform,
arch: process.arch,
device_model: IS_WINDOWS ? 'windows-x86' : null,
capabilities: IS_WINDOWS ? {
wifi_provisioning: false,
display: 'terminal',
dashboard_proxy: true,
terminal_proxy: false,
ap_mode: false,
} : undefined,
...this._dashInfo,
};
this._send(msg);
@@ -515,6 +527,11 @@ class ClawClient {
// ── Hostname ─────────────────────────────────────────────────────────────────
_setHostname(clawId) {
if (IS_WINDOWS) {
log.info('clawd', `Windows/x86 mode: skip hostname change for claw-${clawId}`);
return;
}
const hostname = `claw-${clawId}`;
// 运行时 hostname(无需文件权限)
@@ -683,6 +700,11 @@ class ClawClient {
}
async _handleUpgrade(msg) {
if (IS_WINDOWS) {
this._sendUpgradeProgress(0, 'failed', true, 'Windows/x86 self-upgrade is not implemented yet');
return;
}
const targetVersion = msg.version;
const installDir = path.dirname(__dirname); // /opt/clawd 或同等安装目录
const scriptPath = path.join(installDir, 'tools', 'update-clawd.sh');
+2 -5
View File
@@ -2,13 +2,10 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const { getConfigDir } = require('./platform-paths');
// 生产环境用 /etc/clawd/,开发环境用 ~/.clawd/
const CONFIG_DIR = process.env.CLAWD_CONFIG_DIR
|| (process.getuid && process.getuid() === 0
? '/etc/clawd'
: path.join(os.homedir(), '.clawd'));
const CONFIG_DIR = getConfigDir();
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
+57 -1
View File
@@ -4,6 +4,8 @@ const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { execSync } = require('child_process');
const os = require('os');
const { getPersistentFile } = require('./platform-paths');
/**
* 生成硬件唯一指纹作为 box_id。
@@ -19,7 +21,59 @@ const { execSync } = require('child_process');
* 有线 MAC 适用于嵌入式设备(网卡焊在主板,由固件烧录,不会更换)。
*/
const PERSIST_FILE = '/etc/clawd/.box_id';
const PERSIST_FILE = getPersistentFile('.box_id');
function readCommand(cmd, opts = {}) {
try {
return execSync(cmd, {
timeout: opts.timeout || 5000,
encoding: 'utf8',
windowsHide: true,
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
} catch (_) {
return null;
}
}
function getWindowsMachineGuid() {
const out = readCommand('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid');
const m = out && out.match(/MachineGuid\s+REG_SZ\s+([^\r\n]+)/i);
return m ? m[1].trim().toLowerCase() : null;
}
function getWindowsBiosUuid() {
const ps = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "(Get-CimInstance Win32_ComputerSystemProduct).UUID"';
const uuid = readCommand(ps);
if (!uuid) return null;
const clean = uuid.trim().toLowerCase();
if (clean === '00000000-0000-0000-0000-000000000000') return null;
if (clean === 'ffffffff-ffff-ffff-ffff-ffffffffffff') return null;
return clean.replace(/-/g, '');
}
function getWindowsMac() {
const ifaces = os.networkInterfaces();
const macs = [];
for (const addrs of Object.values(ifaces)) {
for (const addr of addrs || []) {
const mac = String(addr.mac || '').replace(/:/g, '').toLowerCase();
if (mac && mac.length === 12 && mac !== '000000000000') macs.push(mac);
}
}
return macs.sort()[0] || null;
}
function getWindowsBoxId() {
const machineGuid = getWindowsMachineGuid();
const biosUuid = getWindowsBiosUuid();
const mac = getWindowsMac();
if (machineGuid || biosUuid || mac) {
const raw = [machineGuid || '', biosUuid || '', mac || ''].join(':');
return crypto.createHash('sha256').update(raw).digest('hex').slice(0, 32);
}
return getPersistentUUID();
}
// ── 1. /etc/machine-id ───────────────────────────────────────────────────────
function getMachineId() {
@@ -119,6 +173,8 @@ function getPersistentUUID() {
// ── 主函数 ────────────────────────────────────────────────────────────────────
function getBoxId() {
if (process.platform === 'win32') return getWindowsBoxId();
const machineId = getMachineId();
const cpuSerial = getCpuSerial();
const ethMac = getEthMac();
+40 -22
View File
@@ -1,16 +1,16 @@
'use strict';
const { execSync, spawn } = require('child_process');
const { execSync, execFileSync, spawn } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const https = require('https');
const log = require('./logger');
const { Watchdog } = require('./watchdog');
const { IS_WINDOWS, getConfigDir, getOpenClawConfigCandidates } = require('./platform-paths');
const CONFIG_DIR = process.env.CLAWD_CONFIG_DIR
|| (process.getuid && process.getuid() === 0 ? '/etc/clawd' : path.join(os.homedir(), '.clawd'));
const FRPC_BIN = path.join(CONFIG_DIR, 'frpc');
const CONFIG_DIR = getConfigDir();
const FRPC_BIN = path.join(CONFIG_DIR, IS_WINDOWS ? 'frpc.exe' : 'frpc');
const FRPC_CONFIG = path.join(CONFIG_DIR, 'frpc.toml');
const FRP_VERSION = '0.62.0';
@@ -26,9 +26,7 @@ function findTtydBin() {
/** openclaw 持久化配置(JSON),结构与原 YAML 解析结果一致。 */
const OPENCLAW_JSON_CANDIDATES = [
path.join(os.homedir(), '.openclaw', 'openclaw.json'),
'/home/sts/.openclaw/openclaw.json',
'/root/.openclaw/openclaw.json',
...getOpenClawConfigCandidates(),
];
/**
@@ -77,23 +75,37 @@ async function downloadFrpc() {
};
const frpArch = archMap[arch] || 'amd64';
const filename = `frp_${FRP_VERSION}_${platform}_${frpArch}.tar.gz`;
const ext = IS_WINDOWS ? 'zip' : 'tar.gz';
const filename = `frp_${FRP_VERSION}_${platform}_${frpArch}.${ext}`;
const releaseBase = 'https://git.cutos.ai/claw-daemon/fatedier/releases/download';
const attachmentMap = {
'linux/arm64': 'https://git.cutos.ai/attachments/071748ed-955b-44c0-8dfb-38396b5dae6e',
};
const url = attachmentMap[`${platform}/${frpArch}`] || `${releaseBase}/v${FRP_VERSION}/${filename}`;
const tmpFile = `/tmp/${filename}`;
const tmpFile = path.join(os.tmpdir(), filename);
log.info('frpc', `下载 frpc ${FRP_VERSION} (${platform}/${frpArch})...`);
await downloadFile(url, tmpFile);
fs.mkdirSync(CONFIG_DIR, { recursive: true });
execSync(`tar -xzf ${tmpFile} -C /tmp && cp /tmp/frp_${FRP_VERSION}_${platform}_${frpArch}/frpc ${FRPC_BIN}`, {
stdio: 'inherit'
});
fs.chmodSync(FRPC_BIN, 0o755);
const packageDir = `frp_${FRP_VERSION}_${platform}_${frpArch}`;
if (IS_WINDOWS) {
const extractDir = path.join(os.tmpdir(), `clawd-frp-${FRP_VERSION}-${Date.now()}`);
fs.mkdirSync(extractDir, { recursive: true });
execFileSync('powershell.exe', [
'-NoProfile',
'-ExecutionPolicy', 'Bypass',
'-Command',
`Expand-Archive -Force -LiteralPath '${tmpFile.replace(/'/g, "''")}' -DestinationPath '${extractDir.replace(/'/g, "''")}'`,
], { stdio: 'inherit', windowsHide: true });
fs.copyFileSync(path.join(extractDir, packageDir, 'frpc.exe'), FRPC_BIN);
} else {
execSync(`tar -xzf ${tmpFile} -C /tmp && cp /tmp/${packageDir}/frpc ${FRPC_BIN}`, {
stdio: 'inherit'
});
fs.chmodSync(FRPC_BIN, 0o755);
}
log.info('frpc', `frpc 已安装到 ${FRPC_BIN}`);
}
@@ -103,6 +115,11 @@ async function downloadFrpc() {
* ttyd 绑定 127.0.0.1:7681,供 frpc 代理。
*/
async function startTtyd() {
if (IS_WINDOWS) {
log.info('ttyd', 'Windows/x86 mode: terminal proxy is disabled');
return false;
}
const ttydBin = findTtydBin();
if (!ttydBin) {
log.warn('ttyd', '未找到 ttyd,请重新运行 install.sh');
@@ -150,13 +167,20 @@ function downloadFile(url, dest) {
function writeFrpcConfig(clawId, frpConfig, sshSecretKey) {
const { auth_token, dashboard_local_port = 18789 } = frpConfig;
const ttyRemotePort = 10000 + Number(clawId);
const stcpBlock = sshSecretKey ? `
const stcpBlock = (!IS_WINDOWS && sshSecretKey) ? `
[[proxies]]
name = "ssh-${clawId}-secret"
type = "stcp"
secretKey = "${sshSecretKey}"
localPort = 22
` : '';
const ttyBlock = IS_WINDOWS ? '' : `
[[proxies]]
name = "tty-${clawId}"
type = "tcp"
localPort = ${TTYD_PORT}
remotePort = ${ttyRemotePort}
`;
const toml = `# 由 clawd 自动生成,请勿手动修改
serverAddr = "frp.claw.cutos.ai"
serverPort = 443
@@ -173,16 +197,10 @@ name = "dashboard-${clawId}"
type = "http"
localPort = ${dashboard_local_port}
subdomain = "${clawId}"
[[proxies]]
name = "tty-${clawId}"
type = "tcp"
localPort = ${TTYD_PORT}
remotePort = ${ttyRemotePort}
${stcpBlock}`;
${ttyBlock}${stcpBlock}`;
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(FRPC_CONFIG, toml, 'utf8');
log.info('frpc', `frpc.toml 已写入: dashboard subdomain=${clawId}, tty tcp-port=${ttyRemotePort}${sshSecretKey ? ', ssh stcp=enabled' : ''}`);
log.info('frpc', `frpc.toml 已写入: dashboard subdomain=${clawId}${IS_WINDOWS ? '' : `, tty tcp-port=${ttyRemotePort}`}${stcpBlock ? ', ssh stcp=enabled' : ''}`);
}
/**
+5
View File
@@ -4,6 +4,11 @@ const log = require('./logger');
const { isRK3566, isRK3588, readDeviceModel } = require('./led/detect');
function loadImpl() {
if (process.platform === 'win32') {
log.info('led', 'LED/VFD backend -> terminal (Windows/x86)');
return require('./led/terminal');
}
const forced = String(process.env.CLAWD_LED_IMPL || '').trim().toLowerCase();
const model = readDeviceModel();
+85
View File
@@ -0,0 +1,85 @@
'use strict';
const log = require('../logger');
function box(lines) {
const width = Math.max(...lines.map((line) => line.length), 28);
const top = `+${'-'.repeat(width + 2)}+`;
const body = lines.map((line) => `| ${line.padEnd(width)} |`);
return ['', top, ...body, top, ''].join('\n');
}
class BasicLed {
constructor(name) {
this.name = name;
this._current = null;
}
on() { this._set('on'); }
off() { this._set('off'); }
blink() { this._set('blink'); }
destroy() { this._set('off'); }
_set(next) {
if (this._current === next) return;
this._current = next;
log.debug('led', `[terminal] ${this.name} ${next}`);
}
}
class StatusLed {
setSetup() { log.info('status', 'SETUP / waiting for activation'); }
setApps() { log.info('status', 'APPS / activated'); }
off() { log.debug('status', 'off'); }
}
class Display {
showAP() {
log.info('display', box([
'Claw Daemon',
'Waiting for network',
'Windows/x86 has no AP mode',
]));
}
showConn() {
log.info('display', box([
'Claw Daemon',
'Connecting to claw cloud...',
]));
}
showErr0() {
log.info('display', box([
'Claw Daemon',
'Connection error',
]));
}
showTime() {
log.info('display', box([
'Claw Daemon',
'Running on Windows/x86',
]));
}
showPin(pin) {
const s = String(pin || '').trim();
log.info('display', box([
'Claw Daemon Activation',
`PIN: ${s}`,
'Enter this PIN in the web console',
]));
}
}
class LanLed {
start() { log.debug('led', '[terminal] LAN monitor ignored'); }
stop() { log.debug('led', '[terminal] LAN monitor stopped'); }
}
const led = new BasicLed('network');
led.bt = new BasicLed('bt');
led.status = new StatusLed();
led.display = new Display();
led.lan = new LanLed();
module.exports = led;
+2 -3
View File
@@ -2,12 +2,11 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const { getConfigDir } = require('./platform-paths');
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
const CONFIG_DIR = process.env.CLAWD_CONFIG_DIR
|| (process.getuid && process.getuid() === 0 ? '/etc/clawd' : path.join(os.homedir(), '.clawd'));
const CONFIG_DIR = getConfigDir();
const LOG_DIR = process.env.CLAWD_LOG_DIR || path.join(CONFIG_DIR, 'logs');
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
+44
View File
@@ -4,6 +4,7 @@ const { execSync, spawnSync, spawn } = require('child_process');
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';
@@ -69,6 +70,11 @@ function _firstScanWiredIfaceWithCarrier() {
* 优先级:CLAWD_ETH_IFACE → 存在 end0 则只用 end0 → 否则扫描 sysfs。
*/
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;
@@ -80,6 +86,7 @@ function getWiredIfaceWithCarrier() {
}
function hasWiredCarrier() {
if (IS_WINDOWS) return _localNetworkEntries().length > 0;
return getWiredIfaceWithCarrier() !== null;
}
@@ -88,6 +95,7 @@ function hasWiredCarrier() {
* 若配置的接口在 sysfs 中不存在(常见为开发机无 end0),则退回与 hasWiredCarrier() 一致,避免灯永远灭。
*/
function hasLanCableCarrier() {
if (IS_WINDOWS) return hasWiredCarrier();
const iface = _ethIfaceEnvOrDefault();
if (_netIfaceExists(iface)) return _sysfsCarrierUp(iface);
return hasWiredCarrier();
@@ -115,6 +123,7 @@ function _tryPingWiredInternet() {
* 仅经有线口 ping 公网(不依赖默认路由)。
*/
function hasWiredInternetProbe() {
if (IS_WINDOWS) return hasInternet();
return _tryPingWiredInternet();
}
@@ -123,6 +132,8 @@ function hasWiredInternetProbe() {
* 注意:NetworkManager 的 limited/local 可能只是 AP 本地网络或 captive 状态,不能当公网可用。
*/
function hasInternet() {
if (IS_WINDOWS) return _localNetworkEntries().length > 0;
const wifiSta = isWifiStaConnected();
const wired = getWiredIfaceWithCarrier();
@@ -144,6 +155,7 @@ function hasInternet() {
* 必须 TYPE 精确为 wifi,不能用 grep wifi(会误匹配 wifi-p2p,导致选到 p2p-dev-wlan0STA/热点均失败)。
*/
function getWifiIface() {
if (IS_WINDOWS) return '';
if (AP_IFACE) return AP_IFACE;
try {
const out = run('nmcli -t -f DEVICE,TYPE device');
@@ -174,6 +186,7 @@ function getWifiIface() {
* 扫描周围 WiFi,返回 [{ ssid, signal, security }]
*/
function scanWifi() {
if (IS_WINDOWS) return [];
const iface = getWifiIface();
try {
// 先触发一次扫描
@@ -266,6 +279,10 @@ function nmcliAsync(args, timeoutMs = 60000) {
* @returns {Promise<{ success: boolean, error?: string }>}
*/
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}`);
@@ -522,6 +539,10 @@ function _activateHotspot(ssid, iface, timeoutMs = 8000) {
* 启动 WiFi AP 热点
*/
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'}`;
@@ -609,6 +630,8 @@ function _parseNmcliTerseLine(line) {
* 列出已保存的 WiFi STA 连接(排除自身热点),按 autoconnect-priority 从高到低排序。
*/
function listSavedWifiConnections() {
if (IS_WINDOWS) return [];
const profiles = [];
try {
const out = run('nmcli -t -f NAME,UUID,TYPE,AUTOCONNECT,AUTOCONNECT-PRIORITY connection show');
@@ -664,6 +687,10 @@ async function _ensureActiveWifiAutoconnect() {
* clawd 只做调度;真正的认证、DHCP、重连细节仍交给 NM。
*/
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();
@@ -702,6 +729,8 @@ async function connectSavedWifiConnections() {
* 不用 device 列表按 `:` 拆字段(连接名含冒号会错;state 含 connecting 勿误匹配 connected)。
*/
function isWifiStaConnected() {
if (IS_WINDOWS) return false;
const iface = getWifiIface();
let state;
let conn;
@@ -719,21 +748,36 @@ function isWifiStaConnected() {
}
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 _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 });
+14
View File
@@ -8,6 +8,7 @@ const crypto = require('crypto');
const { exec } = require('child_process');
const log = require('./logger');
const { resolveOpenclawConfigFile } = require('./frpc');
const { IS_WINDOWS } = require('./platform-paths');
const DEFAULT_BASE_URL = 'https://api.cutos.ai/v1';
const FETCH_TIMEOUT_MS = 10_000;
@@ -90,6 +91,19 @@ function writeJsonFile(filePath, obj) {
* 使用异步 exec,不阻塞 Node.js 事件循环,避免干扰 LED / VFD 等后续操作。
*/
function restartGateway() {
if (IS_WINDOWS) {
exec('taskkill /IM openclaw-gateway.exe /F', (err) => {
if (err && err.code !== 128 && err.code !== 1) {
log.warn('openclaw-provider', `restartGateway: ${err.message}`);
} else if (!err) {
log.info('openclaw-provider', 'openclaw-gateway.exe stopped; waiting for supervisor to restart it');
} else {
log.info('openclaw-provider', 'openclaw-gateway.exe is not running');
}
});
return;
}
exec('pkill -9 -x openclaw-gateway', (err) => {
if (err && err.code !== 1) {
log.warn('openclaw-provider', `restartGateway: ${err.message}`);
+47
View File
@@ -0,0 +1,47 @@
'use strict';
const os = require('os');
const path = require('path');
const IS_WINDOWS = process.platform === 'win32';
function windowsProgramData() {
return process.env.PROGRAMDATA || 'C:\\ProgramData';
}
function getConfigDir() {
if (process.env.CLAWD_CONFIG_DIR) return process.env.CLAWD_CONFIG_DIR;
if (IS_WINDOWS) return path.join(windowsProgramData(), 'OpenClaw', 'clawd');
return process.getuid && process.getuid() === 0
? '/etc/clawd'
: path.join(os.homedir(), '.clawd');
}
function getPersistentFile(name) {
return path.join(getConfigDir(), name);
}
function getOpenClawConfigCandidates() {
if (IS_WINDOWS) {
const candidates = [
path.join(os.homedir(), '.openclaw', 'openclaw.json'),
path.join(windowsProgramData(), 'OpenClaw', 'openclaw.json'),
path.join(windowsProgramData(), 'OpenClaw', 'config', 'openclaw.json'),
];
if (process.env.OPENCLAW_CONFIG) candidates.unshift(process.env.OPENCLAW_CONFIG);
return candidates;
}
return [
path.join(os.homedir(), '.openclaw', 'openclaw.json'),
'/home/sts/.openclaw/openclaw.json',
'/root/.openclaw/openclaw.json',
];
}
module.exports = {
IS_WINDOWS,
getConfigDir,
getPersistentFile,
getOpenClawConfigCandidates,
};
+24
View File
@@ -6,6 +6,7 @@ const { hasInternet, hasWiredInternetProbe, hasSavedWifiConnection, connectSaved
const { DnsHijack } = require('./dns-hijack');
const { CaptiveServer } = require('./captive-server');
const led = require('./led');
const { IS_WINDOWS } = require('./platform-paths');
const MONITOR_INTERVAL_MS = 15_000;
const WIFI_RECONNECT_MAX_ROUNDS = 3;
@@ -40,6 +41,16 @@ class ProvisionManager extends EventEmitter {
isApMode() { return this._state === 'ap'; }
async start() {
if (IS_WINDOWS) {
led.off();
this._state = hasInternet() ? 'wired' : 'idle';
log.info('provision', 'Windows/x86 mode: AP provisioning is disabled; waiting for existing network');
if (this._state === 'wired') this._emitNetworkReady();
else led.display.showAP();
this._startMonitor();
return;
}
led.off(); // WiFi 灯初始状态:熄灭
// WiFi STA 已连接 → 直接进入 STA 模式
@@ -228,6 +239,19 @@ class ProvisionManager extends EventEmitter {
}
async _monitorTick() {
if (IS_WINDOWS) {
if (hasInternet()) {
if (this._state !== 'wired') {
this._state = 'wired';
this._emitNetworkReady();
}
} else if (this._state !== 'idle') {
this._state = 'idle';
led.display.showAP();
}
return;
}
if (this._state === 'connecting') return;
const wifiUp = isWifiStaConnected();