use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::exit::{CtlError, CtlResult};
const DEFAULT_SERVER: &str = "http://127.0.0.1:8080";
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
pub server_url: Option<String>,
pub api_token: Option<String>,
}
impl Config {
pub fn effective_server(&self) -> String {
self.server_url
.clone()
.or_else(|| std::env::var("CELLCTL_SERVER").ok())
.unwrap_or_else(|| DEFAULT_SERVER.to_string())
}
pub fn effective_token(&self) -> Option<String> {
self.api_token
.clone()
.or_else(|| std::env::var("CELLCTL_TOKEN").ok())
}
}
pub fn config_path() -> CtlResult<PathBuf> {
if let Ok(explicit) = std::env::var("CELLCTL_CONFIG") {
return Ok(PathBuf::from(explicit));
}
let home =
home::home_dir().ok_or_else(|| CtlError::usage("cannot determine home directory"))?;
Ok(home.join(".cellctl").join("config"))
}
pub fn load() -> CtlResult<Config> {
let path = config_path()?;
if !path.exists() {
return Ok(Config::default());
}
let raw = fs::read_to_string(&path)
.map_err(|e| CtlError::usage(format!("read {}: {e}", path.display())))?;
let cfg: Config = toml::from_str(&raw)
.map_err(|e| CtlError::usage(format!("parse {}: {e}", path.display())))?;
Ok(cfg)
}
pub fn save(cfg: &Config) -> CtlResult<()> {
let path = config_path()?;
if let Some(parent) = path.parent() {
ensure_dir(parent)?;
}
let raw = toml::to_string_pretty(cfg)
.map_err(|e| CtlError::usage(format!("serialize config: {e}")))?;
write_owner_only(&path, raw.as_bytes())
.map_err(|e| CtlError::usage(format!("write {}: {e}", path.display())))?;
Ok(())
}
fn write_owner_only(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
use std::io::Write as _;
#[cfg(unix)]
{
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
let mut f = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(path)?;
f.write_all(bytes)?;
f.sync_all()?;
fs::set_permissions(path, fs::Permissions::from_mode(0o600))?;
Ok(())
}
#[cfg(not(unix))]
{
let mut f = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)?;
f.write_all(bytes)?;
f.sync_all()?;
Ok(())
}
}
fn ensure_dir(p: &Path) -> CtlResult<()> {
fs::create_dir_all(p).map_err(|e| CtlError::usage(format!("mkdir {}: {e}", p.display())))
}