1use crate::config::Config;
12use crate::distro::{get_package_manager, PackageManager};
13use crate::rollback::RollbackManager;
14use crate::utils::run_command;
15use log::info;
16use std::error::Error;
17
18pub fn deploy_applications(
32    config: &Config,
33    rollback: &RollbackManager,
34) -> Result<(), Box<dyn Error>> {
35    info!("Deploying applications...");
36
37    let snapshot = rollback.create_snapshot()?;
38
39    for app in &config.deployed_apps {
40        deploy_app(app, &config.server_role)?;
41    }
42
43    rollback.commit_snapshot(snapshot)?;
44
45    info!("Application deployment completed");
46    Ok(())
47}
48
49pub fn deploy_app(app: &str, server_role: &str) -> Result<(), Box<dyn Error>> {
60    match app {
61        "nginx" => deploy_nginx()?,
62        "apache" => deploy_apache()?,
63        "mysql" => deploy_mysql()?,
64        "postgresql" => deploy_postgresql()?,
65        "php" => deploy_php(server_role)?,
66        "nodejs" => deploy_nodejs()?,
67        "python" => deploy_python()?,
68        _ => return Err(format!("Unsupported application: {}", app).into()),
69    }
70    Ok(())
71}
72
73pub fn deploy_nginx() -> Result<(), Box<dyn Error>> {
82    let package_manager = get_package_manager()?;
83
84    match package_manager {
85        PackageManager::Apt => run_command("apt", &["install", "-y", "nginx"])?,
86        PackageManager::Yum => run_command("yum", &["install", "-y", "nginx"])?,
87        PackageManager::Dnf => run_command("dnf", &["install", "-y", "nginx"])?,
88    }
89
90    run_command("systemctl", &["start", "nginx"])?;
91    run_command("systemctl", &["enable", "nginx"])?;
92
93    Ok(())
94}
95
96pub fn deploy_apache() -> Result<(), Box<dyn Error>> {
105    let package_manager = get_package_manager()?;
106
107    match package_manager {
108        PackageManager::Apt => run_command("apt", &["install", "-y", "apache2"])?,
109        PackageManager::Yum => run_command("yum", &["install", "-y", "httpd"])?,
110        PackageManager::Dnf => run_command("dnf", &["install", "-y", "httpd"])?,
111    }
112
113    if let Err(_) = run_command("systemctl", &["start", "apache2"]) {
114        run_command("systemctl", &["start", "httpd"])?;
115    }
116    if let Err(_) = run_command("systemctl", &["enable", "apache2"]) {
117        run_command("systemctl", &["enable", "httpd"])?;
118    }
119
120    Ok(())
121}
122
123pub fn deploy_mysql() -> Result<(), Box<dyn Error>> {
133    let package_manager = get_package_manager()?;
134
135    match package_manager {
136        PackageManager::Apt => run_command("apt", &["install", "-y", "mysql-server"])?,
137        PackageManager::Yum => run_command("yum", &["install", "-y", "mysql-server"])?,
138        PackageManager::Dnf => run_command("dnf", &["install", "-y", "mysql-server"])?,
139    }
140
141    run_command("systemctl", &["start", "mysql"])?;
142    run_command("systemctl", &["enable", "mysql"])?;
143
144    run_command("mysql_secure_installation", &[])?;
146
147    Ok(())
148}
149
150pub fn deploy_postgresql() -> Result<(), Box<dyn Error>> {
160    let package_manager = get_package_manager()?;
161
162    match package_manager {
163        PackageManager::Apt => run_command(
164            "apt",
165            &["install", "-y", "postgresql", "postgresql-contrib"],
166        )?,
167        PackageManager::Yum => run_command(
168            "yum",
169            &["install", "-y", "postgresql-server", "postgresql-contrib"],
170        )?,
171        PackageManager::Dnf => run_command(
172            "dnf",
173            &["install", "-y", "postgresql-server", "postgresql-contrib"],
174        )?,
175    }
176
177    if package_manager != PackageManager::Apt {
179        run_command("postgresql-setup", &["--initdb"])?;
180    }
181
182    run_command("systemctl", &["start", "postgresql"])?;
183    run_command("systemctl", &["enable", "postgresql"])?;
184
185    Ok(())
186}
187
188pub fn deploy_php(server_role: &str) -> Result<(), Box<dyn Error>> {
201    let package_manager = get_package_manager()?;
202
203    match package_manager {
204        PackageManager::Apt => {
205            run_command("apt", &["install", "-y", "php", "php-fpm", "php-mysql"])?;
206            if server_role == "web" {
207                run_command("apt", &["install", "-y", "libapache2-mod-php"])?;
208            }
209        }
210        PackageManager::Yum | PackageManager::Dnf => {
211            let install_cmd = if package_manager == PackageManager::Yum {
212                "yum"
213            } else {
214                "dnf"
215            };
216            run_command(
217                install_cmd,
218                &["install", "-y", "php", "php-fpm", "php-mysqlnd"],
219            )?;
220            if server_role == "web" {
221                run_command(install_cmd, &["install", "-y", "php-apache"])?;
222            }
223        }
224    }
225
226    run_command("systemctl", &["start", "php-fpm"])?;
227    run_command("systemctl", &["enable", "php-fpm"])?;
228
229    Ok(())
230}
231
232pub fn deploy_nodejs() -> Result<(), Box<dyn Error>> {
241    run_command(
243        "curl",
244        &[
245            "-o-",
246            "https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh",
247            "|",
248            "bash",
249        ],
250    )?;
251    run_command("source", &["~/.nvm/nvm.sh"])?;
252    run_command("nvm", &["install", "node"])?;
253    run_command("nvm", &["use", "node"])?;
254
255    run_command("npm", &["install", "-g", "pm2"])?;
257
258    Ok(())
259}
260
261pub fn deploy_python() -> Result<(), Box<dyn Error>> {
270    let package_manager = get_package_manager()?;
271
272    match package_manager {
273        PackageManager::Apt => run_command(
274            "apt",
275            &["install", "-y", "python3", "python3-pip", "python3-venv"],
276        )?,
277        PackageManager::Yum => run_command("yum", &["install", "-y", "python3", "python3-pip"])?,
278        PackageManager::Dnf => run_command("dnf", &["install", "-y", "python3", "python3-pip"])?,
279    }
280
281    run_command("pip3", &["install", "virtualenv"])?;
283
284    Ok(())
285}
286
287pub fn setup_web_server_config(app: &str) -> Result<(), Box<dyn Error>> {
288    match app {
289        "nginx" => setup_nginx_config()?,
290        "apache" => setup_apache_config()?,
291        _ => return Err(format!("Unsupported web server: {}", app).into()),
292    }
293    Ok(())
294}
295
296fn setup_nginx_config() -> Result<(), Box<dyn Error>> {
297    let nginx_config = r#"
298server {
299    listen 80 default_server;
300    listen [::]:80 default_server;
301    root /var/www/html;
302    index index.html index.htm index.nginx-debian.html;
303    server_name _;
304    location / {
305        try_files $uri $uri/ =404;
306    }
307}
308"#;
309    std::fs::write("/etc/nginx/sites-available/default", nginx_config)?;
310    run_command("systemctl", &["reload", "nginx"])?;
311    Ok(())
312}
313
314fn setup_apache_config() -> Result<(), Box<dyn Error>> {
315    let apache_config = r#"
316<VirtualHost *:80>
317    ServerAdmin webmaster@localhost
318    DocumentRoot /var/www/html
319    ErrorLog ${APACHE_LOG_DIR}/error.log
320    CustomLog ${APACHE_LOG_DIR}/access.log combined
321</VirtualHost>
322"#;
323    std::fs::write(
324        "/etc/apache2/sites-available/000-default.conf",
325        apache_config,
326    )?;
327    if let Err(_) = run_command("systemctl", &["reload", "apache2"]) {
328        run_command("systemctl", &["reload", "httpd"])?;
329    }
330    Ok(())
331}
332
333pub fn setup_database(db: &str) -> Result<(), Box<dyn Error>> {
334    match db {
335        "mysql" => setup_mysql()?,
336        "postgresql" => setup_postgresql()?,
337        _ => return Err(format!("Unsupported database: {}", db).into()),
338    }
339    Ok(())
340}
341
342fn setup_mysql() -> Result<(), Box<dyn Error>> {
343    let password = generate_secure_password();
345
346    run_command(
348        "mysql",
349        &[
350            "-e",
351            &format!(
352                "ALTER USER 'root'@'localhost' IDENTIFIED BY '{}';",
353                password
354            ),
355        ],
356    )?;
357    run_command("mysql", &["-e", "DELETE FROM mysql.user WHERE User='';"])?;
358    run_command("mysql", &["-e", "FLUSH PRIVILEGES;"])?;
359
360    std::fs::write("/root/.mysql_root_password", &password)?;
363
364    Ok(())
365}
366
367fn setup_postgresql() -> Result<(), Box<dyn Error>> {
368    let password = generate_secure_password();
370
371    run_command(
373        "sudo",
374        &[
375            "-u",
376            "postgres",
377            "psql",
378            "-c",
379            &format!("ALTER USER postgres PASSWORD '{}';", password),
380        ],
381    )?;
382
383    std::fs::write("/root/.postgres_password", &password)?;
386
387    Ok(())
388}
389
390fn generate_secure_password() -> String {
399    use rand::Rng;
400    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
401                            abcdefghijklmnopqrstuvwxyz\
402                            0123456789)(*&^%$#@!~";
403    const PASSWORD_LEN: usize = 20;
404    let mut rng = rand::thread_rng();
405
406    let password: String = (0..PASSWORD_LEN)
407        .map(|_| {
408            let idx = rng.gen_range(0..CHARSET.len());
409            CHARSET[idx] as char
410        })
411        .collect();
412
413    password
414}
415
416fn create_sample_web_app(app_type: &str) -> Result<(), Box<dyn Error>> {
430    match app_type {
431        "php" => {
432            let php_content = r#"
433<?php
434echo "Hello, World! This is a sample PHP application.";
435?>
436"#;
437            std::fs::write("/var/www/html/index.php", php_content)?;
438        }
439        "nodejs" => {
440            let node_content = r#"
441const http = require('http');
442const server = http.createServer((req, res) => {
443  res.statusCode = 200;
444  res.setHeader('Content-Type', 'text/plain');
445  res.end('Hello, World! This is a sample Node.js application.');
446});
447server.listen(3000, '127.0.0.1', () => {
448  console.log('Server running on http://127.0.0.1:3000/');
449});
450"#;
451            std::fs::write("/root/app.js", node_content)?;
452            run_command("pm2", &["start", "/root/app.js"])?;
453        }
454        "python" => {
455            let python_content = r#"
456from flask import Flask
457app = Flask(__name__)
458
459@app.route('/')
460def hello_world():
461    return 'Hello, World! This is a sample Python Flask application.'
462
463if __name__ == '__main__':
464    app.run(host='0.0.0.0', port=5000)
465"#;
466            std::fs::write("/root/app.py", python_content)?;
467            run_command("pip3", &["install", "flask"])?;
468            run_command("python3", &["/root/app.py", "&"])?;
469        }
470        _ => return Err(format!("Unsupported application type: {}", app_type).into()),
471    }
472    Ok(())
473}
474
475fn setup_firewall_rules(config: &Config) -> Result<(), Box<dyn Error>> {
488    let package_manager = get_package_manager()?;
489
490    match package_manager {
491        PackageManager::Apt => {
492            run_command("ufw", &["allow", "OpenSSH"])?;
493            run_command("ufw", &["allow", "80/tcp"])?;
494            run_command("ufw", &["allow", "443/tcp"])?;
495            for rule in &config.custom_firewall_rules {
496                run_command("ufw", &["allow", rule])?;
497            }
498            run_command("ufw", &["enable"])?;
499        }
500        PackageManager::Yum | PackageManager::Dnf => {
501            run_command("firewall-cmd", &["--permanent", "--add-service=ssh"])?;
502            run_command("firewall-cmd", &["--permanent", "--add-service=http"])?;
503            run_command("firewall-cmd", &["--permanent", "--add-service=https"])?;
504            for rule in &config.custom_firewall_rules {
505                run_command("firewall-cmd", &["--permanent", "--add-port=", rule])?;
506            }
507            run_command("firewall-cmd", &["--reload"])?;
508        }
509    }
510    Ok(())
511}