const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); const repoRoot = path.join(__dirname, "..", ".."); const recoveryDir = path.join(repoRoot, "data", "recovery"); const markerPath = path.join(recoveryDir, "update-marker.json"); const safeModeFlagPath = path.join(recoveryDir, "safe-mode.flag"); function ensureRecoveryDir() { fs.mkdirSync(recoveryDir, { recursive: true }); } function readRecoveryMarker() { try { if (!fs.existsSync(markerPath)) return null; const marker = JSON.parse(fs.readFileSync(markerPath, "utf8")); return marker && typeof marker === "object" ? marker : null; } catch { return { id: "unreadable", status: "failed", error: "Recovery marker could not be parsed.", marker_path: markerPath }; } } function writeRecoveryMarker(marker) { ensureRecoveryDir(); const next = { id: marker.id || crypto.randomUUID(), status: marker.status || "pending", updated_at: new Date().toISOString(), ...marker }; fs.writeFileSync(markerPath, JSON.stringify(next, null, 2), "utf8"); return next; } function createRecoveryMarker(details) { return writeRecoveryMarker({ id: crypto.randomUUID(), status: "pending", created_at: new Date().toISOString(), ...details }); } function updateRecoveryMarker(values) { const current = readRecoveryMarker() || {}; return writeRecoveryMarker({ ...current, ...values }); } function markRecoveryMarkerFailed(error) { return updateRecoveryMarker({ status: "failed", error: error?.message || String(error || "Update failed.") }); } function markRecoveryMarkerComplete(values = {}) { return updateRecoveryMarker({ ...values, status: "completed", completed_at: new Date().toISOString() }); } function clearRecoveryMarker() { try { fs.rmSync(markerPath, { force: true }); } catch { // ignore cleanup failures } } function markStartupVerification() { const marker = readRecoveryMarker(); if (!marker) return null; if (marker.status === "completed") { clearRecoveryMarker(); return { ...marker, cleared: true }; } if (["pending", "applying", "verifying"].includes(marker.status)) { return writeRecoveryMarker({ ...marker, status: "stale", stale_at: new Date().toISOString(), error: marker.error || "Previous update did not complete startup verification." }); } return marker; } function isSafeModeRequested(argv = process.argv, env = process.env) { return env.LUMI_SAFE_MODE === "1" || env.SAFE_MODE === "1" || argv.includes("--safe-mode") || fs.existsSync(safeModeFlagPath); } function safeModeStatus() { const marker = readRecoveryMarker(); const requested = isSafeModeRequested(); const incomplete = marker && ["pending", "applying", "verifying", "failed", "stale"].includes(marker.status); return { requested, active: process.env.SAFE_MODE === "1" || process.env.LUMI_SAFE_MODE === "1", marker, has_incomplete_marker: Boolean(incomplete), safe_mode_flag: fs.existsSync(safeModeFlagPath), marker_path: markerPath, flag_path: safeModeFlagPath }; } module.exports = { recoveryDir, markerPath, safeModeFlagPath, readRecoveryMarker, writeRecoveryMarker, createRecoveryMarker, updateRecoveryMarker, markRecoveryMarkerFailed, markRecoveryMarkerComplete, clearRecoveryMarker, markStartupVerification, isSafeModeRequested, safeModeStatus };