mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-07 17:24:31 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bec52dca1 | |||
| ce599d306f |
@@ -10,7 +10,6 @@ import Logger from '../loaders/logger';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
import { DependenceTypes } from '../data/dependence';
|
||||
import { FormData } from 'undici';
|
||||
import os from 'os';
|
||||
|
||||
export * from './share';
|
||||
|
||||
@@ -591,162 +590,3 @@ export function getUninstallCommand(
|
||||
export function isDemoEnv() {
|
||||
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);
|
||||
}
|
||||
|
||||
+34
-5
@@ -13,9 +13,29 @@ import { isValidToken } from '../shared/auth';
|
||||
import path from 'path';
|
||||
|
||||
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.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
|
||||
// This allows the rest of the app to work without baseUrl awareness
|
||||
if (config.baseUrl) {
|
||||
@@ -36,7 +56,7 @@ export default ({ app }: { app: Application }) => {
|
||||
secret: config.jwt.secret,
|
||||
algorithms: ['HS384'],
|
||||
}).unless({
|
||||
path: [...config.apiWhiteList, /^\/(?!api\/).*/],
|
||||
path: [...config.apiWhiteList, /^(\/(?!api\/).*)$/i],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -51,19 +71,20 @@ export default ({ app }: { app: Application }) => {
|
||||
});
|
||||
|
||||
app.use(async (req: Request, res, next) => {
|
||||
if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) {
|
||||
const pathLower = req.path.toLowerCase();
|
||||
if (!['/open/', '/api/'].some((x) => pathLower.startsWith(x))) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const headerToken = getToken(req);
|
||||
if (req.path.startsWith('/open/')) {
|
||||
if (pathLower.startsWith('/open/')) {
|
||||
const apps = await shareStore.getApps();
|
||||
const doc = apps?.filter((x) =>
|
||||
x.tokens?.find((y) => y.value === headerToken),
|
||||
)?.[0];
|
||||
if (doc && doc.tokens && doc.tokens.length > 0) {
|
||||
const currentToken = doc.tokens.find((x) => x.value === headerToken);
|
||||
const keyMatch = req.path.match(/\/open\/([a-z]+)\/*/);
|
||||
const keyMatch = pathLower.match(/\/open\/([a-z]+)\/*/);
|
||||
const key = keyMatch && keyMatch[1];
|
||||
if (
|
||||
doc.scopes.includes(key as any) &&
|
||||
@@ -98,7 +119,15 @@ export default ({ app }: { app: Application }) => {
|
||||
});
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) {
|
||||
const pathLower = req.path.toLowerCase();
|
||||
if (
|
||||
![
|
||||
'/api/user/init',
|
||||
'/api/user/notification/init',
|
||||
'/open/user/init',
|
||||
'/open/user/notification/init',
|
||||
].includes(req.path)
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
const authInfo =
|
||||
|
||||
+24
-10
@@ -17,7 +17,6 @@ import {
|
||||
readDirs,
|
||||
rmPath,
|
||||
setSystemTimezone,
|
||||
updateLinuxMirrorFile,
|
||||
} from '../config/util';
|
||||
import {
|
||||
DependenceModel,
|
||||
@@ -215,11 +214,33 @@ export default class SystemService {
|
||||
onEnd?: () => void,
|
||||
) {
|
||||
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') {
|
||||
return;
|
||||
}
|
||||
const command = await updateLinuxMirrorFile(info.linuxMirror || '');
|
||||
let hasError = false;
|
||||
const content = await fs.promises.readFile('/etc/apk/repositories', {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
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(
|
||||
command,
|
||||
{
|
||||
@@ -233,15 +254,8 @@ export default class SystemService {
|
||||
message: 'update linux mirror end',
|
||||
});
|
||||
onEnd?.();
|
||||
if (!hasError) {
|
||||
await this.updateAuthDb({
|
||||
...oDoc,
|
||||
info: { ...oDoc.info, ...info },
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: async (message: string) => {
|
||||
hasError = true;
|
||||
this.sockService.sendMessage({ type: 'updateLinuxMirror', message });
|
||||
},
|
||||
onLog: async (message: string) => {
|
||||
|
||||
Reference in New Issue
Block a user