mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-08 13:50:15 +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.
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
|
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import { request } from 'undici';
|
|
import { HealthCheckRequest, HealthCheckResponse } from '../protos/health';
|
|
import config from '../config';
|
|
import { database } from '../data';
|
|
|
|
function formatError(error: unknown): string {
|
|
if (!(error instanceof Error)) {
|
|
return String(error);
|
|
}
|
|
|
|
const detailedError = error as Error & {
|
|
cause?: unknown;
|
|
code?: string;
|
|
errors?: unknown[];
|
|
};
|
|
const details = [error.message, detailedError.code];
|
|
if (Array.isArray(detailedError.errors)) {
|
|
details.push(...detailedError.errors.map(formatError));
|
|
} else if (detailedError.cause) {
|
|
details.push(formatError(detailedError.cause));
|
|
}
|
|
|
|
return [...new Set(details.filter(Boolean))].join(': ') || error.name;
|
|
}
|
|
|
|
async function getRecentSystemLog(lineLimit = 300): Promise<string> {
|
|
try {
|
|
const entries = await fs.readdir(config.systemLogPath);
|
|
const latestLog = entries
|
|
.filter((entry) => entry.endsWith('.log'))
|
|
.sort()
|
|
.at(-1);
|
|
if (!latestLog) {
|
|
return `No system log found in ${config.systemLogPath}`;
|
|
}
|
|
const content = await fs.readFile(
|
|
path.join(config.systemLogPath, latestLog),
|
|
'utf8',
|
|
);
|
|
return content.split('\n').slice(-lineLimit).join('\n').trim();
|
|
} catch (error) {
|
|
return `Unable to read system log from ${config.systemLogPath}: ${
|
|
error instanceof Error ? error.message : String(error)
|
|
}`;
|
|
}
|
|
}
|
|
|
|
const check = async (
|
|
call: ServerUnaryCall<HealthCheckRequest, HealthCheckResponse>,
|
|
callback: sendUnaryData<HealthCheckResponse>,
|
|
) => {
|
|
switch (call.request.service) {
|
|
// Local scheduler liveness only: never call HTTP from this probe.
|
|
case 'scheduler':
|
|
return callback(null, { status: database.healthy ? 1 : 2 });
|
|
case 'cron': {
|
|
const healthUrl = `http://localhost:${config.port}${
|
|
config.baseUrl || ''
|
|
}/api/health`;
|
|
let failure = '';
|
|
try {
|
|
const response = await request(healthUrl, {
|
|
method: 'GET',
|
|
headersTimeout: 5000,
|
|
bodyTimeout: 5000,
|
|
});
|
|
const body = (await response.body.json()) as {
|
|
code?: number;
|
|
data?: { status?: string };
|
|
};
|
|
if (
|
|
response.statusCode >= 200 &&
|
|
response.statusCode < 300 &&
|
|
body.code === 200 &&
|
|
body.data?.status === 'ok'
|
|
) {
|
|
return callback(null, { status: 1 });
|
|
}
|
|
failure = `HTTP ${response.statusCode}: ${JSON.stringify(body)}`;
|
|
} catch (error) {
|
|
failure = formatError(error);
|
|
}
|
|
|
|
const systemLog = await getRecentSystemLog();
|
|
const containerHint =
|
|
process.env.QL_CONTAINER === 'true'
|
|
? 'PM2 file logging is disabled in containers. Check `docker logs <container>` for early startup errors.'
|
|
: 'Check `pm2 logs qinglong --lines 300` for early startup errors.';
|
|
return callback(
|
|
new Error(
|
|
[
|
|
`HTTP health check failed: ${healthUrl}`,
|
|
failure,
|
|
containerHint,
|
|
`Recent system log (${config.systemLogPath}):`,
|
|
systemLog,
|
|
]
|
|
.filter(Boolean)
|
|
.join('\n'),
|
|
),
|
|
);
|
|
}
|
|
|
|
default:
|
|
return callback(null, { status: 1 });
|
|
}
|
|
};
|
|
|
|
export { check };
|