25 Commits

Author SHA1 Message Date
d7a58e00da added listing all running containers
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 1m13s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 3m20s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 4m7s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m22s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 1s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
2025-09-29 14:54:03 +02:00
d2efc64487 moved container functions into new mod 2025-09-28 18:54:17 +02:00
1f23c303c1 removed overloading metrics print statements
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 1m10s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 3m6s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m43s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m14s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
2025-09-28 17:46:42 +02:00
1cc85bfa14 added debugging for docker image
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 4s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m10s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 3m6s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m54s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m12s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
2025-09-28 01:00:26 +02:00
8bac357dc6 added debugging for docker image
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 1m10s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 3m9s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m54s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m12s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 1s
Rust Cross-Platform Build / Create Tag (push) Successful in 4s
2025-09-28 00:36:52 +02:00
7154c01f7a added search for docker image in different locations
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 5s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m6s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m45s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m30s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m19s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
2025-09-28 00:24:47 +02:00
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
11 changed files with 577 additions and 141 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

@@ -19,9 +19,11 @@ nvml-wrapper = "0.11"
nvml-wrapper-sys = "0.9.0"
anyhow = "1.0.98"
# Docker .env loading
config = "0.13"
dotenvy = "0.15"
regex = "1.11.3"
# 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::docker::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

@@ -0,0 +1,97 @@
use crate::models::DockerContainer;
use bollard::query_parameters::{ListContainersOptions};
use bollard::Docker;
pub async fn get_available_container(docker: &Docker) -> Vec<DockerContainer> {
println!("=== DOCKER CONTAINER LIST ===");
let options = Some(ListContainersOptions {
all: true,
..Default::default()
});
let containers_list = match docker.list_containers(options).await {
Ok(containers) => {
println!("Available containers ({}):", containers.len());
containers.into_iter()
.filter_map(|container| {
container.id.as_ref()?; // Skip if no ID
let id = container.id?;
let short_id = if id.len() > 12 { &id[..12] } else { &id };
let name = container.names
.and_then(|names| names.into_iter().next())
.map(|name| name.trim_start_matches('/').to_string())
.unwrap_or_else(|| "unknown".to_string());
let image = container.image
.as_ref()
.map(|img| img.to_string())
.unwrap_or_else(|| "unknown".to_string());
let status = container.status
.as_ref()
.map(|s| match s.to_lowercase().as_str() {
s if s.contains("up") || s.contains("running") => "running".to_string(),
s if s.contains("exited") || s.contains("stopped") => "stopped".to_string(),
_ => s.to_string(),
})
.unwrap_or_else(|| "unknown".to_string());
println!(" - ID: {}, Image: {:?}, Name: {}", short_id, container.image, name);
Some(DockerContainer {
ID: short_id.to_string(),
image,
Name: name,
Status: status,
_net_in: 0.0,
_net_out: 0.0,
_cpu_load: 0.0,
})
})
.collect()
}
Err(e) => {
eprintln!("Failed to list containers: {}", e);
Vec::new()
}
};
containers_list
}
pub fn extract_container_id(line: &str) -> Option<String> {
// Split by slashes and take the last part
if let Some(last_part) = line.split('/').last() {
let last_part = last_part.trim();
// Remove common suffixes
let clean_id = last_part
.trim_end_matches(".scope")
.trim_start_matches("docker-")
.trim_start_matches("crio-")
.trim_start_matches("containerd-");
// Check if it looks like a container ID (hex characters)
if clean_id.chars().all(|c| c.is_ascii_hexdigit()) && clean_id.len() >= 12 {
return Some(clean_id.to_string());
}
// If it's not pure hex, try to extract hex sequence
let hex_part: String = clean_id.chars()
.take_while(|c| c.is_ascii_hexdigit())
.collect();
if hex_part.len() >= 12 {
return Some(hex_part);
}
}
None
}

View File

