512 lines
20 KiB
Rust
512 lines
20 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use anyhow::Result;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TemplateConfig {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub version: String,
|
|
pub min_hetzner_plan: String,
|
|
#[serde(rename = "min_hetzner_plan_num")]
|
|
pub min_hetzner_plan_num: u32,
|
|
#[serde(rename = "estimated_monthly_cost")]
|
|
pub estimated_monthly_cost: f64,
|
|
pub services: Vec<ServiceConfig>,
|
|
pub requirements: TemplateRequirements,
|
|
#[serde(rename = "estimated_time_minutes")]
|
|
pub estimated_time_minutes: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServiceConfig {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub image: String,
|
|
pub ports: Vec<String>,
|
|
#[serde(default)]
|
|
pub environment: Vec<EnvVar>,
|
|
#[serde(default)]
|
|
pub volumes: Vec<String>,
|
|
#[serde(rename = "resource_profile", default)]
|
|
pub resource_profile: String,
|
|
#[serde(rename = "has_persistent_data", default)]
|
|
pub has_persistent_data: bool,
|
|
#[serde(rename = "is_critical", default)]
|
|
pub is_critical: bool,
|
|
#[serde(default)]
|
|
pub optional: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct EnvVar {
|
|
pub name: String,
|
|
pub value: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TemplateRequirements {
|
|
#[serde(rename = "min_nodes")]
|
|
pub min_nodes: i32,
|
|
#[serde(rename = "max_nodes")]
|
|
pub max_nodes: i32,
|
|
#[serde(rename = "supports_ha", default)]
|
|
pub supports_ha: bool,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct TemplateValidation {
|
|
pub valid: bool,
|
|
pub warnings: Vec<String>,
|
|
}
|
|
|
|
impl TemplateConfig {
|
|
/// Load all available templates
|
|
pub async fn all_templates() -> Vec<TemplateConfig> {
|
|
vec![
|
|
Self::db_node_template(),
|
|
Self::worker_node_template(),
|
|
Self::control_plane_node_template(),
|
|
Self::monitoring_node_template(),
|
|
Self::worker_db_combo_template(),
|
|
Self::worker_monitor_combo_template(),
|
|
Self::all_in_one_template(),
|
|
]
|
|
}
|
|
|
|
/// Load template by ID
|
|
pub async fn from_template_id(id: &str) -> Result<Self> {
|
|
let templates = Self::all_templates().await;
|
|
templates.into_iter()
|
|
.find(|t| t.id == id)
|
|
.ok_or_else(|| anyhow::anyhow!("Template not found: {}", id))
|
|
}
|
|
|
|
pub fn validate(&self) -> TemplateValidation {
|
|
let mut warnings = Vec::new();
|
|
|
|
if self.min_hetzner_plan_num < 11 {
|
|
warnings.push("Plan CX11 is minimum recommended".to_string());
|
|
}
|
|
|
|
if self.services.is_empty() {
|
|
warnings.push("Template has no services".to_string());
|
|
}
|
|
|
|
if self.requirements.max_nodes > 1 && !self.requirements.supports_ha {
|
|
warnings.push("Multiple nodes but HA not supported".to_string());
|
|
}
|
|
|
|
TemplateValidation {
|
|
valid: warnings.is_empty(),
|
|
warnings,
|
|
}
|
|
}
|
|
|
|
// Template definitions
|
|
|
|
fn db_node_template() -> Self {
|
|
Self {
|
|
id: "db-node".to_string(),
|
|
name: "Database Node".to_string(),
|
|
description: "PostgreSQL with Patroni for HA clustering".to_string(),
|
|
version: "1.0".to_string(),
|
|
min_hetzner_plan: "CX21".to_string(),
|
|
min_hetzner_plan_num: 21,
|
|
estimated_monthly_cost: 6.94,
|
|
estimated_time_minutes: 15,
|
|
services: vec![
|
|
ServiceConfig {
|
|
id: "postgresql".to_string(),
|
|
name: "PostgreSQL".to_string(),
|
|
image: "registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2".to_string(),
|
|
ports: vec!["5432:5432".to_string(), "8008:8008".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["postgres_data:/var/lib/postgresql/data".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "etcd".to_string(),
|
|
name: "etcd".to_string(),
|
|
image: "quay.io/coreos/etcd:v3.5.9".to_string(),
|
|
ports: vec!["2379:2379".to_string(), "2380:2380".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["etcd_data:/etcd-data".to_string()],
|
|
resource_profile: "minimal".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "haproxy".to_string(),
|
|
name: "HAProxy".to_string(),
|
|
image: "haproxy:2.8-alpine".to_string(),
|
|
ports: vec!["5433:5433".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "minimal".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: false,
|
|
optional: false,
|
|
},
|
|
],
|
|
requirements: TemplateRequirements {
|
|
min_nodes: 3,
|
|
max_nodes: 7,
|
|
supports_ha: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn worker_node_template() -> Self {
|
|
Self {
|
|
id: "worker-node".to_string(),
|
|
name: "Worker Node".to_string(),
|
|
description: "API worker nodes for horizontal scaling".to_string(),
|
|
version: "1.0".to_string(),
|
|
min_hetzner_plan: "CX11".to_string(),
|
|
min_hetzner_plan_num: 11,
|
|
estimated_monthly_cost: 3.69,
|
|
estimated_time_minutes: 10,
|
|
services: vec![
|
|
ServiceConfig {
|
|
id: "worker".to_string(),
|
|
name: "MadBase Worker".to_string(),
|
|
image: "madbase/worker:latest".to_string(),
|
|
ports: vec!["8002:8002".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "cpu_intensive".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "vmagent".to_string(),
|
|
name: "VictoriaMetrics Agent".to_string(),
|
|
image: "victoriametrics/vmagent:latest".to_string(),
|
|
ports: vec!["8429:8429".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["./config/vmagent.yml:/etc/vmagent/prometheus.yml:ro".to_string()],
|
|
resource_profile: "minimal".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: false,
|
|
optional: true,
|
|
},
|
|
],
|
|
requirements: TemplateRequirements {
|
|
min_nodes: 1,
|
|
max_nodes: 20,
|
|
supports_ha: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn control_plane_node_template() -> Self {
|
|
Self {
|
|
id: "control-plane-node".to_string(),
|
|
name: "Control Plane Node".to_string(),
|
|
description: "Management APIs and Studio UI".to_string(),
|
|
version: "1.0".to_string(),
|
|
min_hetzner_plan: "CX11".to_string(),
|
|
min_hetzner_plan_num: 11,
|
|
estimated_monthly_cost: 3.69,
|
|
estimated_time_minutes: 12,
|
|
services: vec![
|
|
ServiceConfig {
|
|
id: "proxy".to_string(),
|
|
name: "Gateway Proxy".to_string(),
|
|
image: "madbase/proxy:latest".to_string(),
|
|
ports: vec!["8080:8080".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "control".to_string(),
|
|
name: "Control Plane API".to_string(),
|
|
image: "madbase/control:latest".to_string(),
|
|
ports: vec!["8001:8001".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "grafana".to_string(),
|
|
name: "Grafana".to_string(),
|
|
image: "grafana/grafana:latest".to_string(),
|
|
ports: vec!["3030:3030".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["grafana_data:/var/lib/grafana".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: false,
|
|
optional: true,
|
|
},
|
|
],
|
|
requirements: TemplateRequirements {
|
|
min_nodes: 1,
|
|
max_nodes: 2,
|
|
supports_ha: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn monitoring_node_template() -> Self {
|
|
Self {
|
|
id: "monitoring-node".to_string(),
|
|
name: "Monitoring Node".to_string(),
|
|
description: "Centralized metrics and logging".to_string(),
|
|
version: "1.0".to_string(),
|
|
min_hetzner_plan: "CX11".to_string(),
|
|
min_hetzner_plan_num: 11,
|
|
estimated_monthly_cost: 3.69,
|
|
estimated_time_minutes: 10,
|
|
services: vec![
|
|
ServiceConfig {
|
|
id: "victoriametrics".to_string(),
|
|
name: "VictoriaMetrics".to_string(),
|
|
image: "victoriametrics/victoria-metrics:latest".to_string(),
|
|
ports: vec!["8428:8428".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["vm_data:/victoria-metrics-data".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: false,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "loki".to_string(),
|
|
name: "Loki".to_string(),
|
|
image: "grafana/loki:latest".to_string(),
|
|
ports: vec!["3100:3100".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["loki_data:/loki".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: false,
|
|
optional: false,
|
|
},
|
|
],
|
|
requirements: TemplateRequirements {
|
|
min_nodes: 1,
|
|
max_nodes: 2,
|
|
supports_ha: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn worker_db_combo_template() -> Self {
|
|
Self {
|
|
id: "worker-db-combo".to_string(),
|
|
name: "Worker + Database Combo".to_string(),
|
|
description: "Combined worker and database node for smaller deployments".to_string(),
|
|
version: "1.0".to_string(),
|
|
min_hetzner_plan: "CX31".to_string(),
|
|
min_hetzner_plan_num: 31,
|
|
estimated_monthly_cost: 14.21,
|
|
estimated_time_minutes: 20,
|
|
services: vec![
|
|
ServiceConfig {
|
|
id: "postgresql".to_string(),
|
|
name: "PostgreSQL".to_string(),
|
|
image: "registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2".to_string(),
|
|
ports: vec!["5432:5432".to_string(), "8008:8008".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["postgres_data:/var/lib/postgresql/data".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "etcd".to_string(),
|
|
name: "etcd".to_string(),
|
|
image: "quay.io/coreos/etcd:v3.5.9".to_string(),
|
|
ports: vec!["2379:2379".to_string(), "2380:2380".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["etcd_data:/etcd-data".to_string()],
|
|
resource_profile: "minimal".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "haproxy".to_string(),
|
|
name: "HAProxy".to_string(),
|
|
image: "haproxy:2.8-alpine".to_string(),
|
|
ports: vec!["5433:5433".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "minimal".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: false,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "worker".to_string(),
|
|
name: "MadBase Worker".to_string(),
|
|
image: "madbase/worker:latest".to_string(),
|
|
ports: vec!["8002:8002".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "cpu_intensive".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "vmagent".to_string(),
|
|
name: "VictoriaMetrics Agent".to_string(),
|
|
image: "victoriametrics/vmagent:latest".to_string(),
|
|
ports: vec!["8429:8429".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["./config/vmagent.yml:/etc/vmagent/prometheus.yml:ro".to_string()],
|
|
resource_profile: "minimal".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: false,
|
|
optional: false,
|
|
},
|
|
],
|
|
requirements: TemplateRequirements {
|
|
min_nodes: 1,
|
|
max_nodes: 2,
|
|
supports_ha: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn worker_monitor_combo_template() -> Self {
|
|
Self {
|
|
id: "worker-monitor-combo".to_string(),
|
|
name: "Worker + Monitoring Combo".to_string(),
|
|
description: "Worker node with local VictoriaMetrics and Loki".to_string(),
|
|
version: "1.0".to_string(),
|
|
min_hetzner_plan: "CX21".to_string(),
|
|
min_hetzner_plan_num: 21,
|
|
estimated_monthly_cost: 6.94,
|
|
estimated_time_minutes: 15,
|
|
services: vec![
|
|
ServiceConfig {
|
|
id: "worker".to_string(),
|
|
name: "MadBase Worker".to_string(),
|
|
image: "madbase/worker:latest".to_string(),
|
|
ports: vec!["8002:8002".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "cpu_intensive".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "victoriametrics".to_string(),
|
|
name: "VictoriaMetrics".to_string(),
|
|
image: "victoriametrics/victoria-metrics:latest".to_string(),
|
|
ports: vec!["8428:8428".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["vm_data:/victoria-metrics-data".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: false,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "loki".to_string(),
|
|
name: "Loki".to_string(),
|
|
image: "grafana/loki:latest".to_string(),
|
|
ports: vec!["3100:3100".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["loki_data:/loki".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: false,
|
|
optional: false,
|
|
},
|
|
],
|
|
requirements: TemplateRequirements {
|
|
min_nodes: 1,
|
|
max_nodes: 3,
|
|
supports_ha: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn all_in_one_template() -> Self {
|
|
Self {
|
|
id: "all-in-one".to_string(),
|
|
name: "All-in-One Development Node".to_string(),
|
|
description: "Complete MadBase stack on a single server".to_string(),
|
|
version: "1.0".to_string(),
|
|
min_hetzner_plan: "CX41".to_string(),
|
|
min_hetzner_plan_num: 41,
|
|
estimated_monthly_cost: 25.60,
|
|
estimated_time_minutes: 25,
|
|
services: vec![
|
|
ServiceConfig {
|
|
id: "postgresql".to_string(),
|
|
name: "PostgreSQL".to_string(),
|
|
image: "registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2".to_string(),
|
|
ports: vec!["5432:5432".to_string(), "8008:8008".to_string()],
|
|
environment: vec![],
|
|
volumes: vec!["postgres_data:/var/lib/postgresql/data".to_string()],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: true,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "worker".to_string(),
|
|
name: "MadBase Worker".to_string(),
|
|
image: "madbase/worker:latest".to_string(),
|
|
ports: vec!["8002:8002".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "cpu_intensive".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "proxy".to_string(),
|
|
name: "Gateway Proxy".to_string(),
|
|
image: "madbase/proxy:latest".to_string(),
|
|
ports: vec!["8080:8080".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
ServiceConfig {
|
|
id: "control".to_string(),
|
|
name: "Control Plane API".to_string(),
|
|
image: "madbase/control:latest".to_string(),
|
|
ports: vec!["8001:8001".to_string()],
|
|
environment: vec![],
|
|
volumes: vec![],
|
|
resource_profile: "balanced".to_string(),
|
|
has_persistent_data: false,
|
|
is_critical: true,
|
|
optional: false,
|
|
},
|
|
],
|
|
requirements: TemplateRequirements {
|
|
min_nodes: 1,
|
|
max_nodes: 1,
|
|
supports_ha: false,
|
|
},
|
|
}
|
|
}
|
|
}
|