Files
FileHost/server/plugins/startup.ts
T
2026-10-17 18:15:42 +08:00

165 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { getDataDir } from '../utils/data-dir'
import { promises as fs } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { runAutoDeleteCleanup } from '../utils/auto-delete'
import { migrateLocalImagesToIndex, purgeOrphanImageIndexRows, reconcileLegacyImageKeys, repairLocalImageIndexMetadata } from '../utils/image-index'
import { ensureDefaultBackends } from '../utils/storage-backends'
import { cleanupLocalAtomicTempFiles } from '../utils/storage/local'
import { cleanupExpiredSiteSyncArtifacts } from '../utils/site-sync/artifacts'
import { recoverInterruptedBackupJobs } from '../utils/backup/jobs'
import { cleanupExpiredSiteSyncChunkSessions } from '../utils/site-sync/chunks'
import {
cleanupTranscodedSources,
killOrphanFfmpeg,
purgeOrphanVideoAssets,
resumePendingTranscodes
} from '../utils/transcode'
import { logException, logInfo } from '../utils/logger'
export default defineNitroPlugin(() => {
const port = process.env.NITRO_PORT || process.env.PORT || '6892'
logInfo('PicHost starting', {
port,
dataDir: getDataDir(),
nodeEnv: process.env.NODE_ENV || 'development',
imageBaseUrl: process.env.IMAGE_BASE_URL || '(request origin)',
storageBackend: process.env.STORAGE_BACKEND || 'db',
logLevel: process.env.LOG_LEVEL || 'info'
})
ensureDefaultBackends()
try {
const interrupted = recoverInterruptedBackupJobs()
if (interrupted > 0) {
logInfo('已将异常退出时未完成的任务标记为失败', { interrupted })
}
} catch (error) {
logException('恢复异常退出的备份任务失败', error)
}
void (async () => {
try {
const removed = await cleanupLocalAtomicTempFiles()
if (removed > 0) {
logInfo('清理过期的原子写入临时文件', { removed })
}
} catch (error) {
logException('清理原子写入临时文件失败', error)
}
})()
void (async () => {
try {
const removed = await cleanupExpiredSiteSyncChunkSessions()
if (removed > 0) {
logInfo('清理过期站点同步分块', { removed })
}
} catch (error) {
logException('清理站点同步分块失败', error)
}
})()
void (async () => {
try {
const result = await cleanupExpiredSiteSyncArtifacts()
if (result.jobs > 0) {
logInfo('清理过期站点同步残留', result)
}
} catch (error) {
logException('清理站点同步残留失败', error)
}
})()
/** 清理上次异常退出留下的转码临时目录 */
void (async () => {
try {
const entries = await fs.readdir(tmpdir(), { withFileTypes: true })
for (const entry of entries) {
if (
!entry.isDirectory()
|| !entry.name.startsWith('pichost-hls-')
|| entry.name.startsWith('pichost-hls-download-')
) continue
await fs.rm(join(tmpdir(), entry.name), { recursive: true, force: true })
.catch(() => {})
}
} catch {
// 清理失败不影响启动
}
})()
void (async () => {
const dataDir = getDataDir()
try {
logInfo('开始同步存储索引:扫描本地磁盘', {
path: `${dataDir}/images/, ${dataDir}/files/, ${dataDir}/videos/`
})
const inserted = await migrateLocalImagesToIndex()
if (inserted > 0) {
logInfo('磁盘扫描完成:已补全缺失索引', { inserted })
} else {
logInfo('磁盘扫描完成:索引与磁盘一致,无需补全')
}
logInfo('开始归一化遗留图片路径(如 blog/ → images/blog/)')
const reconciled = await reconcileLegacyImageKeys()
if (reconciled.updated > 0 || reconciled.removed > 0) {
logInfo('遗留路径归一化完成', reconciled)
} else {
logInfo('遗留路径归一化完成:无需调整')
}
logInfo('开始校验索引与磁盘一致性,清理无文件的孤儿记录')
const purged = await purgeOrphanImageIndexRows()
if (purged > 0) {
logInfo('孤儿索引清理完成', { removed: purged })
} else {
logInfo('孤儿索引清理完成:未发现孤儿记录')
}
logInfo('开始修复存量 Content-Type 元数据')
const repaired = repairLocalImageIndexMetadata()
if (repaired > 0) {
logInfo('Content-Type 修复完成', { repaired })
} else {
logInfo('Content-Type 修复完成:无需修复')
}
logInfo('图片索引同步结束')
logInfo('开始恢复未完成的视频转码任务')
await killOrphanFfmpeg()
const purgedAssets = await purgeOrphanVideoAssets()
if (purgedAssets > 0) {
logInfo('清理无主的视频附属文件', { removed: purgedAssets })
}
const removedSources = await cleanupTranscodedSources()
if (removedSources > 0) {
logInfo('清理转码完成后的源视频', { removed: removedSources })
}
resumePendingTranscodes()
} catch (error) {
logException('图片索引同步失败', error)
}
})()
void (async () => {
try {
logInfo('开始执行启动时自动删除检查')
const result = await runAutoDeleteCleanup()
if (result.skipped) {
logInfo('自动删除检查完成:策略未启用')
} else if (result.deleted > 0 || result.failed > 0) {
logInfo('自动删除检查完成', { ...result })
} else {
logInfo('自动删除检查完成:无到期图片')
}
} catch (error) {
logException('启动时自动删除失败', error)
}
})()
})