@@ -0,0 +1,19 @@
pub mod container;
pub mod serverclientcomm;
use std::error::Error;
use crate::models::DockerContainer;
#[derive(Debug, Clone)]
pub struct DockerInfo {
pub number: Option<u16>,
pub net_in_total: Option<f64>,
pub net_out_total: Option<f64>,
pub dockers: Option<Vec<DockerContainer>>,
}
impl DockerInfo {
pub async fn collect() -> Result<Self, Box<dyn Error + Send + Sync>> {
Ok(Self { number: None, net_in_total: None, net_out_total: None, dockers: None })
}
}

View File

@@ -0,0 +1,152 @@
use crate::models::ServerMessage;
use crate::docker::container::{extract_container_id, get_available_container};
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>> {
// First, let's debug the environment
get_available_container(docker).await;
// Get the current container ID from /proc/self/cgroup
let container_id = match std::fs::read_to_string("/proc/self/cgroup") {
Ok(content) => {
let mut found_id = None;
println!("Searching cgroup for container ID...");
for line in content.lines() {
println!("Checking line: {}", line);
// Look for container runtime indicators
if line.contains("docker") || line.contains("crio") || line.contains("containerd") {
if let Some(id) = extract_container_id(line) {
println!("Found potential container ID: {}", id);
found_id = Some(id);
break;
}
}
}
found_id
}
Err(e) => {
eprintln!("Error reading cgroup file: {}", e);
return Ok(None);
}
};
let container_id = match container_id {
Some(id) if !id.is_empty() => {
println!("Using container ID: '{}'", id);
id
}
_ => {
eprintln!("Could not find valid container ID in cgroup");
return Ok(None);
}
};
// Try to inspect the container
println!("Attempting to inspect container with ID: '{}'", container_id);
match docker.inspect_container(&container_id, None::<InspectContainerOptions>).await {
Ok(container_info) => {
if let Some(config) = container_info.config {
if let Some(image) = config.image {
println!("Successfully found image: {}", image);
return Ok(Some(image));
}
}
eprintln!("Container inspected but no image found in config");
Ok(Some("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(())
}

View File

@@ -1,130 +1,138 @@
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();
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,
});
}
// Get component temperatures
let components = Components::new_with_refreshed_list();
for component in &components {
if let Some(temperature) = component.temperature() {
// 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;
}
}
}
}
// 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: Some(total as f64),
used: Some(used as f64),
free: Some((total - used) as f64),
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,
})
}
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;
fn calculate_disk_totals(disks: &Disks) -> (f64, f64, f64) {
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;
}
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.mount_point()
);
}
let components = Components::new_with_refreshed_list();
for component in &components {
if let Some(temperature) = component.temperature() {
println!(
"Component_Label: {}, Temperature: {}°C",
component.label(),
temperature
);
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();
}
}
// 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.");
(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;
if let Ok(output) = Command::new("df")
let output = Command::new("df")
.arg("-B1")
.arg("--output=size,used")
.output()
{
.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() == 2 {
if let (Ok(size), Ok(used)) =
(parts[0].parse::<u64>(), parts[1].parse::<u64>())
{
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;
}
}
}
total_size as f64 // in Bytes
} else {
0.0
}
}
#[cfg(not(target_os = "linux"))]
{
0.0
}
};
let usage = if total_size > 0.0 {
let usage = if total_size > 0 {
(total_used as f64 / total_size as f64) * 100.0
} else {
0.0
};
Ok((
total_size,
total_used as f64,
total_available as f64,
usage as f64,
)) // Disk-Temp bleibt 0.0 ohne spezielle Hardware
Ok((total_size as f64, total_used as f64, total_available as f64, usage))
}
#[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 docker;
use std::env;
use std::error::Error;
@@ -12,6 +13,7 @@ use std::marker::Send;
use std::marker::Sync;
use std::result::Result;
use tokio::task::JoinHandle;
use bollard::Docker;
async fn flatten<T>(
handle: JoinHandle<Result<T, Box<dyn Error + Send + Sync>>>,
@@ -25,14 +27,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 docker::serverclientcomm::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 +64,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 +89,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,29 @@ 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,
}
#[derive(Debug, Serialize, Clone)]
pub struct DockerContainer {
pub ID: String,
pub image: String,
pub Name: String,
pub Status: String, // "running";"stopped";others
pub _net_in: f64,
pub _net_out: f64,
pub _cpu_load: f64,
}

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