1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
//! # Monitoring Module
//!
//! This module provides functionality for setting up a comprehensive monitoring system
//! using Prometheus, Grafana, and Node Exporter. It handles the installation, configuration,
//! and deployment of these tools across different Linux distributions.

use crate::config::Config;
use crate::distro::{get_package_manager, PackageManager};
use crate::rollback::RollbackManager;
use crate::utils::run_command;
use log::info;
use std::error::Error;

/// Sets up the monitoring system based on the provided configuration.
///
/// This function orchestrates the installation and configuration of Prometheus, Grafana,
/// and Node Exporter. If monitoring is disabled in the configuration, it skips the setup.
///
/// # Arguments
///
/// * `config` - A reference to the `Config` struct containing user-defined configuration options
/// * `rollback` - A reference to the `RollbackManager` for managing system state
///
/// # Errors
///
/// Returns an error if any part of the monitoring setup process fails.
pub fn setup_monitoring(config: &Config, rollback: &RollbackManager) -> Result<(), Box<dyn Error>> {
    if config.monitoring {
        info!("Setting up monitoring...");

        let snapshot = rollback.create_snapshot()?;

        install_monitoring_tools(config)?;
        configure_prometheus()?;
        setup_grafana()?;
        setup_node_exporter()?;

        rollback.commit_snapshot(snapshot)?;

        info!("Monitoring setup completed");
    } else {
        info!("Monitoring setup skipped as per user preference");
    }
    Ok(())
}

/// Installs Prometheus and Grafana based on the system's package manager.
///
/// # Arguments
///
/// * `config` - A reference to the `Config` struct (unused in the current implementation)
///
/// # Errors
///
/// Returns an error if the installation of either Prometheus or Grafana fails.
pub fn install_monitoring_tools(config: &Config) -> Result<(), Box<dyn Error>> {
    let package_manager = get_package_manager()?;

    // Install Prometheus
    match package_manager {
        PackageManager::Apt => {
            run_command("apt", &["update"])?;
            run_command("apt", &["install", "-y", "prometheus"])?;
        }
        PackageManager::Yum | PackageManager::Dnf => {
            // For CentOS/Fedora, we need to install from source
            install_prometheus_from_source()?;
        }
    }

    // Install Grafana
    match package_manager {
        PackageManager::Apt => {
            run_command(
                "apt",
                &[
                    "install",
                    "-y",
                    "apt-transport-https",
                    "software-properties-common",
                    "wget",
                ],
            )?;
            run_command(
                "wget",
                &[
                    "-q",
                    "-O",
                    "/usr/share/keyrings/grafana.key",
                    "https://packages.grafana.com/gpg.key",
                ],
            )?;
            run_command("echo", &["deb [signed-by=/usr/share/keyrings/grafana.key] https://packages.grafana.com/oss/deb stable main", ">", "/etc/apt/sources.list.d/grafana.list"])?;
            run_command("apt", &["update"])?;
            run_command("apt", &["install", "-y", "grafana"])?;
        }
        PackageManager::Yum | PackageManager::Dnf => {
            run_command(
                "wget",
                &[
                    "-q",
                    "-O",
                    "/etc/yum.repos.d/grafana.repo",
                    "https://packages.grafana.com/oss/rpm/grafana.repo",
                ],
            )?;
            match package_manager {
                PackageManager::Yum => run_command("yum", &["install", "-y", "grafana"])?,
                PackageManager::Dnf => run_command("dnf", &["install", "-y", "grafana"])?,
                _ => unreachable!(),
            }
        }
    }

    Ok(())
}

/// Configures Prometheus with a basic scrape configuration.
///
/// This function creates a basic Prometheus configuration file and
/// restarts the Prometheus service.
///
/// # Errors
///
/// Returns an error if writing the configuration file or restarting the service fails.
pub fn configure_prometheus() -> Result<(), Box<dyn Error>> {
    let prometheus_config = r#"
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets: ['localhost:9100']
"#;
    std::fs::write("/etc/prometheus/prometheus.yml", prometheus_config)?;

    run_command("systemctl", &["restart", "prometheus"])?;
    run_command("systemctl", &["enable", "prometheus"])?;

    Ok(())
}

/// Sets up and starts the Grafana server.
///
/// This function starts the Grafana server and enables it to start on boot.
/// Additional configuration (like adding data sources or creating dashboards)
/// could be added here in the future.
///
/// # Errors
///
/// Returns an error if starting or enabling the Grafana service fails.
pub fn setup_grafana() -> Result<(), Box<dyn Error>> {
    run_command("systemctl", &["start", "grafana-server"])?;
    run_command("systemctl", &["enable", "grafana-server"])?;

    // Here we will add code to configure Grafana via its API
    // For example, adding data sources, creating dashboards, etc.

    Ok(())
}

