298 lines
8.7 KiB
JavaScript
Raw Normal View History

/**
* GMP-Agent 守护进程 · 核心框架骨架
* 工单编号: GH-GMP-004
* 开发者: 培园A04 (5TH-LE-HK-A04)
* 职责: Express服务启动 · 路由注册 · 模块生命周期管理 · 健康监控 · 优雅停机
*
* 架构定位: HLDP-ARCH-001 分发层[D-1] 执行体
* GMP三层: L1(GMP协议·manifest) L2(GMP-Agent·本文件) L3(MCP接口)
*/
const express = require('express');
const path = require('path');
const fs = require('fs');
const { createLogger } = require('./lib/logger');
const { loadConfig } = require('./lib/config');
const webhookRouter = require('./webhook');
const { ModuleInstaller } = require('./installer');
const { ModuleUninstaller } = require('./uninstaller');
const logger = createLogger('gmp-agent');
class GMPAgent {
constructor() {
this.app = express();
this.server = null;
this.config = null;
this.installer = null;
this.uninstaller = null;
this.installedModules = new Map(); // moduleName -> moduleInfo
this.startTime = Date.now();
this.status = 'initializing';
}
/**
* 初始化Agent
*/
async init() {
logger.info('[GMP-Agent] 初始化开始...');
// 1. 加载配置
this.config = loadConfig();
logger.info('[GMP-Agent] 配置加载完成', { port: this.config.port, modulesDir: this.config.modulesDir });
// 2. 确保模块目录存在
this._ensureDirectories();
// 3. 初始化安装器和卸载器
this.installer = new ModuleInstaller(this.config, this.installedModules);
this.uninstaller = new ModuleUninstaller(this.config, this.installedModules);
// 4. 扫描已安装模块
await this._scanInstalledModules();
// 5. 配置Express中间件
this._setupMiddleware();
// 6. 注册路由
this._setupRoutes();
// 7. 注册优雅停机
this._setupGracefulShutdown();
this.status = 'ready';
logger.info('[GMP-Agent] 初始化完成, 已发现 ' + this.installedModules.size + ' 个已安装模块');
}
/**
* 确保必要目录存在
*/
_ensureDirectories() {
const dirs = [
this.config.modulesDir,
this.config.logsDir,
this.config.tempDir
];
for (const dir of dirs) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
logger.info('[GMP-Agent] 创建目录: ' + dir);
}
}
}
/**
* 扫描已安装模块目录, 加载manifest
*/
async _scanInstalledModules() {
const modulesDir = this.config.modulesDir;
if (!fs.existsSync(modulesDir)) return;
const entries = fs.readdirSync(modulesDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const manifestPath = path.join(modulesDir, entry.name, 'manifest.yaml');
if (fs.existsSync(manifestPath)) {
try {
// 简单解析manifest (生产环境用js-yaml)
const manifestRaw = fs.readFileSync(manifestPath, 'utf-8');
const moduleInfo = {
name: entry.name,
path: path.join(modulesDir, entry.name),
manifestPath: manifestPath,
installedAt: fs.statSync(manifestPath).mtime.toISOString(),
status: 'installed'
};
this.installedModules.set(entry.name, moduleInfo);
logger.info('[GMP-Agent] 发现已安装模块: ' + entry.name);
} catch (err) {
logger.warn('[GMP-Agent] 模块manifest读取失败: ' + entry.name + ' -> ' + err.message);
}
}
}
}
/**
* 配置Express中间件
*/
_setupMiddleware() {
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true }));
// 请求日志
this.app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info(req.method + ' ' + req.originalUrl + ' ' + res.statusCode + ' ' + duration + 'ms');
});
next();
});
}
/**
* 注册路由
*/
_setupRoutes() {
// 健康检查
this.app.get('/health', (req, res) => {
res.json({
status: this.status,
uptime: Math.floor((Date.now() - this.startTime) / 1000),
modulesCount: this.installedModules.size,
version: this.config.version,
timestamp: new Date().toISOString()
});
});
// 模块列表
this.app.get('/api/modules', (req, res) => {
const modules = [];
for (const [name, info] of this.installedModules) {
modules.push({
name: name,
status: info.status,
path: info.path,
installedAt: info.installedAt
});
}
res.json({ modules: modules, total: modules.length });
});
// 模块详情
this.app.get('/api/modules/:name', (req, res) => {
const info = this.installedModules.get(req.params.name);
if (!info) {
return res.status(404).json({ error: '模块不存在: ' + req.params.name });
}
res.json(info);
});
// 安装模块
this.app.post('/api/modules/install', async (req, res) => {
try {
const { repoUrl, moduleName, branch } = req.body;
if (!moduleName) {
return res.status(400).json({ error: '缺少moduleName参数' });
}
const result = await this.installer.install({ repoUrl, moduleName, branch });
res.json(result);
} catch (err) {
logger.error('[GMP-Agent] 安装失败: ' + err.message);
res.status(500).json({ error: err.message });
}
});
// 卸载模块
this.app.post('/api/modules/uninstall', async (req, res) => {
try {
const { moduleName } = req.body;
if (!moduleName) {
return res.status(400).json({ error: '缺少moduleName参数' });
}
const result = await this.uninstaller.uninstall({ moduleName });
res.json(result);
} catch (err) {
logger.error('[GMP-Agent] 卸载失败: ' + err.message);
res.status(500).json({ error: err.message });
}
});
// GitHub Webhook路由
this.app.use('/webhook', webhookRouter(this));
// 系统信息
this.app.get('/api/system', (req, res) => {
res.json({
agent: 'GMP-Agent',
version: this.config.version,
nodeVersion: process.version,
platform: process.platform,
arch: process.arch,
memory: process.memoryUsage(),
uptime: process.uptime(),
pid: process.pid
});
});
// 404
this.app.use((req, res) => {
res.status(404).json({ error: 'Not Found', path: req.originalUrl });
});
// 错误处理
this.app.use((err, req, res, next) => {
logger.error('[GMP-Agent] 未捕获错误: ' + err.message);
res.status(500).json({ error: 'Internal Server Error' });
});
}
/**
* 优雅停机
*/
_setupGracefulShutdown() {
const shutdown = (signal) => {
logger.info('[GMP-Agent] 收到 ' + signal + ' 信号, 开始优雅停机...');
this.status = 'shutting_down';
if (this.server) {
this.server.close(() => {
logger.info('[GMP-Agent] HTTP服务已关闭');
process.exit(0);
});
// 10秒超时强制退出
setTimeout(() => {
logger.warn('[GMP-Agent] 优雅停机超时, 强制退出');
process.exit(1);
}, 10000);
} else {
process.exit(0);
}
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('uncaughtException', (err) => {
logger.error('[GMP-Agent] 未捕获异常: ' + err.message + '\n' + err.stack);
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
logger.error('[GMP-Agent] 未处理Promise拒绝: ' + String(reason));
});
}
/**
* 启动服务
*/
async start() {
await this.init();
const port = this.config.port;
this.server = this.app.listen(port, () => {
this.status = 'running';
logger.info('========================================');
logger.info(' GMP-Agent 守护进程已启动');
logger.info(' 端口: ' + port);
logger.info(' 版本: ' + this.config.version);
logger.info(' 模块目录: ' + this.config.modulesDir);
logger.info(' 已安装模块: ' + this.installedModules.size + ' 个');
logger.info(' 健康检查: http://localhost:' + port + '/health');
logger.info(' Webhook: http://localhost:' + port + '/webhook/github');
logger.info('========================================');
});
}
}
// 启动
const agent = new GMPAgent();
agent.start().catch((err) => {
console.error('[GMP-Agent] 启动失败:', err);
process.exit(1);
});
module.exports = { GMPAgent };