Compare commits

..
7 changed files with 72 additions and 87 deletions
-2
View File
@@ -206,7 +206,6 @@ export default (app: Router) => {
}), }),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try { try {
let { filename, content, path } = req.body as { let { filename, content, path } = req.body as {
filename: string; filename: string;
@@ -224,7 +223,6 @@ export default (app: Router) => {
await writeFileWithLock(filePath, content); await writeFileWithLock(filePath, content);
return res.send({ code: 200 }); return res.send({ code: 200 });
} catch (e) { } catch (e) {
logger.error('🔥 error saving script: %o', e);
return next(e); return next(e);
} }
}, },
+5 -1
View File
@@ -123,7 +123,11 @@ export default ({ app }: { app: Application }) => {
app.use(rewrite('/open/*', '/api/$1')); app.use(rewrite('/open/*', '/api/$1'));
app.use(config.api.prefix, routes()); app.use(config.api.prefix, routes());
app.get('*', (_, res, next) => { app.get('*', (req, res, next) => {
// Don't serve index.html for API routes
if (req.path.startsWith('/api/')) {
return next();
}
const indexPath = path.join(frontendPath, 'index.html'); const indexPath = path.join(frontendPath, 'index.html');
res.sendFile(indexPath, (err) => { res.sendFile(indexPath, (err) => {
if (err) { if (err) {
+1 -4
View File
@@ -467,10 +467,7 @@ export default class CronService {
for (const doc of docs) { for (const doc of docs) {
// Kill all running instances of this task // Kill all running instances of this task
try { try {
if (doc.pid) { const command = this.makeCommand(doc);
await killTask(doc.pid);
}
const command = doc.command.replace(/\s+/g, ' ').trim();
await killAllTasks(command); await killAllTasks(command);
this.logger.info( this.logger.info(
`[panel][停止所有运行中的任务实例] 任务ID: ${doc.id}, 命令: ${command}`, `[panel][停止所有运行中的任务实例] 任务ID: ${doc.id}, 命令: ${command}`,
+1 -2
View File
@@ -13,7 +13,6 @@ import {
stepPosition, stepPosition,
} from '../data/env'; } from '../data/env';
import { writeFileWithLock } from '../shared/utils'; import { writeFileWithLock } from '../shared/utils';
import { sequelize } from '../data';
@Service() @Service()
export default class EnvService { export default class EnvService {
@@ -147,7 +146,7 @@ export default class EnvService {
} }
try { try {
const result = await this.find(condition, [ const result = await this.find(condition, [
[sequelize.literal('COALESCE(`isPinned`, 0)'), 'DESC'], ['isPinned', 'DESC'],
['position', 'DESC'], ['position', 'DESC'],
['createdAt', 'ASC'], ['createdAt', 'ASC'],
]); ]);
-11
View File
@@ -16,17 +16,6 @@ export class HttpServerService {
metricsService.record('http_service_start', 1, { metricsService.record('http_service_start', 1, {
port: port.toString(), port: port.toString(),
}); });
// Set server timeouts to prevent premature connection drops
if (this.server) {
// Timeout for receiving the entire request (including body) - 5 minutes
this.server.requestTimeout = 300000;
// Timeout for headers - 2 minutes
this.server.headersTimeout = 120000;
// Keep-alive timeout - 65 seconds (slightly more than typical load balancer timeout)
this.server.keepAliveTimeout = 65000;
}
resolve(this.server); resolve(this.server);
}); });
+4 -42
View File
@@ -1,9 +1,8 @@
import { lock } from 'proper-lockfile'; import { lock } from 'proper-lockfile';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import { writeFile, open, chmod, FileHandle } from 'fs/promises'; import { writeFile, open, chmod } from 'fs/promises';
import { fileExist } from '../config/util'; import { fileExist } from '../config/util';
import Logger from '../loaders/logger';
function getUniqueLockPath(filePath: string) { function getUniqueLockPath(filePath: string) {
const sanitizedPath = filePath const sanitizedPath = filePath
@@ -20,32 +19,13 @@ export async function writeFileWithLock(
if (typeof options === 'string') { if (typeof options === 'string') {
options = { encoding: options }; options = { encoding: options };
} }
// Ensure file exists before locking
if (!(await fileExist(filePath))) { if (!(await fileExist(filePath))) {
let fileHandle: FileHandle | undefined; const fileHandle = await open(filePath, 'w');
try { fileHandle.close();
fileHandle = await open(filePath, 'w');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to create file ${filePath}: ${errorMessage}`);
} finally {
if (fileHandle !== undefined) {
try {
await fileHandle.close();
} catch (closeError) {
// Log close error but don't throw to avoid masking the original error
Logger.error(`Failed to close file handle for ${filePath}:`, closeError);
} }
}
}
}
const lockfilePath = getUniqueLockPath(filePath); const lockfilePath = getUniqueLockPath(filePath);
let release: (() => Promise<void>) | undefined;
try { const release = await lock(filePath, {
release = await lock(filePath, {
retries: { retries: {
retries: 10, retries: 10,
factor: 2, factor: 2,
@@ -54,27 +34,9 @@ export async function writeFileWithLock(
}, },
lockfilePath, lockfilePath,
}); });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to acquire lock for ${filePath}: ${errorMessage}`);
}
try {
await writeFile(filePath, content, { encoding: 'utf8', ...options }); await writeFile(filePath, content, { encoding: 'utf8', ...options });
if (options?.mode) { if (options?.mode) {
await chmod(filePath, options.mode); await chmod(filePath, options.mode);
} }
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to write to file ${filePath}: ${errorMessage}`);
} finally {
if (release) {
try {
await release(); await release();
} catch (error) {
// Log but don't throw on release failure
Logger.error(`Failed to release lock for ${filePath}:`, error);
}
}
}
} }
+46 -10
View File
@@ -1,11 +1,47 @@
version: 2.20.1 version: 2.20.0
changeLogLink: https://t.me/jiao_long/433 changeLogLink: https://t.me/jiao_long/432
publishTime: 2025-12-26 22:00 publishTime: 2025-12-10 01:05
changeLog: | changeLog: |
1. 修复获取依赖管理列表 1. 定时任务(cron / task)相关的大量修复 & 增强
2. notify.js 修复 TG_PROXY_AUTH 参数拼接
3. QLAPI.notify larkSecret 参数 修复 cron 解析错误(修复 parse cron / 升级 cron-parser
4. 修复 cron parser 定时规则校验 修复集群模式下定时任务可能不执行(race condition
5. 修复设置 baseUrl 后无法访问 定时任务支持订阅筛选
6. 修复环境变量排序 定时任务支持排序调整
7. 修复定时任务无法停止 定时任务支持自定义日志文件或无日志
修复任务实例默认值
任务支持单实例 / 多实例模式
修复 task 命令软链可能失败问题
2. 日志系统相关的大更新
修复日志目录逻辑
修复 pm2 日志目录
优化日志写入(stream pooling
3. 环境变量(env)系统的改进与修复
修复环境变量复制到剪贴板时可能失败
添加环境变量“置顶”功能
修复 QlPort 与 QlGrpcPort 环境变量在 host network 模式下被忽略
增加全局 SSH 私钥配置
4. Docker / 非 root 用户 / Alpine 兼容性增强
新增非 root Docker 用户支持,自动初始化命令
修复 Alpine 容器 DNS 解析失败(设置 ndots:0
修复 PM2 在 ARM 路由器(Node.js 不兼容)上的启动失败
移除 nginx(可能是考虑更轻量的镜像运行)
5. API 安全与校验增强
Dependencies GET endpoint 增加校验
Script API routes 增加输入校验
修复 JWT 认证问题
Feishu 机器人通知增加签名校验
QLAPI 增加 cron task 管理功能
修复 URIError(错误 cookie 导致白屏)
6. 系统设置
新增多终端/多平台的并发登录会话支持