151 lines
3.9 KiB
TypeScript
151 lines
3.9 KiB
TypeScript
import {
|
|
runSsh,
|
|
shellQuote,
|
|
resolveFolderRemoteDir
|
|
} from "../utils/ssh.ts"
|
|
|
|
import backupOptions from "../config/backup-options.json"
|
|
|
|
export const BACKUP_HOUR = Number(process.env.BACKUPS_HOUR) || 19
|
|
|
|
type BackupTarget = {
|
|
name: string
|
|
}
|
|
|
|
type LatestBackupInfo = {
|
|
fileName: string | null
|
|
modifiedAt: string | null
|
|
}
|
|
|
|
const backupTargets = backupOptions as BackupTarget[]
|
|
|
|
function toLabel(name: string) {
|
|
if (name === "sirh") return "SIRH"
|
|
return name.charAt(0).toUpperCase() + name.slice(1)
|
|
}
|
|
|
|
function pad(value: number) {
|
|
return String(value).padStart(2, "0")
|
|
}
|
|
|
|
function formatDateKey(date: Date) {
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
|
}
|
|
|
|
function getExpectedBackupDate(now: Date) {
|
|
const expected = new Date(now)
|
|
if (now.getHours() < BACKUP_HOUR) {
|
|
expected.setDate(expected.getDate() - 1)
|
|
}
|
|
|
|
expected.setHours(BACKUP_HOUR, 0, 0, 0)
|
|
return expected
|
|
}
|
|
|
|
function extractBackupDate(fileName: string | null) {
|
|
if (!fileName) return null
|
|
|
|
const normalized = fileName.replace(/[^0-9]/g, "")
|
|
|
|
const yearFirst = normalized.match(/(20\d{2})(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])/)
|
|
if (yearFirst) {
|
|
return `${yearFirst[1]}-${yearFirst[2]}-${yearFirst[3]}`
|
|
}
|
|
|
|
const dayFirst = normalized.match(/(0[1-9]|[12]\d|3[01])(0[1-9]|1[0-2])(20\d{2})/)
|
|
if (dayFirst) {
|
|
return `${dayFirst[3]}-${dayFirst[2]}-${dayFirst[1]}`
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function parseRemoteTimestamp(value: string) {
|
|
const timestamp = Number(value)
|
|
if (!Number.isFinite(timestamp) || timestamp <= 0) {
|
|
return null
|
|
}
|
|
|
|
return new Date(timestamp * 1000).toISOString()
|
|
}
|
|
|
|
async function getLatestBackupInfo(remoteDir: string): Promise<LatestBackupInfo> {
|
|
const output = await runSsh(
|
|
`cd ${shellQuote(remoteDir)} && for file in *; do [ -e "$file" ] || continue; printf '%s\\t%s\\n' "$(stat -c '%Y' "$file")" "$file"; done | sort -rn | head -n 1`
|
|
)
|
|
|
|
const line = output.trim()
|
|
if (!line) {
|
|
return { fileName: null, modifiedAt: null }
|
|
}
|
|
|
|
const [timestamp, ...nameParts] = line.split("\t")
|
|
const fileName = nameParts.join("\t").trim() || null
|
|
|
|
return {
|
|
fileName,
|
|
modifiedAt: parseRemoteTimestamp(timestamp)
|
|
}
|
|
}
|
|
|
|
export default defineEventHandler(async () => {
|
|
const now = new Date()
|
|
const expectedBackupDate = getExpectedBackupDate(now)
|
|
const expectedDateKey = formatDateKey(expectedBackupDate)
|
|
const checkedAt = now.toISOString()
|
|
|
|
const results = await Promise.all(
|
|
backupTargets.map(async (target) => {
|
|
try {
|
|
const remoteDir = await resolveFolderRemoteDir(target.name)
|
|
if (!remoteDir) {
|
|
return {
|
|
label: toLabel(target.name),
|
|
folder: target.name,
|
|
ok: false,
|
|
status: 0,
|
|
checkedAt,
|
|
latestBackup: null,
|
|
latestBackupAt: null,
|
|
backupDate: null,
|
|
expectedBackupDate: expectedDateKey,
|
|
error: "Dossier de backup introuvable"
|
|
}
|
|
}
|
|
|
|
const latestBackupInfo = await getLatestBackupInfo(remoteDir)
|
|
const backupDate = extractBackupDate(latestBackupInfo.fileName)
|
|
const ok = backupDate === expectedDateKey
|
|
|
|
return {
|
|
label: toLabel(target.name),
|
|
folder: target.name,
|
|
ok,
|
|
status: ok ? 200 : 0,
|
|
checkedAt,
|
|
latestBackup: latestBackupInfo.fileName,
|
|
latestBackupAt: latestBackupInfo.modifiedAt,
|
|
backupDate,
|
|
expectedBackupDate: expectedDateKey,
|
|
error: latestBackupInfo.fileName ? undefined : "Aucun backup trouve"
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
label: toLabel(target.name),
|
|
folder: target.name,
|
|
ok: false,
|
|
status: 0,
|
|
checkedAt,
|
|
latestBackup: null,
|
|
latestBackupAt: null,
|
|
backupDate: null,
|
|
expectedBackupDate: expectedDateKey,
|
|
error: "Erreur lors de la verification"
|
|
}
|
|
}
|
|
})
|
|
)
|
|
|
|
return { results }
|
|
})
|