19 Commits

Author SHA1 Message Date
813bf4e407 cleaned up disk information
All checks were successful
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 5s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 5s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m4s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m37s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m21s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 1m55s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
Rust Cross-Platform Build / Create Tag (push) Successful in 4s
2025-09-27 23:48:37 +02:00
bf6f89c954 added error handling for client version print 2025-09-27 23:48:02 +02:00
dbe87fedb6 added error handling for client version print 2025-09-27 21:36:59 +02:00
67b24b33aa added inital server communcation task 2025-09-27 21:34:30 +02:00
67ebbdaa19 added image screening for current container 2025-09-26 23:36:09 +02:00
6fd275802c removed unneccessary conditions for if statement
All checks were successful
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 4s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 6s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m10s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m43s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m32s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m2s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
2025-09-26 13:20:26 +02:00
9018adf998 removed unused env
All checks were successful
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 5s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 5s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m12s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m46s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m33s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m3s
Rust Cross-Platform Build / Create Tag (push) Has been skipped
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
2025-09-26 11:40:02 +02:00
3124697f10 removed unused env 2025-09-26 11:39:05 +02:00
30382fedef changed secrets to AUTOMATION_*
Some checks failed
Rust Cross-Platform Build / Detect Rust Project (push) Has been cancelled
Rust Cross-Platform Build / Run Tests (push) Has been cancelled
Rust Cross-Platform Build / Set Tag Name (push) Has been cancelled
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Has been cancelled
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Has been cancelled
Rust Cross-Platform Build / Build and Push Docker Image (push) Has been cancelled
Rust Cross-Platform Build / Create Tag (push) Has been cancelled
Rust Cross-Platform Build / Workflow Summary (push) Has been cancelled
2025-09-26 11:16:38 +02:00
8910155524 removed setup_rust
Some checks failed
Rust Cross-Platform Build / Detect Rust Project (push) Has been cancelled
Rust Cross-Platform Build / Run Tests (push) Has been cancelled
Rust Cross-Platform Build / Set Tag Name (push) Has been cancelled
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Has been cancelled
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Has been cancelled
Rust Cross-Platform Build / Build and Push Docker Image (push) Has been cancelled
Rust Cross-Platform Build / Create Tag (push) Has been cancelled
Rust Cross-Platform Build / Workflow Summary (push) Has been cancelled
2025-09-26 11:15:30 +02:00
7a68df41ac removed redundant and unvalid conditions
Some checks failed
Rust Cross-Platform Build / Detect Rust Project (push) Has been cancelled
Rust Cross-Platform Build / Setup Rust Toolchain (push) Has been cancelled
Rust Cross-Platform Build / Run Tests (push) Has been cancelled
Rust Cross-Platform Build / Set Tag Name (push) Has been cancelled
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Has been cancelled
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Has been cancelled
Rust Cross-Platform Build / Build and Push Docker Image (push) Has been cancelled
Rust Cross-Platform Build / Create Tag (push) Has been cancelled
Rust Cross-Platform Build / Workflow Summary (push) Has been cancelled
2025-09-26 02:06:25 +02:00
60ce51cd82 depends on docker-build
Some checks failed
Rust Cross-Platform Build / Create Tag (push) Blocked by required conditions
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 4s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 4s
Rust Cross-Platform Build / Setup Rust Toolchain (push) Successful in 23s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m0s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 1m26s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 2m6s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 1m50s
Rust Cross-Platform Build / Workflow Summary (push) Has been cancelled
2025-09-26 01:53:41 +02:00
54fca8b1d3 added docker secrets
Some checks failed
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 4s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 4s
Rust Cross-Platform Build / Setup Rust Toolchain (push) Successful in 23s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m4s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m37s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m16s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 1m54s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 1s
Rust Cross-Platform Build / Create Tag (push) Failing after 12m40s
2025-09-26 00:15:29 +02:00
aa876d9e5d added newer action version for login
Some checks failed
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 5s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 4s
Rust Cross-Platform Build / Setup Rust Toolchain (push) Successful in 23s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m6s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m35s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m25s
Rust Cross-Platform Build / Create Tag (push) Successful in 4s
Rust Cross-Platform Build / Build and Push Docker Image (push) Failing after 2m11s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 1s
2025-09-26 00:00:24 +02:00
88625ff986 added docker api for restart and client updat
Some checks failed
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 5s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 4s
Rust Cross-Platform Build / Setup Rust Toolchain (push) Successful in 23s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m0s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m25s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m2s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
Rust Cross-Platform Build / Build and Push Docker Image (push) Failing after 12s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 1s
2025-09-25 22:02:11 +02:00
428be53fff added docker api for restart and client updat 2025-09-25 22:02:00 +02:00
83cb815e76 added docker image update option 2025-09-23 23:27:13 +02:00
755617c86f removed loggin option for client 2025-09-23 21:28:19 +02:00
314bf8c327 added server comm 2025-09-21 01:40:32 +02:00
9 changed files with 433 additions and 127 deletions

