use std::io::Write;
use std::path::PathBuf;
use mail4agent_api::{MailError, ParticipantId};
use mail4agent_core::ParticipantPermissions;
use crate::service::MailboxService;
const BOOTSTRAP_OPERATOR_ID: &str = "operator";
#[derive(Debug, thiserror::Error)]
pub enum BootstrapError {
#[error("mailbox: {0}")]
Mailbox(MailError),
#[error("resolve home directory to write the operator key")]
NoHomeDir,
#[error("create {0}: {1}")]
CreateDir(PathBuf, std::io::Error),
#[error(
"operator key file {0} already exists but no operator participant is registered -- \
refusing to overwrite a secret that may still be in use; remove the file by hand \
only after confirming it is stale"
)]
KeyFileAlreadyExists(PathBuf),
#[error("write {0}: {1}")]
WriteKeyFile(PathBuf, std::io::Error),
}
pub async fn ensure_bootstrap_operator(service: &MailboxService) -> Result<(), BootstrapError> {
let id = ParticipantId::new(BOOTSTRAP_OPERATOR_ID).map_err(BootstrapError::Mailbox)?;
let already_registered = service
.participant_exists(id.clone())
.await
.map_err(BootstrapError::Mailbox)?;
if already_registered {
tracing::info!(participant = BOOTSTRAP_OPERATOR_ID, "bootstrap operator already registered");
return Ok(());
}
let key_path = operator_key_path()?;
if let Some(parent) = key_path.parent() {
std::fs::create_dir_all(parent).map_err(|err| BootstrapError::CreateDir(parent.to_path_buf(), err))?;
}
if key_path.exists() {
return Err(BootstrapError::KeyFileAlreadyExists(key_path));
}
let permissions = ParticipantPermissions { may_send: true, may_read: true, operator: true };
let secret = service
.register_participant(id, Some("bootstrap operator".to_string()), permissions)
.await
.map_err(BootstrapError::Mailbox)?;
write_key_file_fresh(&key_path, &secret)?;
tracing::info!(path = %key_path.display(), "bootstrap operator registered; secret written to disk");
Ok(())
}
fn operator_key_path() -> Result<PathBuf, BootstrapError> {
let base = directories::BaseDirs::new().ok_or(BootstrapError::NoHomeDir)?;
Ok(base.home_dir().join(".mail4agent").join("operator-key.raw"))
}
fn write_key_file_fresh(path: &PathBuf, secret: &str) -> Result<(), BootstrapError> {
let mut file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.map_err(|err| {
if err.kind() == std::io::ErrorKind::AlreadyExists {
BootstrapError::KeyFileAlreadyExists(path.clone())
} else {
BootstrapError::WriteKeyFile(path.clone(), err)
}
})?;
file.write_all(secret.as_bytes())
.map_err(|err| BootstrapError::WriteKeyFile(path.clone(), err))?;
Ok(())
}