server_forge/
monitoring.rs

1//! # Monitoring Module
2//!
3//! This module provides functionality for setting up a comprehensive monitoring system
4//! using Prometheus, Grafana, and Node Exporter. It handles the installation, configuration,
5//! and deployment of these tools across different Linux distributions.
6
7use crate::config::Config;
8use crate::distro::{get_package_manager, PackageManager};
9use crate::rollback::RollbackManager;
10use crate::utils::run_command;
11use log::info;
12use std::error::Error;
13
14/// Sets up the monitoring system based on the provided configuration.
15///
16/// This function orchestrates the installation and configuration of Prometheus, Grafana,
17/// and Node Exporter. If monitoring is disabled in the configuration, it skips the setup.
18///
19/// # Arguments
20///
21/// * `config` - A reference to the `Config` struct containing user-defined configuration options
22/// * `rollback` - A reference to the `RollbackManager` for managing system state
23///
24/// # Errors
25///
26/// Returns an error if any part of the monitoring setup process fails.
27pub fn setup_monitoring(config: &Config, rollback: &RollbackManager) -> Result<(), Box<dyn Error>> {
28    if config.monitoring {
29        info!("Setting up monitoring...");
30
31        let snapshot = rollback.create_snapshot()?;
32
33        install_monitoring_tools(config)?;
34        configure_prometheus()?;
35        setup_grafana()?;
36        setup_node_exporter()?;
37
38        rollback.commit_snapshot(snapshot)?;
39
40        info!("Monitoring setup completed");
41    } else {
42        info!("Monitoring setup skipped as per user preference");
43    }
44    Ok(())
45}
46
47/// Installs Prometheus and Grafana based on the system's package manager.
48///
49/// # Arguments
50///
51/// * `config` - A reference to the `Config` struct (unused in the current implementation)
52///
53/// # Errors
54///
55/// Returns an error if the installation of either Prometheus or Grafana fails.
56pub fn install_monitoring_tools(config: &Config) -> Result<(), Box<dyn Error>> {
57    let package_manager = get_package_manager()?;
58
59    // Install Prometheus
60    match package_manager {
61        PackageManager::Apt => {
62            run_command("apt", &["update"])?;
63            run_command("apt", &["install", "-y", "prometheus"])?;
64        }
65        PackageManager::Yum | PackageManager::Dnf => {
66            // For CentOS/Fedora, we need to install from source
67            install_prometheus_from_source()?;
68        }
69    }
70
71    // Install Grafana
72    match package_manager {
73        PackageManager::Apt => {
74            run_command(
75                "apt",
76                &[
77                    "install",
78                    "-y",
79                    "apt-transport-https",
80                    "software-properties-common",
81                    "wget",
82                ],
83            )?;
84            run_command(
85                "wget",
86                &[
87                    "-q",
88                    "-O",
89                    "/usr/share/keyrings/grafana.key",
90                    "https://packages.grafana.com/gpg.key",
91                ],
92            )?;
93            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"])?;
94            run_command("apt", &["update"])?;
95            run_command("apt", &["install", "-y", "grafana"])?;
96        }
97        PackageManager::Yum | PackageManager::Dnf => {
98            run_command(
99                "wget",
100                &[
101                    "-q",
102                    "-O",
103                    "/etc/yum.repos.d/grafana.repo",
104                    "https://packages.grafana.com/oss/rpm/grafana.repo",
105                ],
106            )?;
107            match package_manager {
108                PackageManager::Yum => run_command("yum", &["install", "-y", "grafana"])?,
109                PackageManager::Dnf => run_command("dnf", &["install", "-y", "grafana"])?,
110                _ => unreachable!(),
111            }
112        }
113    }
114
115    Ok(())
116}
117
118/// Configures Prometheus with a basic scrape configuration.
119///
120/// This function creates a basic Prometheus configuration file and
121/// restarts the Prometheus service.
122///
123/// # Errors
124///
125/// Returns an error if writing the configuration file or restarting the service fails.
126pub fn configure_prometheus() -> Result<(), Box<dyn Error>> {
127    let prometheus_config = r#"
128global:
129  scrape_interval: 15s
130
131scrape_configs:
132  - job_name: 'node'
133    static_configs:
134      - targets: ['localhost:9100']
135"#;
136    std::fs::write("/etc/prometheus/prometheus.yml", prometheus_config)?;
137
138    run_command("systemctl", &["restart", "prometheus"])?;
139    run_command("systemctl", &["enable", "prometheus"])?;
140
141    Ok(())
142}
143
144/// Sets up and starts the Grafana server.
145///
146/// This function starts the Grafana server and enables it to start on boot.
147/// Additional configuration (like adding data sources or creating dashboards)
148/// could be added here in the future.
149///
150/// # Errors
151///
152/// Returns an error if starting or enabling the Grafana service fails.
153pub fn setup_grafana() -> Result<(), Box<dyn Error>> {
154    run_command("systemctl", &["start", "grafana-server"])?;
155    run_command("systemctl", &["enable", "grafana-server"])?;
156
157    // Here we will add code to configure Grafana via its API
158    // For example, adding data sources, creating dashboards, etc.
159
160    Ok(())
161}
162
163/// Sets up and starts the Node Exporter.
164///
165/// This function installs Node Exporter (either via package manager or from source),
166/// starts the Node Exporter service, and enables it to start on boot.
167///
168/// # Errors
169///
170/// Returns an error if installation, starting, or enabling the Node Exporter service fails.
171pub fn setup_node_exporter() -> Result<(), Box<dyn Error>> {
172    let package_manager = get_package_manager()?;
173
174    match package_manager {
175        PackageManager::Apt => {
176            run_command("apt", &["install", "-y", "prometheus-node-exporter"])?;
177        }
178        PackageManager::Yum | PackageManager::Dnf => {
179            // For CentOS/Fedora, we need to install from source
180            install_node_exporter_from_source()?;
181        }
182    }
183
184    run_command("systemctl", &["start", "node_exporter"])?;
185    run_command("systemctl", &["enable", "node_exporter"])?;
186
187    Ok(())
188}
189
190/// Installs Prometheus from source.
191///
192/// This function is used for systems where Prometheus is not available
193/// through the package manager (e.g., CentOS, Fedora).
194///
195/// # Errors
196///
197/// Returns an error if any step of the source installation process fails.
198pub fn install_prometheus_from_source() -> Result<(), Box<dyn Error>> {
199    run_command("wget", &["https://github.com/prometheus/prometheus/releases/download/v2.30.3/prometheus-2.30.3.linux-amd64.tar.gz"])?;
200    run_command("tar", &["xvfz", "prometheus-2.30.3.linux-amd64.tar.gz"])?;
201    run_command("mv", &["prometheus-2.30.3.linux-amd64", "prometheus"])?;
202
203    // Create Prometheus user
204    run_command(
205        "useradd",
206        &["--no-create-home", "--shell", "/bin/false", "prometheus"],
207    )?;
208
209    // Create directories and set ownership
210    run_command("mkdir", &["/etc/prometheus", "/var/lib/prometheus"])?;
211    run_command(
212        "chown",
213        &[
214            "prometheus:prometheus",
215            "/etc/prometheus",
216            "/var/lib/prometheus",
217        ],
218    )?;
219
220    // Move binaries and set ownership
221    run_command(
222        "mv",
223        &[
224            "prometheus/prometheus",
225            "prometheus/promtool",
226            "/usr/local/bin/",
227        ],
228    )?;
229    run_command(
230        "chown",
231        &[
232            "prometheus:prometheus",
233            "/usr/local/bin/prometheus",
234            "/usr/local/bin/promtool",
235        ],
236    )?;
237
238    // Move config files and set ownership
239    run_command(
240        "mv",
241        &[
242            "prometheus/consoles",
243            "prometheus/console_libraries",
244            "/etc/prometheus/",
245        ],
246    )?;
247    run_command(
248        "mv",
249        &[
250            "prometheus/prometheus.yml",
251            "/etc/prometheus/prometheus.yml",
252        ],
253    )?;
254    run_command("chown", &["-R", "prometheus:prometheus", "/etc/prometheus"])?;
255
256    // Create systemd service file
257    let service_file = r#"[Unit]
258Description=Prometheus
259Wants=network-online.target
260After=network-online.target
261
262[Service]
263User=prometheus
264Group=prometheus
265Type=simple
266ExecStart=/usr/local/bin/prometheus \
267    --config.file /etc/prometheus/prometheus.yml \
268    --storage.tsdb.path /var/lib/prometheus/ \
269    --web.console.templates=/etc/prometheus/consoles \
270    --web.console.libraries=/etc/prometheus/console_libraries
271
272[Install]
273WantedBy=multi-user.target
274"#;
275    std::fs::write("/etc/systemd/system/prometheus.service", service_file)?;
276
277    run_command("systemctl", &["daemon-reload"])?;
278
279    Ok(())
280}
281
282/// Installs Node Exporter from source.
283///
284/// This function is used for systems where Node Exporter is not available
285/// through the package manager (e.g., CentOS, Fedora).
286///
287/// # Errors
288///
289/// Returns an error if any step of the source installation process fails.
290pub fn install_node_exporter_from_source() -> Result<(), Box<dyn Error>> {
291    run_command("wget", &["https://github.com/prometheus/node_exporter/releases/download/v1.2.2/node_exporter-1.2.2.linux-amd64.tar.gz"])?;
292    run_command("tar", &["xvfz", "node_exporter-1.2.2.linux-amd64.tar.gz"])?;
293
294    // Create Node Exporter user
295    run_command(
296        "useradd",
297        &["--no-create-home", "--shell", "/bin/false", "node_exporter"],
298    )?;
299
300    // Move binary and set ownership
301    run_command(
302        "mv",
303        &[
304            "node_exporter-1.2.2.linux-amd64/node_exporter",
305            "/usr/local/bin/",
306        ],
307    )?;
308    run_command(
309        "chown",
310        &[
311            "node_exporter:node_exporter",
312            "/usr/local/bin/node_exporter",
313        ],
314    )?;
315
316    // Create systemd service file
317    let service_file = r#"[Unit]
318Description=Node Exporter
319Wants=network-online.target
320After=network-online.target
321
322[Service]
323User=node_exporter
324Group=node_exporter
325Type=simple
326ExecStart=/usr/local/bin/node_exporter
327
328[Install]
329WantedBy=multi-user.target
330"#;
331    std::fs::write("/etc/systemd/system/node_exporter.service", service_file)?;
332
333    run_command("systemctl", &["daemon-reload"])?;
334
335    Ok(())
336}