use crate::error::{CliError, Result};
use colored::Colorize;
use lmrc_config_validator::LmrcConfig;
use lmrc_pipeline::steps::{
BootstrapInitStep, ProvisionStep, SetupDatabaseStep, SetupDnsStep, SetupK8sStep,
SetupLoadBalancerStep, SetupQueueStep, SetupSslStep, SetupVaultStep,
};
use lmrc_pipeline::{Pipeline, PipelineContext};
use std::fs;
use std::path::PathBuf;
pub async fn git() -> Result<()> {
println!("{}", "Initializing git repository...".green().bold());
validate_lmrc_workspace()?;
if PathBuf::from(".git").exists() {
println!(" {} Git repository already initialized", "✓".bright_green());
return Ok(());
}
std::process::Command::new("git")
.arg("init")
.status()
.map_err(|e| CliError::IoError(format!("Failed to initialize git: {}", e)))?;
std::process::Command::new("git")
.args(&["add", "."])
.status()
.map_err(|e| CliError::IoError(format!("Failed to stage files: {}", e)))?;
std::process::Command::new("git")
.args(&["commit", "-m", "Initial commit"])
.status()
.map_err(|e| CliError::IoError(format!("Failed to create initial commit: {}", e)))?;
println!(" {} Git repository initialized", "✓".bright_green());
println!("\nNext steps:");
println!(" - lmrc setup remote # Configure GitLab remote");
println!(" - lmrc setup secrets # Upload CI/CD secrets");
Ok(())
}
pub async fn remote() -> Result<()> {
println!("{}", "Configuring GitLab remote...".green().bold());
validate_lmrc_workspace()?;
let config = load_config()?;
let gitlab_config = config
.infrastructure
.gitlab
.as_ref()
.ok_or_else(|| CliError::Config("GitLab configuration not found in lmrc.toml".to_string()))?;
let remote_url = format!("{}/{}/{}.git", gitlab_config.url, gitlab_config.namespace, config.project.name);
println!(" {} Remote URL: {}", "→".bright_blue(), remote_url.bright_white());
let output = std::process::Command::new("git")
.args(&["remote", "get-url", "origin"])
.output();
if output.is_ok() && output.unwrap().status.success() {
println!(" {} Remote 'origin' already configured", "✓".bright_green());
} else {
std::process::Command::new("git")
.args(&["remote", "add", "origin", &remote_url])
.status()
.map_err(|e| CliError::IoError(format!("Failed to add remote: {}", e)))?;
println!(" {} Remote 'origin' configured", "✓".bright_green());
}
println!(" {} Pushing to remote...", "→".bright_blue());
let status = std::process::Command::new("git")
.args(&["push", "-u", "origin", "main"])
.status()
.map_err(|e| CliError::IoError(format!("Failed to push: {}", e)))?;
if status.success() {
println!(" {} Code pushed to GitLab", "✓".bright_green());
} else {
return Err(CliError::IoError("Failed to push to remote".to_string()));
}
println!("\nNext step:");
println!(" - lmrc setup secrets # Upload CI/CD secrets");
Ok(())
}
pub async fn secrets() -> Result<()> {
println!("{}", "Uploading CI/CD secrets to GitLab...".green().bold());
validate_lmrc_workspace()?;
let env_file = check_env_bootstrap_file()?;
let (gitlab_url, gitlab_token, gitlab_project) = load_required_env_vars(&env_file)?;
let config = load_config()?;
let bootstrap_step = BootstrapInitStep::new(env_file, gitlab_url, gitlab_token, gitlab_project);
let ctx = PipelineContext::new(config)
.map_err(|e| CliError::Pipeline(format!("Failed to create pipeline context: {}", e)))?;
Pipeline::new(ctx)
.add_step(bootstrap_step)
.run()
.await
.map_err(|e| CliError::Pipeline(format!("Failed to upload secrets: {}", e)))?;
println!(" {} CI/CD secrets uploaded to GitLab", "✓".bright_green());
Ok(())
}
pub async fn servers() -> Result<()> {
println!("{}", "Provisioning servers...".green().bold());
validate_lmrc_workspace()?;
let config = load_config()?;
let ctx = PipelineContext::new(config)
.map_err(|e| CliError::Pipeline(format!("Failed to create pipeline context: {}", e)))?;
Pipeline::new(ctx)
.add_step(ProvisionStep::default())
.run()
.await
.map_err(|e| CliError::Pipeline(format!("Server provisioning failed: {}", e)))?;
println!(" {} Servers provisioned", "✓".bright_green());
println!("\nNext step:");
println!(" - lmrc setup network # Configure networking");
Ok(())
}
pub async fn network() -> Result<()> {
println!("{}", "Configuring networking...".green().bold());
validate_lmrc_workspace()?;
println!(" {} Network configuration is handled during server provisioning", "ℹ".bright_blue());
println!("\nNext step:");
println!(" - lmrc setup kubernetes # Install Kubernetes");
Ok(())
}
pub async fn kubernetes() -> Result<()> {
println!("{}", "Installing Kubernetes (K3s)...".green().bold());
validate_lmrc_workspace()?;
let config = load_config()?;
let ctx = PipelineContext::new(config)
.map_err(|e| CliError::Pipeline(format!("Failed to create pipeline context: {}", e)))?;
Pipeline::new(ctx)
.add_step(SetupK8sStep)
.run()
.await
.map_err(|e| CliError::Pipeline(format!("Kubernetes setup failed: {}", e)))?;
println!(" {} Kubernetes installed", "✓".bright_green());
println!("\nNext steps:");
println!(" - lmrc setup database # Setup PostgreSQL");
println!(" - lmrc setup ingress # Setup ingress controller");
Ok(())
}
pub async fn database() -> Result<()> {
println!("{}", "Setting up PostgreSQL...".green().bold());
validate_lmrc_workspace()?;
let config = load_config()?;
let ctx = PipelineContext::new(config)
.map_err(|e| CliError::Pipeline(format!("Failed to create pipeline context: {}", e)))?;
Pipeline::new(ctx)
.add_step(SetupDatabaseStep)
.run()
.await
.map_err(|e| CliError::Pipeline(format!("Database setup failed: {}", e)))?;
println!(" {} PostgreSQL configured", "✓".bright_green());
Ok(())
}
pub async fn queue() -> Result<()> {
println!("{}", "Setting up RabbitMQ...".green().bold());
validate_lmrc_workspace()?;
let config = load_config()?;
let ctx = PipelineContext::new(config)
.map_err(|e| CliError::Pipeline(format!("Failed to create pipeline context: {}", e)))?;
Pipeline::new(ctx)
.add_step(SetupQueueStep)
.run()
.await
.map_err(|e| CliError::Pipeline(format!("Queue setup failed: {}", e)))?;
println!(" {} RabbitMQ installed and configured", "✓".bright_green());
Ok(())
}
pub async fn dns() -> Result<()> {
println!("{}", "Configuring DNS records...".green().bold());
validate_lmrc_workspace()?;
let config = load_config()?;
let ctx = PipelineContext::new(config)
.map_err(|e| CliError::Pipeline(format!("Failed to create pipeline context: {}", e)))?;
Pipeline::new(ctx)
.add_step(SetupDnsStep)
.run()
.await
.map_err(|e| CliError::Pipeline(format!("DNS setup failed: {}", e)))?;
println!(" {} DNS records configured", "✓".bright_green());
Ok(())
}
pub async fn ingress() -> Result<()> {
println!("{}", "Setting up ingress controller...".green().bold());
validate_lmrc_workspace()?;
println!(" {} Ingress is configured as part of K3s setup", "ℹ".bright_blue());
Ok(())
}
pub async fn infra() -> Result<()> {
println!("{}", "Setting up complete infrastructure...".green().bold());
println!("{}", "=".repeat(60).bright_blue());
validate_lmrc_workspace()?;
let config = load_config()?;
let ctx = PipelineContext::new(config.clone())
.map_err(|e| CliError::Pipeline(format!("Failed to create pipeline context: {}", e)))?;
let mut pipeline = Pipeline::new(ctx);
pipeline = pipeline
.add_step(ProvisionStep::default())
.add_step(SetupLoadBalancerStep) .add_step(SetupVaultStep) .add_step(SetupK8sStep)
.add_step(SetupDatabaseStep);
if config.infrastructure.rabbitmq.is_some() {
pipeline = pipeline.add_step(SetupQueueStep); }
pipeline = pipeline
.add_step(SetupSslStep::default()) .add_step(SetupDnsStep);
pipeline.run()
.await
.map_err(|e| CliError::Pipeline(format!("Infrastructure setup failed: {}", e)))?;
println!();
println!("{}", "=".repeat(60).bright_green());
println!("{}", "Infrastructure setup complete!".bright_green().bold());
println!("{}", "=".repeat(60).bright_green());
println!("\nNote: Some components may require manual installation steps. Check output above for details.");
Ok(())
}
pub async fn all() -> Result<()> {
println!("{}", "Setting up complete project...".bright_blue().bold());
println!("{}", "=".repeat(60).bright_blue());
git().await?;
println!();
remote().await?;
println!();
secrets().await?;
println!();
infra().await?;
println!();
println!("{}", "=".repeat(60).bright_green());
println!("{}", "Complete setup finished!".bright_green().bold());
println!("{}", "=".repeat(60).bright_green());
println!("\nYour project is fully configured and ready for deployment.");
Ok(())
}
fn validate_lmrc_workspace() -> Result<()> {
if !PathBuf::from("lmrc.toml").exists() {
return Err(CliError::Config(
"lmrc.toml not found. This command must be run from an LMRC-generated project root."
.to_string(),
));
}
if !PathBuf::from("Cargo.toml").exists() {
return Err(CliError::Config(
"Cargo.toml not found. This command must be run from an LMRC-generated project root."
.to_string(),
));
}
Ok(())
}
fn load_config() -> Result<LmrcConfig> {
LmrcConfig::from_file(&PathBuf::from("lmrc.toml"))
.map_err(|e| CliError::Config(format!("Failed to load lmrc.toml: {}", e)))
}
fn check_env_bootstrap_file() -> Result<PathBuf> {
let env_file = PathBuf::from(".env.bootstrap");
if !env_file.exists() {
return Err(CliError::Config(
".env.bootstrap file not found.\n\n\
Please create a .env.bootstrap file with your CI/CD variables:\n\n\
# Required variables:\n\
GITLAB_URL=https://gitlab.com\n\
GITLAB_TOKEN=your-gitlab-token\n\
GITLAB_PROJECT=your-org/your-project\n\n\
# Infrastructure tokens:\n\
HETZNER_API_TOKEN=your-hetzner-token\n\
CLOUDFLARE_API_TOKEN=your-cloudflare-token\n\n\
# Add other variables as needed...\n"
.to_string(),
));
}
Ok(env_file)
}
fn load_required_env_vars(env_file: &PathBuf) -> Result<(String, String, String)> {
let content = fs::read_to_string(env_file)
.map_err(|e| CliError::IoError(format!("Failed to read .env.bootstrap: {}", e)))?;
let mut gitlab_url = None;
let mut gitlab_token = None;
let mut gitlab_project = None;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim();
let value = if (value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\''))
{
&value[1..value.len() - 1]
} else {
value
};
match key {
"GITLAB_URL" => gitlab_url = Some(value.to_string()),
"GITLAB_TOKEN" => gitlab_token = Some(value.to_string()),
"GITLAB_PROJECT" => gitlab_project = Some(value.to_string()),
_ => {}
}
}
}
let gitlab_url = gitlab_url
.ok_or_else(|| CliError::Config("GITLAB_URL not found in .env.bootstrap".to_string()))?;
let gitlab_token = gitlab_token
.ok_or_else(|| CliError::Config("GITLAB_TOKEN not found in .env.bootstrap".to_string()))?;
let gitlab_project = gitlab_project.ok_or_else(|| {
CliError::Config("GITLAB_PROJECT not found in .env.bootstrap".to_string())
})?;
Ok((gitlab_url, gitlab_token, gitlab_project))
}