#![cfg_attr(not(test), forbid(unsafe_code))]
use anyhow::{Context, Result};
pub fn boot_checks(data_dir: &std::path::Path) -> Result<()> {
use ocserve_store::pragma;
pragma::assert_version_ok().context("sqlite version gate")?;
let probe = data_dir.join(".boot-probe");
std::fs::write(&probe, b"ok")
.with_context(|| format!("write probe in {}", data_dir.display()))?;
let _ = std::fs::remove_file(&probe);
match stat_free_bytes(data_dir) {
Ok(free) if free < 128 * 1024 * 1024 => {
anyhow::bail!("only {free} bytes free; need >128 MB (2x WAL limit)");
}
Ok(free) => tracing::debug!("disk headroom {free} bytes"),
Err(e) => tracing::warn!("disk headroom check skipped: {e:#}"), }
let db = ocserve_store::writer::db_path(data_dir);
let conn = pragma::open_writer(&db).context("open/create database")?;
ocserve_store::schema::migrate(&conn).context("schema migrate")?;
let pruned = ocserve_store::enforce_event_retention(&conn).context("event retention")?;
if pruned > 0 {
tracing::info!("event retention at boot: {pruned} rows pruned");
}
conn.execute_batch(
"CREATE VIRTUAL TABLE temp.boot_fts_probe USING fts5(x, tokenize='trigram');
INSERT INTO temp.boot_fts_probe VALUES ('boot fts probe');
SELECT count(*) FROM temp.boot_fts_probe WHERE boot_fts_probe MATCH '\"ts prob\"';
DROP TABLE temp.boot_fts_probe;",
)
.context("FTS5 roundtrip (broken FTS build?)")?;
tracing::info!("boot checks OK");
Ok(())
}
pub fn stat_free_bytes(p: &std::path::Path) -> Result<u64> {
let out = std::process::Command::new("df").arg("-k").arg(p).output()?;
let text = String::from_utf8_lossy(&out.stdout);
let line = text.lines().nth(1).context("df output")?;
let avail: u64 = line
.split_whitespace()
.nth(3)
.context("df avail column")?
.parse()
.context("df avail parse")?;
Ok(avail * 1024)
}