feat(remote): 添加 XCOpenCodeWeb 服务管理

- 新增 XCOpenCodeWebService.js 服务模块
- 支持启动/停止/健康检测(每10秒)
- 随 remote 服务启动/退出
- 配置文件添加 xcopencodeweb 配置
- 修复 opencode 默认端口配置
This commit is contained in:
2026-03-14 16:02:05 +08:00
parent 50cd1e29c9
commit 9b22b647f2
3 changed files with 172 additions and 2 deletions

View File

@@ -28,8 +28,11 @@
"configPath": "./frp/frpc.toml"
},
"opencode": {
"enabled": true
},
"xcopencodeweb": {
"enabled": true,
"port": 3002
"port": 9999
},
"gitea": {
"enabled": true

View File

@@ -124,7 +124,17 @@ class App {
const opencodeConfig = config.getSection('opencode') || {};
return new OpenCodeService({
enabled: opencodeConfig.enabled !== false,
port: opencodeConfig.port || 3002
port: opencodeConfig.port
});
});
this.container.register('xcOpenCodeWebService', (c) => {
const XCOpenCodeWebService = require('../services/opencode/XCOpenCodeWebService');
const config = c.resolve('config');
const xcopencodewebConfig = config.getSection('xcopencodeweb') || {};
return new XCOpenCodeWebService({
enabled: xcopencodewebConfig.enabled !== false,
port: xcopencodewebConfig.port
});
});
@@ -206,6 +216,10 @@ class App {
openCodeService.start();
logger.info('OpenCode service started');
const xcOpenCodeWebService = this.container.resolve('xcOpenCodeWebService');
xcOpenCodeWebService.start();
logger.info('XCOpenCodeWeb service started');
const giteaService = this.container.resolve('giteaService');
giteaService.start();
logger.info('Gitea service started');
@@ -488,6 +502,10 @@ class App {
openCodeService.stop();
logger.info('OpenCode service stopped');
const xcOpenCodeWebService = this.container.resolve('xcOpenCodeWebService');
xcOpenCodeWebService.stop();
logger.info('XCOpenCodeWeb service stopped');
const giteaService = this.container.resolve('giteaService');
giteaService.stop();
logger.info('Gitea service stopped');

View File

@@ -0,0 +1,149 @@
const { spawn } = require('child_process');
const path = require('path');
const logger = require('../../utils/logger');
class XCOpenCodeWebService {
constructor(options = {}) {
this.process = null;
this.isRunning = false;
this.port = options.port || 9999;
this.enabled = options.enabled !== false;
this.healthCheckInterval = 10000;
this.healthCheckTimeout = 2000;
this.healthCheckTimer = null;
}
getExePath() {
const exeName = 'XCOpenCodeWeb.exe';
const basePath = path.join(__dirname, '../../xcopencodeweb');
return path.join(basePath, exeName);
}
getExeArgs() {
return ['--port', this.port.toString()];
}
async checkHealth() {
try {
const timeoutMs = this.healthCheckTimeout;
const fetchPromise = fetch(`http://127.0.0.1:${this.port}`);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeoutMs)
);
const response = await Promise.race([fetchPromise, timeoutPromise]);
return response.ok || response.status === 401;
} catch (error) {
logger.warn('[XCOpenCodeWebService] Health check failed:', error.message);
return false;
}
}
startHealthCheck() {
if (this.healthCheckTimer) {
clearInterval(this.healthCheckTimer);
}
this.healthCheckTimer = setInterval(async () => {
const isHealthy = await this.checkHealth();
if (!isHealthy && this.isRunning) {
logger.warn('[XCOpenCodeWebService] Service not responding');
}
}, this.healthCheckInterval);
}
stopHealthCheck() {
if (this.healthCheckTimer) {
clearInterval(this.healthCheckTimer);
this.healthCheckTimer = null;
}
}
start() {
if (!this.enabled) {
logger.info('XCOpenCodeWeb service is disabled');
return;
}
if (this.isRunning && this.process) {
logger.warn('XCOpenCodeWeb service is already running');
return;
}
try {
const exePath = this.getExePath();
const exeArgs = this.getExeArgs();
logger.info(`[XCOpenCodeWebService] Starting from: ${exePath} with args: ${exeArgs.join(' ')}`);
this.process = spawn(exePath, exeArgs, {
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
});
this.process.stdout.on('data', (data) => {
const output = data.toString().trim();
if (output) {
logger.info(`[XCOpenCodeWeb] ${output}`);
}
});
this.process.stderr.on('data', (data) => {
const output = data.toString().trim();
if (output) {
logger.error(`[XCOpenCodeWeb error] ${output}`);
}
});
this.process.on('error', (error) => {
logger.error('[XCOpenCodeWebService] Process error', { error: error.message });
this.isRunning = false;
this.process = null;
});
this.process.on('exit', (code) => {
logger.info('[XCOpenCodeWebService] Process exited', { code });
this.isRunning = false;
this.process = null;
});
this.isRunning = true;
this.startHealthCheck();
logger.info(`[XCOpenCodeWebService] Started successfully on port ${this.port}`);
} catch (error) {
logger.error('[XCOpenCodeWebService] Failed to start', { error: error.message });
this.isRunning = false;
}
}
stop() {
this.stopHealthCheck();
if (!this.process) {
logger.info('[XCOpenCodeWebService] Not running');
return;
}
try {
logger.info('[XCOpenCodeWebService] Stopping...');
this.process.kill();
this.process = null;
this.isRunning = false;
logger.info('[XCOpenCodeWebService] Stopped');
} catch (error) {
logger.error('[XCOpenCodeWebService] Failed to stop', { error: error.message });
}
}
getStatus() {
return {
running: this.isRunning,
port: this.port
};
}
}
module.exports = XCOpenCodeWebService;