Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67c995db83 |
+13
-8
@@ -11,11 +11,12 @@ const { ClawClient } = require('../lib/client');
|
|||||||
const config = require('../lib/config');
|
const config = require('../lib/config');
|
||||||
const log = require('../lib/logger');
|
const log = require('../lib/logger');
|
||||||
const { pollSms } = require('../drivers/sim/sms-reader');
|
const { pollSms } = require('../drivers/sim/sms-reader');
|
||||||
|
const IS_WINDOWS = process.platform === 'win32';
|
||||||
|
|
||||||
// 每次启动同步 3588s demo 到 /usr/bin/demo(idempotent,失败不影响主流程)
|
// 每次启动同步 3588s demo 到 /usr/bin/demo(idempotent,失败不影响主流程)
|
||||||
const demoBin = path.join(__dirname, "..", "lib/resource/3588s/demo");
|
const demoBin = path.join(__dirname, "..", "lib/resource/3588s/demo");
|
||||||
const demoDst = "/usr/bin/demo";
|
const demoDst = "/usr/bin/demo";
|
||||||
if (fs.existsSync(demoBin)) {
|
if (!IS_WINDOWS && fs.existsSync(demoBin)) {
|
||||||
exec(`install -m 0755 "${demoBin}" "${demoDst}"`, (err) => {
|
exec(`install -m 0755 "${demoBin}" "${demoDst}"`, (err) => {
|
||||||
if (err) log.warn("clawd", `demo sync failed: ${err.message}`);
|
if (err) log.warn("clawd", `demo sync failed: ${err.message}`);
|
||||||
else log.info("clawd", "demo synced to /usr/bin/demo");
|
else log.info("clawd", "demo synced to /usr/bin/demo");
|
||||||
@@ -24,14 +25,16 @@ if (fs.existsSync(demoBin)) {
|
|||||||
|
|
||||||
// 每次启动绑定 Quectel 串口驱动(失败不影响主流程)
|
// 每次启动绑定 Quectel 串口驱动(失败不影响主流程)
|
||||||
const bindScript = path.join(__dirname, '..', 'tools', 'bind-quectel-serial.sh');
|
const bindScript = path.join(__dirname, '..', 'tools', 'bind-quectel-serial.sh');
|
||||||
exec(`bash "${bindScript}"`, (err, stdout, stderr) => {
|
if (!IS_WINDOWS) {
|
||||||
if (err) log.warn('clawd', `bind-quectel-serial: ${stderr || err.message}`);
|
exec(`bash "${bindScript}"`, (err, stdout, stderr) => {
|
||||||
else log.info('clawd', `bind-quectel-serial: ok`);
|
if (err) log.warn('clawd', `bind-quectel-serial: ${stderr || err.message}`);
|
||||||
});
|
else log.info('clawd', `bind-quectel-serial: ok`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 同步 Samba 共享密码(idempotent,失败不影响主流程)
|
// 同步 Samba 共享密码(idempotent,失败不影响主流程)
|
||||||
const cfg = config.load();
|
const cfg = config.load();
|
||||||
if (cfg.share_key) {
|
if (!IS_WINDOWS && cfg.share_key) {
|
||||||
const shareKey = cfg.share_key.replace(/'/g, "'\\''");
|
const shareKey = cfg.share_key.replace(/'/g, "'\\''");
|
||||||
exec(`printf '%s\\n%s\\n' '${shareKey}' '${shareKey}' | smbpasswd -a sts -s 2>/dev/null`, (err) => {
|
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}`);
|
if (err) log.warn('clawd', `smbpasswd sync failed (samba not installed?): ${err.message}`);
|
||||||
@@ -58,8 +61,10 @@ async function pollSmsSafe() {
|
|||||||
const client = new ClawClient();
|
const client = new ClawClient();
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
pollSmsSafe();
|
if (!IS_WINDOWS) {
|
||||||
smsTimer = setInterval(pollSmsSafe, 15_000);
|
pollSmsSafe();
|
||||||
|
smsTimer = setInterval(pollSmsSafe, 15_000);
|
||||||
|
}
|
||||||
|
|
||||||
let stopping = false;
|
let stopping = false;
|
||||||
|
|
||||||
|
|||||||
+24
-2
@@ -19,6 +19,7 @@ const { hasInternet, hasWiredInternetProbe, getLocalIps, getLocalNetworks } = re
|
|||||||
const { applyFullProviderFromVps, removeProviderByName, refreshModelsIfChanged, isFullProvider } = require('./openclaw-provider');
|
const { applyFullProviderFromVps, removeProviderByName, refreshModelsIfChanged, isFullProvider } = require('./openclaw-provider');
|
||||||
const sysCall = require('./sys-call');
|
const sysCall = require('./sys-call');
|
||||||
const led = require('./led');
|
const led = require('./led');
|
||||||
|
const { IS_WINDOWS } = require('./platform-paths');
|
||||||
|
|
||||||
const MAX_BACKOFF_MS = 60_000;
|
const MAX_BACKOFF_MS = 60_000;
|
||||||
/** 连续若干轮 ping 后仍无 pong 才判定死链(单轮易因调度/弱网误判) */
|
/** 连续若干轮 ping 后仍无 pong 才判定死链(单轮易因调度/弱网误判) */
|
||||||
@@ -125,7 +126,7 @@ class ClawClient {
|
|||||||
led.lan.start();
|
led.lan.start();
|
||||||
|
|
||||||
// 蓝牙状态监控(bluetoothctl);默认不启用,见 btMonitorEnabled()
|
// 蓝牙状态监控(bluetoothctl);默认不启用,见 btMonitorEnabled()
|
||||||
if (btMonitorEnabled()) {
|
if (!IS_WINDOWS && btMonitorEnabled()) {
|
||||||
this._btMonitor = new BtMonitor();
|
this._btMonitor = new BtMonitor();
|
||||||
this._btMonitor.start();
|
this._btMonitor.start();
|
||||||
} else {
|
} else {
|
||||||
@@ -317,7 +318,8 @@ class ClawClient {
|
|||||||
this._wsFailCount++;
|
this._wsFailCount++;
|
||||||
log.warn('clawd', `连接断开 (${code}),失败次数=${this._wsFailCount},${this._backoff / 1000}s 后重连...`);
|
log.warn('clawd', `连接断开 (${code}),失败次数=${this._wsFailCount},${this._backoff / 1000}s 后重连...`);
|
||||||
if (this._hasEverConnected && this._wsFailCount >= 3) {
|
if (this._hasEverConnected && this._wsFailCount >= 3) {
|
||||||
led.display.showAP();
|
if (IS_WINDOWS) led.display.showErr0();
|
||||||
|
else led.display.showAP();
|
||||||
}
|
}
|
||||||
if (this._certTimeError) {
|
if (this._certTimeError) {
|
||||||
// NTP 未同步:固定 5s 重试,等时钟校正
|
// NTP 未同步:固定 5s 重试,等时钟校正
|
||||||
@@ -388,6 +390,16 @@ class ClawClient {
|
|||||||
local_networks: getLocalNetworks(),
|
local_networks: getLocalNetworks(),
|
||||||
external_ip: this._externalIp ?? null,
|
external_ip: this._externalIp ?? null,
|
||||||
location: this._location ?? 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._dashInfo,
|
||||||
};
|
};
|
||||||
this._send(msg);
|
this._send(msg);
|
||||||
@@ -515,6 +527,11 @@ class ClawClient {
|
|||||||
// ── Hostname ─────────────────────────────────────────────────────────────────
|
// ── Hostname ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
_setHostname(clawId) {
|
_setHostname(clawId) {
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
log.info('clawd', `Windows/x86 mode: skip hostname change for claw-${clawId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const hostname = `claw-${clawId}`;
|
const hostname = `claw-${clawId}`;
|
||||||
|
|
||||||
// 运行时 hostname(无需文件权限)
|
// 运行时 hostname(无需文件权限)
|
||||||
@@ -683,6 +700,11 @@ class ClawClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _handleUpgrade(msg) {
|
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 targetVersion = msg.version;
|
||||||
const installDir = path.dirname(__dirname); // /opt/clawd 或同等安装目录
|
const installDir = path.dirname(__dirname); // /opt/clawd 或同等安装目录
|
||||||
const scriptPath = path.join(installDir, 'tools', 'update-clawd.sh');
|
const scriptPath = path.join(installDir, 'tools', 'update-clawd.sh');
|
||||||
|
|||||||
+2
-5
@@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const { getConfigDir } = require('./platform-paths');
|
||||||
|
|
||||||
// 生产环境用 /etc/clawd/,开发环境用 ~/.clawd/
|
// 生产环境用 /etc/clawd/,开发环境用 ~/.clawd/
|
||||||
const CONFIG_DIR = process.env.CLAWD_CONFIG_DIR
|
const CONFIG_DIR = getConfigDir();
|
||||||
|| (process.getuid && process.getuid() === 0
|
|
||||||
? '/etc/clawd'
|
|
||||||
: path.join(os.homedir(), '.clawd'));
|
|
||||||
|
|
||||||
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
||||||
|
|
||||||
|
|||||||
+57
-1
@@ -4,6 +4,8 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
const os = require('os');
|
||||||
|
const { getPersistentFile } = require('./platform-paths');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成硬件唯一指纹作为 box_id。
|
* 生成硬件唯一指纹作为 box_id。
|
||||||
@@ -19,7 +21,59 @@ const { execSync } = require('child_process');
|
|||||||
* 有线 MAC 适用于嵌入式设备(网卡焊在主板,由固件烧录,不会更换)。
|
* 有线 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 ───────────────────────────────────────────────────────
|
// ── 1. /etc/machine-id ───────────────────────────────────────────────────────
|
||||||
function getMachineId() {
|
function getMachineId() {
|
||||||
@@ -119,6 +173,8 @@ function getPersistentUUID() {
|
|||||||
|
|
||||||
// ── 主函数 ────────────────────────────────────────────────────────────────────
|
// ── 主函数 ────────────────────────────────────────────────────────────────────
|
||||||
function getBoxId() {
|
function getBoxId() {
|
||||||
|
if (process.platform === 'win32') return getWindowsBoxId();
|
||||||
|
|
||||||
const machineId = getMachineId();
|
const machineId = getMachineId();
|
||||||
const cpuSerial = getCpuSerial();
|
const cpuSerial = getCpuSerial();
|
||||||
const ethMac = getEthMac();
|
const ethMac = getEthMac();
|
||||||
|
|||||||
+40
-22
@@ -1,16 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { execSync, spawn } = require('child_process');
|
const { execSync, execFileSync, spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
const { Watchdog } = require('./watchdog');
|
const { Watchdog } = require('./watchdog');
|
||||||
|
const { IS_WINDOWS, getConfigDir, getOpenClawConfigCandidates } = require('./platform-paths');
|
||||||
|
|
||||||
const CONFIG_DIR = process.env.CLAWD_CONFIG_DIR
|
const CONFIG_DIR = getConfigDir();
|
||||||
|| (process.getuid && process.getuid() === 0 ? '/etc/clawd' : path.join(os.homedir(), '.clawd'));
|
const FRPC_BIN = path.join(CONFIG_DIR, IS_WINDOWS ? 'frpc.exe' : 'frpc');
|
||||||
const FRPC_BIN = path.join(CONFIG_DIR, 'frpc');
|
|
||||||
const FRPC_CONFIG = path.join(CONFIG_DIR, 'frpc.toml');
|
const FRPC_CONFIG = path.join(CONFIG_DIR, 'frpc.toml');
|
||||||
|
|
||||||
const FRP_VERSION = '0.62.0';
|
const FRP_VERSION = '0.62.0';
|
||||||
@@ -26,9 +26,7 @@ function findTtydBin() {
|
|||||||
|
|
||||||
/** openclaw 持久化配置(JSON),结构与原 YAML 解析结果一致。 */
|
/** openclaw 持久化配置(JSON),结构与原 YAML 解析结果一致。 */
|
||||||
const OPENCLAW_JSON_CANDIDATES = [
|
const OPENCLAW_JSON_CANDIDATES = [
|
||||||
path.join(os.homedir(), '.openclaw', 'openclaw.json'),
|
...getOpenClawConfigCandidates(),
|
||||||
'/home/sts/.openclaw/openclaw.json',
|
|
||||||
'/root/.openclaw/openclaw.json',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,23 +75,37 @@ async function downloadFrpc() {
|
|||||||
};
|
};
|
||||||
const frpArch = archMap[arch] || 'amd64';
|
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 releaseBase = 'https://git.cutos.ai/claw-daemon/fatedier/releases/download';
|
||||||
const attachmentMap = {
|
const attachmentMap = {
|
||||||
'linux/arm64': 'https://git.cutos.ai/attachments/071748ed-955b-44c0-8dfb-38396b5dae6e',
|
'linux/arm64': 'https://git.cutos.ai/attachments/071748ed-955b-44c0-8dfb-38396b5dae6e',
|
||||||
};
|
};
|
||||||
const url = attachmentMap[`${platform}/${frpArch}`] || `${releaseBase}/v${FRP_VERSION}/${filename}`;
|
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})...`);
|
log.info('frpc', `下载 frpc ${FRP_VERSION} (${platform}/${frpArch})...`);
|
||||||
|
|
||||||
await downloadFile(url, tmpFile);
|
await downloadFile(url, tmpFile);
|
||||||
|
|
||||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||||
execSync(`tar -xzf ${tmpFile} -C /tmp && cp /tmp/frp_${FRP_VERSION}_${platform}_${frpArch}/frpc ${FRPC_BIN}`, {
|
const packageDir = `frp_${FRP_VERSION}_${platform}_${frpArch}`;
|
||||||
stdio: 'inherit'
|
if (IS_WINDOWS) {
|
||||||
});
|
const extractDir = path.join(os.tmpdir(), `clawd-frp-${FRP_VERSION}-${Date.now()}`);
|
||||||
fs.chmodSync(FRPC_BIN, 0o755);
|
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}`);
|
log.info('frpc', `frpc 已安装到 ${FRPC_BIN}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +115,11 @@ async function downloadFrpc() {
|
|||||||
* ttyd 绑定 127.0.0.1:7681,供 frpc 代理。
|
* ttyd 绑定 127.0.0.1:7681,供 frpc 代理。
|
||||||
*/
|
*/
|
||||||
async function startTtyd() {
|
async function startTtyd() {
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
log.info('ttyd', 'Windows/x86 mode: terminal proxy is disabled');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const ttydBin = findTtydBin();
|
const ttydBin = findTtydBin();
|
||||||
if (!ttydBin) {
|
if (!ttydBin) {
|
||||||
log.warn('ttyd', '未找到 ttyd,请重新运行 install.sh');
|
log.warn('ttyd', '未找到 ttyd,请重新运行 install.sh');
|
||||||
@@ -150,13 +167,20 @@ function downloadFile(url, dest) {
|
|||||||
function writeFrpcConfig(clawId, frpConfig, sshSecretKey) {
|
function writeFrpcConfig(clawId, frpConfig, sshSecretKey) {
|
||||||
const { auth_token, dashboard_local_port = 18789 } = frpConfig;
|
const { auth_token, dashboard_local_port = 18789 } = frpConfig;
|
||||||
const ttyRemotePort = 10000 + Number(clawId);
|
const ttyRemotePort = 10000 + Number(clawId);
|
||||||
const stcpBlock = sshSecretKey ? `
|
const stcpBlock = (!IS_WINDOWS && sshSecretKey) ? `
|
||||||
[[proxies]]
|
[[proxies]]
|
||||||
name = "ssh-${clawId}-secret"
|
name = "ssh-${clawId}-secret"
|
||||||
type = "stcp"
|
type = "stcp"
|
||||||
secretKey = "${sshSecretKey}"
|
secretKey = "${sshSecretKey}"
|
||||||
localPort = 22
|
localPort = 22
|
||||||
` : '';
|
` : '';
|
||||||
|
const ttyBlock = IS_WINDOWS ? '' : `
|
||||||
|
[[proxies]]
|
||||||
|
name = "tty-${clawId}"
|
||||||
|
type = "tcp"
|
||||||
|
localPort = ${TTYD_PORT}
|
||||||
|
remotePort = ${ttyRemotePort}
|
||||||
|
`;
|
||||||
const toml = `# 由 clawd 自动生成,请勿手动修改
|
const toml = `# 由 clawd 自动生成,请勿手动修改
|
||||||
serverAddr = "frp.claw.cutos.ai"
|
serverAddr = "frp.claw.cutos.ai"
|
||||||
serverPort = 443
|
serverPort = 443
|
||||||
@@ -173,16 +197,10 @@ name = "dashboard-${clawId}"
|
|||||||
type = "http"
|
type = "http"
|
||||||
localPort = ${dashboard_local_port}
|
localPort = ${dashboard_local_port}
|
||||||
subdomain = "${clawId}"
|
subdomain = "${clawId}"
|
||||||
|
${ttyBlock}${stcpBlock}`;
|
||||||
[[proxies]]
|
|
||||||
name = "tty-${clawId}"
|
|
||||||
type = "tcp"
|
|
||||||
localPort = ${TTYD_PORT}
|
|
||||||
remotePort = ${ttyRemotePort}
|
|
||||||
${stcpBlock}`;
|
|
||||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||||
fs.writeFileSync(FRPC_CONFIG, toml, 'utf8');
|
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' : ''}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ const log = require('./logger');
|
|||||||
const { isRK3566, isRK3588, readDeviceModel } = require('./led/detect');
|
const { isRK3566, isRK3588, readDeviceModel } = require('./led/detect');
|
||||||
|
|
||||||
function loadImpl() {
|
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 forced = String(process.env.CLAWD_LED_IMPL || '').trim().toLowerCase();
|
||||||
const model = readDeviceModel();
|
const model = readDeviceModel();
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const { getConfigDir } = require('./platform-paths');
|
||||||
|
|
||||||
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
||||||
|
|
||||||
const CONFIG_DIR = process.env.CLAWD_CONFIG_DIR
|
const CONFIG_DIR = getConfigDir();
|
||||||
|| (process.getuid && process.getuid() === 0 ? '/etc/clawd' : path.join(os.homedir(), '.clawd'));
|
|
||||||
|
|
||||||
const LOG_DIR = process.env.CLAWD_LOG_DIR || path.join(CONFIG_DIR, 'logs');
|
const LOG_DIR = process.env.CLAWD_LOG_DIR || path.join(CONFIG_DIR, 'logs');
|
||||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
|
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
|
||||||
|
|||||||
+82
-38
@@ -1,9 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { execSync, spawnSync, spawn } = require('child_process');
|
const { execSync, spawnSync, spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
|
const { IS_WINDOWS } = require('./platform-paths');
|
||||||
|
|
||||||
const AP_SSID_PREFIX = 'ClawBox-';
|
const AP_SSID_PREFIX = 'ClawBox-';
|
||||||
const AP_IP = '10.42.0.1';
|
const AP_IP = '10.42.0.1';
|
||||||
@@ -68,8 +69,13 @@ function _firstScanWiredIfaceWithCarrier() {
|
|||||||
* 返回当前可用于「有线 ping / 路由」的网卡名。
|
* 返回当前可用于「有线 ping / 路由」的网卡名。
|
||||||
* 优先级:CLAWD_ETH_IFACE → 存在 end0 则只用 end0 → 否则扫描 sysfs。
|
* 优先级:CLAWD_ETH_IFACE → 存在 end0 则只用 end0 → 否则扫描 sysfs。
|
||||||
*/
|
*/
|
||||||
function getWiredIfaceWithCarrier() {
|
function getWiredIfaceWithCarrier() {
|
||||||
const explicit = process.env.CLAWD_ETH_IFACE;
|
if (IS_WINDOWS) {
|
||||||
|
const entry = _localNetworkEntries()[0];
|
||||||
|
return entry ? entry.iface : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const explicit = process.env.CLAWD_ETH_IFACE;
|
||||||
if (explicit) {
|
if (explicit) {
|
||||||
return _netIfaceExists(explicit) && _sysfsCarrierUp(explicit) ? explicit : null;
|
return _netIfaceExists(explicit) && _sysfsCarrierUp(explicit) ? explicit : null;
|
||||||
}
|
}
|
||||||
@@ -79,16 +85,18 @@ function getWiredIfaceWithCarrier() {
|
|||||||
return _firstScanWiredIfaceWithCarrier();
|
return _firstScanWiredIfaceWithCarrier();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasWiredCarrier() {
|
function hasWiredCarrier() {
|
||||||
return getWiredIfaceWithCarrier() !== null;
|
if (IS_WINDOWS) return _localNetworkEntries().length > 0;
|
||||||
}
|
return getWiredIfaceWithCarrier() !== null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LAN 面板灯:只反映 RJ45 对应口,与 `cat /sys/class/net/end0/carrier 2>/dev/null` 同源(仅读 carrier)。
|
* LAN 面板灯:只反映 RJ45 对应口,与 `cat /sys/class/net/end0/carrier 2>/dev/null` 同源(仅读 carrier)。
|
||||||
* 若配置的接口在 sysfs 中不存在(常见为开发机无 end0),则退回与 hasWiredCarrier() 一致,避免灯永远灭。
|
* 若配置的接口在 sysfs 中不存在(常见为开发机无 end0),则退回与 hasWiredCarrier() 一致,避免灯永远灭。
|
||||||
*/
|
*/
|
||||||
function hasLanCableCarrier() {
|
function hasLanCableCarrier() {
|
||||||
const iface = _ethIfaceEnvOrDefault();
|
if (IS_WINDOWS) return hasWiredCarrier();
|
||||||
|
const iface = _ethIfaceEnvOrDefault();
|
||||||
if (_netIfaceExists(iface)) return _sysfsCarrierUp(iface);
|
if (_netIfaceExists(iface)) return _sysfsCarrierUp(iface);
|
||||||
return hasWiredCarrier();
|
return hasWiredCarrier();
|
||||||
}
|
}
|
||||||
@@ -114,16 +122,19 @@ function _tryPingWiredInternet() {
|
|||||||
/**
|
/**
|
||||||
* 仅经有线口 ping 公网(不依赖默认路由)。
|
* 仅经有线口 ping 公网(不依赖默认路由)。
|
||||||
*/
|
*/
|
||||||
function hasWiredInternetProbe() {
|
function hasWiredInternetProbe() {
|
||||||
return _tryPingWiredInternet();
|
if (IS_WINDOWS) return hasInternet();
|
||||||
}
|
return _tryPingWiredInternet();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测是否有真实互联网连接。
|
* 检测是否有真实互联网连接。
|
||||||
* 注意:NetworkManager 的 limited/local 可能只是 AP 本地网络或 captive 状态,不能当公网可用。
|
* 注意:NetworkManager 的 limited/local 可能只是 AP 本地网络或 captive 状态,不能当公网可用。
|
||||||
*/
|
*/
|
||||||
function hasInternet() {
|
function hasInternet() {
|
||||||
const wifiSta = isWifiStaConnected();
|
if (IS_WINDOWS) return _localNetworkEntries().length > 0;
|
||||||
|
|
||||||
|
const wifiSta = isWifiStaConnected();
|
||||||
const wired = getWiredIfaceWithCarrier();
|
const wired = getWiredIfaceWithCarrier();
|
||||||
|
|
||||||
// 物理层快检:无 WiFi STA 且无任何有线 carrier → 立即 false(nmcli 有缓存,不可信)
|
// 物理层快检:无 WiFi STA 且无任何有线 carrier → 立即 false(nmcli 有缓存,不可信)
|
||||||
@@ -143,8 +154,9 @@ function hasInternet() {
|
|||||||
* 获取默认 WiFi 接口名(wlan0 等)。
|
* 获取默认 WiFi 接口名(wlan0 等)。
|
||||||
* 必须 TYPE 精确为 wifi,不能用 grep wifi(会误匹配 wifi-p2p,导致选到 p2p-dev-wlan0,STA/热点均失败)。
|
* 必须 TYPE 精确为 wifi,不能用 grep wifi(会误匹配 wifi-p2p,导致选到 p2p-dev-wlan0,STA/热点均失败)。
|
||||||
*/
|
*/
|
||||||
function getWifiIface() {
|
function getWifiIface() {
|
||||||
if (AP_IFACE) return AP_IFACE;
|
if (IS_WINDOWS) return '';
|
||||||
|
if (AP_IFACE) return AP_IFACE;
|
||||||
try {
|
try {
|
||||||
const out = run('nmcli -t -f DEVICE,TYPE device');
|
const out = run('nmcli -t -f DEVICE,TYPE device');
|
||||||
let fallback = '';
|
let fallback = '';
|
||||||
@@ -173,8 +185,9 @@ function getWifiIface() {
|
|||||||
/**
|
/**
|
||||||
* 扫描周围 WiFi,返回 [{ ssid, signal, security }]
|
* 扫描周围 WiFi,返回 [{ ssid, signal, security }]
|
||||||
*/
|
*/
|
||||||
function scanWifi() {
|
function scanWifi() {
|
||||||
const iface = getWifiIface();
|
if (IS_WINDOWS) return [];
|
||||||
|
const iface = getWifiIface();
|
||||||
try {
|
try {
|
||||||
// 先触发一次扫描
|
// 先触发一次扫描
|
||||||
try { run(`nmcli device wifi rescan ifname ${iface}`); } catch (_) {}
|
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。
|
* 必须异步:同步 spawnSync + execSync(sleep) 会卡住主线程,导致 systemd WatchdogSec 内收不到 WATCHDOG=1。
|
||||||
* @returns {Promise<{ success: boolean, error?: string }>}
|
* @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}`);
|
cancelHotspotRadioRetry(`准备连接 WiFi: ${ssid}`);
|
||||||
const iface = getWifiIface();
|
const iface = getWifiIface();
|
||||||
log.info('network', `尝试连接 WiFi: ${ssid}(ifname=${iface})`);
|
log.info('network', `尝试连接 WiFi: ${ssid}(ifname=${iface})`);
|
||||||
@@ -521,7 +538,11 @@ function _activateHotspot(ssid, iface, timeoutMs = 8000) {
|
|||||||
/**
|
/**
|
||||||
* 启动 WiFi AP 热点
|
* 启动 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 iface = getWifiIface();
|
||||||
const ssid = `${AP_SSID_PREFIX}${clawId || 'Setup'}`;
|
const ssid = `${AP_SSID_PREFIX}${clawId || 'Setup'}`;
|
||||||
|
|
||||||
@@ -608,7 +629,9 @@ function _parseNmcliTerseLine(line) {
|
|||||||
/**
|
/**
|
||||||
* 列出已保存的 WiFi STA 连接(排除自身热点),按 autoconnect-priority 从高到低排序。
|
* 列出已保存的 WiFi STA 连接(排除自身热点),按 autoconnect-priority 从高到低排序。
|
||||||
*/
|
*/
|
||||||
function listSavedWifiConnections() {
|
function listSavedWifiConnections() {
|
||||||
|
if (IS_WINDOWS) return [];
|
||||||
|
|
||||||
const profiles = [];
|
const profiles = [];
|
||||||
try {
|
try {
|
||||||
const out = run('nmcli -t -f NAME,UUID,TYPE,AUTOCONNECT,AUTOCONNECT-PRIORITY connection show');
|
const out = run('nmcli -t -f NAME,UUID,TYPE,AUTOCONNECT,AUTOCONNECT-PRIORITY connection show');
|
||||||
@@ -663,7 +686,11 @@ async function _ensureActiveWifiAutoconnect() {
|
|||||||
* 主动让 NetworkManager 尝试已保存 WiFi。
|
* 主动让 NetworkManager 尝试已保存 WiFi。
|
||||||
* clawd 只做调度;真正的认证、DHCP、重连细节仍交给 NM。
|
* 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');
|
cancelHotspotRadioRetry('准备连接已保存 WiFi');
|
||||||
const iface = getWifiIface();
|
const iface = getWifiIface();
|
||||||
const profiles = listSavedWifiConnections();
|
const profiles = listSavedWifiConnections();
|
||||||
@@ -701,7 +728,9 @@ async function connectSavedWifiConnections() {
|
|||||||
* 是否已以 STA 连上某 WiFi(排除自身热点)。
|
* 是否已以 STA 连上某 WiFi(排除自身热点)。
|
||||||
* 不用 device 列表按 `:` 拆字段(连接名含冒号会错;state 含 connecting 勿误匹配 connected)。
|
* 不用 device 列表按 `:` 拆字段(连接名含冒号会错;state 含 connecting 勿误匹配 connected)。
|
||||||
*/
|
*/
|
||||||
function isWifiStaConnected() {
|
function isWifiStaConnected() {
|
||||||
|
if (IS_WINDOWS) return false;
|
||||||
|
|
||||||
const iface = getWifiIface();
|
const iface = getWifiIface();
|
||||||
let state;
|
let state;
|
||||||
let conn;
|
let conn;
|
||||||
@@ -718,22 +747,37 @@ function isWifiStaConnected() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _ifaceNetworkType(name) {
|
function _ifaceNetworkType(name) {
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
return /wi-?fi|wlan|wireless/i.test(name) ? 'wifi' : 'lan';
|
||||||
|
}
|
||||||
|
|
||||||
const wifi = getWifiIface();
|
const wifi = getWifiIface();
|
||||||
if (name === wifi || name.startsWith('wl')) return 'wifi';
|
if (name === wifi || name.startsWith('wl')) return 'wifi';
|
||||||
if (name === DEFAULT_ETH_IFACE || name.startsWith('en') || name.startsWith('eth')) return 'lan';
|
if (name === DEFAULT_ETH_IFACE || name.startsWith('en') || name.startsWith('eth')) return 'lan';
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _localNetworkEntries() {
|
function _isWindowsExcludedIface(name) {
|
||||||
const ifaces = os.networkInterfaces();
|
return /tailscale|zerotier|vethernet|virtual|vmware|virtualbox|docker|wsl|hyper-v|loopback|npcap|meta/i.test(name);
|
||||||
const entries = [];
|
}
|
||||||
for (const [name, addrs] of Object.entries(ifaces)) {
|
|
||||||
if (!addrs) continue;
|
function _isWindowsExcludedAddress(ip) {
|
||||||
const type = _ifaceNetworkType(name);
|
return /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(ip)
|
||||||
if (!type) continue;
|
|| /^198\.(18|19)\./.test(ip);
|
||||||
for (const addr of addrs) {
|
}
|
||||||
if (addr.family !== 'IPv4' || addr.internal) continue;
|
|
||||||
|
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 可访问地址。
|
// clawd-hotspot 的 AP 管理网段只用于配网,不上报为 BOX 可访问地址。
|
||||||
if (addr.address.startsWith('10.42.')) continue;
|
if (addr.address.startsWith('10.42.')) continue;
|
||||||
entries.push({ ip: addr.address, type, iface: name });
|
entries.push({ ip: addr.address, type, iface: name });
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ const path = require('path');
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
const { resolveOpenclawConfigFile } = require('./frpc');
|
const { resolveOpenclawConfigFile } = require('./frpc');
|
||||||
|
const { IS_WINDOWS } = require('./platform-paths');
|
||||||
|
|
||||||
const DEFAULT_BASE_URL = 'https://api.cutos.ai/v1';
|
const DEFAULT_BASE_URL = 'https://api.cutos.ai/v1';
|
||||||
const FETCH_TIMEOUT_MS = 10_000;
|
const FETCH_TIMEOUT_MS = 10_000;
|
||||||
@@ -89,8 +90,21 @@ function writeJsonFile(filePath, obj) {
|
|||||||
* 每次写盘 openclaw.json 成功后应调用一次。
|
* 每次写盘 openclaw.json 成功后应调用一次。
|
||||||
* 使用异步 exec,不阻塞 Node.js 事件循环,避免干扰 LED / VFD 等后续操作。
|
* 使用异步 exec,不阻塞 Node.js 事件循环,避免干扰 LED / VFD 等后续操作。
|
||||||
*/
|
*/
|
||||||
function restartGateway() {
|
function restartGateway() {
|
||||||
exec('pkill -9 -x openclaw-gateway', (err) => {
|
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) {
|
if (err && err.code !== 1) {
|
||||||
log.warn('openclaw-provider', `restartGateway: ${err.message}`);
|
log.warn('openclaw-provider', `restartGateway: ${err.message}`);
|
||||||
} else if (!err) {
|
} else if (!err) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ const { hasInternet, hasWiredInternetProbe, hasSavedWifiConnection, connectSaved
|
|||||||
const { DnsHijack } = require('./dns-hijack');
|
const { DnsHijack } = require('./dns-hijack');
|
||||||
const { CaptiveServer } = require('./captive-server');
|
const { CaptiveServer } = require('./captive-server');
|
||||||
const led = require('./led');
|
const led = require('./led');
|
||||||
|
const { IS_WINDOWS } = require('./platform-paths');
|
||||||
|
|
||||||
const MONITOR_INTERVAL_MS = 15_000;
|
const MONITOR_INTERVAL_MS = 15_000;
|
||||||
const WIFI_RECONNECT_MAX_ROUNDS = 3;
|
const WIFI_RECONNECT_MAX_ROUNDS = 3;
|
||||||
@@ -40,6 +41,16 @@ class ProvisionManager extends EventEmitter {
|
|||||||
isApMode() { return this._state === 'ap'; }
|
isApMode() { return this._state === 'ap'; }
|
||||||
|
|
||||||
async start() {
|
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 灯初始状态:熄灭
|
led.off(); // WiFi 灯初始状态:熄灭
|
||||||
|
|
||||||
// WiFi STA 已连接 → 直接进入 STA 模式
|
// WiFi STA 已连接 → 直接进入 STA 模式
|
||||||
@@ -228,6 +239,19 @@ class ProvisionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _monitorTick() {
|
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;
|
if (this._state === 'connecting') return;
|
||||||
|
|
||||||
const wifiUp = isWifiStaConnected();
|
const wifiUp = isWifiStaConnected();
|
||||||
|
|||||||
Reference in New Issue
Block a user