use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
use crate::detect::Accel;
use crate::output;
const CONFIG_FILENAME: &str = "knishio.toml";
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Config {
pub validator: ValidatorConfig,
pub docker: DockerConfig,
pub database: DatabaseConfig,
pub default_accel: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ValidatorConfig {
pub url: String,
pub insecure_tls: bool,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct DockerConfig {
pub compose_file: String,
pub postgres_container: String,
pub validator_container: String,
pub accel: HashMap<String, AccelProfile>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct AccelProfile {
pub files: Vec<String>,
pub native_validator: bool,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct DatabaseConfig {
pub user: String,
pub name: String,
}
impl Default for Config {
fn default() -> Self {
Self {
validator: ValidatorConfig::default(),
docker: DockerConfig::default(),
database: DatabaseConfig::default(),
default_accel: None,
}
}
}
impl Default for ValidatorConfig {
fn default() -> Self {
Self {
url: "https://localhost:8080".into(),
insecure_tls: false,
}
}
}
impl Default for DockerConfig {
fn default() -> Self {
Self {
compose_file: "docker-compose.standalone.yml".into(),
postgres_container: "knishio-postgres".into(),
validator_container: "knishio-validator".into(),
accel: default_accel_map(),
}
}
}
impl Default for AccelProfile {
fn default() -> Self {
Self {
files: Vec::new(),
native_validator: false,
}
}
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
user: "knishio".into(),
name: "knishio".into(),
}
}
}
fn default_accel_map() -> HashMap<String, AccelProfile> {
let mut m = HashMap::new();
m.insert(
"cpu".into(),
AccelProfile {
files: vec!["docker-compose.standalone.yml".into()],
native_validator: false,
},
);
m.insert(
"cuda".into(),
AccelProfile {
files: vec![
"docker-compose.standalone.yml".into(),
"docker-compose.cuda.yml".into(),
],
native_validator: false,
},
);
m.insert(
"dmr".into(),
AccelProfile {
files: vec![
"docker-compose.standalone.yml".into(),
"docker-compose.dmr.yml".into(),
],
native_validator: false,
},
);
m.insert(
"metal-native".into(),
AccelProfile {
files: vec!["docker-compose.metal.yml".into()],
native_validator: true,
},
);
m.insert(
"rocm".into(),
AccelProfile {
files: vec![
"docker-compose.standalone.yml".into(),
"docker-compose.rocm.yml".into(),
],
native_validator: false,
},
);
m.insert(
"vulkan".into(),
AccelProfile {
files: vec![
"docker-compose.standalone.yml".into(),
"docker-compose.vulkan.yml".into(),
],
native_validator: false,
},
);
m
}
impl Config {
pub fn load(search_start: &Path) -> Self {
let mut config = match find_config_file(search_start) {
Some(path) => match Self::from_file(&path) {
Ok(cfg) => {
output::info(&format!("Config loaded from {}", path.display()));
cfg
}
Err(e) => {
output::warn(&format!("Failed to parse {}: {}", path.display(), e));
Config::default()
}
},
None => Config::default(),
};
for (k, v) in default_accel_map() {
config.docker.accel.entry(k).or_insert(v);
}
config.apply_env_overrides();
config
}
fn from_file(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: Config = toml::from_str(&content)?;
Ok(config)
}
fn apply_env_overrides(&mut self) {
if let Ok(val) = std::env::var("KNISHIO_URL") {
self.validator.url = val;
}
if let Ok(val) = std::env::var("KNISHIO_PG_CONTAINER") {
self.docker.postgres_container = val;
}
if let Ok(val) = std::env::var("KNISHIO_VALIDATOR_CONTAINER") {
self.docker.validator_container = val;
}
if let Ok(val) = std::env::var("KNISHIO_DB_USER") {
self.database.user = val;
}
if let Ok(val) = std::env::var("KNISHIO_DB_NAME") {
self.database.name = val;
}
if let Ok(val) = std::env::var("KNISHIO_INSECURE_TLS") {
self.validator.insecure_tls =
val.eq_ignore_ascii_case("true") || val == "1";
}
if let Ok(val) = std::env::var("KNISHIO_ACCEL") {
self.default_accel = Some(val);
}
}
pub fn with_url_override(mut self, cli_url: &str) -> Self {
let clap_default = "https://localhost:8080";
if cli_url != clap_default || self.validator.url == clap_default {
self.validator.url = cli_url.to_string();
}
self
}
pub fn accel_files(&self, accel: Accel) -> &[String] {
self.docker
.accel
.get(accel.config_key())
.map(|p| p.files.as_slice())
.unwrap_or(&[])
}
pub fn accel_is_native(&self, accel: Accel) -> bool {
self.docker
.accel
.get(accel.config_key())
.map(|p| p.native_validator)
.unwrap_or(false)
}
}
fn find_config_file(start: &Path) -> Option<std::path::PathBuf> {
let mut dir = start.to_path_buf();
loop {
let candidate = dir.join(CONFIG_FILENAME);
if candidate.exists() {
return Some(candidate);
}
let candidate = dir.join("knishio-validator-rust").join(CONFIG_FILENAME);
if candidate.exists() {
return Some(candidate);
}
let candidate = dir
.join("servers")
.join("knishio-validator-rust")
.join(CONFIG_FILENAME);
if candidate.exists() {
return Some(candidate);
}
if !dir.pop() {
break;
}
}
None
}