Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot]andwhyour df7f13c6bf Optimize writeFileWithLock to avoid redundant chmod calls
- Track if file is newly created to skip redundant chmod
- Only call chmod for existing files that need permission changes
- Improves performance and reduces unnecessary system calls

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
2025-11-07 16:20:30 +00:00
copilot-swe-agent[bot]andwhyour 62831835a5 Fix SSH config file permissions race condition
- Modified writeFileWithLock to create files with correct permissions immediately
- Changed string mode values to proper octal numbers (0o600, 0o400)
- This eliminates the race condition where files existed with wrong permissions

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
2025-11-07 16:18:49 +00:00
copilot-swe-agent[bot] 8998b4078f Initial plan 2025-11-07 16:07:38 +00:00
whyour 73f8f3c5fa 增加 agent
Create a new agent configuration file for QL agent.
2025-11-08 00:05:56 +08:00
whyour 494ccb3c08 更新 workflow 2025-11-03 00:09:36 +08:00
whyour 399728b433 修复 jwt 认证 2025-11-02 22:28:58 +08:00
whyour 52a1de5063 更新 demo 地址 2025-11-02 21:54:19 +08:00
whyour 18f27a9a69 移除 nginx 2025-11-02 19:29:59 +08:00
whyour 07951964a1 修复模块注入 2025-10-26 22:32:03 +08:00
涛之雨andGitHub a1f888af59 Add validation to dependencies GET endpoint and update service logic (#2778)
* Add validation to dependencies GET endpoint and update service logic

* fix https://github.com/whyour/qinglong/pull/2778/files/6063bc3a67fb329de9b90f7c93524b862bd9eb93#r2266494581

* remove default condition type

* fix query mistakes
2025-10-11 23:23:13 +08:00
涛之雨andGitHub f7472b6e74 Add input validation to script API routes (#2777)
* Add input validation to script API routes

* 优化脚本 API 路由的错误处理逻辑

* Fix optional path compatibility checks

* remove file
2025-10-11 23:20:26 +08:00
whyour a7baeba755 修复 task 命令可能软链失败 2025-10-11 23:15:54 +08:00
涛之雨andGitHub e4f733320d Enable debug backend (#2776) 2025-08-19 11:13:33 +08:00
32 changed files with 13375 additions and 10411 deletions
+1 -2
View File
@@ -1,6 +1,5 @@
GRPC_PORT=5500 GRPC_PORT=5500
BACK_PORT=5600 BACK_PORT=5700
PORT=5700
LOG_LEVEL='info' LOG_LEVEL='info'
+4
View File
@@ -0,0 +1,4 @@
---
name: Bug Fixer
description: Fix this issue following our error handling pattern.
---
@@ -20,12 +20,14 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: wearerequired/git-mirror-action@v1 - uses: Yikun/hub-mirror-action@master
env:
SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PK }}
with: with:
source-repo: https://github.com/whyour/qinglong.git src: github/whyour
destination-repo: git@gitlab.com:whyour/qinglong.git dst: gitlab/whyour
dst_key: ${{ secrets.GITLAB_SSH_PK }}
dst_token: ${{ secrets.GITLAB_TOKEN }}
static_list: "qinglong"
force_update: true
code_gitee: code_gitee:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -33,12 +35,14 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: wearerequired/git-mirror-action@v1 - uses: Yikun/hub-mirror-action@master
env:
SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PK }}
with: with:
source-repo: https://github.com/whyour/qinglong.git src: github/whyour
destination-repo: git@gitee.com:whyour/qinglong.git dst: gitee/whyour
dst_key: ${{ secrets.GITLAB_SSH_PK }}
dst_token: ${{ secrets.GITEE_TOKEN }}
static_list: "qinglong"
force_update: true
build-static: build-static:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -82,12 +86,14 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: wearerequired/git-mirror-action@v1 - uses: Yikun/hub-mirror-action@master
env:
SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PK }}
with: with:
source-repo: https://github.com/whyour/qinglong-static.git src: github/whyour
destination-repo: git@gitlab.com:whyour/qinglong-static.git dst: gitlab/whyour
dst_key: ${{ secrets.GITLAB_SSH_PK }}
dst_token: ${{ secrets.GITLAB_TOKEN }}
static_list: "qinglong-static"
force_update: true
static_gitee: static_gitee:
needs: build-static needs: build-static
@@ -96,12 +102,14 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: wearerequired/git-mirror-action@v1 - uses: Yikun/hub-mirror-action@master
env:
SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PK }}
with: with:
source-repo: https://github.com/whyour/qinglong-static.git src: github/whyour
destination-repo: git@gitee.com:whyour/qinglong-static.git dst: gitee/whyour
dst_key: ${{ secrets.GITLAB_SSH_PK }}
dst_token: ${{ secrets.GITEE_TOKEN }}
static_list: "qinglong-static"
force_update: true
build: build:
needs: build-static needs: build-static
+1 -1
View File
@@ -17,7 +17,7 @@ export default defineConfig({
publicPath: process.env.NODE_ENV === 'production' ? './' : '/', publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
proxy: { proxy: {
[`${baseUrl}api`]: { [`${baseUrl}api`]: {
target: 'http://127.0.0.1:5600/', target: 'http://127.0.0.1:5700/',
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
pathRewrite: { [`^${baseUrl}api`]: '/api' }, pathRewrite: { [`^${baseUrl}api`]: '/api' },
+2 -2
View File
@@ -18,9 +18,9 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat [docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong [docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
[Demo](http://demo.ninesix.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong) [Demo](http://demo.qinglong.online:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
[演示](http://demo.ninesix.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg) [演示](http://demo.qinglong.online:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
</div> </div>
![cover](https://user-images.githubusercontent.com/22700758/244847235-8dc1ca21-e03f-4606-9458-0541fab60413.png) ![cover](https://user-images.githubusercontent.com/22700758/244847235-8dc1ca21-e03f-4606-9458-0541fab60413.png)
+2 -2
View File
@@ -20,9 +20,9 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat [docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=flat
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong [docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
[Demo](http://demo.ninesix.cc:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong) [Demo](http://demo.qinglong.online:4433/) / [Issues](https://github.com/whyour/qinglong/issues) / [Telegram Channel](https://t.me/jiao_long) / [Buy Me a Coffee](https://www.buymeacoffee.com/qinglong)
[演示](http://demo.ninesix.cc:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg) [演示](http://demo.qinglong.online:4433/) / [反馈](https://github.com/whyour/qinglong/issues) / [Telegram 频道](https://t.me/jiao_long) / [打赏开发者](https://user-images.githubusercontent.com/22700758/244744295-29cd0cd1-c8bb-4ea1-adf6-29bd390ad4dd.jpg)
</div> </div>
![cover](https://user-images.githubusercontent.com/22700758/244847235-8dc1ca21-e03f-4606-9458-0541fab60413.png) ![cover](https://user-images.githubusercontent.com/22700758/244847235-8dc1ca21-e03f-4606-9458-0541fab60413.png)
+22 -11
View File
@@ -8,17 +8,28 @@ const route = Router();
export default (app: Router) => { export default (app: Router) => {
app.use('/dependencies', route); app.use('/dependencies', route);
route.get('/', async (req: Request, res: Response, next: NextFunction) => { route.get(
const logger: Logger = Container.get('logger'); '/',
try { celebrate({
const dependenceService = Container.get(DependenceService); query:
const data = await dependenceService.dependencies(req.query as any); Joi.object({
return res.send({ code: 200, data }); searchValue: Joi.string().optional().allow(''),
} catch (e) { type: Joi.string().optional().allow(''),
logger.error('🔥 error: %o', e); status: Joi.string().optional().allow(''),
return next(e); }),
} }),
}); async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const dependenceService = Container.get(DependenceService);
const data = await dependenceService.dependencies(req.query as any);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.post( route.post(
'/', '/',
+86 -45
View File
@@ -24,55 +24,68 @@ const upload = multer({ storage: storage });
export default (app: Router) => { export default (app: Router) => {
app.use('/scripts', route); app.use('/scripts', route);
route.get('/', async (req: Request, res: Response, next: NextFunction) => { route.get(
const logger: Logger = Container.get('logger'); '/',
try { celebrate({
let result: IFile[] = []; query: Joi.object({
const blacklist = [ path: Joi.string().optional().allow(''),
'node_modules', }),
'.git', }),
'.pnpm', async (req: Request, res: Response, next: NextFunction) => {
'pnpm-lock.yaml', const logger: Logger = Container.get('logger');
'yarn.lock', try {
'package-lock.json', let result: IFile[] = [];
]; const blacklist = [
if (req.query.path) { 'node_modules',
result = await readDir( '.git',
req.query.path as string, '.pnpm',
config.scriptPath, 'pnpm-lock.yaml',
blacklist, 'yarn.lock',
); 'package-lock.json',
} else { ];
result = await readDirs( if (req.query.path) {
config.scriptPath, result = await readDir(
config.scriptPath, req.query.path as string,
blacklist, config.scriptPath,
(a, b) => { blacklist,
if (a.type === b.type) { );
return a.title.localeCompare(b.title); } else {
} else { result = await readDirs(
return a.type === 'directory' ? -1 : 1; config.scriptPath,
} config.scriptPath,
}, blacklist,
); (a, b) => {
if (a.type === b.type) {
return a.title.localeCompare(b.title);
} else {
return a.type === 'directory' ? -1 : 1;
}
},
);
}
res.send({
code: 200,
data: result,
});
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
} }
res.send({ });
code: 200,
data: result,
});
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
});
route.get( route.get(
'/detail', '/detail',
celebrate({
query: Joi.object({
path: Joi.string().optional().allow(''),
file: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
const scriptService = Container.get(ScriptService); const scriptService = Container.get(ScriptService);
const content = await scriptService.getFile( const content = await scriptService.getFile(
req.query.path as string, req.query?.path as string || '',
req.query.file as string, req.query.file as string,
); );
res.send({ code: 200, data: content }); res.send({ code: 200, data: content });
@@ -84,11 +97,19 @@ export default (app: Router) => {
route.get( route.get(
'/:file', '/:file',
celebrate({
params: Joi.object({
file: Joi.string().required(),
}),
query: Joi.object({
path: Joi.string().optional().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
const scriptService = Container.get(ScriptService); const scriptService = Container.get(ScriptService);
const content = await scriptService.getFile( const content = await scriptService.getFile(
req.query.path as string, req.query?.path as string || '',
req.params.file, req.params.file,
); );
res.send({ code: 200, data: content }); res.send({ code: 200, data: content });
@@ -101,6 +122,15 @@ export default (app: Router) => {
route.post( route.post(
'/', '/',
upload.single('file'), upload.single('file'),
celebrate({
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().optional().allow(''),
content: Joi.string().optional().allow(''),
originFilename: Joi.string().optional().allow(''),
directory: Joi.string().optional().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
let { filename, path, content, originFilename, directory } = let { filename, path, content, originFilename, directory } =
@@ -201,7 +231,7 @@ export default (app: Router) => {
celebrate({ celebrate({
body: Joi.object({ body: Joi.object({
filename: Joi.string().required(), filename: Joi.string().required(),
path: Joi.string().allow(''), path: Joi.string().optional().allow(''),
type: Joi.string().optional(), type: Joi.string().optional(),
}), }),
}), }),
@@ -211,6 +241,9 @@ export default (app: Router) => {
filename: string; filename: string;
path: string; path: string;
}; };
if (!path) {
path = '';
}
const scriptService = Container.get(ScriptService); const scriptService = Container.get(ScriptService);
const filePath = scriptService.checkFilePath(path, filename); const filePath = scriptService.checkFilePath(path, filename);
if (!filePath) { if (!filePath) {
@@ -276,6 +309,9 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');
try { try {
let { filename, content, path } = req.body; let { filename, content, path } = req.body;
if (!path) {
path = '';
}
const { name, ext } = parse(filename); const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`); const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
await writeFileWithLock(filePath, content || ''); await writeFileWithLock(filePath, content || '');
@@ -301,6 +337,9 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
let { filename, path, pid } = req.body; let { filename, path, pid } = req.body;
if (!path) {
path = '';
}
const { name, ext } = parse(filename); const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`); const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
const logPath = join(config.logPath, path, `${name}.swap`); const logPath = join(config.logPath, path, `${name}.swap`);
@@ -328,12 +367,14 @@ export default (app: Router) => {
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
let { filename, path, type, newFilename } = req.body as { let { filename, path, newFilename } = req.body as {
filename: string; filename: string;
path: string; path: string;
type: string;
newFilename: string; newFilename: string;
}; };
if (!path) {
path = '';
}
const filePath = join(config.scriptPath, path, filename); const filePath = join(config.scriptPath, path, filename);
const newPath = join(config.scriptPath, path, newFilename); const newPath = join(config.scriptPath, path, newFilename);
await fs.rename(filePath, newPath); await fs.rename(filePath, newPath);
+17 -6
View File
@@ -27,6 +27,13 @@ class Application {
constructor() { constructor() {
this.app = express(); this.app = express();
// 创建一个全局中间件,删除查询参数中的t
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
if (req.query.t) {
delete req.query.t;
}
next();
});
} }
async start() { async start() {
@@ -54,8 +61,7 @@ class Application {
if (metadata) { if (metadata) {
if (!this.isShuttingDown) { if (!this.isShuttingDown) {
Logger.error( Logger.error(
`${metadata.serviceType} worker ${worker.process.pid} died (${ `${metadata.serviceType} worker ${worker.process.pid} died (${signal || code
signal || code
}). Restarting...`, }). Restarting...`,
); );
const newWorker = this.forkWorker(metadata.serviceType); const newWorker = this.forkWorker(metadata.serviceType);
@@ -85,11 +91,14 @@ class Application {
} }
private async initializeDatabase() { private async initializeDatabase() {
await require('./loaders/db').default(); const dbLoader = await import('./loaders/db');
await dbLoader.default();
} }
private setupMiddlewares() { private setupMiddlewares() {
this.app.use(helmet()); this.app.use(helmet({
contentSecurityPolicy: false,
}));
this.app.use(cors(config.cors)); this.app.use(cors(config.cors));
this.app.use(compression()); this.app.use(compression());
this.app.use(monitoringMiddleware); this.app.use(monitoringMiddleware);
@@ -175,14 +184,16 @@ class Application {
const { HttpServerService } = await import('./services/http'); const { HttpServerService } = await import('./services/http');
this.httpServerService = Container.get(HttpServerService); this.httpServerService = Container.get(HttpServerService);
await require('./loaders/app').default({ app: this.app }); const appLoader = await import('./loaders/app');
await appLoader.default({ app: this.app });
const server = await this.httpServerService.initialize( const server = await this.httpServerService.initialize(
this.app, this.app,
config.port, config.port,
); );
await require('./loaders/server').default({ server }); const serverLoader = await import('./loaders/server');
await (serverLoader.default as any)({ server });
this.setupWorkerShutdown('http'); this.setupWorkerShutdown('http');
} }
+1 -1
View File
@@ -29,7 +29,7 @@ interface Config {
} }
const config: Config = { const config: Config = {
port: parseInt(process.env.BACK_PORT || '5600', 10), port: parseInt(process.env.BACK_PORT || '5700', 10),
grpcPort: parseInt(process.env.GRPC_PORT || '5500', 10), grpcPort: parseInt(process.env.GRPC_PORT || '5500', 10),
nodeEnv: process.env.NODE_ENV || 'development', nodeEnv: process.env.NODE_ENV || 'development',
isDevelopment: process.env.NODE_ENV === 'development', isDevelopment: process.env.NODE_ENV === 'development',
+7 -1
View File
@@ -13,7 +13,7 @@ async function linkToNodeModule(src: string, dst?: string) {
if (!stats) { if (!stats) {
await fs.symlink(source, target, 'dir'); await fs.symlink(source, target, 'dir');
} }
} catch (error) {} } catch (error) { }
} }
async function linkCommand() { async function linkCommand() {
@@ -36,6 +36,12 @@ async function linkCommand() {
const source = path.join(config.rootPath, 'shell', link.src); const source = path.join(config.rootPath, 'shell', link.src);
const target = path.join(commandDir, link.dest); const target = path.join(commandDir, link.dest);
const tmpTarget = path.join(commandDir, link.tmp); const tmpTarget = path.join(commandDir, link.tmp);
try {
const stats = await fs.lstat(tmpTarget);
if (stats) {
await fs.unlink(tmpTarget);
}
} catch (error) { }
await fs.symlink(source, tmpTarget); await fs.symlink(source, tmpTarget);
await fs.rename(tmpTarget, target); await fs.rename(tmpTarget, target);
} }
+18 -5
View File
@@ -9,6 +9,7 @@ import rewrite from 'express-urlrewrite';
import { errors } from 'celebrate'; import { errors } from 'celebrate';
import { serveEnv } from '../config/serverEnv'; import { serveEnv } from '../config/serverEnv';
import { IKeyvStore, shareStore } from '../shared/store'; import { IKeyvStore, shareStore } from '../shared/store';
import path from 'path';
export default ({ app }: { app: Application }) => { export default ({ app }: { app: Application }) => {
app.set('trust proxy', 'loopback'); app.set('trust proxy', 'loopback');
@@ -19,12 +20,15 @@ export default ({ app }: { app: Application }) => {
app.use(bodyParser.json({ limit: '50mb' })); app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })); app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
const frontendPath = path.join(config.rootPath, 'static/dist');
app.use(express.static(frontendPath));
app.use( app.use(
expressjwt({ expressjwt({
secret: config.jwt.secret, secret: config.jwt.secret,
algorithms: ['HS384'], algorithms: ['HS384'],
}).unless({ }).unless({
path: [...config.apiWhiteList, /^\/open\//], path: [...config.apiWhiteList, /^\/(?!api\/).*/],
}), }),
); );
@@ -39,6 +43,10 @@ export default ({ app }: { app: Application }) => {
}); });
app.use(async (req: Request, res, next) => { app.use(async (req: Request, res, next) => {
if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) {
return next();
}
const headerToken = getToken(req); const headerToken = getToken(req);
if (req.path.startsWith('/open/')) { if (req.path.startsWith('/open/')) {
const apps = await shareStore.getApps(); const apps = await shareStore.getApps();
@@ -110,10 +118,15 @@ 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.use((req, res, next) => { app.get('*', (_, res, next) => {
const err: any = new Error('Not Found'); const indexPath = path.join(frontendPath, 'index.html');
err['status'] = 404; res.sendFile(indexPath, (err) => {
next(err); if (err) {
const err: any = new Error('Not Found');
err['status'] = 404;
next(err);
}
});
}); });
app.use(errors()); app.use(errors());
+2 -1
View File
@@ -89,7 +89,8 @@ export default async () => {
setTimeout(async () => { setTimeout(async () => {
await dependenceService.installDependenceOneByOne(docs); await dependenceService.installDependenceOneByOne(docs);
require('./bootAfter').default(); const bootAfterLoader = await import('./bootAfter');
bootAfterLoader.default();
}, 5000); }, 5000);
}; };
+2 -2
View File
@@ -16,7 +16,7 @@ const addCron = (
} }
Logger.info( Logger.info(
'[schedule][创建定时任务], 任务ID: %s, 名称: %s, cron: %s, 执行命令: %s', '[schedule][创建定时任务] 任务ID: %s, 名称: %s, cron: %s, 执行命令: %s',
id, id,
name, name,
schedule, schedule,
@@ -26,7 +26,7 @@ const addCron = (
if (extra_schedules?.length) { if (extra_schedules?.length) {
extra_schedules.forEach((x) => { extra_schedules.forEach((x) => {
Logger.info( Logger.info(
'[schedule][创建定时任务], 任务ID: %s, 名称: %s, cron: %s, 执行命令: %s', '[schedule][创建定时任务] 任务ID: %s, 名称: %s, cron: %s, 执行命令: %s',
id, id,
name, name,
x.schedule, x.schedule,
+1 -1
View File
@@ -10,7 +10,7 @@ const delCron = (
for (const id of call.request.ids) { for (const id of call.request.ids) {
if (scheduleStacks.has(id)) { if (scheduleStacks.has(id)) {
Logger.info( Logger.info(
'[schedule][取消定时任务], 任务ID: %s', '[schedule][取消定时任务] 任务ID: %s',
id, id,
); );
scheduleStacks.get(id)?.forEach(x => x.cancel()); scheduleStacks.get(id)?.forEach(x => x.cancel());
+1 -1
View File
@@ -593,7 +593,7 @@ export default class CronService {
files.map(async (x) => ({ files.map(async (x) => ({
filename: x, filename: x,
directory: relativeDir.replace(config.logPath, ''), directory: relativeDir.replace(config.logPath, ''),
time: (await fs.lstat(`${dir}/${x}`)).mtime.getTime(), time: (await fs.lstat(`${dir}/${x}`)).birthtimeMs,
})), })),
) )
).sort((a, b) => b.time - a.time); ).sort((a, b) => b.time - a.time);
+12 -14
View File
@@ -28,7 +28,7 @@ export default class DependenceService {
constructor( constructor(
@Inject('logger') private logger: winston.Logger, @Inject('logger') private logger: winston.Logger,
private sockService: SockService, private sockService: SockService,
) {} ) { }
public async create(payloads: Dependence[]): Promise<Dependence[]> { public async create(payloads: Dependence[]): Promise<Dependence[]> {
const tabs = payloads.map((x) => { const tabs = payloads.map((x) => {
@@ -98,34 +98,32 @@ export default class DependenceService {
searchValue, searchValue,
type, type,
status, status,
}: { searchValue: string; type: string; status: string }, }: {
searchValue: string;
type: keyof typeof DependenceTypes;
status: string;
},
sort: any = [], sort: any = [],
query: any = {}, query: any = {},
): Promise<Dependence[]> { ): Promise<Dependence[]> {
let condition = { let condition = query;
...query, if (DependenceTypes[type]) {
type: DependenceTypes[type as any], condition.type = DependenceTypes[type];
}; }
if (status) { if (status) {
condition.status = status.split(',').map(Number); condition.status = status.split(',').map(Number);
} }
if (searchValue) { if (searchValue) {
const encodeText = encodeURI(searchValue); const encodeText = encodeURI(searchValue);
const reg = { condition.name = {
[Op.or]: [ [Op.or]: [
{ [Op.like]: `%${searchValue}%` }, { [Op.like]: `%${searchValue}%` },
{ [Op.like]: `%${encodeText}%` }, { [Op.like]: `%${encodeText}%` },
], ],
}; };
condition = {
...condition,
name: reg,
};
} }
try { try {
const result = await this.find(condition, sort); return await this.find(condition, sort);
return result as any;
} catch (error) { } catch (error) {
throw error; throw error;
} }
+4 -4
View File
@@ -139,7 +139,7 @@ export default class ScheduleService {
) { ) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info( this.logger.info(
'[panel][创建cron任务], 任务ID: %s, cron: %s, 任务名: %s, 执行命令: %s', '[panel][创建cron任务] 任务ID: %s, cron: %s, 任务名: %s, 执行命令: %s',
_id, _id,
schedule, schedule,
name, name,
@@ -172,7 +172,7 @@ export default class ScheduleService {
async cancelCronTask({ id = 0, name }: ScheduleTaskType) { async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info('[panel][取消定时任务], 任务名: %s', name); this.logger.info('[panel][取消定时任务] 任务名: %s', name);
if (this.scheduleStacks.has(_id)) { if (this.scheduleStacks.has(_id)) {
this.scheduleStacks.get(_id)?.cancel(); this.scheduleStacks.get(_id)?.cancel();
this.scheduleStacks.delete(_id); this.scheduleStacks.delete(_id);
@@ -187,7 +187,7 @@ export default class ScheduleService {
) { ) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info( this.logger.info(
'[panel][创建interval任务], 任务ID: %s, 任务名: %s, 执行命令: %s', '[panel][创建interval任务] 任务ID: %s, 任务名: %s, 执行命令: %s',
_id, _id,
name, name,
command, command,
@@ -232,7 +232,7 @@ export default class ScheduleService {
async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) { async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info( this.logger.info(
'[panel][取消interval任务], 任务ID: %s, 任务名: %s', '[panel][取消interval任务] 任务ID: %s, 任务名: %s',
_id, _id,
name, name,
); );
+7 -3
View File
@@ -26,12 +26,13 @@ export default class SshKeyService {
if (_exist) { if (_exist) {
config = await fs.readFile(this.sshConfigFilePath, { encoding: 'utf-8' }); config = await fs.readFile(this.sshConfigFilePath, { encoding: 'utf-8' });
} else { } else {
await writeFileWithLock(this.sshConfigFilePath, ''); await writeFileWithLock(this.sshConfigFilePath, '', { mode: 0o600 });
} }
if (!config.includes(this.sshConfigHeader)) { if (!config.includes(this.sshConfigHeader)) {
await writeFileWithLock( await writeFileWithLock(
this.sshConfigFilePath, this.sshConfigFilePath,
`${this.sshConfigHeader}\n\n${config}`, `${this.sshConfigHeader}\n\n${config}`,
{ mode: 0o600 },
); );
} }
} }
@@ -45,8 +46,7 @@ export default class SshKeyService {
path.join(this.sshPath, alias), path.join(this.sshPath, alias),
`${key}${os.EOL}`, `${key}${os.EOL}`,
{ {
encoding: 'utf8', mode: 0o400,
mode: '400',
}, },
); );
} catch (error) { } catch (error) {
@@ -81,6 +81,10 @@ export default class SshKeyService {
await writeFileWithLock( await writeFileWithLock(
`${path.join(this.sshPath, `${alias}.config`)}`, `${path.join(this.sshPath, `${alias}.config`)}`,
config, config,
{
encoding: 'utf8',
mode: 0o600,
},
); );
} }
+1 -1
View File
@@ -384,7 +384,7 @@ export default class SubscriptionService {
files.map(async (x) => ({ files.map(async (x) => ({
filename: x, filename: x,
directory: relativeDir.replace(config.logPath, ''), directory: relativeDir.replace(config.logPath, ''),
time: (await fs.lstat(`${dir}/${x}`)).mtime.getTime(), time: (await fs.lstat(`${dir}/${x}`)).birthtimeMs,
})), })),
) )
).sort((a, b) => b.time - a.time); ).sort((a, b) => b.time - a.time);
+8 -3
View File
@@ -19,9 +19,13 @@ export async function writeFileWithLock(
if (typeof options === 'string') { if (typeof options === 'string') {
options = { encoding: options }; options = { encoding: options };
} }
let isNewFile = false;
if (!(await fileExist(filePath))) { if (!(await fileExist(filePath))) {
const fileHandle = await open(filePath, 'w'); // Create the file with the specified mode if provided, otherwise use default
fileHandle.close(); const fileMode = options?.mode || 0o666;
const fileHandle = await open(filePath, 'w', fileMode);
await fileHandle.close();
isNewFile = true;
} }
const lockfilePath = getUniqueLockPath(filePath); const lockfilePath = getUniqueLockPath(filePath);
@@ -35,7 +39,8 @@ export async function writeFileWithLock(
lockfilePath, lockfilePath,
}); });
await writeFile(filePath, content, { encoding: 'utf8', ...options }); await writeFile(filePath, content, { encoding: 'utf8', ...options });
if (options?.mode) { // Only chmod if the file already existed (not just created with the correct mode)
if (!isNewFile && options?.mode) {
await chmod(filePath, options.mode); await chmod(filePath, options.mode);
} }
await release(); await release();
+1 -2
View File
@@ -39,7 +39,6 @@ RUN set -x \
tzdata \ tzdata \
perl \ perl \
openssl \ openssl \
nginx \
nodejs \ nodejs \
jq \ jq \
openssh \ openssh \
@@ -84,6 +83,6 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
WORKDIR ${QL_DIR} WORKDIR ${QL_DIR}
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
CMD curl -sf --noproxy '*' http://127.0.0.1:5600/api/health || exit 1 CMD curl -sf --noproxy '*' http://127.0.0.1:5700/api/health || exit 1
ENTRYPOINT ["./docker/docker-entrypoint.sh"] ENTRYPOINT ["./docker/docker-entrypoint.sh"]
+1 -2
View File
@@ -39,7 +39,6 @@ RUN set -x \
tzdata \ tzdata \
perl \ perl \
openssl \ openssl \
nginx \
nodejs \ nodejs \
jq \ jq \
openssh \ openssh \
@@ -84,6 +83,6 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
WORKDIR ${QL_DIR} WORKDIR ${QL_DIR}
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \ HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
CMD curl -sf --noproxy '*' http://127.0.0.1:5600/api/health || exit 1 CMD curl -sf --noproxy '*' http://127.0.0.1:5700/api/health || exit 1
ENTRYPOINT ["./docker/docker-entrypoint.sh"] ENTRYPOINT ["./docker/docker-entrypoint.sh"]
+3 -9
View File
@@ -14,26 +14,20 @@ log_with_style() {
log_with_style "INFO" "🚀 1. 检测配置文件..." log_with_style "INFO" "🚀 1. 检测配置文件..."
import_config "$@" import_config "$@"
make_dir /etc/nginx/conf.d
make_dir /run/nginx
init_nginx
fix_config fix_config
pm2 l &>/dev/null pm2 l &>/dev/null
log_with_style "INFO" "🔄 2. 启动 nginx..." log_with_style "INFO" "⚙️ 2. 启动 pm2 服务..."
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
log_with_style "INFO" "⚙️ 3. 启动 pm2 服务..."
reload_pm2 reload_pm2
if [[ $AutoStartBot == true ]]; then if [[ $AutoStartBot == true ]]; then
log_with_style "INFO" "🤖 4. 启动 bot..." log_with_style "INFO" "🤖 3. 启动 bot..."
nohup ql bot >$dir_log/bot.log 2>&1 & nohup ql bot >$dir_log/bot.log 2>&1 &
fi fi
if [[ $EnableExtraShell == true ]]; then if [[ $EnableExtraShell == true ]]; then
log_with_style "INFO" "🛠️ 5. 执行自定义脚本..." log_with_style "INFO" "🛠️ 4. 执行自定义脚本..."
nohup ql extra >$dir_log/extra.log 2>&1 & nohup ql extra >$dir_log/extra.log 2>&1 &
fi fi
-61
View File
@@ -1,61 +0,0 @@
upstream baseApi {
server 0.0.0.0:5600;
}
map $http_upgrade $connection_upgrade {
default keep-alive;
'websocket' upgrade;
}
server {
IPV4_CONFIG
IPV6_CONFIG
ssl_session_timeout 5m;
location QL_BASE_URLapi/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://baseApi/api/;
proxy_buffering off;
proxy_redirect default;
proxy_connect_timeout 1800;
proxy_send_timeout 1800;
proxy_read_timeout 1800;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location QL_BASE_URLopen/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://baseApi/open/;
proxy_buffering off;
proxy_redirect default;
proxy_connect_timeout 1800;
proxy_send_timeout 1800;
proxy_read_timeout 1800;
}
gzip on;
gzip_static on;
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.0;
QL_ROOT_CONFIG
location QL_BASE_URL_LOCATION {
QL_ALIAS_CONFIG
index index.html index.htm;
try_files $uri QL_BASE_URLindex.html;
}
location ~ .*\.(html)$ {
add_header Cache-Control no-cache;
}
}
-45
View File
@@ -1,45 +0,0 @@
user root;
worker_processes auto;
pcre_jit on;
error_log /var/log/nginx/error.log warn;
include /etc/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
client_max_body_size 4096m;
client_body_buffer_size 20m;
keepalive_timeout 65;
sendfile on;
tcp_nodelay on;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:2m;
gzip on;
gzip_static on;
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.0;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
include /etc/nginx/conf.d/*.conf;
}
+12 -2
View File
@@ -1,5 +1,15 @@
{ {
"watch": ["back", ".env"], "watch": [
"back",
".env"
],
"ext": "js,ts,json", "ext": "js,ts,json",
"exec": "ts-node -P ./back/tsconfig.json ./back/app.ts" "env": {
"NODE_ENV": "development",
"TS_NODE_PROJECT": "./back/tsconfig.json"
},
"verbose": true,
"execMap": {
"ts": "node --require ts-node/register"
}
} }
+5 -7
View File
@@ -2,13 +2,12 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "concurrently -n w: npm:start:*", "start": "concurrently -n w: npm:start:*",
"start:back": "nodemon", "start:back": "nodemon ./back/app.ts",
"start:front": "max dev", "start:front": "max dev",
"build:front": "max build", "build:front": "max build",
"build:back": "tsc -p back/tsconfig.json", "build:back": "tsc -p back/tsconfig.json",
"panel": "npm run build:back && node static/build/app.js", "panel": "npm run build:back && node static/build/app.js",
"gen:proto": "protoc --experimental_allow_proto3_optional --plugin=./node_modules/.bin/protoc-gen-ts_proto ./back/protos/*.proto --ts_proto_out=./ --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true,snakeToCamel=false", "gen:proto": "protoc --experimental_allow_proto3_optional --plugin=./node_modules/.bin/protoc-gen-ts_proto ./back/protos/*.proto --ts_proto_out=./ --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true,snakeToCamel=false",
"gen:api": "python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./back/protos/api.proto",
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
"postinstall": "max setup 2>/dev/null || true", "postinstall": "max setup 2>/dev/null || true",
"test": "umi-test", "test": "umi-test",
@@ -55,8 +54,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.12.3", "@grpc/grpc-js": "^1.14.0",
"@grpc/proto-loader": "^0.7.13", "@grpc/proto-loader": "^0.8.0",
"@otplib/preset-default": "^12.0.1", "@otplib/preset-default": "^12.0.1",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"celebrate": "^15.0.3", "celebrate": "^15.0.3",
@@ -81,11 +80,10 @@
"node-schedule": "^2.1.0", "node-schedule": "^2.1.0",
"nodemailer": "^6.9.16", "nodemailer": "^6.9.16",
"p-queue-cjs": "7.3.4", "p-queue-cjs": "7.3.4",
"@bufbuild/protobuf": "^2.2.3", "@bufbuild/protobuf": "^2.10.0",
"ps-tree": "^1.2.0", "ps-tree": "^1.2.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"serve-handler": "^6.1.6",
"sockjs": "^0.3.24", "sockjs": "^0.3.24",
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3", "sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3",
"toad-scheduler": "^3.0.1", "toad-scheduler": "^3.0.1",
@@ -99,7 +97,7 @@
"@keyv/sqlite": "^4.0.1", "@keyv/sqlite": "^4.0.1",
"proper-lockfile": "^4.1.2", "proper-lockfile": "^4.1.2",
"compression": "^1.7.4", "compression": "^1.7.4",
"helmet": "^6.0.1" "helmet": "^8.1.0"
}, },
"devDependencies": { "devDependencies": {
"moment": "2.30.1", "moment": "2.30.1",
+13116 -10046
View File
File diff suppressed because it is too large Load Diff
+8 -48
View File
@@ -41,14 +41,9 @@ add_cron_api() {
fi fi
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/crons?t=$currentTimeStamp" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"sub_id\":$sub_id}" \ --data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"sub_id\":$sub_id}" \
--compressed --compressed
) )
@@ -76,15 +71,10 @@ update_cron_api() {
fi fi
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/crons?t=$currentTimeStamp" \
-X 'PUT' \ -X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \ --data-raw "{\"name\":\"${name//\"/\\\"}\",\"command\":\"${command//\"/\\\"}\",\"schedule\":\"$schedule\",\"id\":\"$id\"}" \
--compressed --compressed
) )
@@ -108,15 +98,10 @@ update_cron_command_api() {
fi fi
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/crons?t=$currentTimeStamp" \
-X 'PUT' \ -X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--data-raw "{\"command\":\"${command//\"/\\\"}\",\"id\":\"$id\"}" \ --data-raw "{\"command\":\"${command//\"/\\\"}\",\"id\":\"$id\"}" \
--compressed --compressed
) )
@@ -133,15 +118,10 @@ del_cron_api() {
local ids="$1" local ids="$1"
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/crons?t=$currentTimeStamp" \
-X 'DELETE' \ -X 'DELETE' \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--data-raw "[$ids]" \ --data-raw "[$ids]" \
--compressed --compressed
) )
@@ -163,15 +143,10 @@ update_cron() {
local runningTime="${6:-0}" local runningTime="${6:-0}"
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/crons/status?t=$currentTimeStamp" \
-X 'PUT' \ -X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--data-raw "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \ --data-raw "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \
--compressed --compressed
) )
@@ -190,15 +165,10 @@ notify_api() {
local content="$2" local content="$2"
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/system/notify?t=$currentTimeStamp" \
-X 'PUT' \ -X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--data-raw "{\"title\":\"${title//\"/\\\"}\",\"content\":\"${content//\"/\\\"}\"}" \ --data-raw "{\"title\":\"${title//\"/\\\"}\",\"content\":\"${content//\"/\\\"}\"}" \
--compressed --compressed
) )
@@ -215,14 +185,9 @@ find_cron_api() {
local params="$1" local params="$1"
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/detail?$params&t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/crons/detail?$params&t=$currentTimeStamp" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--compressed --compressed
) )
data=$(echo "$api" | jq -r .data) data=$(echo "$api" | jq -r .data)
@@ -239,15 +204,10 @@ update_auth_config() {
local tip="$2" local tip="$2"
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/auth/reset?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/open/system/auth/reset?t=$currentTimeStamp" \
-X 'PUT' \ -X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer ${__ql_token__}" \ -H "Authorization: Bearer ${__ql_token__}" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
-H "Content-Type: application/json;charset=UTF-8" \ -H "Content-Type: application/json;charset=UTF-8" \
-H "Origin: http://0.0.0.0:5700" \
-H "Referer: http://0.0.0.0:5700/crontab" \
-H "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7" \
--data-raw "{$body}" \ --data-raw "{$body}" \
--compressed --compressed
) )
+1 -18
View File
@@ -20,10 +20,6 @@ copy_dep() {
echo -e "---> 复制一份 $file_notify_js_sample$file_notify_js\n" echo -e "---> 复制一份 $file_notify_js_sample$file_notify_js\n"
cp -fv $file_notify_js_sample $file_notify_js cp -fv $file_notify_js_sample $file_notify_js
echo -e "---> 通知文件复制完成\n" echo -e "---> 通知文件复制完成\n"
echo -e "---> 2. 复制nginx配置文件\n"
init_nginx
echo -e "---> 配置文件复制完成\n"
} }
pm2_log() { pm2_log() {
@@ -34,18 +30,6 @@ pm2_log() {
tail -n 300 "$panelError" tail -n 300 "$panelError"
} }
check_nginx() {
local nginxPid=$(ps -eo pid,command | grep nginx | grep -v grep)
echo -e "=====> 检测nginx服务\n$nginxPid"
if [[ $nginxPid ]]; then
echo -e "\n=====> nginx服务正常\n"
nginx -s reload
else
echo -e "\n=====> nginx服务异常,重新启动nginx\n"
nginx -c /etc/nginx/nginx.conf
fi
}
check_ql() { check_ql() {
local api=$(curl -s --noproxy "*" "http://0.0.0.0:5700") local api=$(curl -s --noproxy "*" "http://0.0.0.0:5700")
echo -e "\n=====> 检测面板\n\n$api\n" echo -e "\n=====> 检测面板\n\n$api\n"
@@ -58,7 +42,7 @@ check_pm2() {
pm2_log pm2_log
local currentTimeStamp=$(date +%s) local currentTimeStamp=$(date +%s)
local api=$( local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/system?t=$currentTimeStamp" \ curl -s --noproxy "*" "http://0.0.0.0:5700/api/system?t=$currentTimeStamp" \
-H 'Accept: */*' \ -H 'Accept: */*' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \
-H 'Referer: http://0.0.0.0:5700/crontab' \ -H 'Referer: http://0.0.0.0:5700/crontab' \
@@ -78,7 +62,6 @@ main() {
reset_env reset_env
copy_dep copy_dep
check_ql check_ql
check_nginx
check_pm2 check_pm2
reload_pm2 reload_pm2
echo -e "\n=====> 检测结束\n" echo -e "\n=====> 检测结束\n"
-44
View File
@@ -48,8 +48,6 @@ export file_notify_py=$dir_scripts/notify.py
export file_notify_js=$dir_scripts/sendNotify.js export file_notify_js=$dir_scripts/sendNotify.js
export file_test_js=$dir_scripts/ql_sample.js export file_test_js=$dir_scripts/ql_sample.js
export file_test_py=$dir_scripts/ql_sample.py export file_test_py=$dir_scripts/ql_sample.py
export nginx_app_conf=$dir_root/docker/front.conf
export nginx_conf=$dir_root/docker/nginx.conf
export dep_notify_py=$dir_dep/notify.py export dep_notify_py=$dir_dep/notify.py
export dep_notify_js=$dir_dep/sendNotify.js export dep_notify_js=$dir_dep/sendNotify.js
@@ -211,10 +209,6 @@ fix_config() {
cp -f $file_test_py_sample $file_test_py cp -f $file_test_py_sample $file_test_py
fi fi
if [[ -s /etc/nginx/conf.d/default.conf ]]; then
cat /dev/null >/etc/nginx/conf.d/default.conf
fi
if [[ ! -s $dep_notify_js ]]; then if [[ ! -s $dep_notify_js ]]; then
cp -f $file_notify_js_sample $dep_notify_js cp -f $file_notify_js_sample $dep_notify_js
fi fi
@@ -334,44 +328,6 @@ format_timestamp() {
fi fi
} }
init_nginx() {
cp -f $nginx_conf /etc/nginx/nginx.conf
cp -f $nginx_app_conf /etc/nginx/conf.d/front.conf
local location_url="/"
local aliasStr=""
local rootStr=""
if [[ $ql_base_url != "/" ]]; then
if [[ $ql_base_url != /* ]]; then
ql_base_url="/$ql_base_url"
fi
if [[ $ql_base_url != */ ]]; then
ql_base_url="$ql_base_url/"
fi
location_url="^~${ql_base_url%*/}"
aliasStr="alias ${dir_static}/dist;"
if ! grep -q "<base href=\"$ql_base_url\">" "${dir_static}/dist/index.html"; then
awk -v text="<base href=\"$ql_base_url\">" '/<link/ && !inserted {print text; inserted=1} 1' "${dir_static}/dist/index.html" >temp.html
mv temp.html "${dir_static}/dist/index.html"
fi
else
rootStr="root ${dir_static}/dist;"
fi
sed -i "s,QL_ALIAS_CONFIG,${aliasStr},g" /etc/nginx/conf.d/front.conf
sed -i "s,QL_ROOT_CONFIG,${rootStr},g" /etc/nginx/conf.d/front.conf
sed -i "s,QL_BASE_URL_LOCATION,${location_url},g" /etc/nginx/conf.d/front.conf
sed -i "s,QL_BASE_URL,${ql_base_url},g" /etc/nginx/conf.d/front.conf
local ipv6=$(ip a | grep inet6)
local ipv6Str=""
if [[ $ipv6 ]]; then
ipv6Str="listen [::]:${ql_port} ipv6only=on;"
fi
local ipv4Str="listen ${ql_port};"
sed -i "s,IPV6_CONFIG,${ipv6Str},g" /etc/nginx/conf.d/front.conf
sed -i "s,IPV4_CONFIG,${ipv4Str},g" /etc/nginx/conf.d/front.conf
}
get_env_array() { get_env_array() {
exported_variables=() exported_variables=()
while IFS= read -r line; do while IFS= read -r line; do