/// Sets up and starts the Node Exporter.
///
/// This function installs Node Exporter (either via package manager or from source),
/// starts the Node Exporter service, and enables it to start on boot.
///
/// # Errors
///
/// Returns an error if installation, starting, or enabling the Node Exporter service fails.
pub fn setup_node_exporter() -> Result<(), Box<dyn Error>> {
    let package_manager = get_package_manager()?;

    match package_manager {
        PackageManager::Apt => {
            run_command("apt", &["install", "-y", "prometheus-node-exporter"])?;
        }
        PackageManager::Yum | PackageManager::Dnf => {
            // For CentOS/Fedora, we need to install from source
            install_node_exporter_from_source()?;
        }
    }

    run_command("systemctl", &["start", "node_exporter"])?;
    run_command("systemctl", &["enable", "node_exporter"])?;

    Ok(())
}

/// Installs Prometheus from source.
///
/// This function is used for systems where Prometheus is not available
/// through the package manager (e.g., CentOS, Fedora).
///
/// # Errors
///
/// Returns an error if any step of the source installation process fails.
pub fn install_prometheus_from_source() -> Result<(), Box<dyn Error>> {
    run_command("wget", &["https://github.com/prometheus/prometheus/releases/download/v2.30.3/prometheus-2.30.3.linux-amd64.tar.gz"])?;
    run_command("tar", &["xvfz", "prometheus-2.30.3.linux-amd64.tar.gz"])?;
    run_command("mv", &["prometheus-2.30.3.linux-amd64", "prometheus"])?;

    // Create Prometheus user
    run_command(
        "useradd",
        &["--no-create-home", "--shell", "/bin/false", "prometheus"],
    )?;

    // Create directories and set ownership
    run_command("mkdir", &["/etc/prometheus", "/var/lib/prometheus"])?;
    run_command(
        "chown",
        &[
            "prometheus:prometheus",
            "/etc/prometheus",
            "/var/lib/prometheus",
        ],
    )?;

    // Move binaries and set ownership
    run_command(
        "mv",
        &[
            "prometheus/prometheus",
            "prometheus/promtool",
            "/usr/local/bin/",
        ],
    )?;
    run_command(
        "chown",
        &[
            "prometheus:prometheus",
            "/usr/local/bin/prometheus",
            "/usr/local/bin/promtool",
        ],
    )?;

    // Move config files and set ownership
    run_command(
        "mv",
        &[
            "prometheus/consoles",
            "prometheus/console_libraries",
            "/etc/prometheus/",
        ],
    )?;
    run_command(
        "mv",
        &[
            "prometheus/prometheus.yml",
            "/etc/prometheus/prometheus.yml",
        ],
    )?;
    run_command("chown", &["-R", "prometheus:prometheus", "/etc/prometheus"])?;

    // Create systemd service file
    let service_file = r#"[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
    --config.file /etc/prometheus/prometheus.yml \
    --storage.tsdb.path /var/lib/prometheus/ \
    --web.console.templates=/etc/prometheus/consoles \
    --web.console.libraries=/etc/prometheus/console_libraries

[Install]
WantedBy=multi-user.target
"#;
    std::fs::write("/etc/systemd/system/prometheus.service", service_file)?;

    run_command("systemctl", &["daemon-reload"])?;

    Ok(())
}

/// Installs Node Exporter from source.
///
/// This function is used for systems where Node Exporter is not available
/// through the package manager (e.g., CentOS, Fedora).
///
/// # Errors
///
/// Returns an error if any step of the source installation process fails.
pub fn install_node_exporter_from_source() -> Result<(), Box<dyn Error>> {
    run_command("wget", &["https://github.com/prometheus/node_exporter/releases/download/v1.2.2/node_exporter-1.2.2.linux-amd64.tar.gz"])?;
    run_command("tar", &["xvfz", "node_exporter-1.2.2.linux-amd64.tar.gz"])?;

    // Create Node Exporter user
    run_command(
        "useradd",
        &["--no-create-home", "--shell", "/bin/false", "node_exporter"],
    )?;

    // Move binary and set ownership
    run_command(
        "mv",
        &[
            "node_exporter-1.2.2.linux-amd64/node_exporter",
            "/usr/local/bin/",
        ],
    )?;
    run_command(
        "chown",
        &[
            "node_exporter:node_exporter",
            "/usr/local/bin/node_exporter",
        ],
    )?;

    // Create systemd service file
    let service_file = r#"[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target
"#;
    std::fs::write("/etc/systemd/system/node_exporter.service", service_file)?;

    run_command("systemctl", &["daemon-reload"])?;

    Ok(())
}