2026-07-20 09:04:57 +07:00
|
|
|
//! Bootstrap binary — seeds initial system data idempotently.
|
|
|
|
|
//!
|
|
|
|
|
//! Creates default permissions, roles, and admin user if they don't
|
|
|
|
|
//! already exist. Run once after first deployment.
|
|
|
|
|
//!
|
|
|
|
|
//! Usage: cargo run --bin bootstrap
|
|
|
|
|
|
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
|
|
|
println!("Zesdex Bootstrap — seeding initial data...");
|
|
|
|
|
|
|
|
|
|
let store = zesdex_domain::core::Store::new();
|
|
|
|
|
store.ensure_dirs()?;
|
|
|
|
|
|
|
|
|
|
// Seed default settings if not present
|
|
|
|
|
let settings_path = store.base_dir.join("settings.json");
|
|
|
|
|
if !settings_path.exists() {
|
|
|
|
|
let settings = zesdex_domain::cms::Settings::default();
|
|
|
|
|
let content = serde_json::to_string_pretty(&settings)?;
|
2026-07-20 12:26:08 +07:00
|
|
|
let tmp = store.base_dir.join("settings.json.tmp");
|
|
|
|
|
std::fs::write(&tmp, &content)?;
|
|
|
|
|
std::fs::File::open(&tmp)?.sync_all()?;
|
|
|
|
|
std::fs::rename(&tmp, &settings_path)?;
|
2026-07-20 09:04:57 +07:00
|
|
|
println!(" ✓ Default settings created");
|
|
|
|
|
} else {
|
|
|
|
|
println!(" · Settings already exist, skipping");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Seed default app config if not present
|
|
|
|
|
let config_path = store.base_dir.join("app_config.json");
|
|
|
|
|
if !config_path.exists() {
|
|
|
|
|
let config = zesdex_domain::cms::AppConfig::default();
|
|
|
|
|
let content = serde_json::to_string_pretty(&config)?;
|
2026-07-20 12:26:08 +07:00
|
|
|
let tmp = store.base_dir.join("app_config.json.tmp");
|
|
|
|
|
std::fs::write(&tmp, &content)?;
|
|
|
|
|
std::fs::File::open(&tmp)?.sync_all()?;
|
|
|
|
|
std::fs::rename(&tmp, &config_path)?;
|
2026-07-20 09:04:57 +07:00
|
|
|
println!(" ✓ Default app_config created");
|
|
|
|
|
} else {
|
|
|
|
|
println!(" · App config already exists, skipping");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("Bootstrap complete.");
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|