use anyhow::Context;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::logging::log_info;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HylixConfig {
pub default_backend: BackendType,
pub scaffold_repo: String,
pub devnet: DevnetConfig,
pub build: BuildConfig,
pub bake_profile: String,
pub test: TestConfig,
pub run: RunConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestConfig {
pub print_server_logs: bool,
pub clean_server_data: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunConfig {
pub clean_server_data: bool,
pub server_port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, clap::ValueEnum)]
pub enum BackendType {
Sp1,
Risc0,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DevnetConfig {
pub node_image: String,
pub wallet_server_image: String,
pub wallet_ui_image: String,
pub node_port: u16,
pub da_port: u16,
pub wallet_api_port: u16,
pub wallet_ws_port: u16,
pub wallet_ui_port: u16,
pub indexer_port: u16,
pub postgres_port: u16,
pub auto_start: bool,
pub container_env: ContainerEnvConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ContainerEnvConfig {
pub node: Vec<String>,
pub indexer: Vec<String>,
pub wallet_server: Vec<String>,
pub wallet_ui: Vec<String>,
pub postgres: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BuildConfig {
pub release: bool,
pub jobs: Option<u32>,
pub extra_flags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BakeProfile {
pub name: String,
pub accounts: Vec<AccountConfig>,
pub funds: Vec<FundConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountConfig {
pub name: String,
pub password: String,
pub invite_code: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundConfig {
pub from: String,
pub from_password: String,
pub amount: u64,
pub token: String,
pub to: String,
}
impl Default for HylixConfig {
fn default() -> Self {
Self {
default_backend: BackendType::Risc0,
scaffold_repo: "https://github.com/hyli-org/app-scaffold".to_string(),
devnet: DevnetConfig::default(),
build: BuildConfig::default(),
bake_profile: "bobalice".to_string(),
test: TestConfig::default(),
run: RunConfig::default(),
}
}
}
impl Default for DevnetConfig {
fn default() -> Self {
Self {
node_image: "ghcr.io/hyli-org/hyli:0.14.0-rc3".to_string(),
wallet_server_image: "ghcr.io/hyli-org/wallet/wallet-server:main".to_string(),
wallet_ui_image: "ghcr.io/hyli-org/wallet/wallet-ui:main".to_string(),
da_port: 4141,
node_port: 4321,
indexer_port: 4322,
postgres_port: 5432,
wallet_ui_port: 8080,
wallet_api_port: 4000,
wallet_ws_port: 8081,
auto_start: true,
container_env: ContainerEnvConfig::default(),
}
}
}
impl Default for TestConfig {
fn default() -> Self {
Self {
print_server_logs: false,
clean_server_data: true,
}
}
}
impl Default for RunConfig {
fn default() -> Self {
Self {
clean_server_data: false,
server_port: 9002,
}
}
}
impl HylixConfig {
pub fn load() -> crate::error::HylixResult<Self> {
let config_path = Self::config_path()?;
if config_path.exists() {
let content = std::fs::read_to_string(&config_path)?;
let config: Self = toml::from_str(&content)
.map_err(crate::error::HylixError::Toml)
.with_context(|| {
format!(
"Failed to load configuration from file {}",
config_path.display()
)
})?;
Ok(config)
} else {
let config = Self::default();
config.save()?;
log_info(&format!(
"Created default configuration in file {}",
config_path.display()
));
Ok(config)
}
}
pub fn save(&self) -> crate::error::HylixResult<()> {
let config_path = Self::config_path()?;
let config_dir = config_path.parent().unwrap();
std::fs::create_dir_all(config_dir)?;
let content = toml::to_string_pretty(self)?;
std::fs::write(&config_path, content)?;
Ok(())
}
pub fn backup() -> crate::error::HylixResult<()> {
let config_path = Self::config_path()?;
let config_dir = config_path.parent().unwrap();
let backup_path = config_dir.join(format!(
"config.toml.{}.backup",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
));
std::fs::copy(&config_path, &backup_path)?;
log_info(&format!(
"Backed up configuration to {}",
backup_path.display()
));
Ok(())
}
fn config_path() -> crate::error::HylixResult<PathBuf> {
let config_dir = dirs::config_dir()
.ok_or_else(|| crate::error::HylixError::config("Could not find config directory"))?;
Ok(config_dir.join("hylix").join("config.toml"))
}
fn profiles_dir() -> crate::error::HylixResult<PathBuf> {
let config_dir = dirs::config_dir()
.ok_or_else(|| crate::error::HylixError::config("Could not find config directory"))?;
Ok(config_dir.join("hylix").join("profiles"))
}
pub fn load_bake_profile(&self, profile_name: &str) -> crate::error::HylixResult<BakeProfile> {
let profiles_dir = Self::profiles_dir()?;
let profile_path = profiles_dir.join(format!("{}.toml", profile_name));
if !profile_path.exists() {
return Err(crate::error::HylixError::config(format!(
"Profile '{}' not found at {}",
profile_name,
profile_path.display()
)));
}
let content = std::fs::read_to_string(&profile_path)?;
let profile: BakeProfile = toml::from_str(&content)
.map_err(crate::error::HylixError::Toml)
.with_context(|| {
format!(
"Failed to load profile from file {}",
profile_path.display()
)
})?;
log_info(&format!(
"Loaded profile '{}' from {}",
profile_name,
profile_path.display()
));
Ok(profile)
}
pub fn create_default_profile(&self) -> crate::error::HylixResult<()> {
let profiles_dir = Self::profiles_dir()?;
std::fs::create_dir_all(&profiles_dir)?;
let profile_path = profiles_dir.join("bobalice.toml");
if !profile_path.exists() {
let default_profile = BakeProfile {
name: "bobalice".to_string(),
accounts: vec![
AccountConfig {
name: "bob".to_string(),
password: "hylisecure".to_string(),
invite_code: "vip".to_string(),
},
AccountConfig {
name: "alice".to_string(),
password: "hylisecure".to_string(),
invite_code: "vip".to_string(),
},
],
funds: vec![
FundConfig {
from: "hyli".to_string(),
from_password: "hylisecure".to_string(),
amount: 1000,
token: "oranj".to_string(),
to: "bob".to_string(),
},
FundConfig {
from: "hyli".to_string(),
from_password: "hylisecure".to_string(),
amount: 1000,
token: "oranj".to_string(),
to: "alice".to_string(),
},
FundConfig {
from: "hyli".to_string(),
from_password: "hylisecure".to_string(),
amount: 500,
token: "oxygen".to_string(),
to: "bob".to_string(),
},
FundConfig {
from: "bob".to_string(),
from_password: "hylisecure".to_string(),
amount: 50,
token: "oxygen".to_string(),
to: "alice".to_string(),
},
],
};
let content = toml::to_string_pretty(&default_profile)?;
std::fs::write(&profile_path, content)?;
log_info(&format!(
"Created default bobalice profile at {}",
profile_path.display()
));
}
Ok(())
}
}