mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-06 17:34:34 +08:00
Use MikroORM's official Node SQLite dialect with bounded database isolation, preserve legacy schema and data for rollback, and require Node 22.17 or newer. Update Debian runtimes and retain ARMv6 musl compatibility without a SQLite addon. Complete short archive writes, stop orphaned SQL workers after owner death, preserve buffered startup requests, and prestart the archived backend on every platform. Cover real package installation, migration, failure recovery, and legacy database compatibility. Keep authentication storage failures distinct from expired sessions with a pinned Keyv runtime, propagated cache errors, and retryable HTTP 503 responses. Preserve genuine 401 failures and verify legacy cache bytes, initialization safety, and session recovery.
221 lines
7.1 KiB
TypeScript
221 lines
7.1 KiB
TypeScript
import { textSearch } from '../data/queries';
|
|
import { Query, QuerySpec, orderBy } from '../data/queries';
|
|
import DependenceService from '../services/dependence';
|
|
import { exec } from 'child_process';
|
|
import { Container } from 'typedi';
|
|
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
|
|
import CronService from '../services/cron';
|
|
import EnvService from '../services/env';
|
|
import { DependenceModel, DependenceStatus } from '../data/dependence';
|
|
import config from '../config';
|
|
import { CrontabViewModel, CronViewType } from '../data/cronView';
|
|
import { initPosition } from '../data/env';
|
|
import { AuthDataType, SystemModel } from '../data/system';
|
|
import SystemService from '../services/system';
|
|
import UserService from '../services/user';
|
|
import { writeFile, readFile } from 'fs/promises';
|
|
import { fileExist, isDemoEnv, safeJSONParse } from '../config/util';
|
|
import OpenService from '../services/open';
|
|
import { shareStore } from '../shared/store';
|
|
import Logger from './logger';
|
|
import cronClient from '../schedule/client';
|
|
import { InstanceStatus, RunningInstanceModel } from '../data/runningInstance';
|
|
import { setLang, systemLang } from '../shared/i18n';
|
|
export default async () => {
|
|
const cronService = Container.get(CronService);
|
|
const envService = Container.get(EnvService);
|
|
const dependenceService = Container.get(DependenceService);
|
|
const systemService = Container.get(SystemService);
|
|
const userService = Container.get(UserService);
|
|
const openService = Container.get(OpenService);
|
|
// 初始化增加系统配置
|
|
await openService.initializeSystemApp();
|
|
const systemConfig = await SystemModel.getOrCreate({
|
|
type: AuthDataType.systemConfig,
|
|
});
|
|
await SystemModel.getOrCreate({ type: AuthDataType.notification });
|
|
const authConfig = await SystemModel.getOrCreate({
|
|
type: AuthDataType.authConfig,
|
|
});
|
|
if (!authConfig?.info || isDemoEnv()) {
|
|
let authInfo = {
|
|
username: 'admin',
|
|
password: 'admin',
|
|
};
|
|
try {
|
|
const authFileExist = await fileExist(config.authConfigFile);
|
|
if (authFileExist) {
|
|
const content = await readFile(config.authConfigFile, 'utf8');
|
|
authInfo = safeJSONParse(content);
|
|
}
|
|
} catch (error) {
|
|
Logger.warn('Failed to read auth config file, using default credentials');
|
|
}
|
|
await SystemModel.nativeUpdate(
|
|
{ id: authConfig.id },
|
|
{
|
|
info: authInfo,
|
|
type: AuthDataType.authConfig,
|
|
},
|
|
);
|
|
}
|
|
const installDependencies = async () => {
|
|
const docs = await DependenceModel.find(
|
|
{},
|
|
{
|
|
orderBy: orderBy([
|
|
['type', 'DESC'],
|
|
['createdAt', 'DESC'],
|
|
]),
|
|
},
|
|
);
|
|
await DependenceModel.nativeUpdate(
|
|
{ id: docs.map((x) => x.id!) },
|
|
{ status: DependenceStatus.queued, log: [] },
|
|
);
|
|
setTimeout(async () => {
|
|
await dependenceService.installDependenceOneByOne(docs);
|
|
const bootAfterLoader = await import('./bootAfter');
|
|
bootAfterLoader.default();
|
|
}, 5000);
|
|
};
|
|
// 初始化更新 linux/python/nodejs 镜像源配置
|
|
if (systemConfig.info?.pythonMirror) {
|
|
systemService.updatePythonMirror({
|
|
pythonMirror: systemConfig.info?.pythonMirror,
|
|
});
|
|
}
|
|
if (systemConfig.info?.linuxMirror) {
|
|
systemService.updateLinuxMirror(
|
|
{
|
|
linuxMirror: systemConfig.info?.linuxMirror,
|
|
},
|
|
undefined,
|
|
() => installDependencies(),
|
|
);
|
|
} else {
|
|
installDependencies();
|
|
}
|
|
if (systemConfig.info?.nodeMirror) {
|
|
systemService.updateNodeMirror({
|
|
nodeMirror: systemConfig.info?.nodeMirror,
|
|
});
|
|
}
|
|
// 初始化新增默认全部任务视图
|
|
CrontabViewModel.find({ type: CronViewType.系统, name: '全部任务' }, {}).then(
|
|
(docs) => {
|
|
if (docs.length === 0) {
|
|
CrontabViewModel.insert({
|
|
name: '全部任务',
|
|
type: CronViewType.系统,
|
|
position: initPosition / 2,
|
|
});
|
|
}
|
|
},
|
|
);
|
|
// 初始化更新所有任务状态为空闲
|
|
await CrontabModel.nativeUpdate({}, { status: CrontabStatus.idle });
|
|
// 清空所有运行中的实例记录(服务重启后进程已不存在)
|
|
await RunningInstanceModel.nativeUpdate(
|
|
{ status: InstanceStatus.running },
|
|
{
|
|
status: InstanceStatus.stopped,
|
|
finished_at: Math.floor(Date.now() / 1000),
|
|
// The previous process's exit code cannot be recovered after a restart.
|
|
},
|
|
);
|
|
// 初始化时执行一次所有的 ql repo 任务
|
|
CrontabModel.find(
|
|
{
|
|
isDisabled: { ['$ne']: 1 },
|
|
$or: [
|
|
...textSearch(['command'], 'ql repo').$or,
|
|
...textSearch(['command'], 'ql raw').$or,
|
|
],
|
|
},
|
|
{},
|
|
).then((docs) => {
|
|
for (let i = 0; i < docs.length; i++) {
|
|
const doc = docs[i];
|
|
if (doc) {
|
|
exec(doc.command);
|
|
}
|
|
}
|
|
});
|
|
// 更新2.11.3以前的脚本路径
|
|
CrontabModel.find(
|
|
{
|
|
$or: ['scripts', 'config', 'log', 'db'].flatMap(
|
|
(folder) =>
|
|
textSearch(['command'], `/${config.rootPath}/${folder}/`).$or,
|
|
),
|
|
},
|
|
{},
|
|
).then(async (docs) => {
|
|
for (let i = 0; i < docs.length; i++) {
|
|
const doc = docs[i];
|
|
if (doc) {
|
|
if (doc.command.includes(`${config.rootPath}/scripts/`)) {
|
|
await CrontabModel.nativeUpdate(
|
|
{ id: doc.id },
|
|
{ command: doc.command.replace(`${config.rootPath}/scripts/`, '') },
|
|
);
|
|
}
|
|
if (doc.command.includes(`${config.rootPath}/log/`)) {
|
|
await CrontabModel.nativeUpdate(
|
|
{ id: doc.id },
|
|
{
|
|
command: `${config.dataPath}/log/${doc.command.replace(
|
|
`${config.rootPath}/log/`,
|
|
'',
|
|
)}`,
|
|
},
|
|
);
|
|
}
|
|
if (doc.command.includes(`${config.rootPath}/config/`)) {
|
|
await CrontabModel.nativeUpdate(
|
|
{ id: doc.id },
|
|
{
|
|
command: `${config.dataPath}/config/${doc.command.replace(
|
|
`${config.rootPath}/config/`,
|
|
'',
|
|
)}`,
|
|
},
|
|
);
|
|
}
|
|
if (doc.command.includes(`${config.rootPath}/db/`)) {
|
|
await CrontabModel.nativeUpdate(
|
|
{ id: doc.id },
|
|
{
|
|
command: `${config.dataPath}/db/${doc.command.replace(
|
|
`${config.rootPath}/db/`,
|
|
'',
|
|
)}`,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
// 初始化语言(必须在 autosave_crontab 之前)
|
|
const lang = systemConfig.info?.lang || systemLang();
|
|
setLang(lang);
|
|
// 确保 lang_env.sh 存在
|
|
try {
|
|
const langEnvExist = await fileExist(config.langEnvFile);
|
|
if (!langEnvExist) {
|
|
await writeFile(config.langEnvFile, `export QL_LANG='${lang}'\n`);
|
|
}
|
|
} catch {}
|
|
// 初始化保存一次ck和定时任务数据
|
|
cronClient.readiness.configure(async () => {
|
|
await cronService.autosave_crontab(true);
|
|
await systemService.restoreCronConcurrency();
|
|
});
|
|
await cronClient.readiness.recover();
|
|
await envService.set_envs();
|
|
const authInfo = await userService.getAuthInfo();
|
|
await shareStore.updateAuthInfo(authInfo);
|
|
await openService.refreshApps();
|
|
};
|