use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tracing::{info, warn};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiteFSConfig {
pub fuse: FuseConfig,
pub data: DataConfig,
pub proxy: Option<ProxyConfig>,
pub lease: LeaseConfig,
pub log: Option<LogConfig>,
pub consul: Option<ConsulConfig>,
#[serde(rename = "static")]
pub static_config: Option<StaticConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FuseConfig {
pub dir: PathBuf,
#[serde(default = "default_debug")]
pub debug: bool,
#[serde(default = "default_allow_other")]
pub allow_other: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataConfig {
pub dir: PathBuf,
#[serde(default = "default_compress")]
pub compress: bool,
#[serde(default = "default_retention")]
pub retention: String,
#[serde(default = "default_retention_monitor_interval")]
pub retention_monitor_interval: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyConfig {
#[serde(default = "default_proxy_addr")]
pub addr: String,
pub target: String,
pub db: String,
#[serde(default = "default_passthrough")]
pub passthrough: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeaseConfig {
#[serde(rename = "type", default = "default_lease_type")]
pub lease_type: String,
pub advertise_url: Option<String>,
pub candidate: Option<bool>,
pub promote: Option<bool>,
pub demote: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogConfig {
#[serde(default = "default_log_format")]
pub format: String,
#[serde(default = "default_log_level")]
pub level: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsulConfig {
pub url: String,
pub advertise_url: String,
pub key: Option<String>,
pub ttl: Option<String>,
pub lock_ttl: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StaticConfig {
pub primary: bool,
pub hostname: String,
pub advertise_url: String,
}
fn default_debug() -> bool { false }
fn default_allow_other() -> bool { false }
fn default_compress() -> bool { true }
fn default_retention() -> String { "24h".to_string() }
fn default_retention_monitor_interval() -> String { "1h".to_string() }
fn default_proxy_addr() -> String { ":20202".to_string() }
fn default_passthrough() -> Vec<String> { vec![] }
fn default_lease_type() -> String { "static".to_string() }
fn default_log_format() -> String { "text".to_string() }
fn default_log_level() -> String { "info".to_string() }
impl Default for LiteFSConfig {
fn default() -> Self {
Self {
fuse: FuseConfig {
dir: PathBuf::from("/litefs"),
debug: false,
allow_other: false,
},
data: DataConfig {
dir: PathBuf::from("/var/lib/litefs"),
compress: true,
retention: "24h".to_string(),
retention_monitor_interval: "1h".to_string(),
},
proxy: Some(ProxyConfig {
addr: ":20202".to_string(),
target: "localhost:8080".to_string(),
db: "db".to_string(),
passthrough: vec![],
}),
lease: LeaseConfig {
lease_type: "static".to_string(),
advertise_url: None,
candidate: Some(true),
promote: Some(true),
demote: Some(false),
},
log: Some(LogConfig {
format: "text".to_string(),
level: "info".to_string(),
}),
consul: None,
static_config: Some(StaticConfig {
primary: true,
hostname: "localhost".to_string(),
advertise_url: "http://localhost:20202".to_string(),
}),
}
}
}
impl LiteFSConfig {
pub fn for_local_dev(machine_id: &str, mount_dir: PathBuf, data_dir: PathBuf, is_primary: bool) -> Self {
Self {
fuse: FuseConfig {
dir: mount_dir,
debug: true,
allow_other: true,
},
data: DataConfig {
dir: data_dir,
compress: true,
retention: "24h".to_string(),
retention_monitor_interval: "1h".to_string(),
},
proxy: Some(ProxyConfig {
addr: ":20202".to_string(),
target: "localhost:8080".to_string(),
db: "db".to_string(),
passthrough: vec![],
}),
lease: LeaseConfig {
lease_type: "static".to_string(),
advertise_url: Some(format!("http://{}:20202", machine_id)),
candidate: Some(is_primary),
promote: Some(is_primary),
demote: Some(false),
},
log: Some(LogConfig {
format: "text".to_string(),
level: "debug".to_string(),
}),
consul: None,
static_config: Some(StaticConfig {
primary: is_primary,
hostname: machine_id.to_string(),
advertise_url: format!("http://{}:20202", machine_id),
}),
}
}
pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
serde_yaml::to_string(self)
}
pub fn from_yaml(yaml: &str) -> Result<Self, serde_yaml::Error> {
serde_yaml::from_str(yaml)
}
pub fn from_production_config(content: &str, machine_id: &str, app_name: &str) -> Result<Self, anyhow::Error> {
use anyhow::{Context, bail};
let mut config: LiteFSConfig = serde_yaml::from_str(content)
.context("Failed to parse production LiteFS configuration")?;
match config.lease.lease_type.as_str() {
"consul" => {
info!("Converting Consul lease to static lease for local development");
config.lease.lease_type = "static".to_string();
config.lease.candidate = Some(true);
config.lease.promote = Some(true);
config.lease.advertise_url = Some(format!("http://{}:20202", machine_id));
}
"static" => {
info!("Production config already uses static lease - adapting for local");
config.lease.advertise_url = Some(format!("http://{}:20202", machine_id));
}
other => {
warn!("Unknown lease type '{}' in production config, using static", other);
config.lease.lease_type = "static".to_string();
config.lease.candidate = Some(true);
config.lease.promote = Some(true);
config.lease.advertise_url = Some(format!("http://{}:20202", machine_id));
}
}
if let Some(ref mut proxy) = config.proxy {
if !proxy.addr.contains("localhost") && !proxy.addr.starts_with(':') {
warn!("Adapting proxy address from {} to :20202", proxy.addr);
proxy.addr = ":20202".to_string();
}
if proxy.target.is_empty() {
bail!("Production config has empty proxy target");
}
info!("Using proxy configuration: {} -> {}", proxy.addr, proxy.target);
}
let base_path = std::env::var("MINIFLY_DATA_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("./minifly-data"))
.join(app_name)
.join("litefs")
.join(machine_id);
config.fuse.dir = base_path.join("mount");
config.data.dir = base_path.join("data");
if let Err(e) = std::fs::create_dir_all(&config.fuse.dir) {
warn!("Failed to create FUSE dir: {}", e);
}
if let Err(e) = std::fs::create_dir_all(&config.data.dir) {
warn!("Failed to create data dir: {}", e);
}
config.fuse.debug = true;
config.fuse.allow_other = true;
match &mut config.log {
Some(log) => {
log.level = "debug".to_string();
log.format = "text".to_string(); }
None => {
config.log = Some(LogConfig {
level: "debug".to_string(),
format: "text".to_string(),
});
}
}
config.static_config = Some(StaticConfig {
primary: true,
hostname: machine_id.to_string(),
advertise_url: format!("http://{}:20202", machine_id),
});
config.consul = None;
info!("Successfully adapted production LiteFS config for local development");
info!("Mount dir: {:?}", config.fuse.dir);
info!("Data dir: {:?}", config.data.dir);
Ok(config)
}
}