mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-08 01:34:33 +08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 86e3d8736b | |||
| 85d32d00e1 | |||
| 6a52e2f804 | |||
| b4cb72c5d0 | |||
| 79fd4ec58f | |||
| 49ed5c6632 | |||
| ffba7ee4a1 | |||
| 91d080472f | |||
| 826f214f0f | |||
| ca150dc6a6 | |||
| ae5db60211 | |||
| 490bdc15f6 | |||
| 1377fe31aa | |||
| 3d1cf6bf2e | |||
| 6ac755b478 | |||
| e70fa83074 | |||
| 41521f1d08 | |||
| 8b7bef097c | |||
| 618756d4a9 | |||
| 70d0c71414 | |||
| 65eb9c806d | |||
| 50718ba714 | |||
| 7c3bb90d12 | |||
| f66390eabd | |||
| 481646501c | |||
| 5baa8e4391 | |||
| 3d11210aeb | |||
| e1863407f2 | |||
| 0c28b63ab7 | |||
| 8b8bd279c6 |
@@ -45,7 +45,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
version: '8.3.1'
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -85,8 +85,6 @@ jobs:
|
||||
git push --force --quiet gitlab ${GITHUB_BRANCH}:${GITHUB_BRANCH}
|
||||
|
||||
build:
|
||||
if: github.ref != 'refs/heads/master'
|
||||
|
||||
needs: build-static
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -99,7 +97,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
version: '8.3.1'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
@@ -131,11 +129,14 @@ jobs:
|
||||
ghcr.io/${{ github.repository }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
# nightly, master, pr-2, 1.2.3, 1.2, 1
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=schedule,pattern=nightly
|
||||
type=edge
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', 'master') }}
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
import { defineConfig } from '@umijs/max';
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
|
||||
const baseUrl = process.env.QlBaseUrl || '/';
|
||||
export default defineConfig({
|
||||
hash: true,
|
||||
antd: {},
|
||||
outputPath: 'static/dist',
|
||||
fastRefresh: true,
|
||||
favicons: ['./images/favicon.svg'],
|
||||
favicons: [`https://qn.whyour.cn/favicon.svg`],
|
||||
mfsu: {
|
||||
strategy: 'eager',
|
||||
},
|
||||
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
||||
proxy: {
|
||||
'/api/public': {
|
||||
[`${baseUrl}api/public`]: {
|
||||
target: 'http://127.0.0.1:5400/',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^/api/public': '/api/' },
|
||||
pathRewrite: { [`^${baseUrl}api/public`]: '/api' },
|
||||
},
|
||||
'/api': {
|
||||
[`${baseUrl}api`]: {
|
||||
target: 'http://127.0.0.1:5600/',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
pathRewrite: { [`^${baseUrl}api`]: '/api' },
|
||||
},
|
||||
},
|
||||
chainWebpack: ((config: any) => {
|
||||
@@ -38,6 +40,7 @@ export default defineConfig({
|
||||
'react-dom': 'window.ReactDOM',
|
||||
},
|
||||
headScripts: [
|
||||
`./api/env.js`,
|
||||
'https://gw.alipayobjects.com/os/lib/react/18.2.0/umd/react.production.min.js',
|
||||
'https://gw.alipayobjects.com/os/lib/react-dom/18.2.0/umd/react-dom.production.min.js',
|
||||
],
|
||||
|
||||
+1
-1
@@ -198,7 +198,7 @@ $ git clone git@github.com:whyour/qinglong.git
|
||||
$ cd qinglong
|
||||
$ cp .env.example .env
|
||||
# Recommended use pnpm https://pnpm.io/zh/installation
|
||||
$ npm install -g pnpm
|
||||
$ npm install -g pnpm@8.3.1
|
||||
$ pnpm install
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
@@ -59,6 +59,8 @@ podman run -dit \
|
||||
--network bridge \
|
||||
-v $PWD/ql/data:/ql/data \
|
||||
-p 5700:5700 \
|
||||
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
|
||||
-e QlBaseUrl="/" \
|
||||
--name qinglong \
|
||||
--hostname qinglong \
|
||||
docker.io/whyour/qinglong:latest
|
||||
@@ -97,6 +99,8 @@ systemctl restart docker
|
||||
docker run -dit \
|
||||
-v $PWD/ql/data:/ql/data \
|
||||
-p 5700:5700 \
|
||||
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
|
||||
-e QlBaseUrl="/" \
|
||||
--name qinglong \
|
||||
--hostname qinglong \
|
||||
--restart unless-stopped \
|
||||
@@ -197,7 +201,7 @@ $ git clone git@github.com:whyour/qinglong.git
|
||||
$ cd qinglong
|
||||
$ cp .env.example .env
|
||||
# 推荐使用 pnpm https://pnpm.io/zh/installation
|
||||
$ npm install -g pnpm
|
||||
$ npm install -g pnpm@8.3.1
|
||||
$ pnpm install
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
+4
-3
@@ -5,6 +5,7 @@ import { Logger } from 'winston';
|
||||
import config from '../config';
|
||||
import * as fs from 'fs';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import { join } from 'path';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
@@ -41,11 +42,11 @@ export default (app: Router) => {
|
||||
}
|
||||
if (req.params.file.includes('sample')) {
|
||||
content = getFileContentByName(
|
||||
`${config.samplePath}${req.params.file}`,
|
||||
join(config.samplePath, req.params.file),
|
||||
);
|
||||
} else {
|
||||
content = getFileContentByName(
|
||||
`${config.configPath}${req.params.file}`,
|
||||
join(config.configPath, req.params.file),
|
||||
);
|
||||
}
|
||||
res.send({ code: 200, data: content });
|
||||
@@ -70,7 +71,7 @@ export default (app: Router) => {
|
||||
if (config.blackFileList.includes(name)) {
|
||||
res.send({ code: 403, message: '文件无法访问' });
|
||||
}
|
||||
const path = `${config.configPath}${name}`;
|
||||
const path = join(config.configPath, name);
|
||||
fs.writeFileSync(path, content);
|
||||
res.send({ code: 200, message: '保存成功' });
|
||||
} catch (e) {
|
||||
|
||||
+3
-3
@@ -94,7 +94,7 @@ export default (app: Router) => {
|
||||
path += '/';
|
||||
}
|
||||
if (!path.startsWith('/')) {
|
||||
path = `${config.scriptPath}${path}`;
|
||||
path = join(config.scriptPath, path);
|
||||
}
|
||||
if (config.writePathList.every((x) => !path.startsWith(x))) {
|
||||
return res.send({
|
||||
@@ -124,7 +124,7 @@ export default (app: Router) => {
|
||||
if (fs.existsSync(originFilePath)) {
|
||||
fs.copyFileSync(
|
||||
originFilePath,
|
||||
`${config.bakPath}${originFilename.replace(/\//g, '')}`,
|
||||
join(config.bakPath, originFilename.replace(/\//g, '')),
|
||||
);
|
||||
if (filename !== originFilename) {
|
||||
fs.unlinkSync(originFilePath);
|
||||
@@ -207,7 +207,7 @@ export default (app: Router) => {
|
||||
let { filename } = req.body as {
|
||||
filename: string;
|
||||
};
|
||||
const filePath = `${config.scriptPath}${filename}`;
|
||||
const filePath = join(config.scriptPath, filename);
|
||||
// const stats = fs.statSync(filePath);
|
||||
// res.set({
|
||||
// 'Content-Type': 'application/octet-stream', //告诉浏览器这是一个二进制文件
|
||||
|
||||
+5
-1
@@ -170,6 +170,9 @@ export default (app: Router) => {
|
||||
await systemService.run(
|
||||
{ ...req.body, logPath },
|
||||
{
|
||||
onStart: async (cp, startTime) => {
|
||||
res.setHeader('QL-Task-Pid', `${cp.pid}`);
|
||||
},
|
||||
onEnd: async (cp, endTime, diff) => {
|
||||
res.end();
|
||||
},
|
||||
@@ -195,7 +198,8 @@ export default (app: Router) => {
|
||||
'/command-stop',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
command: Joi.string().required(),
|
||||
command: Joi.string().optional(),
|
||||
pid: Joi.number().optional(),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
@@ -18,6 +18,7 @@ async function startServer() {
|
||||
const server = app
|
||||
.listen(config.port, () => {
|
||||
Logger.debug(`✌️ 后端服务启动成功!`);
|
||||
process.send?.('ready');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
@@ -92,6 +92,11 @@ export default {
|
||||
'/api/system',
|
||||
'/api/user/init',
|
||||
'/api/user/notification/init',
|
||||
'/open/user/login',
|
||||
'/open/user/two-factor/login',
|
||||
'/open/system',
|
||||
'/open/user/init',
|
||||
'/open/user/notification/init',
|
||||
],
|
||||
versionFile,
|
||||
lastVersionFile,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
let pickedEnv: Record<string, string>;
|
||||
|
||||
function getPickedEnv() {
|
||||
if (pickedEnv) return pickedEnv;
|
||||
const picked = pick(process.env, ['QlBaseUrl']);
|
||||
pickedEnv = picked as Record<string, string>;
|
||||
return picked;
|
||||
}
|
||||
|
||||
export function serveEnv(_req: Request, res: Response) {
|
||||
res.type('.js');
|
||||
res.send(
|
||||
Object.entries(getPickedEnv())
|
||||
.map(([k, v]) => `window.__ENV__${k}=${JSON.stringify(v)};`)
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
import { Sequelize, Transaction } from 'sequelize';
|
||||
import config from '../config/index';
|
||||
import { join } from 'path';
|
||||
|
||||
export const sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: `${config.dbPath}database.sqlite`,
|
||||
storage: join(config.dbPath, 'database.sqlite'),
|
||||
logging: false,
|
||||
retry: {
|
||||
max: 10,
|
||||
|
||||
+13
-10
@@ -14,22 +14,25 @@ import handler from 'serve-handler';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { EnvModel } from '../data/env';
|
||||
import { errors } from 'celebrate';
|
||||
import path from 'path';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
import { serveEnv } from '../config/serverEnv';
|
||||
|
||||
export default ({ app }: { app: Application }) => {
|
||||
app.enable('trust proxy');
|
||||
app.use(cors());
|
||||
app.get(`${config.api.prefix}/env.js`, serveEnv);
|
||||
app.use(`${config.api.prefix}/static`, express.static(config.uploadPath));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.path.startsWith('/api') || req.path.startsWith('/open')) {
|
||||
next();
|
||||
} else {
|
||||
return handler(req, res, {
|
||||
public: 'static/dist',
|
||||
rewrites: [{ source: '**', destination: '/index.html' }],
|
||||
});
|
||||
}
|
||||
});
|
||||
app.use(
|
||||
'/api/public',
|
||||
createProxyMiddleware({
|
||||
target: `http://localhost:${config.publicPort}/api`,
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '/api/public': '' },
|
||||
}),
|
||||
);
|
||||
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import ScheduleService from '../services/schedule';
|
||||
import SubscriptionService from '../services/subscription';
|
||||
import config from '../config';
|
||||
import { fileExist } from '../config/util';
|
||||
import { join } from 'path';
|
||||
|
||||
export default async () => {
|
||||
const systemService = Container.get(SystemService);
|
||||
@@ -11,8 +12,9 @@ export default async () => {
|
||||
const subscriptionService = Container.get(SubscriptionService);
|
||||
|
||||
// 生成内置token
|
||||
let tokenCommand = `tsx ${config.rootPath}/back/token.ts`;
|
||||
const tokenFile = `${config.rootPath}static/build/token.js`;
|
||||
let tokenCommand = `tsx ${join(config.rootPath, 'back/token.ts')}`;
|
||||
const tokenFile = join(config.rootPath, 'static/build/token.js');
|
||||
|
||||
if (await fileExist(tokenFile)) {
|
||||
tokenCommand = `node ${tokenFile}`;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,14 @@ export default async ({ server }: { server: Server }) => {
|
||||
process.on('SIGINT', () => {
|
||||
Logger.info('✌️ Server need close');
|
||||
server.close(() => {
|
||||
Logger.info('✌️ Server closed');
|
||||
process.exit(0);
|
||||
setTimeout(() => {
|
||||
process.exit();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Forcing server close !!!');
|
||||
process.exit(1);
|
||||
}, 15000);
|
||||
});
|
||||
};
|
||||
|
||||
+3
-3
@@ -1,5 +1,4 @@
|
||||
import express from 'express';
|
||||
import { exec } from 'child_process';
|
||||
import Logger from './loaders/logger';
|
||||
import config from './config';
|
||||
import { HealthClient } from './protos/health';
|
||||
@@ -14,9 +13,9 @@ const client = new HealthClient(
|
||||
app.get('/api/health', (req, res) => {
|
||||
client.check({ service: 'cron' }, (err, response) => {
|
||||
if (err) {
|
||||
return res.status(500).send({ error: err });
|
||||
return res.status(500).send({ code: 500, error: err });
|
||||
}
|
||||
return res.status(200).send({ data: response });
|
||||
return res.status(200).send({ code: 200, data: response });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +25,7 @@ app
|
||||
await require('./loaders/db').default();
|
||||
|
||||
Logger.debug(`✌️ 公共服务启动成功!`);
|
||||
process.send?.('ready');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
@@ -17,10 +17,13 @@ const check = async (
|
||||
if (res.includes('200')) {
|
||||
return callback(null, { status: 1 });
|
||||
}
|
||||
const errLog = await promiseExec(
|
||||
const panelErrLog = await promiseExec(
|
||||
`tail -n 300 ~/.pm2/logs/panel-error.log`,
|
||||
);
|
||||
return callback(new Error(errLog));
|
||||
const scheduleErrLog = await promiseExec(
|
||||
`tail -n 300 ~/.pm2/logs/schedule-error.log`,
|
||||
);
|
||||
return callback(new Error(`${scheduleErrLog}\n${panelErrLog}`));
|
||||
|
||||
default:
|
||||
return callback(null, { status: 1 });
|
||||
|
||||
@@ -13,8 +13,12 @@ server.addService(CronService, { addCron, delCron });
|
||||
server.bindAsync(
|
||||
`localhost:${config.cronPort}`,
|
||||
ServerCredentials.createInsecure(),
|
||||
() => {
|
||||
(err, port) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
server.start();
|
||||
Logger.debug(`✌️ 定时服务启动成功!`);
|
||||
process.send?.('ready');
|
||||
},
|
||||
);
|
||||
|
||||
+83
-19
@@ -93,7 +93,11 @@ export default class NotificationService {
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return typeof res.id === 'number';
|
||||
if (typeof res.id === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -109,7 +113,11 @@ export default class NotificationService {
|
||||
headers: { Authorization: 'Bearer ' + goCqHttpBotToken },
|
||||
})
|
||||
.json();
|
||||
return res.retcode === 0;
|
||||
if (res.retcode === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -128,7 +136,11 @@ export default class NotificationService {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
.json();
|
||||
return res.errno === 0 || res.data.errno === 0;
|
||||
if (res.errno === 0 || res.data.errno === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -147,9 +159,14 @@ export default class NotificationService {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
.json();
|
||||
return (
|
||||
res.content.result.length !== undefined && res.content.result.length > 0
|
||||
);
|
||||
if (
|
||||
res.content.result.length !== undefined &&
|
||||
res.content.result.length > 0
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -166,7 +183,11 @@ export default class NotificationService {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
.json();
|
||||
return res.success;
|
||||
if (res.success) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -190,7 +211,11 @@ export default class NotificationService {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
.json();
|
||||
return res.code === 200;
|
||||
if (res.code === 200) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -234,7 +259,11 @@ export default class NotificationService {
|
||||
agent,
|
||||
})
|
||||
.json();
|
||||
return !!res.ok;
|
||||
if (res.ok) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -263,7 +292,11 @@ export default class NotificationService {
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return res.errcode === 0;
|
||||
if (res.errcode === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -284,7 +317,11 @@ export default class NotificationService {
|
||||
},
|
||||
})
|
||||
.json();
|
||||
return res.errcode === 0;
|
||||
if (res.errcode === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -359,7 +396,11 @@ export default class NotificationService {
|
||||
)
|
||||
.json();
|
||||
|
||||
return res.errcode === 0;
|
||||
if (res.errcode === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -403,8 +444,11 @@ export default class NotificationService {
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return res.code === 0;
|
||||
if (res.code === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -422,7 +466,11 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
|
||||
return res.ret === 0;
|
||||
if (res.ret === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -444,7 +492,11 @@ export default class NotificationService {
|
||||
})
|
||||
.json();
|
||||
|
||||
return res.code === 200;
|
||||
if (res.code === 200) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -468,7 +520,11 @@ export default class NotificationService {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.json();
|
||||
return res.StatusCode === 0;
|
||||
if (res.StatusCode === 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -495,7 +551,11 @@ export default class NotificationService {
|
||||
|
||||
transporter.close();
|
||||
|
||||
return !!info.messageId;
|
||||
if (info.messageId) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(info));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
@@ -529,7 +589,11 @@ export default class NotificationService {
|
||||
};
|
||||
try {
|
||||
const res = await got(formatUrl, options);
|
||||
return String(res.statusCode).startsWith('20');
|
||||
if (String(res.statusCode).startsWith('20')) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(res));
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response ? error.response.body : error);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@ export default class SshKeyService {
|
||||
if (host === 'github.com') {
|
||||
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa\n PubkeyAcceptedAlgorithms +ssh-rsa`;
|
||||
}
|
||||
const proxyStr = proxy ? ` ProxyCommand nc -v -x ${proxy} %h %p\n` : '';
|
||||
const proxyStr = proxy
|
||||
? ` ProxyCommand nc -v -x ${proxy} %h %p 2>/dev/null\n`
|
||||
: '';
|
||||
const config = `Host ${alias}\n Hostname ${host}\n IdentityFile ${path.join(
|
||||
this.sshPath,
|
||||
alias,
|
||||
|
||||
+13
-4
@@ -190,13 +190,22 @@ export default class SystemService {
|
||||
);
|
||||
}
|
||||
|
||||
public async stop({ command }: { command: string }) {
|
||||
public async stop({ command, pid }: { command: string; pid: number }) {
|
||||
if (!pid && !command) {
|
||||
return { code: 400, message: '参数错误' };
|
||||
}
|
||||
|
||||
if (pid) {
|
||||
await killTask(pid);
|
||||
return { code: 200 };
|
||||
}
|
||||
|
||||
if (!command.startsWith(TASK_COMMAND)) {
|
||||
command = `${TASK_COMMAND} ${command}`;
|
||||
}
|
||||
const pid = await getPid(command);
|
||||
if (pid) {
|
||||
await killTask(pid);
|
||||
const _pid = await getPid(command);
|
||||
if (_pid) {
|
||||
await killTask(_pid);
|
||||
return { code: 200 };
|
||||
} else {
|
||||
return { code: 400, message: '任务未找到' };
|
||||
|
||||
+20
-11
@@ -1,6 +1,11 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import { createRandomString, getNetIp, getPlatform } from '../config/util';
|
||||
import {
|
||||
createRandomString,
|
||||
fileExist,
|
||||
getNetIp,
|
||||
getPlatform,
|
||||
} from '../config/util';
|
||||
import config from '../config';
|
||||
import * as fs from 'fs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
@@ -205,17 +210,16 @@ export default class UserService {
|
||||
}
|
||||
|
||||
private initAuthInfo() {
|
||||
const newPassword = createRandomString(16, 22);
|
||||
fs.writeFileSync(
|
||||
config.authConfigFile,
|
||||
JSON.stringify({
|
||||
username: 'admin',
|
||||
password: newPassword,
|
||||
password: 'admin',
|
||||
}),
|
||||
);
|
||||
return {
|
||||
code: 100,
|
||||
message: '已初始化密码,请前往auth.json查看并重新登录',
|
||||
message: '未找到认证文件,重新初始化',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -240,13 +244,18 @@ export default class UserService {
|
||||
return { code: 200, data: avatar, message: '更新成功' };
|
||||
}
|
||||
|
||||
public getUserInfo(): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
fs.readFile(config.authConfigFile, 'utf8', (err, data) => {
|
||||
if (err) console.log(err);
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
public async getUserInfo(): Promise<any> {
|
||||
const authFileExist = await fileExist(config.authConfigFile);
|
||||
if (!authFileExist) {
|
||||
fs.writeFileSync(
|
||||
config.authConfigFile,
|
||||
JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
}),
|
||||
);
|
||||
}
|
||||
return this.getAuthInfo();
|
||||
}
|
||||
|
||||
public initTwoFactor() {
|
||||
|
||||
+6
-5
@@ -3,9 +3,9 @@ COPY package.json .npmrc pnpm-lock.yaml /tmp/build/
|
||||
RUN set -x \
|
||||
&& apk update \
|
||||
&& apk add nodejs npm git \
|
||||
&& npm i -g pnpm \
|
||||
&& npm i -g pnpm@8.3.1 \
|
||||
&& cd /tmp/build \
|
||||
&& pnpm install --prod
|
||||
&& pnpm --registry https://registry.npmmirror.com install --prod
|
||||
|
||||
FROM python:3.10-alpine
|
||||
|
||||
@@ -29,7 +29,6 @@ RUN set -x \
|
||||
&& apk upgrade \
|
||||
&& apk --no-cache add -f bash \
|
||||
coreutils \
|
||||
moreutils \
|
||||
git \
|
||||
curl \
|
||||
wget \
|
||||
@@ -41,6 +40,7 @@ RUN set -x \
|
||||
jq \
|
||||
openssh \
|
||||
procps \
|
||||
netcat-openbsd \
|
||||
npm \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& apk update \
|
||||
@@ -49,7 +49,8 @@ RUN set -x \
|
||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||
&& git config --global user.name "qinglong" \
|
||||
&& git config --global http.postBuffer 524288000 \
|
||||
&& npm install -g pnpm \
|
||||
&& npm install -g pnpm@8.3.1 \
|
||||
&& cd && pnpm config set registry https://registry.npmmirror.com \
|
||||
&& pnpm add -g pm2 tsx \
|
||||
&& rm -rf /root/.pnpm-store \
|
||||
&& rm -rf /root/.local/share/pnpm/store \
|
||||
@@ -71,7 +72,7 @@ COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
|
||||
|
||||
WORKDIR ${QL_DIR}
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=2s --retries=10 \
|
||||
HEALTHCHECK --interval=5s --timeout=2s --retries=20 \
|
||||
CMD curl -sf http://127.0.0.1:5400/api/health || exit 1
|
||||
|
||||
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
version: '2'
|
||||
services:
|
||||
web:
|
||||
# alpine 基础镜像版本
|
||||
image: whyour/qinglong:latest
|
||||
# debian-slim 基础镜像版本
|
||||
# image: whyour/qinglong:debian
|
||||
volumes:
|
||||
- ./data:/ql/data
|
||||
ports:
|
||||
- "0.0.0.0:5700:5700"
|
||||
environment:
|
||||
# 部署路径非必须,以斜杠开头和结尾,比如 /test/
|
||||
QlBaseUrl: '/'
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://127.0.0.1:5400/api/health", "||", "exit", "1"]
|
||||
interval: 2m
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
+14
-18
@@ -10,12 +10,21 @@ echo -e "======================1. 检测配置文件========================\n"
|
||||
make_dir /etc/nginx/conf.d
|
||||
make_dir /run/nginx
|
||||
init_nginx
|
||||
fix_config
|
||||
|
||||
pm2 l &>/dev/null
|
||||
pm2 flush &>/dev/null
|
||||
|
||||
echo -e "======================2. 安装依赖========================\n"
|
||||
patch_version
|
||||
if [[ $PipMirror ]]; then
|
||||
pip3 config set global.index-url $PipMirror
|
||||
fi
|
||||
current_npm_registry=$(cd && pnpm config get registry)
|
||||
is_equal_registry=$(echo $current_npm_registry | grep "${NpmMirror}")
|
||||
if [[ "$is_equal_registry" == "" ]]; then
|
||||
cd && pnpm config set registry $NpmMirror
|
||||
pnpm install -g
|
||||
fi
|
||||
update_depend
|
||||
echo
|
||||
|
||||
@@ -23,36 +32,23 @@ echo -e "======================3. 启动nginx========================\n"
|
||||
nginx -s reload 2>/dev/null || nginx -c /etc/nginx/nginx.conf
|
||||
echo -e "nginx启动成功...\n"
|
||||
|
||||
echo -e "======================4. 启动面板监控========================\n"
|
||||
pm2 delete public &>/dev/null
|
||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time
|
||||
echo -e "监控服务启动成功...\n"
|
||||
|
||||
echo -e "======================5. 启动主服务========================\n"
|
||||
pm2 delete panel &>/dev/null
|
||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time
|
||||
echo -e "主服务启动成功...\n"
|
||||
|
||||
echo -e "======================6. 启动定时服务========================\n"
|
||||
pm2 delete schedule &>/dev/null
|
||||
pm2 start $dir_static/build/schedule/index.js -n schedule --source-map-support --time
|
||||
echo -e "定时任务启动成功...\n"
|
||||
echo -e "======================4. 启动pm2服务========================\n"
|
||||
reload_pm2
|
||||
|
||||
if [[ $AutoStartBot == true ]]; then
|
||||
echo -e "======================7. 启动bot========================\n"
|
||||
echo -e "======================5. 启动bot========================\n"
|
||||
nohup ql -l bot >$dir_log/bot.log 2>&1 &
|
||||
echo -e "bot后台启动中...\n"
|
||||
fi
|
||||
|
||||
if [[ $EnableExtraShell == true ]]; then
|
||||
echo -e "======================8. 执行自定义脚本========================\n"
|
||||
echo -e "====================6. 执行自定义脚本========================\n"
|
||||
nohup ql -l extra >$dir_log/extra.log 2>&1 &
|
||||
echo -e "自定义脚本后台执行中...\n"
|
||||
fi
|
||||
|
||||
echo -e "############################################################\n"
|
||||
echo -e "容器启动成功..."
|
||||
echo -e "\n请先访问5700端口,登录成功面板之后再执行添加定时任务..."
|
||||
echo -e "############################################################\n"
|
||||
|
||||
crond -f >/dev/null
|
||||
|
||||
+10
-6
@@ -14,31 +14,33 @@ map $http_upgrade $connection_upgrade {
|
||||
server {
|
||||
listen 5700;
|
||||
IPV6_CONFIG
|
||||
root /ql/static/dist;
|
||||
ssl_session_timeout 5m;
|
||||
|
||||
location QL_BASE_URL/api/public/ {
|
||||
location QL_BASE_URLapi/public/ {
|
||||
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://publicApi/api/;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
location QL_BASE_URL/api/ {
|
||||
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_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
location QL_BASE_URL/open/ {
|
||||
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;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
@@ -49,10 +51,12 @@ server {
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.0;
|
||||
QL_ROOT_CONFIG
|
||||
|
||||
location QL_BASE_URL/ {
|
||||
location QL_BASE_URL_LOCATION {
|
||||
QL_ALIAS_CONFIG
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ QL_BASE_URL/index.html;
|
||||
try_files $uri QL_BASE_URLindex.html;
|
||||
}
|
||||
|
||||
location ~ .*\.(html)$ {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'schedule',
|
||||
max_restarts: 10,
|
||||
kill_timeout: 15000,
|
||||
wait_ready: true,
|
||||
listen_timeout: 10000,
|
||||
source_map_support: true,
|
||||
time: true,
|
||||
script: 'static/build/schedule/index.js',
|
||||
},
|
||||
{
|
||||
name: 'public',
|
||||
max_restarts: 10,
|
||||
kill_timeout: 15000,
|
||||
wait_ready: true,
|
||||
listen_timeout: 10000,
|
||||
source_map_support: true,
|
||||
time: true,
|
||||
script: 'static/build/public.js',
|
||||
},
|
||||
{
|
||||
name: 'panel',
|
||||
max_restarts: 10,
|
||||
kill_timeout: 15000,
|
||||
wait_ready: true,
|
||||
listen_timeout: 10000,
|
||||
source_map_support: true,
|
||||
time: true,
|
||||
script: 'static/build/app.js',
|
||||
},
|
||||
],
|
||||
};
|
||||
+2
-1
@@ -72,6 +72,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.8.2",
|
||||
"hpagent": "^0.1.2",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
@@ -86,7 +87,7 @@
|
||||
"sequelize": "^6.25.5",
|
||||
"serve-handler": "^6.1.3",
|
||||
"sockjs": "^0.3.24",
|
||||
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.2",
|
||||
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3",
|
||||
"toad-scheduler": "^1.6.0",
|
||||
"typedi": "^0.10.0",
|
||||
"uuid": "^8.3.2",
|
||||
|
||||
Generated
+3013
-2720
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 6.8 KiB |
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
create_token() {
|
||||
local token_command="tsx ${dir_root}/back/token.ts"
|
||||
local token_file="${dir_root}static/build/token.js"
|
||||
local token_file="${dir_root}/static/build/token.js"
|
||||
if [[ -f $token_file ]]; then
|
||||
token_command="node ${token_file}"
|
||||
fi
|
||||
|
||||
+12
-8
@@ -74,19 +74,23 @@ check_pm2() {
|
||||
fi
|
||||
}
|
||||
|
||||
start_public() {
|
||||
echo -e "=====> 启动公开服务\n"
|
||||
pm2 delete public --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
|
||||
}
|
||||
|
||||
main() {
|
||||
echo -e "=====> 开始检测"
|
||||
npm i -g pnpm
|
||||
npm i -g pnpm@8.3.1
|
||||
patch_version
|
||||
|
||||
apk add procps netcat-openbsd
|
||||
|
||||
if [[ $PipMirror ]]; then
|
||||
pip3 config set global.index-url $PipMirror
|
||||
fi
|
||||
if [[ $NpmMirror ]]; then
|
||||
cd && pnpm config set registry $NpmMirror
|
||||
pnpm install -g
|
||||
fi
|
||||
|
||||
pnpm add -g pm2 tsx
|
||||
reset_env
|
||||
start_public
|
||||
copy_dep
|
||||
check_ql
|
||||
check_nginx
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ run_else() {
|
||||
local relative_path="${file_param%/*}"
|
||||
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||
cd ${relative_path}
|
||||
file_param=${file_param/$relative_path\//}
|
||||
file_param=${file_param/$relative_path\//.\/}
|
||||
fi
|
||||
|
||||
shift
|
||||
|
||||
+44
-63
@@ -18,6 +18,7 @@ dir_update_log=$dir_log/update
|
||||
ql_static_repo=$dir_repo/static
|
||||
|
||||
## 文件
|
||||
file_ecosystem_js=$dir_root/ecosystem.config.js
|
||||
file_config_sample=$dir_sample/config.sample.sh
|
||||
file_env=$dir_config/env.sh
|
||||
file_sharecode=$dir_config/sharecode.sh
|
||||
@@ -34,7 +35,6 @@ file_notify_js_sample=$dir_sample/notify.js
|
||||
file_notify_py_sample=$dir_sample/notify.py
|
||||
file_notify_py=$dir_scripts/notify.py
|
||||
file_notify_js=$dir_scripts/sendNotify.js
|
||||
task_error_log_path=$dir_log/task_error.log
|
||||
nginx_app_conf=$dir_root/docker/front.conf
|
||||
nginx_conf=$dir_root/docker/nginx.conf
|
||||
dep_notify_py=$dir_dep/notify.py
|
||||
@@ -67,7 +67,7 @@ import_config() {
|
||||
[[ -f $file_config_user ]] && . $file_config_user
|
||||
[[ -f $file_env ]] && . $file_env
|
||||
|
||||
ql_base_url=${QlBaseUrl:-""}
|
||||
ql_base_url=${QlBaseUrl:-"/"}
|
||||
command_timeout_time=${CommandTimeoutTime:-""}
|
||||
proxy_url=${ProxyUrl:-""}
|
||||
file_extensions=${RepoFileExtensions:-"js py"}
|
||||
@@ -98,6 +98,9 @@ set_proxy() {
|
||||
unset_proxy() {
|
||||
unset http_proxy
|
||||
unset https_proxy
|
||||
unset ftp_proxy
|
||||
unset all_proxy
|
||||
unset no_proxy
|
||||
}
|
||||
|
||||
make_dir() {
|
||||
@@ -262,17 +265,6 @@ npm_install_sub() {
|
||||
fi
|
||||
}
|
||||
|
||||
npm_install_1() {
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
|
||||
cd $dir_work
|
||||
echo -e "运行 pnpm install...\n"
|
||||
npm_install_sub
|
||||
[[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 pnpm install...\n"
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
npm_install_2() {
|
||||
local dir_current=$(pwd)
|
||||
local dir_work=$1
|
||||
@@ -280,10 +272,6 @@ npm_install_2() {
|
||||
cd $dir_work
|
||||
echo -e "安装 $dir_work 依赖包...\n"
|
||||
npm_install_sub
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n"
|
||||
npm_install_1 $dir_work
|
||||
fi
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
@@ -315,11 +303,13 @@ git_clone_scripts() {
|
||||
echo -e "开始克隆仓库 $url 到 $dir\n"
|
||||
|
||||
set_proxy "$proxy"
|
||||
|
||||
git clone --depth=1 $part_cmd $url $dir
|
||||
exit_status=$?
|
||||
unset_proxy
|
||||
|
||||
reset_branch "$branch"
|
||||
reset_branch "$branch" "$dir"
|
||||
|
||||
unset_proxy
|
||||
}
|
||||
|
||||
git_pull_scripts() {
|
||||
@@ -329,19 +319,25 @@ git_pull_scripts() {
|
||||
local proxy="$3"
|
||||
cd $dir_work
|
||||
echo -e "开始更新仓库:$dir_work"
|
||||
set_proxy "$proxy"
|
||||
|
||||
if [[ ! $branch ]]; then
|
||||
branch=$(cd $dir_work && git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
|
||||
fi
|
||||
|
||||
local pre_commit_id=$(git rev-parse --short HEAD)
|
||||
set_proxy "$proxy"
|
||||
git fetch --depth=1 --all
|
||||
git pull --depth=1 &>/dev/null
|
||||
exit_status=$?
|
||||
unset_proxy
|
||||
reset_branch "$branch" "$dir_work"
|
||||
|
||||
reset_branch "$branch"
|
||||
git fetch --depth 1 origin $branch
|
||||
exit_status=$?
|
||||
|
||||
reset_branch "$branch" "$dir_work"
|
||||
local cur_commit_id=$(git rev-parse --short HEAD)
|
||||
if [[ $cur_commit_id != $pre_commit_id ]]; then
|
||||
exit_status=0
|
||||
fi
|
||||
|
||||
unset_proxy
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
@@ -359,19 +355,16 @@ reset_romote_url() {
|
||||
git init
|
||||
git remote add origin $url &>/dev/null
|
||||
fi
|
||||
|
||||
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
reset_branch() {
|
||||
local branch="$1"
|
||||
local part_cmd="HEAD"
|
||||
if [[ $branch ]]; then
|
||||
part_cmd="origin/${branch}"
|
||||
git checkout -B "$branch" &>/dev/null
|
||||
git branch --set-upstream-to=$part_cmd $branch &>/dev/null
|
||||
fi
|
||||
local part_cmd="origin/${branch}"
|
||||
git remote set-branches origin $branch
|
||||
git reset --hard $part_cmd &>/dev/null
|
||||
git checkout -b $branch $part_cmd &>/dev/null
|
||||
}
|
||||
|
||||
random_range() {
|
||||
@@ -381,16 +374,11 @@ random_range() {
|
||||
}
|
||||
|
||||
reload_pm2() {
|
||||
pm2 l &>/dev/null
|
||||
cd $dir_root
|
||||
# 代理会影响 grpc 服务
|
||||
unset_proxy
|
||||
pm2 flush &>/dev/null
|
||||
|
||||
echo -e "启动面板服务\n"
|
||||
pm2 delete panel --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null
|
||||
|
||||
echo -e "启动定时服务\n"
|
||||
pm2 delete schedule --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/schedule/index.js -n schedule --source-map-support --time &>/dev/null
|
||||
pm2 startOrGracefulReload $file_ecosystem_js
|
||||
}
|
||||
|
||||
diff_time() {
|
||||
@@ -440,19 +428,6 @@ format_timestamp() {
|
||||
}
|
||||
|
||||
patch_version() {
|
||||
# 兼容pnpm@7
|
||||
pnpm setup &>/dev/null
|
||||
source ~/.bashrc
|
||||
apk add procps
|
||||
|
||||
if [[ $PipMirror ]]; then
|
||||
pip3 config set global.index-url $PipMirror
|
||||
fi
|
||||
if [[ $NpmMirror ]]; then
|
||||
cd && pnpm config set registry $NpmMirror
|
||||
pnpm install -g --force
|
||||
fi
|
||||
|
||||
git config --global pull.rebase false
|
||||
|
||||
cp -f $dir_root/.env.example $dir_root/.env
|
||||
@@ -492,8 +467,20 @@ patch_version() {
|
||||
init_nginx() {
|
||||
cp -fv $nginx_conf /etc/nginx/nginx.conf
|
||||
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
|
||||
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
|
||||
|
||||
local location_url="/"
|
||||
local aliasStr=""
|
||||
local rootStr=""
|
||||
if [[ $ql_base_url != "/" ]]; then
|
||||
location_url="^~${ql_base_url%*/}"
|
||||
aliasStr="alias ${dir_static}/dist;"
|
||||
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
|
||||
|
||||
ipv6=$(ip a | grep inet6)
|
||||
ipv6Str=""
|
||||
if [[ $ipv6 ]]; then
|
||||
@@ -509,11 +496,6 @@ handle_task_before() {
|
||||
|
||||
[[ $is_macos -eq 0 ]] && check_server
|
||||
|
||||
if [[ -s $task_error_log_path ]]; then
|
||||
cat $task_error_log_path
|
||||
echo -e "加载 config.sh 出错,请手动检查"
|
||||
fi
|
||||
|
||||
. $file_task_before "$@"
|
||||
}
|
||||
|
||||
@@ -536,6 +518,5 @@ init_env
|
||||
detect_termux
|
||||
detect_macos
|
||||
define_cmd
|
||||
fix_config
|
||||
|
||||
import_config $1 2>$task_error_log_path
|
||||
import_config $1
|
||||
|
||||
+6
-9
@@ -243,10 +243,9 @@ update_qinglong() {
|
||||
echo -e "使用 ${mirror} 源更新...\n"
|
||||
export isFirstStartServer=false
|
||||
|
||||
local all_branch=$(cd ${dir_root} && git branch -a)
|
||||
local primary_branch="master"
|
||||
if [[ "${all_branch}" =~ "${current_branch}" ]]; then
|
||||
primary_branch="${current_branch}"
|
||||
if [[ "${QL_BRANCH}" == "develop" ]]; then
|
||||
primary_branch="develop"
|
||||
fi
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
||||
reset_romote_url ${dir_root} "https://${mirror}.com/whyour/qinglong.git" ${primary_branch}
|
||||
@@ -257,6 +256,9 @@ update_qinglong() {
|
||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
||||
update_depend
|
||||
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
||||
[[ "$ql_depend_old" != "$ql_depend_new" ]] && npm_install_2 $dir_root
|
||||
|
||||
update_qinglong_static "$1" "$primary_branch"
|
||||
else
|
||||
echo -e "\n更新青龙源文件失败,请检查网络...\n"
|
||||
@@ -454,12 +456,6 @@ main() {
|
||||
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
|
||||
fi
|
||||
|
||||
if [[ -s $task_error_log_path ]]; then
|
||||
eval cat $task_error_log_path $cmd
|
||||
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
|
||||
eval echo $cmd
|
||||
fi
|
||||
|
||||
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
|
||||
eval echo -e "请移除 -l 参数" $cmd
|
||||
exit 1
|
||||
@@ -467,6 +463,7 @@ main() {
|
||||
|
||||
case $p1 in
|
||||
update)
|
||||
fix_config
|
||||
eval update_qinglong "$2" $cmd
|
||||
;;
|
||||
extra)
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
const baseUrl = window.__ENV__QlBaseUrl || '/';
|
||||
|
||||
export function modifyClientRenderOpts(memo: any) {
|
||||
return {
|
||||
...memo,
|
||||
publicPath: baseUrl,
|
||||
basename: baseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function modifyContextOpts(memo: any) {
|
||||
return {
|
||||
...memo,
|
||||
basename: baseUrl,
|
||||
};
|
||||
}
|
||||
@@ -332,8 +332,14 @@ select:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.ant-pro-sider-logo {
|
||||
h1 {
|
||||
margin-left: 5px !important;
|
||||
.title {
|
||||
height: 32px;
|
||||
margin: 0 5px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
vertical-align: middle;
|
||||
animation: pro-layout-title-hide 0.3s;
|
||||
}
|
||||
|
||||
img {
|
||||
|
||||
+23
-10
@@ -98,8 +98,7 @@ export default function () {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => setInitLoading(false));
|
||||
});
|
||||
};
|
||||
|
||||
const getUser = (needLoading = true) => {
|
||||
@@ -120,6 +119,22 @@ export default function () {
|
||||
});
|
||||
};
|
||||
|
||||
const getHealthStatus = () => {
|
||||
request
|
||||
.get(`${config.apiPrefix}public/health`)
|
||||
.then((res) => {
|
||||
if (res?.data?.status === 1) {
|
||||
getSystemInfo();
|
||||
} else {
|
||||
history.push('/error');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
history.push('/error');
|
||||
})
|
||||
.finally(() => setInitLoading(false));
|
||||
};
|
||||
|
||||
const reloadUser = (needLoading = false) => {
|
||||
getUser(needLoading);
|
||||
};
|
||||
@@ -131,10 +146,8 @@ export default function () {
|
||||
}, [location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemInfo) {
|
||||
getSystemInfo();
|
||||
}
|
||||
}, [systemInfo]);
|
||||
getHealthStatus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (theme === 'vs-dark') {
|
||||
@@ -267,11 +280,10 @@ export default function () {
|
||||
selectedKeys={[location.pathname]}
|
||||
loading={loading}
|
||||
ErrorBoundary={Sentry.ErrorBoundary}
|
||||
logo={<Image preview={false} src="https://qn.whyour.cn/logo.png" />}
|
||||
// @ts-ignore
|
||||
title={
|
||||
logo={
|
||||
<>
|
||||
<span style={{ fontSize: 16, marginRight: 5 }}>青龙</span>
|
||||
<Image preview={false} src="https://qn.whyour.cn/logo.png" />
|
||||
<span className="title">青龙</span>
|
||||
<a
|
||||
href={systemInfo?.changeLogLink}
|
||||
target="_blank"
|
||||
@@ -300,6 +312,7 @@ export default function () {
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
title={false}
|
||||
menuItemRender={(menuItemProps: any, defaultDom: any) => {
|
||||
if (
|
||||
menuItemProps.isUrl ||
|
||||
|
||||
@@ -253,9 +253,9 @@ const ViewCreateModal = ({
|
||||
name={[name, 'value']}
|
||||
rules={[{ required: true, message: '请输入内容' }]}
|
||||
>
|
||||
{EOperation[filtersValue[name]['operation']] ===
|
||||
{EOperation[filtersValue?.[name]['operation']] ===
|
||||
'select' ? (
|
||||
statusElement(filtersValue[name]['property'])
|
||||
statusElement(filtersValue?.[name]['property'])
|
||||
) : (
|
||||
<Input placeholder="请输入内容" />
|
||||
)}
|
||||
|
||||
@@ -246,7 +246,7 @@ const Dependence = () => {
|
||||
})
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200 && force) {
|
||||
const i = value.findIndex((x) => x.id === data.data[0].id);
|
||||
const i = value.findIndex((x) => x.id === data[0].id);
|
||||
if (i !== -1) {
|
||||
const result = [...value];
|
||||
result.splice(i, 1);
|
||||
|
||||
Vendored
+1
-1
@@ -394,7 +394,7 @@ const Env = () => {
|
||||
if (code === 200) {
|
||||
const newData = [...value];
|
||||
newData.splice(dragIndex, 1);
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
|
||||
newData.splice(hoverIndex, 0, { ...dragRow, ...data });
|
||||
setValue([...newData]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
.code-box {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
width: 80vw;
|
||||
height: 90vh;
|
||||
margin: 16px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid rgba(5, 5, 5, 0.06);
|
||||
@@ -44,9 +45,10 @@
|
||||
}
|
||||
|
||||
.log {
|
||||
height: calc(100vh - 150px);
|
||||
height: calc(90vh - 150px);
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ const Error = () => {
|
||||
needLoading && setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}public/health`)
|
||||
.then(({ status, error }) => {
|
||||
if (status === 1) {
|
||||
return reloadUser();
|
||||
.then(({ error, data }) => {
|
||||
if (data?.status === 1) {
|
||||
return;
|
||||
}
|
||||
if (retryTimes.current > 3) {
|
||||
setData(error?.details);
|
||||
@@ -27,7 +27,6 @@ const Error = () => {
|
||||
}
|
||||
retryTimes.current += 1;
|
||||
setTimeout(() => {
|
||||
reloadUser();
|
||||
getLog(false);
|
||||
}, 3000);
|
||||
})
|
||||
@@ -53,10 +52,33 @@ const Error = () => {
|
||||
<div className="browser-markup"></div>
|
||||
<Alert
|
||||
type="error"
|
||||
message="服务启动超时,请检查如下日志或者进入容器执行 ql -l check 后刷新再试"
|
||||
message={
|
||||
<Typography.Title level={5} type="danger">
|
||||
服务启动超时
|
||||
</Typography.Title>
|
||||
}
|
||||
description={
|
||||
<Typography.Text type="danger">
|
||||
<div>请先按如下方式修复:</div>
|
||||
<div>
|
||||
1. 宿主机执行 docker run --rm -v
|
||||
/var/run/docker.sock:/var/run/docker.sock
|
||||
containrrr/watchtower -cR <容器名>
|
||||
</div>
|
||||
<div>2. 容器内执行 ql -l check、ql -l update</div>
|
||||
<div>
|
||||
3. 如果无法解决,容器内执行 pm2 logs,拷贝执行结果
|
||||
<Typography.Link href="https://github.com/whyour/qinglong/issues/new?assignees=&labels=&template=bug_report.yml">
|
||||
提交 issue
|
||||
</Typography.Link>
|
||||
</div>
|
||||
</Typography.Text>
|
||||
}
|
||||
banner
|
||||
/>
|
||||
<Typography.Paragraph className="log">{data}</Typography.Paragraph>
|
||||
<Typography.Paragraph code className="log">
|
||||
{data}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
) : (
|
||||
<PageLoading tip="启动中,请稍后..." />
|
||||
|
||||
@@ -219,7 +219,7 @@ const Initialization = () => {
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
history.push('/login');
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
去登录
|
||||
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
const baseUrl = window.__ENV__QlBaseUrl || '/';
|
||||
|
||||
export default {
|
||||
siteName: '青龙',
|
||||
apiPrefix: '/api/',
|
||||
apiPrefix: `${baseUrl}api/`,
|
||||
authKey: 'token',
|
||||
|
||||
/* Layout configuration, specify which layout to use for route. */
|
||||
@@ -91,7 +93,7 @@ export default {
|
||||
{ value: 'aibotk', label: '智能微秘书' },
|
||||
{ value: 'iGot', label: 'IGot' },
|
||||
{ value: 'pushPlus', label: 'PushPlus' },
|
||||
{ value: 'chat', label: '群辉chat' },
|
||||
{ value: 'chat', label: '群晖chat' },
|
||||
{ value: 'email', label: '邮箱' },
|
||||
{ value: 'lark', label: '飞书机器人' },
|
||||
{ value: 'webhook', label: '自定义通知' },
|
||||
|
||||
+10
-3
@@ -4,7 +4,7 @@ import config from './config';
|
||||
import { history } from '@umijs/max';
|
||||
|
||||
message.config({
|
||||
duration: 1.5,
|
||||
duration: 2,
|
||||
});
|
||||
|
||||
const time = Date.now();
|
||||
@@ -23,7 +23,10 @@ const errorHandler = function (error: any) {
|
||||
history.push('/login');
|
||||
}
|
||||
} else {
|
||||
message.error(msg);
|
||||
message.error({
|
||||
content: msg,
|
||||
style: { maxWidth: 500, margin: '0 auto' },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(error.message);
|
||||
@@ -66,7 +69,11 @@ _request.interceptors.response.use(async (response) => {
|
||||
const res = await response.clone().json();
|
||||
if (res.code !== 200) {
|
||||
const msg = res.message || res.data;
|
||||
msg && message.error(msg);
|
||||
msg &&
|
||||
message.error({
|
||||
content: msg,
|
||||
style: { maxWidth: 500, margin: '0 auto' },
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -10,3 +10,7 @@ declare module '*.svg' {
|
||||
}
|
||||
|
||||
declare module 'pstree.remy';
|
||||
|
||||
interface Window {
|
||||
__ENV__QlBaseUrl: string;
|
||||
}
|
||||
|
||||
+5
-10
@@ -1,11 +1,6 @@
|
||||
version: 2.15.13
|
||||
changeLogLink: https://t.me/jiao_long/373
|
||||
version: 2.15.15
|
||||
changeLogLink: https://t.me/jiao_long/376
|
||||
changeLog: |
|
||||
1. 增加运行、停止指定命令接口 system/command-run、system/command-stop
|
||||
2. 增加容器健康检查
|
||||
3. 移除执行任务默认超时时间
|
||||
4. 修改 task 命令生成日志逻辑和关联任务查询
|
||||
5. 修复更新任务、环境变量、依赖、订阅状态丢失
|
||||
6. 修改系统通知错误提示
|
||||
7. 修复系统通知 gotify 配置
|
||||
8. 其他 bug 修复
|
||||
1. 支持非跟目录部署,启动时增加参数 -e QlBaseUrl="/test/",参数前后需要斜杠
|
||||
2. 修复 task 执行可执行文件路径,感谢 https://github.com/jzksnsjswkw
|
||||
3. 其他 bug 修复
|
||||
|
||||
Reference in New Issue
Block a user