deployController.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. "use strict";
  2. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  3. if (k2 === undefined) k2 = k;
  4. var desc = Object.getOwnPropertyDescriptor(m, k);
  5. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  6. desc = { enumerable: true, get: function() { return m[k]; } };
  7. }
  8. Object.defineProperty(o, k2, desc);
  9. }) : (function(o, m, k, k2) {
  10. if (k2 === undefined) k2 = k;
  11. o[k2] = m[k];
  12. }));
  13. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  14. Object.defineProperty(o, "default", { enumerable: true, value: v });
  15. }) : function(o, v) {
  16. o["default"] = v;
  17. });
  18. var __importStar = (this && this.__importStar) || (function () {
  19. var ownKeys = function(o) {
  20. ownKeys = Object.getOwnPropertyNames || function (o) {
  21. var ar = [];
  22. for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
  23. return ar;
  24. };
  25. return ownKeys(o);
  26. };
  27. return function (mod) {
  28. if (mod && mod.__esModule) return mod;
  29. var result = {};
  30. if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
  31. __setModuleDefault(result, mod);
  32. return result;
  33. };
  34. })();
  35. Object.defineProperty(exports, "__esModule", { value: true });
  36. exports.listDeployLogs = exports.getDeployLogs = exports.deploy = void 0;
  37. const child_process_1 = require("child_process");
  38. const fs = __importStar(require("fs"));
  39. const path = __importStar(require("path"));
  40. const crypto = __importStar(require("crypto"));
  41. const loggerService_1 = require("../services/loggerService");
  42. const DEPLOY_TOKEN = process.env.DEPLOY_TOKEN || 'mqtt-webhook-secure-2024-abc123xyz';
  43. const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || '';
  44. const DEPLOY_LOG_DIR = path.join(__dirname, '../../logs');
  45. const DEPLOY_BASE_DIR = process.env.DEPLOY_BASE_DIR || '/home/yangfei/mqtt';
  46. if (!fs.existsSync(DEPLOY_LOG_DIR)) {
  47. fs.mkdirSync(DEPLOY_LOG_DIR, { recursive: true });
  48. }
  49. function generateDeployId() {
  50. return `deploy-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  51. }
  52. function getBeijingTime() {
  53. const now = new Date();
  54. const beijingTime = new Date(now.getTime() + (8 * 60 * 60 * 1000));
  55. return beijingTime;
  56. }
  57. function formatBeijingTime(date) {
  58. const beijingTime = new Date(date.getTime() + (8 * 60 * 60 * 1000));
  59. return beijingTime.toISOString().replace('T', ' ').replace('Z', '').substring(0, 19);
  60. }
  61. async function executeDeployCommand(project, deployId) {
  62. return new Promise((resolve, reject) => {
  63. const logFile = path.join(DEPLOY_LOG_DIR, `${deployId}.log`);
  64. const logStream = fs.createWriteStream(logFile, { flags: 'a' });
  65. const startTime = getBeijingTime();
  66. console.log(`[${formatBeijingTime(startTime)}] 开始执行部署: ${project}, 日志文件: ${logFile}`);
  67. logStream.write(`[${formatBeijingTime(startTime)}] 开始执行部署: ${project}\n`);
  68. logStream.write(`部署基础目录: ${DEPLOY_BASE_DIR}\n`);
  69. logStream.write(`DEBUG模式: 已启用\n\n`);
  70. const scriptPath = '/home/yangfei/mqtt/mqtt-dashboard-backend/deploy.sh';
  71. const command = `bash ${scriptPath} ${project}`;
  72. const child = (0, child_process_1.spawn)('/bin/bash', ['-c', command], {
  73. env: {
  74. ...process.env,
  75. PATH: process.env.PATH
  76. }
  77. });
  78. let streamEnded = false;
  79. child.stdout.on('data', (data) => {
  80. if (!streamEnded) {
  81. const output = data.toString();
  82. console.log(`[${formatBeijingTime(new Date())}] ${output.trim()}`);
  83. logStream.write(output);
  84. }
  85. });
  86. child.stderr.on('data', (data) => {
  87. if (!streamEnded) {
  88. const error = data.toString();
  89. console.error(`[${formatBeijingTime(new Date())}] ${error.trim()}`);
  90. logStream.write(`ERROR: ${error}`);
  91. }
  92. });
  93. child.on('close', (code) => {
  94. if (!streamEnded) {
  95. const message = `\n部署完成,退出码: ${code}\n`;
  96. console.log(`[${formatBeijingTime(new Date())}] ${message.trim()}`);
  97. logStream.write(message);
  98. logStream.end();
  99. streamEnded = true;
  100. }
  101. if (code === 0) {
  102. resolve();
  103. }
  104. else {
  105. reject(new Error(`部署失败,退出码: ${code}`));
  106. }
  107. });
  108. child.on('error', (error) => {
  109. if (!streamEnded) {
  110. const message = `部署错误: ${error.message}\n`;
  111. console.error(`[${formatBeijingTime(new Date())}] ${message.trim()}`);
  112. logStream.write(message);
  113. logStream.end();
  114. streamEnded = true;
  115. }
  116. reject(error);
  117. });
  118. });
  119. }
  120. const deploy = async (req, res) => {
  121. try {
  122. let project;
  123. let source = 'manual';
  124. const isWebhook = req.headers['x-gogs-event'] === 'push' ||
  125. req.headers['x-github-event'] === 'push';
  126. if (isWebhook) {
  127. source = 'webhook';
  128. const webhookData = req.body;
  129. console.log(`[${formatBeijingTime(new Date())}] 收到 Webhook 请求`);
  130. console.log(`[${formatBeijingTime(new Date())}] WEBHOOK_SECRET 是否设置: ${!!WEBHOOK_SECRET}`);
  131. console.log(`[${formatBeijingTime(new Date())}] x-gogs-signature: ${req.headers['x-gogs-signature']}`);
  132. console.log(`[${formatBeijingTime(new Date())}] x-hub-signature-256: ${req.headers['x-hub-signature-256']}`);
  133. if (WEBHOOK_SECRET) {
  134. const signature = req.headers['x-gogs-signature'] ||
  135. req.headers['x-hub-signature-256'];
  136. console.log(`[${formatBeijingTime(new Date())}] 检测到的签名: ${signature}`);
  137. console.log(`[${formatBeijingTime(new Date())}] 请求体长度: ${JSON.stringify(req.body).length}`);
  138. if (!signature) {
  139. console.log(`[${formatBeijingTime(new Date())}] 签名验证失败: 未收到签名`);
  140. console.log(`[${formatBeijingTime(new Date())}] 临时禁用签名验证,继续部署`);
  141. }
  142. else {
  143. const isValid = verifyWebhookSignature(JSON.stringify(req.body), signature, WEBHOOK_SECRET);
  144. console.log(`[${formatBeijingTime(new Date())}] 签名验证结果: ${isValid}`);
  145. if (!isValid) {
  146. console.log(`[${formatBeijingTime(new Date())}] 签名验证失败: 签名不匹配`);
  147. console.log(`[${formatBeijingTime(new Date())}] 临时禁用签名验证,继续部署`);
  148. }
  149. }
  150. }
  151. if (webhookData.ref !== 'refs/heads/master') {
  152. res.json({
  153. success: true,
  154. message: '非 master 分支,跳过部署'
  155. });
  156. return;
  157. }
  158. project = determineProjectFromCommits(webhookData.commits);
  159. console.log(`[${formatBeijingTime(new Date())}] 收到 Webhook 部署请求: ${project}`);
  160. }
  161. else {
  162. const { token, timestamp } = req.body;
  163. project = req.body.project;
  164. if (!token || token !== DEPLOY_TOKEN) {
  165. res.status(401).json({
  166. success: false,
  167. message: '无效的部署令牌'
  168. });
  169. return;
  170. }
  171. if (!project || !['frontend', 'backend', 'all'].includes(project)) {
  172. res.status(400).json({
  173. success: false,
  174. message: '无效的项目参数'
  175. });
  176. return;
  177. }
  178. console.log(`[${formatBeijingTime(new Date())}] 收到手动部署请求: ${project}`);
  179. }
  180. const deployId = generateDeployId();
  181. console.log(`[${formatBeijingTime(new Date())}] 部署来源: ${source}, 项目: ${project}, ID: ${deployId}`);
  182. loggerService_1.LoggerService.info('部署任务开始', {
  183. source: 'deploy',
  184. module: 'deploy',
  185. details: JSON.stringify({
  186. deployId,
  187. project,
  188. source,
  189. ip: req.ip,
  190. userAgent: req.get('user-agent')
  191. })
  192. }).catch(err => {
  193. console.error('部署开始日志写入失败:', err);
  194. });
  195. res.json({
  196. success: true,
  197. deployId,
  198. message: '部署已启动',
  199. logUrl: `/api/deploy/logs/${deployId}`
  200. });
  201. executeDeployCommand(project, deployId)
  202. .then(() => {
  203. console.log(`[${formatBeijingTime(new Date())}] 部署成功: ${deployId}`);
  204. loggerService_1.LoggerService.info('部署任务完成', {
  205. source: 'deploy',
  206. module: 'deploy',
  207. details: JSON.stringify({
  208. deployId,
  209. project,
  210. source,
  211. status: 'success'
  212. })
  213. }).catch(err => {
  214. console.error('部署成功日志写入失败:', err);
  215. });
  216. })
  217. .catch((error) => {
  218. console.error(`[${formatBeijingTime(new Date())}] 部署失败: ${deployId}`, error);
  219. loggerService_1.LoggerService.error('部署任务失败', {
  220. source: 'deploy',
  221. module: 'deploy',
  222. details: JSON.stringify({
  223. deployId,
  224. project,
  225. source,
  226. status: 'failed',
  227. error: error instanceof Error ? error.message : '未知错误'
  228. })
  229. }).catch(err => {
  230. console.error('部署失败日志写入失败:', err);
  231. });
  232. });
  233. }
  234. catch (error) {
  235. console.error('部署接口错误:', error);
  236. loggerService_1.LoggerService.error('部署接口错误', {
  237. source: 'deploy',
  238. module: 'deploy',
  239. details: JSON.stringify({
  240. error: error instanceof Error ? error.message : '未知错误',
  241. ip: req.ip,
  242. userAgent: req.get('user-agent')
  243. })
  244. }).catch(err => {
  245. console.error('部署接口错误日志写入失败:', err);
  246. });
  247. res.status(500).json({
  248. success: false,
  249. message: error instanceof Error ? error.message : '部署失败'
  250. });
  251. }
  252. };
  253. exports.deploy = deploy;
  254. function verifyWebhookSignature(payload, signature, secret) {
  255. console.log(`[${new Date().toISOString()}] 签名验证详情:`);
  256. console.log(`[${new Date().toISOString()}] Secret 长度: ${secret.length}`);
  257. console.log(`[${new Date().toISOString()}] Secret 内容: [${secret}]`);
  258. console.log(`[${new Date().toISOString()}] Payload 长度: ${payload.length}`);
  259. console.log(`[${new Date().toISOString()}] Payload 前100字符: ${payload.substring(0, 100)}`);
  260. console.log(`[${new Date().toISOString()}] 收到的签名: ${signature}`);
  261. const expectedSignature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
  262. console.log(`[${new Date().toISOString()}] 计算的签名: ${expectedSignature}`);
  263. const receivedSignature = signature.startsWith('sha256=')
  264. ? signature.substring(7)
  265. : signature;
  266. console.log(`[${new Date().toISOString()}] 处理后的签名: ${receivedSignature}`);
  267. console.log(`[${new Date().toISOString()}] 签名是否匹配: ${receivedSignature === expectedSignature}`);
  268. return crypto.timingSafeEqual(Buffer.from(receivedSignature), Buffer.from(expectedSignature));
  269. }
  270. function determineProjectFromCommits(commits) {
  271. let hasFrontend = false;
  272. let hasBackend = false;
  273. for (const commit of commits) {
  274. const files = [
  275. ...(commit.added || []),
  276. ...(commit.removed || []),
  277. ...(commit.modified || [])
  278. ];
  279. for (const file of files) {
  280. if (file.startsWith('mqtt-dashboard-frontend/')) {
  281. hasFrontend = true;
  282. }
  283. else if (file.startsWith('mqtt-dashboard-backend/')) {
  284. hasBackend = true;
  285. }
  286. }
  287. }
  288. if (hasFrontend && hasBackend)
  289. return 'all';
  290. if (hasFrontend)
  291. return 'frontend';
  292. if (hasBackend)
  293. return 'backend';
  294. return 'all';
  295. }
  296. const getDeployLogs = async (req, res) => {
  297. try {
  298. const { deployId } = req.params;
  299. const logFile = path.join(DEPLOY_LOG_DIR, `${deployId}.log`);
  300. if (!fs.existsSync(logFile)) {
  301. res.status(404).json({
  302. success: false,
  303. message: '日志文件不存在'
  304. });
  305. return;
  306. }
  307. const logs = fs.readFileSync(logFile, 'utf-8');
  308. loggerService_1.LoggerService.info('部署日志查询成功', {
  309. source: 'deploy',
  310. module: 'get_deploy_logs',
  311. details: JSON.stringify({
  312. deployId,
  313. logSize: logs.length,
  314. ip: req.ip
  315. })
  316. }).catch(err => {
  317. console.error('部署日志查询成功日志写入失败:', err);
  318. });
  319. res.json({
  320. success: true,
  321. logs
  322. });
  323. }
  324. catch (error) {
  325. console.error('获取日志错误:', error);
  326. loggerService_1.LoggerService.error('部署日志查询失败', {
  327. source: 'deploy',
  328. module: 'get_deploy_logs',
  329. details: JSON.stringify({
  330. deployId: req.params.deployId,
  331. error: error instanceof Error ? error.message : '未知错误',
  332. ip: req.ip
  333. })
  334. }).catch(err => {
  335. console.error('部署日志查询失败日志写入失败:', err);
  336. });
  337. res.status(500).json({
  338. success: false,
  339. message: error instanceof Error ? error.message : '获取日志失败'
  340. });
  341. }
  342. };
  343. exports.getDeployLogs = getDeployLogs;
  344. const listDeployLogs = async (req, res) => {
  345. try {
  346. const files = fs.readdirSync(DEPLOY_LOG_DIR)
  347. .filter(file => file.endsWith('.log'))
  348. .map(file => {
  349. const filePath = path.join(DEPLOY_LOG_DIR, file);
  350. const stats = fs.statSync(filePath);
  351. return {
  352. deployId: file.replace('.log', ''),
  353. size: stats.size,
  354. created: stats.birthtime,
  355. modified: stats.mtime
  356. };
  357. })
  358. .sort((a, b) => b.created.getTime() - a.created.getTime());
  359. loggerService_1.LoggerService.info('部署日志列表查询成功', {
  360. source: 'deploy',
  361. module: 'list_deploy_logs',
  362. details: JSON.stringify({
  363. logCount: files.length,
  364. ip: req.ip
  365. })
  366. }).catch(err => {
  367. console.error('部署日志列表查询成功日志写入失败:', err);
  368. });
  369. res.json({
  370. success: true,
  371. logs: files
  372. });
  373. }
  374. catch (error) {
  375. console.error('列出日志错误:', error);
  376. loggerService_1.LoggerService.error('部署日志列表查询失败', {
  377. source: 'deploy',
  378. module: 'list_deploy_logs',
  379. details: JSON.stringify({
  380. error: error instanceof Error ? error.message : '未知错误',
  381. ip: req.ip
  382. })
  383. }).catch(err => {
  384. console.error('部署日志列表查询失败日志写入失败:', err);
  385. });
  386. res.status(500).json({
  387. success: false,
  388. message: error instanceof Error ? error.message : '列出日志失败'
  389. });
  390. }
  391. };
  392. exports.listDeployLogs = listDeployLogs;
  393. //# sourceMappingURL=deployController.js.map