View File

@@ -9,11 +9,8 @@ on:
branches: [ "development", "main" ]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
REGISTRY: git.triggermeelmo.com
IMAGE_NAME: donpat1to/watcher-agent
TAG: ${{ github.ref == 'refs/heads/main' && 'latest' || github.ref == 'refs/heads/development' && 'development' || github.ref_type == 'tag' && github.ref_name || 'pr' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -54,24 +51,9 @@ jobs:
exit 1
fi
setup-rust:
name: Setup Rust Toolchain
needs: detect-project
if: ${{ !failure() && !cancelled() }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-gnu
components: rustfmt, clippy
test:
name: Run Tests
needs: [detect-project, setup-rust]
needs: [detect-project]
if: ${{ !failure() && !cancelled() }}
runs-on: ubuntu-latest
steps:
@@ -134,7 +116,7 @@ jobs:
# audit:
# name: Security Audit
# needs: [detect-project, setup-rust]
# needs: [detect-project]
# if: ${{ !failure() && !cancelled() }}
# runs-on: ubuntu-latest
# steps:
@@ -154,7 +136,7 @@ jobs:
build:
name: Build (${{ matrix.target }})
needs: [detect-project, setup-rust, test, audit]
needs: [detect-project, test]
if: ${{ !failure() && !cancelled() }}
runs-on: ubuntu-latest
strategy:
@@ -227,14 +209,14 @@ jobs:
path: dist/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to Docker Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
username: ${{ secrets.AUTOMATION_USERNAME }}
password: ${{ secrets.AUTOMATION_PASSWORD }}
- name: Build Docker image
uses: docker/build-push-action@v4
@@ -250,8 +232,10 @@ jobs:
tag:
name: Create Tag
needs: [build, set-tag]
if: github.event_name == 'push'
needs: [docker-build, build, set-tag]
if: |
github.event_name == 'push' &&
needs.docker-build.result == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

View File

@@ -20,8 +20,11 @@ nvml-wrapper-sys = "0.9.0"
anyhow = "1.0.98"
# Docker .env loading
config = "0.13"
dotenvy = "0.15"
# config = "0.13"
# Docker API access
bollard = "0.19"
futures-util = "0.3"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winuser", "pdh", "ifmib", "iphlpapi", "winerror" ,"wbemcli", "combaseapi"] }

View File

@@ -1,12 +1,16 @@
use std::time::Duration;
use crate::hardware::HardwareInfo;
use crate::models::{HeartbeatDto, IdResponse, MetricDto, RegistrationDto};
use crate::models::{HeartbeatDto, IdResponse, MetricDto, RegistrationDto, ServerMessage, Acknowledgment};
use crate::serverclientcomm::handle_server_message;
use anyhow::Result;
use reqwest::{Client, StatusCode};
use std::error::Error;
use tokio::time::sleep;
use bollard::Docker;
pub async fn register_with_server(
base_url: &str,
) -> Result<(i32, String), Box<dyn Error + Send + Sync>> {
@@ -153,3 +157,85 @@ pub async fn send_metrics(
Ok(())
}
pub async fn listening_to_server(docker: &Docker, base_url: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
let url = format!("{}/api/message", base_url);
let client = reqwest::Client::new();
loop {
// Get message from server
let resp = client.get(&url).send().await;
match resp {
Ok(response) => {
if response.status().is_success() {
match response.json::<ServerMessage>().await {
Ok(msg) => {
// Acknowledge receipt immediately
if let Err(e) = send_acknowledgment(&client, base_url, &msg.message_id, "received", "Message received successfully").await {
eprintln!("Failed to send receipt acknowledgment: {}", e);
}
// Handle the message
let result = handle_server_message(docker, msg.clone()).await;
// Send execution result acknowledgment
let (status, details) = match result {
Ok(_) => ("success", "Message executed successfully".to_string()),
Err(e) => ("error", format!("Execution failed: {}", e)),
};
if let Err(e) = send_acknowledgment(&client, base_url, &msg.message_id, status, &details).await {
eprintln!("Failed to send execution acknowledgment: {}", e);
}
}
Err(e) => {
eprintln!("Failed to parse message: {}", e);
}
}
} else if response.status() == reqwest::StatusCode::NO_CONTENT {
// No new messages, continue polling
println!("No new messages from server");
} else {
eprintln!("Server returned error status: {}", response.status());
}
}
Err(e) => {
eprintln!("Failed to reach server: {}", e);
}
}
// Poll every 5 seconds (or use WebSocket for real-time)
sleep(Duration::from_secs(5)).await;
}
}
async fn send_acknowledgment(
client: &reqwest::Client,
base_url: &str,
message_id: &str,
status: &str,
details: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let ack_url = format!("{}/api/acknowledge", base_url);
let acknowledgment = Acknowledgment {
message_id: message_id.to_string(),
status: status.to_string(),
details: details.to_string(),
};
let response = client
.post(&ack_url)
.json(&acknowledgment)
.send()
.await?;
if response.status().is_success() {
println!("Acknowledgment sent successfully for message {}", message_id);
} else {
eprintln!("Server returned error for acknowledgment: {}", response.status());
}
Ok(())
}

View File

@@ -1,69 +1,55 @@
use std::error::Error;
use crate::models::DiskInfoDetailed;
use std::error::Error;
use anyhow::Result;
use sysinfo::DiskUsage;
use sysinfo::{Component, Components, Disk, Disks, System};
use sysinfo::{Component, Components, Disk, Disks};
use serde::Serialize;
#[derive(Debug)]
#[derive(Serialize, Debug)]
pub struct DiskInfo {
pub total: Option<f64>,
pub used: Option<f64>,
pub free: Option<f64>,
pub total_size: Option<f64>,
pub total_used: Option<f64>,
pub total_available: Option<f64>,
pub total_usage: Option<f64>,
pub detailed_info: Vec<DiskInfoDetailed>,
}
pub async fn get_disk_info() -> Result<DiskInfo> {
pub async fn get_disk_info() -> Result<DiskInfo, Box<dyn std::error::Error + Send + Sync>> {
let disks = Disks::new_with_refreshed_list();
let _disk_types = [
sysinfo::DiskKind::HDD,
sysinfo::DiskKind::SSD,
sysinfo::DiskKind::Unknown(0),
];
let (_, _, _, _) = get_disk_utitlization().unwrap();
let mut total = 0;
let mut used = 0;
let mut detailed_info = Vec::new();
// Collect detailed disk information
for disk in disks.list() {
if disk.total_space() > 100 * 1024 * 1024 {
// > 100MB
total += disk.total_space();
used += disk.total_space() - disk.available_space();
}
}
Ok(DiskInfo {
total: Some(total as f64),
used: Some(used as f64),
free: Some((total - used) as f64),
})
}
pub fn get_disk_utitlization() -> Result<(f64, f64, f64, f64), Box<dyn Error>> {
let mut sys = System::new();
sys.refresh_all();
let mut count = 0;
let mut total_size = 0u64;
let mut total_used = 0u64;
let mut total_available = 0u64;
let disks = Disks::new_with_refreshed_list();
for disk in disks.list() {
// Only print disks with known kind
if disk.kind() == sysinfo::DiskKind::Unknown(0) {
continue;
}
let disk_used = disk.total_space() - disk.available_space();
detailed_info.push(DiskInfoDetailed {
disk_name: disk.name().to_string_lossy().into_owned(),
disk_kind: format!("{:?}", disk.kind()),
disk_total_space: disk.total_space() as f64,
disk_available_space: disk.available_space() as f64,
disk_used_space: disk_used as f64,
disk_mount_point: disk.mount_point().to_string_lossy().into_owned(),
component_disk_label: String::new(),
component_disk_temperature: 0.0,
});
println!(
"Disk_Name: {:?}:\n---- Disk_Kind: {},\n---- Total: {},\n---- Available: {},\n---- Used: {}, \n---- Mount_Point: {:?}",
disk.name(),
disk.kind(),
disk.total_space(),
disk.available_space(),
disk.total_space() - disk.available_space(),
disk_used,
disk.mount_point()
);
}
// Get component temperatures
let components = Components::new_with_refreshed_list();
for component in &components {
if let Some(temperature) = component.temperature() {
@@ -72,59 +58,97 @@ pub fn get_disk_utitlization() -> Result<(f64, f64, f64, f64), Box<dyn Error>> {
component.label(),
temperature
);
}
}
// Berechnungen
let total_size = if count > 0 {
total_size as f64 // in Bytes
} else {
// Fallback: Versuche df unter Linux
println!("Fallback: Using 'df' command to get disk info.");
#[cfg(target_os = "linux")]
{
use std::process::Command;
if let Ok(output) = Command::new("df")
.arg("-B1")
.arg("--output=size,used")
.output()
{
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 2 {
if let (Ok(size), Ok(used)) =
(parts[0].parse::<u64>(), parts[1].parse::<u64>())
{
total_size += size;
total_used += used;
count += 1;
}
}
// Update detailed info with temperature data if it matches a disk component
for disk_info in &mut detailed_info {
if component.label().contains(&disk_info.disk_name) {
disk_info.component_disk_label = component.label().to_string();
disk_info.component_disk_temperature = temperature;
}
total_size as f64 // in Bytes
} else {
0.0
}
}
#[cfg(not(target_os = "linux"))]
{
0.0
}
// Calculate totals (only disks > 100MB)
let (total_size, total_used, total_available) = calculate_disk_totals(&disks);
let (total_size, total_used, total_available, total_usage) = if total_size > 0.0 {
(total_size, total_used, total_available, (total_used / total_size) * 100.0)
} else {
match get_disk_info_fallback() {
Ok(fallback_data) => fallback_data,
Err(_) => (0.0, 0.0, 0.0, 0.0), // Default values if fallback fails
}
};
Ok(DiskInfo {
total_size: if total_size > 0.0 { Some(total_size) } else { None },
total_used: if total_used > 0.0 { Some(total_used) } else { None },
total_available: if total_available > 0.0 { Some(total_available) } else { None },
total_usage: if total_usage > 0.0 { Some(total_usage) } else { None },
detailed_info,
})
}
let usage = if total_size > 0.0 {
fn calculate_disk_totals(disks: &Disks) -> (f64, f64, f64) {
let mut total_size = 0u64;
let mut total_used = 0u64;
let mut total_available = 0u64;
for disk in disks.list() {
if disk.total_space() > 100 * 1024 * 1024 { // > 100MB
total_size += disk.total_space();
total_available += disk.available_space();
total_used += disk.total_space() - disk.available_space();
}
}
(total_size as f64, total_used as f64, total_available as f64)
}
#[cfg(target_os = "linux")]
fn get_disk_info_fallback() -> Result<(f64, f64, f64, f64), Box<dyn Error + Send + Sync>> {
use std::process::Command;
let output = Command::new("df")
.arg("-B1")
.arg("--output=size,used,avail")
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut total_size = 0u64;
let mut total_used = 0u64;
let mut total_available = 0u64;
let mut count = 0;
for line in stdout.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
if let (Ok(size), Ok(used), Ok(avail)) = (
parts[0].parse::<u64>(),
parts[1].parse::<u64>(),
parts[2].parse::<u64>(),
) {
total_size += size;
total_used += used;
total_available += avail;
count += 1;
}
}
}
let usage = if total_size > 0 {
(total_used as f64 / total_size as f64) * 100.0
} else {
0.0
};
Ok((total_size as f64, total_used as f64, total_available as f64, usage))
}
Ok((
total_size,
total_used as f64,
total_available as f64,
usage as f64,
)) // Disk-Temp bleibt 0.0 ohne spezielle Hardware
#[cfg(not(target_os = "linux"))]
fn get_disk_info_fallback() -> Result<(f64, f64, f64, f64), Box<dyn Error + Send + Sync>> {
Ok((0.0, 0.0, 0.0, 0.0))
}
pub fn _get_disk_temp_for_component(component: &Component) -> Option<f64> {

View File

@@ -5,6 +5,7 @@ pub mod api;
pub mod hardware;
pub mod metrics;
pub mod models;
pub mod serverclientcomm;
use std::env;
use std::error::Error;
@@ -12,6 +13,9 @@ use std::marker::Send;
use std::marker::Sync;
use std::result::Result;
use tokio::task::JoinHandle;
use bollard::Docker;
use crate::serverclientcomm::{get_current_image};
async fn flatten<T>(
handle: JoinHandle<Result<T, Box<dyn Error + Send + Sync>>>,
@@ -25,14 +29,30 @@ async fn flatten<T>(
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
let args: Vec<String> = env::args().collect();
// Initialize Docker client
let docker = Docker::connect_with_local_defaults()
.map_err(|e| format!("Failed to connect to Docker: {}", e))?;
// Get current image version
let client_version = match get_current_image(&docker).await {
Ok(Some(version)) => version,
Ok(None) => {
eprintln!("Warning: No image version found");
"unknown".to_string()
}
Err(e) => {
eprintln!("Warning: Could not get current image version: {}", e);
"unknown".to_string()
}
};
println!("Client Version: {}", client_version);
let args: Vec<String> = env::args().collect();
// args[0] is the binary name, args[1] is the first actual argument
if args.len() < 2 {
eprintln!("Usage: {} <server-url>", args[0]);
return Err("Missing server URL argument".into());
}
let server_url = &args[1];
println!("Server URL: {:?}", server_url);
@@ -46,6 +66,13 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
};
// Start background tasks
// Start server listening for commands
let listening_handle = tokio::spawn({
let docker = docker.clone();
let server_url = server_url.to_string();
async move { api::listening_to_server(&docker, &server_url).await }
});
// Start heartbeat in background
let heartbeat_handle = tokio::spawn({
let ip = ip.clone();
@@ -64,14 +91,16 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
}
});
// Warte auf beide Tasks und prüfe explizit auf Fehler
let (heartbeat_handle, metrics_handle) =
tokio::try_join!(flatten(heartbeat_handle), flatten(metrics_handle))?;
// Wait for all tasks and check for errors
let (listening_result, heartbeat_result, metrics_result) = tokio::try_join!(
flatten(listening_handle),
flatten(heartbeat_handle),
flatten(metrics_handle)
)?;
let (heartbeat, metrics) = (heartbeat_handle, metrics_handle);
println!(
"All tasks completed successfully: {:?}, {:?}.",
heartbeat, metrics
"All tasks completed: listening={:?}, heartbeat={:?}, metrics={:?}",
listening_result, heartbeat_result, metrics_result
);
println!("All tasks completed successfully.");

View File

@@ -62,8 +62,8 @@ impl Collector {
gpu_vram_usage: hardware.gpu.vram_used.unwrap_or_default(),
ram_load: hardware.memory.used.unwrap_or_default(),
ram_size: hardware.memory.total.unwrap_or_default(),
disk_size: hardware.disk.total.unwrap_or_default(),
disk_usage: hardware.disk.used.unwrap_or_default(),
disk_size: hardware.disk.total_size.unwrap_or_default(),
disk_usage: hardware.disk.total_used.unwrap_or_default(),
disk_temp: 0.0, // not supported
net_rx: hardware.network.rx_rate.unwrap_or_default(),
net_tx: hardware.network.tx_rate.unwrap_or_default(),

View File

@@ -51,6 +51,18 @@ pub struct MetricDto {
pub net_tx: f64,
}
#[derive(Serialize, Debug)]
pub struct DiskInfoDetailed {
pub disk_name: String,
pub disk_kind: String,
pub disk_total_space: f64,
pub disk_available_space: f64,
pub disk_used_space: f64,
pub disk_mount_point: String,
pub component_disk_label: String,
pub component_disk_temperature: f32,
}
#[derive(Deserialize)]
pub struct IdResponse {
pub id: i32,
@@ -72,3 +84,18 @@ pub struct HardwareDto {
pub ram_size: f64,
pub ip_address: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ServerMessage {
// Define your message structure here
pub message_type: String,
pub data: serde_json::Value,
pub message_id: String, // Add an ID for acknowledgment
}
#[derive(Debug, Serialize, Clone)]
pub struct Acknowledgment {
pub message_id: String,
pub status: String, // "success" or "error"
pub details: String,
}

View File

@@ -0,0 +1,130 @@
use crate::models::{ServerMessage};
use std::error::Error;
use bollard::Docker;
use bollard::query_parameters::{CreateImageOptions, RestartContainerOptions, InspectContainerOptions};
use futures_util::StreamExt;
pub async fn handle_server_message(docker: &Docker, msg: ServerMessage) -> Result<(), Box<dyn Error + Send + Sync>> {
let msg = msg.clone();
println!("Handling server message: {:?}", msg);
// Handle different message types
match msg.message_type.as_str() {
"update_image" => {
if let Some(image_name) = msg.data.get("image").and_then(|v| v.as_str()) {
println!("Received update command for image: {}", image_name);
// Call your update_docker_image function here
update_docker_image(docker, image_name).await?;
Ok(())
} else {
Err("Missing image name in update message".into())
}
}
"restart_container" => {
println!("Received restart container command");
// Call your restart_container function here
restart_container(docker).await?;
Ok(())
}
"stop_agent" => {
println!("Received stop agent command");
// Implement graceful shutdown
std::process::exit(0);
}
_ => {
eprintln!("Unknown message type: {}", msg.message_type);
Err(format!("Unknown message type: {}", msg.message_type).into())
}
}
}
pub async fn update_docker_image(docker: &Docker, image: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
println!("Updating to {}", image);
// 1. Pull new image
let mut stream = docker.create_image(
Some(CreateImageOptions {
from_image: Some(image.to_string()),
..Default::default()
}),
None,
None,
);
// Use the stream with proper trait bounds
while let Some(result) = StreamExt::next(&mut stream).await {
match result {
Ok(progress) => {
if let Some(status) = progress.status {
println!("Pull status: {}", status);
}
}
Err(e) => {
eprintln!("Error pulling image: {}", e);
break;
}
}
}
// 2. Restart the current container
let _ = restart_container(docker).await;
Ok(())
}
pub async fn get_current_image(docker: &Docker) -> Result<Option<String>, Box<dyn Error + Send + Sync>> {
// Get the current container ID from /proc/self/cgroup
let container_id = match std::fs::read_to_string("/proc/self/cgroup") {
Ok(content) => {
content
.lines()
.find_map(|line| {
if line.contains("docker") {
line.split('/').last().map(|s| s.trim().to_string())
} else {
None
}
})
}
Err(e) => {
eprintln!("Error reading cgroup file: {}", e);
return Ok(None);
}
};
let container_id = match container_id {
Some(id) => id,
None => {
eprintln!("Could not find container ID in cgroup");
return Ok(None);
}
};
// Inspect the current container to get its image
match docker.inspect_container(&container_id, None::<InspectContainerOptions>).await {
Ok(container_info) => {
Ok(container_info.config.map(|config| config.image.unwrap_or_else(|| "unknown".to_string())))
}
Err(e) => {
eprintln!("Error inspecting container: {}", e);
Ok(None)
}
}
}
pub async fn restart_container(docker: &Docker) -> Result<(), Box<dyn Error + Send + Sync>> {
if let Ok(container_id) = std::env::var("HOSTNAME") {
println!("Restarting container {}", container_id);
if let Err(e) = docker.restart_container(&container_id, Some(RestartContainerOptions { signal: None, t: Some(0) }))
.await
{
eprintln!("Failed to restart container: {}", e);
}
} else {
eprintln!("No container ID found (HOSTNAME not set?)");
}
Ok(())
}

23
docker-compose.yaml Normal file
View File

@@ -0,0 +1,23 @@
watcher-agent:
image: git.triggermeelmo.com/donpat1to/watcher-agent:development
container_name: watcher-agent
restart: always
privileged: true # Grants full hardware access (use with caution)
env_file: .env
pid: "host"
volumes:
# Mount critical system paths for hardware monitoring
- /sys:/sys:ro # CPU/GPU temps, sensors
- /proc:/proc # Process/CPU stats
- /dev:/dev:ro # Disk/GPU device access
- /var/run/docker.sock:/var/run/docker.sock # Docker API access
- /:/root:ro # Access to for df-command
# Application volumes
- ./config:/app/config:ro
- ./logs:/app/logs
network_mode: host # Uses host network (for correct IP/interface detection)
healthcheck:
test: ["CMD", "/usr/local/bin/WatcherAgent", "healthcheck"]
interval: 30s
timeout: 3s
retries: 3