mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-13 04:02:56 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c329c8acd4 | ||
|
|
d43d563622 | ||
|
|
aa52cfb29d |
@@ -10,6 +10,7 @@ import Logger from '../loaders/logger';
|
|||||||
import { writeFileWithLock } from '../shared/utils';
|
import { writeFileWithLock } from '../shared/utils';
|
||||||
import { DependenceTypes } from '../data/dependence';
|
import { DependenceTypes } from '../data/dependence';
|
||||||
import { FormData } from 'undici';
|
import { FormData } from 'undici';
|
||||||
|
import os from 'os';
|
||||||
|
|
||||||
export * from './share';
|
export * from './share';
|
||||||
|
|
||||||
@@ -590,3 +591,162 @@ export function getUninstallCommand(
|
|||||||
export function isDemoEnv() {
|
export function isDemoEnv() {
|
||||||
return process.env.DeployEnv === 'demo';
|
return process.env.DeployEnv === 'demo';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OS detection for Linux mirror configuration
|
||||||
|
let osType: 'Debian' | 'Ubuntu' | 'Alpine' | undefined;
|
||||||
|
|
||||||
|
async function getOSReleaseInfo(): Promise<string> {
|
||||||
|
try {
|
||||||
|
const osRelease = await fs.readFile('/etc/os-release', 'utf8');
|
||||||
|
return osRelease;
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`Failed to read /etc/os-release: ${error}`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDebian(osReleaseInfo: string): boolean {
|
||||||
|
return osReleaseInfo.includes('Debian');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUbuntu(osReleaseInfo: string): boolean {
|
||||||
|
return osReleaseInfo.includes('Ubuntu');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAlpine(osReleaseInfo: string): boolean {
|
||||||
|
return osReleaseInfo.includes('Alpine');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectOS(): Promise<
|
||||||
|
'Debian' | 'Ubuntu' | 'Alpine' | undefined
|
||||||
|
> {
|
||||||
|
if (osType) return osType;
|
||||||
|
const platform = os.platform();
|
||||||
|
|
||||||
|
if (platform === 'linux') {
|
||||||
|
const osReleaseInfo = await getOSReleaseInfo();
|
||||||
|
// Check Ubuntu before Debian since Ubuntu is based on Debian
|
||||||
|
if (isUbuntu(osReleaseInfo)) {
|
||||||
|
osType = 'Ubuntu';
|
||||||
|
} else if (isDebian(osReleaseInfo)) {
|
||||||
|
osType = 'Debian';
|
||||||
|
} else if (isAlpine(osReleaseInfo)) {
|
||||||
|
osType = 'Alpine';
|
||||||
|
} else {
|
||||||
|
Logger.error(`Unknown Linux Distribution: ${osReleaseInfo}`);
|
||||||
|
console.error(`Unknown Linux Distribution: ${osReleaseInfo}`);
|
||||||
|
}
|
||||||
|
} else if (platform === 'darwin') {
|
||||||
|
osType = undefined;
|
||||||
|
} else {
|
||||||
|
Logger.error(`Unsupported platform: ${platform}`);
|
||||||
|
console.error(`Unsupported platform: ${platform}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return osType;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCurrentMirrorDomain(
|
||||||
|
filePath: string,
|
||||||
|
): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const fileContent = await fs.readFile(filePath, 'utf8');
|
||||||
|
const lines = fileContent.split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim().startsWith('#')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const match = line.match(/https?:\/\/[^\/]+/);
|
||||||
|
if (match) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`Failed to read mirror configuration file ${filePath}: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeRegExp(string: string): string {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function replaceDomainInFile(
|
||||||
|
filePath: string,
|
||||||
|
oldDomainWithScheme: string,
|
||||||
|
newDomainWithScheme: string,
|
||||||
|
): Promise<void> {
|
||||||
|
// Ensure the new domain has a trailing slash before replacement
|
||||||
|
if (!newDomainWithScheme.endsWith('/')) {
|
||||||
|
newDomainWithScheme += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileContent = await fs.readFile(filePath, 'utf8');
|
||||||
|
// Escape special regex characters in the old domain
|
||||||
|
const escapedOldDomain = escapeRegExp(oldDomainWithScheme);
|
||||||
|
let updatedContent = fileContent.replace(
|
||||||
|
new RegExp(escapedOldDomain, 'g'),
|
||||||
|
newDomainWithScheme,
|
||||||
|
);
|
||||||
|
|
||||||
|
await writeFileWithLock(filePath, updatedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _updateLinuxMirror(
|
||||||
|
osType: string,
|
||||||
|
mirrorDomainWithScheme: string,
|
||||||
|
): Promise<string> {
|
||||||
|
let filePath: string, currentDomainWithScheme: string | null;
|
||||||
|
switch (osType) {
|
||||||
|
case 'Debian':
|
||||||
|
filePath = '/etc/apt/sources.list.d/debian.sources';
|
||||||
|
currentDomainWithScheme = await getCurrentMirrorDomain(filePath);
|
||||||
|
if (currentDomainWithScheme) {
|
||||||
|
await replaceDomainInFile(
|
||||||
|
filePath,
|
||||||
|
currentDomainWithScheme,
|
||||||
|
mirrorDomainWithScheme || 'http://deb.debian.org',
|
||||||
|
);
|
||||||
|
return 'apt-get update';
|
||||||
|
} else {
|
||||||
|
throw Error(`Current mirror domain not found.`);
|
||||||
|
}
|
||||||
|
case 'Ubuntu':
|
||||||
|
filePath = '/etc/apt/sources.list.d/ubuntu.sources';
|
||||||
|
currentDomainWithScheme = await getCurrentMirrorDomain(filePath);
|
||||||
|
if (currentDomainWithScheme) {
|
||||||
|
await replaceDomainInFile(
|
||||||
|
filePath,
|
||||||
|
currentDomainWithScheme,
|
||||||
|
mirrorDomainWithScheme || 'http://archive.ubuntu.com',
|
||||||
|
);
|
||||||
|
return 'apt-get update';
|
||||||
|
} else {
|
||||||
|
throw Error(`Current mirror domain not found.`);
|
||||||
|
}
|
||||||
|
case 'Alpine':
|
||||||
|
filePath = '/etc/apk/repositories';
|
||||||
|
currentDomainWithScheme = await getCurrentMirrorDomain(filePath);
|
||||||
|
if (currentDomainWithScheme) {
|
||||||
|
await replaceDomainInFile(
|
||||||
|
filePath,
|
||||||
|
currentDomainWithScheme,
|
||||||
|
mirrorDomainWithScheme || 'http://dl-cdn.alpinelinux.org',
|
||||||
|
);
|
||||||
|
return 'apk update';
|
||||||
|
} else {
|
||||||
|
throw Error(`Current mirror domain not found.`);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw Error('Unsupported OS type for updating mirrors.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateLinuxMirrorFile(mirror: string): Promise<string> {
|
||||||
|
const detectedOS = await detectOS();
|
||||||
|
if (!detectedOS) {
|
||||||
|
throw Error(`Unknown Linux Distribution`);
|
||||||
|
}
|
||||||
|
return await _updateLinuxMirror(detectedOS, mirror);
|
||||||
|
}
|
||||||
|
|||||||
+5
-34
@@ -13,29 +13,9 @@ import { isValidToken } from '../shared/auth';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export default ({ app }: { app: Application }) => {
|
export default ({ app }: { app: Application }) => {
|
||||||
// Security: Enable strict routing to prevent case-insensitive path bypass
|
|
||||||
app.set('case sensitive routing', true);
|
|
||||||
app.set('strict routing', true);
|
|
||||||
app.set('trust proxy', 'loopback');
|
app.set('trust proxy', 'loopback');
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
// Security: Path normalization middleware to prevent case variation attacks
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
const originalPath = req.path;
|
|
||||||
const normalizedPath = originalPath.toLowerCase();
|
|
||||||
|
|
||||||
// Block requests with case variations on protected paths
|
|
||||||
if (originalPath !== normalizedPath &&
|
|
||||||
(normalizedPath.startsWith('/api/') || normalizedPath.startsWith('/open/'))) {
|
|
||||||
return res.status(400).json({
|
|
||||||
code: 400,
|
|
||||||
message: 'Invalid path format'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rewrite URLs to strip baseUrl prefix if configured
|
// Rewrite URLs to strip baseUrl prefix if configured
|
||||||
// This allows the rest of the app to work without baseUrl awareness
|
// This allows the rest of the app to work without baseUrl awareness
|
||||||
if (config.baseUrl) {
|
if (config.baseUrl) {
|
||||||
@@ -56,7 +36,7 @@ export default ({ app }: { app: Application }) => {
|
|||||||
secret: config.jwt.secret,
|
secret: config.jwt.secret,
|
||||||
algorithms: ['HS384'],
|
algorithms: ['HS384'],
|
||||||
}).unless({
|
}).unless({
|
||||||
path: [...config.apiWhiteList, /^(\/(?!api\/).*)$/i],
|
path: [...config.apiWhiteList, /^\/(?!api\/).*/],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -71,20 +51,19 @@ export default ({ app }: { app: Application }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(async (req: Request, res, next) => {
|
app.use(async (req: Request, res, next) => {
|
||||||
const pathLower = req.path.toLowerCase();
|
if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) {
|
||||||
if (!['/open/', '/api/'].some((x) => pathLower.startsWith(x))) {
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerToken = getToken(req);
|
const headerToken = getToken(req);
|
||||||
if (pathLower.startsWith('/open/')) {
|
if (req.path.startsWith('/open/')) {
|
||||||
const apps = await shareStore.getApps();
|
const apps = await shareStore.getApps();
|
||||||
const doc = apps?.filter((x) =>
|
const doc = apps?.filter((x) =>
|
||||||
x.tokens?.find((y) => y.value === headerToken),
|
x.tokens?.find((y) => y.value === headerToken),
|
||||||
)?.[0];
|
)?.[0];
|
||||||
if (doc && doc.tokens && doc.tokens.length > 0) {
|
if (doc && doc.tokens && doc.tokens.length > 0) {
|
||||||
const currentToken = doc.tokens.find((x) => x.value === headerToken);
|
const currentToken = doc.tokens.find((x) => x.value === headerToken);
|
||||||
const keyMatch = pathLower.match(/\/open\/([a-z]+)\/*/);
|
const keyMatch = req.path.match(/\/open\/([a-z]+)\/*/);
|
||||||
const key = keyMatch && keyMatch[1];
|
const key = keyMatch && keyMatch[1];
|
||||||
if (
|
if (
|
||||||
doc.scopes.includes(key as any) &&
|
doc.scopes.includes(key as any) &&
|
||||||
@@ -119,15 +98,7 @@ export default ({ app }: { app: Application }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(async (req, res, next) => {
|
app.use(async (req, res, next) => {
|
||||||
const pathLower = req.path.toLowerCase();
|
if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) {
|
||||||
if (
|
|
||||||
![
|
|
||||||
'/api/user/init',
|
|
||||||
'/api/user/notification/init',
|
|
||||||
'/open/user/init',
|
|
||||||
'/open/user/notification/init',
|
|
||||||
].includes(req.path)
|
|
||||||
) {
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
const authInfo =
|
const authInfo =
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { AuthDataType, SystemModel } from '../data/system';
|
|||||||
import SystemService from '../services/system';
|
import SystemService from '../services/system';
|
||||||
import UserService from '../services/user';
|
import UserService from '../services/user';
|
||||||
import { writeFile, readFile } from 'fs/promises';
|
import { writeFile, readFile } from 'fs/promises';
|
||||||
import { createRandomString, fileExist, isDemoEnv, safeJSONParse } from '../config/util';
|
import { createRandomString, fileExist, safeJSONParse } from '../config/util';
|
||||||
import OpenService from '../services/open';
|
import OpenService from '../services/open';
|
||||||
import { shareStore } from '../shared/store';
|
import { shareStore } from '../shared/store';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
@@ -50,7 +50,7 @@ export default async () => {
|
|||||||
const [authConfig] = await SystemModel.findOrCreate({
|
const [authConfig] = await SystemModel.findOrCreate({
|
||||||
where: { type: AuthDataType.authConfig },
|
where: { type: AuthDataType.authConfig },
|
||||||
});
|
});
|
||||||
if (!authConfig?.info || isDemoEnv()) {
|
if (!authConfig?.info) {
|
||||||
let authInfo = {
|
let authInfo = {
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: 'admin',
|
password: 'admin',
|
||||||
|
|||||||
+10
-24
@@ -17,6 +17,7 @@ import {
|
|||||||
readDirs,
|
readDirs,
|
||||||
rmPath,
|
rmPath,
|
||||||
setSystemTimezone,
|
setSystemTimezone,
|
||||||
|
updateLinuxMirrorFile,
|
||||||
} from '../config/util';
|
} from '../config/util';
|
||||||
import {
|
import {
|
||||||
DependenceModel,
|
DependenceModel,
|
||||||
@@ -214,33 +215,11 @@ export default class SystemService {
|
|||||||
onEnd?: () => void,
|
onEnd?: () => void,
|
||||||
) {
|
) {
|
||||||
const oDoc = await this.getSystemConfig();
|
const oDoc = await this.getSystemConfig();
|
||||||
await this.updateAuthDb({
|
|
||||||
...oDoc,
|
|
||||||
info: { ...oDoc.info, ...info },
|
|
||||||
});
|
|
||||||
let defaultDomain = 'https://dl-cdn.alpinelinux.org';
|
|
||||||
let targetDomain = 'https://dl-cdn.alpinelinux.org';
|
|
||||||
if (os.platform() !== 'linux') {
|
if (os.platform() !== 'linux') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const content = await fs.promises.readFile('/etc/apk/repositories', {
|
const command = await updateLinuxMirrorFile(info.linuxMirror || '');
|
||||||
encoding: 'utf-8',
|
let hasError = false;
|
||||||
});
|
|
||||||
const domainMatch = content.match(/(http.*)\/alpine\/.*/);
|
|
||||||
if (domainMatch) {
|
|
||||||
defaultDomain = domainMatch[1];
|
|
||||||
}
|
|
||||||
if (info.linuxMirror) {
|
|
||||||
targetDomain = info.linuxMirror;
|
|
||||||
}
|
|
||||||
const command = `sed -i 's/${defaultDomain.replace(
|
|
||||||
/\//g,
|
|
||||||
'\\/',
|
|
||||||
)}/${targetDomain.replace(
|
|
||||||
/\//g,
|
|
||||||
'\\/',
|
|
||||||
)}/g' /etc/apk/repositories && apk update -f`;
|
|
||||||
|
|
||||||
this.scheduleService.runTask(
|
this.scheduleService.runTask(
|
||||||
command,
|
command,
|
||||||
{
|
{
|
||||||
@@ -254,8 +233,15 @@ export default class SystemService {
|
|||||||
message: 'update linux mirror end',
|
message: 'update linux mirror end',
|
||||||
});
|
});
|
||||||
onEnd?.();
|
onEnd?.();
|
||||||
|
if (!hasError) {
|
||||||
|
await this.updateAuthDb({
|
||||||
|
...oDoc,
|
||||||
|
info: { ...oDoc.info, ...info },
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: async (message: string) => {
|
onError: async (message: string) => {
|
||||||
|
hasError = true;
|
||||||
this.sockService.sendMessage({ type: 'updateLinuxMirror', message });
|
this.sockService.sendMessage({ type: 'updateLinuxMirror', message });
|
||||||
},
|
},
|
||||||
onLog: async (message: string) => {
|
onLog: async (message: string) => {
|
||||||
|
|||||||
@@ -69,10 +69,9 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
|||||||
|
|
||||||
ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \
|
ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \
|
||||||
PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \
|
PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \
|
||||||
PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \
|
PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3
|
||||||
HOME=/root
|
|
||||||
|
|
||||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \
|
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin \
|
||||||
NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \
|
NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \
|
||||||
PIP_CACHE_DIR=${PYTHON_HOME}/pip \
|
PIP_CACHE_DIR=${PYTHON_HOME}/pip \
|
||||||
PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages
|
PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages
|
||||||
|
|||||||
+2
-3
@@ -69,10 +69,9 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
|||||||
|
|
||||||
ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \
|
ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \
|
||||||
PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \
|
PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \
|
||||||
PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \
|
PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3
|
||||||
HOME=/root
|
|
||||||
|
|
||||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \
|
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin \
|
||||||
NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \
|
NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \
|
||||||
PIP_CACHE_DIR=${PYTHON_HOME}/pip \
|
PIP_CACHE_DIR=${PYTHON_HOME}/pip \
|
||||||
PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages
|
PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
export PATH="$HOME/bin:$PATH"
|
||||||
|
|
||||||
dir_shell=/ql/shell
|
dir_shell=/ql/shell
|
||||||
. $dir_shell/share.sh
|
. $dir_shell/share.sh
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@
|
|||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multer": "2.1.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"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",
|
||||||
|
|||||||
Generated
+255
-564
File diff suppressed because it is too large
Load Diff
@@ -95,8 +95,6 @@ run_normal() {
|
|||||||
if [[ ${file_param} != /* ]] && [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
if [[ ${file_param} != /* ]] && [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
|
||||||
cd ${relative_path}
|
cd ${relative_path}
|
||||||
file_param=${file_param/$relative_path\//}
|
file_param=${file_param/$relative_path\//}
|
||||||
elif [[ ${file_param} == /* ]] && [[ ! -z ${relative_path} ]]; then
|
|
||||||
cd ${relative_path}
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $isJsOrPythonFile == 'false' ]]; then
|
if [[ $isJsOrPythonFile == 'false' ]]; then
|
||||||
|
|||||||
+10
-5
@@ -1,6 +1,11 @@
|
|||||||
version: 2.20.2
|
version: 2.20.1
|
||||||
changeLogLink: https://t.me/jiao_long/434
|
changeLogLink: https://t.me/jiao_long/433
|
||||||
publishTime: 2026-03-01 1800
|
publishTime: 2025-12-26 22:00
|
||||||
changeLog: |
|
changeLog: |
|
||||||
1. 修复 path 安全漏洞(重要)
|
1. 修复获取依赖管理列表
|
||||||
|
2. notify.js 修复 TG_PROXY_AUTH 参数拼接
|
||||||
|
3. QLAPI.notify larkSecret 参数
|
||||||
|
4. 修复 cron parser 定时规则校验
|
||||||
|
5. 修复设置 baseUrl 后无法访问
|
||||||
|
6. 修复环境变量排序
|
||||||
|
7. 修复定时任务无法停止
|
||||||
Reference in New Issue
Block a user