use crate::config::{project_root, Environment, DOCKER_PROD_DIR};
use crate::docker;
use crate::ui;
use anyhow::Result;
use clap::Subcommand;
use std::process::Command;
#[derive(Subcommand)]
pub enum ProdCommand {
Setup,
Ssl,
Up,
Down,
Restart,
Status,
Logs {
service: Option<String>,
},
Shell {
service: String,
},
ReloadNginx,
RenewSsl,
Clean,
Generate,
}
pub fn run(cmd: ProdCommand) -> Result<()> {
docker::check_docker()?;
let env = Environment::Prod;
match cmd {
ProdCommand::Setup => setup()?,
ProdCommand::Ssl => ssl()?,
ProdCommand::Up => up(env)?,
ProdCommand::Down => down(env)?,
ProdCommand::Restart => restart(env)?,
ProdCommand::Status => status(env)?,
ProdCommand::Logs { service } => logs(env, service.as_deref())?,
ProdCommand::Shell { service } => shell(env, &service)?,
ProdCommand::ReloadNginx => reload_nginx(env)?,
ProdCommand::RenewSsl => renew_ssl(env)?,
ProdCommand::Clean => clean(env)?,
ProdCommand::Generate => generate()?,
}
Ok(())
}
fn setup() -> Result<()> {
ui::info("Setting up production environment...");
let root = project_root()?;
let prod_dir = root.join(DOCKER_PROD_DIR);
let env_example = prod_dir.join(".env.example");
let env_file = prod_dir.join(".env");
if env_example.exists() && !env_file.exists() {
std::fs::copy(&env_example, &env_file)?;
ui::success("Created .env file from .env.example");
} else if env_file.exists() {
ui::warning(".env file already exists, skipping");
}
ui::info("Generating secure secrets...");
let script = prod_dir.join("scripts/generate-secrets.sh");
if script.exists() {
let status = Command::new("sh")
.arg(&script)
.current_dir(&prod_dir)
.status()?;
if status.success() {
ui::success("Secrets generated!");
}
} else {
ui::warning("generate-secrets.sh script not found, skipping");
}
ui::warning("IMPORTANT: Update DOMAIN and EMAIL in .env before continuing");
Ok(())
}
fn ssl() -> Result<()> {
ui::info("Initializing Let's Encrypt SSL...");
let root = project_root()?;
let prod_dir = root.join(DOCKER_PROD_DIR);
let script = prod_dir.join("scripts/init-letsencrypt.sh");
if script.exists() {
let status = Command::new("sh")
.arg(&script)
.current_dir(&prod_dir)
.status()?;
if status.success() {
ui::success("SSL setup complete!");
}
} else {
ui::error("init-letsencrypt.sh script not found");
}
Ok(())
}
fn up(env: Environment) -> Result<()> {
ui::info("Starting production services...");
let status = docker::compose(env, &["up", "-d"])?;
if status.success() {
ui::success("Services started!");
status_internal(env)?;
}
Ok(())
}
fn down(env: Environment) -> Result<()> {
ui::info("Stopping production services...");
let status = docker::compose(env, &["down"])?;
if status.success() {
ui::success("Services stopped!");
}
Ok(())
}
fn restart(env: Environment) -> Result<()> {
ui::info("Restarting production services...");
let status = docker::compose(env, &["restart"])?;
if status.success() {
ui::success("Services restarted!");
}
Ok(())
}
fn status(env: Environment) -> Result<()> {
ui::info("Production Services Status:");
status_internal(env)
}
fn status_internal(env: Environment) -> Result<()> {
docker::compose(env, &["ps"])?;
Ok(())
}
fn logs(env: Environment, service: Option<&str>) -> Result<()> {
let args = match service {
Some(s) => vec!["logs", "-f", s],
None => vec!["logs", "-f"],
};
docker::compose(env, &args)?;
Ok(())
}
fn shell(env: Environment, service: &str) -> Result<()> {
docker::exec(env, service, &["sh"])?;
Ok(())
}
fn reload_nginx(env: Environment) -> Result<()> {
docker::exec(env, "nginx", &["nginx", "-s", "reload"])?;
ui::success("Nginx reloaded!");
Ok(())
}
fn renew_ssl(env: Environment) -> Result<()> {
docker::exec(env, "certbot", &["certbot", "renew", "--force-renewal"])?;
docker::exec(env, "nginx", &["nginx", "-s", "reload"])?;
ui::success("SSL certificates renewed!");
Ok(())
}
fn clean(env: Environment) -> Result<()> {
if !ui::confirm_strict("This will destroy ALL production data!") {
ui::info("Aborted.");
return Ok(());
}
let status = docker::compose(env, &["down", "-v"])?;
if status.success() {
ui::success("Cleanup complete!");
}
Ok(())
}
fn generate() -> Result<()> {
ui::info("Regenerating docker-compose.yml...");
let root = project_root()?;
let prod_dir = root.join(DOCKER_PROD_DIR);
let script = prod_dir.join("scripts/generate-compose.sh");
if script.exists() {
let status = Command::new("sh")
.arg(&script)
.current_dir(&prod_dir)
.status()?;
if status.success() {
ui::success("Done!");
}
} else {
ui::error("generate-compose.sh script not found");
}
Ok(())
}