99 lines
4.1 KiB
JavaScript
99 lines
4.1 KiB
JavaScript
import { stat } from "node:fs/promises";
|
|
import { dbPath } from "./db.mjs";
|
|
import { backupSummary } from "./backup.mjs";
|
|
|
|
function envValue(name) {
|
|
return String(process.env[name] || "").trim();
|
|
}
|
|
|
|
function configuredCheck(key, label, envName, detail) {
|
|
const value = envValue(envName);
|
|
return {
|
|
key,
|
|
label,
|
|
status: value ? "configured" : "not-configured",
|
|
severity: value ? "info" : "warning",
|
|
blocking: false,
|
|
detail: value ? `${detail}:已读取 ${envName}` : `${detail}:未设置 ${envName}`,
|
|
envName
|
|
};
|
|
}
|
|
|
|
async function databaseCheck() {
|
|
try {
|
|
const file = await stat(dbPath);
|
|
return {
|
|
key: "business-database",
|
|
label: "业务数据库",
|
|
status: "active-local",
|
|
severity: "warning",
|
|
blocking: false,
|
|
provider: "Node 24 node:sqlite",
|
|
detail: "当前业务真源是本地 SQLite;尚未切换 PostgreSQL 高可用运行时。",
|
|
path: dbPath,
|
|
bytes: Number(file.size || 0),
|
|
modifiedAt: file.mtime?.toISOString?.() || null
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
key: "business-database",
|
|
label: "业务数据库",
|
|
status: "failed",
|
|
severity: "critical",
|
|
blocking: true,
|
|
provider: "Node 24 node:sqlite",
|
|
detail: `数据库文件不可读:${error.message}`,
|
|
path: dbPath
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function systemReadiness() {
|
|
const backups = await backupSummary();
|
|
const allowDevContext = process.env.AI_DRAMA_ALLOW_DEV_CONTEXT === "1";
|
|
const sessionSecret = envValue("AI_DRAMA_SESSION_SECRET");
|
|
const mfaKey = envValue("AI_DRAMA_MFA_ENCRYPTION_KEY");
|
|
const oidcKey = envValue("AI_DRAMA_OIDC_STORAGE_KEY");
|
|
const checks = [
|
|
await databaseCheck(),
|
|
configuredCheck("postgres-target", "PostgreSQL 目标", "PLATFORM_POSTGRES_URL", "目标数据库连接"),
|
|
configuredCheck("redis-target", "Redis 目标", "PLATFORM_REDIS_URL", "目标队列/分布式锁连接"),
|
|
configuredCheck("object-storage-target", "对象存储目标", "PLATFORM_OBJECT_STORAGE_ENDPOINT", "S3-compatible 存储端点"),
|
|
{
|
|
key: "security-secrets",
|
|
label: "生产密钥",
|
|
status: sessionSecret.length >= 32 && mfaKey.length >= 16 && oidcKey.length >= 16 ? "ready" : "needs-config",
|
|
severity: sessionSecret.length >= 32 && mfaKey.length >= 16 && oidcKey.length >= 16 ? "info" : "critical",
|
|
blocking: sessionSecret.length < 32 || mfaKey.length < 16 || oidcKey.length < 16,
|
|
detail: "Session、MFA 和 OIDC 存储密钥必须通过环境变量注入,不写入数据库。",
|
|
configured: { sessionSecret: sessionSecret.length >= 32, mfaKey: mfaKey.length >= 16, oidcKey: oidcKey.length >= 16 }
|
|
},
|
|
{
|
|
key: "dev-context",
|
|
label: "开发上下文旁路",
|
|
status: allowDevContext ? "unsafe" : "ready",
|
|
severity: allowDevContext ? "critical" : "info",
|
|
blocking: allowDevContext,
|
|
detail: allowDevContext ? "AI_DRAMA_ALLOW_DEV_CONTEXT=1,生产部署禁止启用。" : "请求头上下文旁路已关闭,使用真实 session/API client。"
|
|
},
|
|
{
|
|
key: "database-backup",
|
|
label: "数据库快照",
|
|
status: backups.count ? "ready" : "missing",
|
|
severity: backups.count ? "info" : "warning",
|
|
blocking: false,
|
|
detail: backups.count ? `已有 ${backups.count} 个本地快照,最近一次 ${backups.latest?.modifiedAt || "未知"}。` : "还没有本地 SQLite 快照;上线前应先创建并验证备份。"
|
|
}
|
|
];
|
|
const blocking = checks.filter((check) => check.blocking && !["ready", "configured"].includes(check.status)).length;
|
|
const attention = checks.filter((check) => check.severity === "warning" || check.severity === "critical").length;
|
|
return {
|
|
profile: process.env.NODE_ENV === "production" ? "production" : "local-development",
|
|
activeRuntime: { database: "node:sqlite", queue: "database-lease-worker", objectStorage: "local-filesystem" },
|
|
summary: { status: blocking ? "blocked" : attention ? "attention" : "ready", blocking, attention, ready: checks.length - attention },
|
|
checks,
|
|
backups,
|
|
checkedAt: new Date().toISOString()
|
|
};
|
|
}
|