mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-04 16:00:59 +00:00
e732da3b05
* start 1.19.1 * deploy 1.19.1-dev-1 * Global Auto Update rustdoc * support stack additional files * deploy 1.19.1-dev-2 * Fe support additional file language detection * fix tsc * Fix: Example code blocks got interpreted as rust code, leading to compilation errors (#743) * Enhanced Server Stats Dashboard with Performance Optimizations (#746) * Improve the layout of server mini stats in the dashboard. - Server stats and tags made siblings for clearer responsibilities - Changed margin to padding - Unreachable indicator made into an overlay of the stats * feat: optimize dashboard server stats with lazy loading and smart server availability checks - Add enabled prop to ServerStatsMini for conditional data fetching - Implement server availability check (only fetch stats for Ok servers, not NotOk/Disabled) - Prevent 500 errors by avoiding API calls to offline servers - Increase polling interval from 10s to 15s and add 5s stale time - Add useMemo for expensive calculations to reduce re-renders - Add conditional overlay rendering for unreachable servers - Only render stats when showServerStats preference is enabled * fix: show disabled servers with overlay instead of hiding component - Maintain consistent layout by showing disabled state overlay - Prevent UX inconsistency where disabled servers disappeared entirely * fix: show button height * feat: add enhance card animations * cleanup * gen types * deploy 1.19.1-dev-3 * add .ini * deploy 1.19.1-dev-4 * simple configure action args as JSON * server enabled actually defaults false * SendAlert via Action / CLI * fix clippy if let string * deploy 1.19.1-dev-5 * improve cli ergonomics * gen types and fix responses formatting * Add RunStackService API implementing `docker compose run` (#732) * Add RunStackService API implementing `docker compose run` * Add working Procedure configuration * Remove `km execute run` alias. Remove redundant ``#[serde(default)]` on `Option`. * Refactor command from `String` to `Vec<String>` * Implement proper shell escaping * bump deps * Update configuration.md - fix typo: "affect" -> "effect" (#747) * clean up SendAlert doc * deploy 1.19.1-dev-6 * env file args won't double pass env file * deploy 1.19.1-dev-7 * Add Enter Key Support for Dialog Confirmations (#750) * start 1.19.1 * deploy 1.19.1-dev-1 * Implement usePromptHotkeys for enhanced dialog interactions and UX * Refactor usePromptHotkeys to enhance confirm button detection and improve UX * Remove forceConfirmDialog prop from ActionWithDialog and related logic for cleaner implementation * Add dialog descriptions to ConfirmUpdate and ActionWithDialog for better clarity and resolve warnings * fix * Restore forceConfirmDialog prop to ActionWithDialog for enhanced confirmation handling * cleanup * Remove conditional className logic from ConfirmButton --------- Co-authored-by: mbecker20 <max@mogh.tech> * Support complex file depency action resolution * get FE compile * deploy 1.19.1-dev-8 * implement additional file dependency configuration * deploy 1.19.1-dev-9 * UI default file dependency None * default additional file requires is None * deploy 1.19.1-dev-10 * rename additional_files => config_files for clarity * deploy 1.19.1-dev-11 * fix skip serializing if None * deploy 1.19.1-dev-12 * stack file dependency toml parsing aliases * fmt * Add: Server Version Mismatch Warnings & Alert System (#748) * start 1.19.1 * deploy 1.19.1-dev-1 * feat: implement version mismatch warnings in server UI - Replace orange warning colors with yellow for better visibility - Add version mismatch detection that shows warnings instead of OK status Implement responsive "VERSION MISMATCH" badge layout - Update server dashboard to include warning counts - Add backend version comparison logic for GetServersSummary * feat: add warning count to server summary and update backup documentation link * feat: add server version mismatch alert handling and update server summary invalidation logic * fix: correct version mismatch alert config and disabled server display - Use send_version_mismatch_alerts instead of send_unreachable_alerts - Show 'Unknown' instead of 'Disabled' for disabled server versions - Remove commented VersionAlert and Alerts UI components - Update version to 1.19.0 * cleanup * Update TypeScript types after merge * cleanup * cleanup * cleanup * Add "ServerVersionMismatch" to alert types * Adjust color classes for warning states and revert server update invalidation logic --------- Co-authored-by: mbecker20 <max@mogh.tech> * backend for build multi registry push support * deploy 1.19.1-dev-13 * build multi registry configuration * deploy 1.19.1-dev-14 * fix invalid tokens JSON * DeployStackIfChanged restarts also update stack.info.deployed_contents * update deployed services comments * deploy 1.19.1-dev-15 * Enhance server monitoring with load average data and new server monitoring table (#761) * add monitoring page * initial table * moving monitoring table to servers * add cpu load average * typeshare doesnt allow tuples * fix GetHistoricalServerStats * add loadAvg to the server monitoring table * improve styling * add load average chart * multiple colors for average loads chart * make load average chart line and non-stacked * cleanup * use server thresholds * cleanup * Change "Dependents:" to "Services:" in config file service dependency selector * deploy 1.19.1-dev-16 * 1.19.1 --------- Co-authored-by: mbecker20 <max@mogh.tech> Co-authored-by: Marcel Pfennig <82059270+MP-Tool@users.noreply.github.com> Co-authored-by: Brian Bradley <brian.bradley.p@gmail.com> Co-authored-by: Ravi Wolter-Krishan <rkn@gedikas.net> Co-authored-by: jack <45038833+jackra1n@users.noreply.github.com>
312 lines
8.8 KiB
Rust
312 lines
8.8 KiB
Rust
//! # Network Configuration Module
|
|
//!
|
|
//! This module provides manual network interface configuration for multi-NIC Docker environments.
|
|
//! It allows Komodo Core to specify which network interface should be used as the default route
|
|
//! for internet traffic, which is particularly useful in complex networking setups with multiple
|
|
//! network interfaces.
|
|
//!
|
|
//! ## Features
|
|
//! - Automatic container environment detection
|
|
//! - Interface validation (existence and UP state)
|
|
//! - Gateway discovery from routing tables or network configuration
|
|
//! - Safe default route modification with privilege checking
|
|
//! - Comprehensive error handling and logging
|
|
|
|
use anyhow::{Context, anyhow};
|
|
use tokio::process::Command;
|
|
use tracing::{debug, info, trace, warn};
|
|
|
|
/// Standard gateway addresses to test for Docker networks
|
|
const DOCKER_GATEWAY_CANDIDATES: &[&str] = &[".1", ".254"];
|
|
|
|
/// Container environment detection files
|
|
const DOCKERENV_FILE: &str = "/.dockerenv";
|
|
const CGROUP_FILE: &str = "/proc/1/cgroup";
|
|
|
|
/// Check if running in container environment
|
|
fn is_container_environment() -> bool {
|
|
// Check for Docker-specific indicators
|
|
if std::path::Path::new(DOCKERENV_FILE).exists() {
|
|
return true;
|
|
}
|
|
|
|
// Check container environment variable
|
|
if std::env::var("container").is_ok() {
|
|
return true;
|
|
}
|
|
|
|
// Check cgroup for container runtime indicators
|
|
if let Ok(content) = std::fs::read_to_string(CGROUP_FILE)
|
|
&& (content.contains("docker") || content.contains("containerd"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/// Configure internet gateway for specified interface
|
|
pub async fn configure_internet_gateway() {
|
|
use crate::config::core_config;
|
|
|
|
let config = core_config();
|
|
|
|
if !is_container_environment() {
|
|
debug!("Not in container, skipping network configuration");
|
|
return;
|
|
}
|
|
|
|
if !config.internet_interface.is_empty() {
|
|
debug!(
|
|
"Configuring internet interface: {}",
|
|
config.internet_interface
|
|
);
|
|
if let Err(e) =
|
|
configure_manual_interface(&config.internet_interface).await
|
|
{
|
|
warn!("Failed to configure internet gateway: {e:#}");
|
|
}
|
|
} else {
|
|
debug!("No interface specified, using default routing");
|
|
}
|
|
}
|
|
|
|
/// Configure interface as default route
|
|
async fn configure_manual_interface(
|
|
interface_name: &str,
|
|
) -> anyhow::Result<()> {
|
|
// Verify interface exists and is up
|
|
let interface_check = Command::new("ip")
|
|
.args(["addr", "show", interface_name])
|
|
.output()
|
|
.await
|
|
.context("Failed to check interface status")?;
|
|
|
|
if !interface_check.status.success() {
|
|
return Err(anyhow!(
|
|
"Interface '{}' does not exist or is not accessible. Available interfaces can be listed with 'ip addr show'",
|
|
interface_name
|
|
));
|
|
}
|
|
|
|
let interface_info =
|
|
String::from_utf8_lossy(&interface_check.stdout);
|
|
if !interface_info.contains("state UP") {
|
|
return Err(anyhow!(
|
|
"Interface '{}' is not UP. Please ensure the interface is enabled and connected",
|
|
interface_name
|
|
));
|
|
}
|
|
|
|
debug!("Interface {} is UP", interface_name);
|
|
|
|
let gateway = find_gateway(interface_name).await?;
|
|
debug!("Found gateway {} for {}", gateway, interface_name);
|
|
|
|
set_default_gateway(&gateway, interface_name).await?;
|
|
info!(
|
|
"🌐 Configured {} as default gateway via {}",
|
|
interface_name, gateway
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
/// Find gateway for interface
|
|
async fn find_gateway(
|
|
interface_name: &str,
|
|
) -> anyhow::Result<String> {
|
|
// Get interface IP address
|
|
let addr_output = Command::new("ip")
|
|
.args(["addr", "show", interface_name])
|
|
.output()
|
|
.await
|
|
.context("Failed to get interface address")?;
|
|
|
|
let addr_info = String::from_utf8_lossy(&addr_output.stdout);
|
|
let mut ip_cidr = None;
|
|
|
|
// Extract IP/CIDR from interface info
|
|
for line in addr_info.lines() {
|
|
if line.trim().starts_with("inet ") && !line.contains("127.0.0.1")
|
|
{
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if let Some(found_ip_cidr) = parts.get(1) {
|
|
debug!(
|
|
"Interface {} has IP {}",
|
|
interface_name, found_ip_cidr
|
|
);
|
|
ip_cidr = Some(*found_ip_cidr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
let ip_cidr = ip_cidr.ok_or_else(|| anyhow!(
|
|
"Could not find IP address for interface '{}'. Ensure interface has a valid IPv4 address",
|
|
interface_name
|
|
))?;
|
|
|
|
trace!(
|
|
"Finding gateway for interface {} in network {}",
|
|
interface_name, ip_cidr
|
|
);
|
|
|
|
// Try to find gateway from routing table
|
|
let route_output = Command::new("ip")
|
|
.args(["route", "show", "dev", interface_name])
|
|
.output()
|
|
.await
|
|
.context("Failed to get routes for interface")?;
|
|
|
|
if route_output.status.success() {
|
|
let routes = String::from_utf8(route_output.stdout)?;
|
|
trace!("Routes for {}: {}", interface_name, routes.trim());
|
|
|
|
// Look for routes with gateway
|
|
for line in routes.lines() {
|
|
if line.contains("via") {
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if let Some(via_idx) = parts.iter().position(|&x| x == "via")
|
|
&& let Some(&gateway) = parts.get(via_idx + 1)
|
|
{
|
|
trace!(
|
|
"Found gateway {} for {} from routing table",
|
|
gateway, interface_name
|
|
);
|
|
return Ok(gateway.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Derive gateway from network configuration (Docker standard: .1)
|
|
if let Some(network_base) = ip_cidr.split('/').next() {
|
|
let ip_parts: Vec<&str> = network_base.split('.').collect();
|
|
if ip_parts.len() == 4 {
|
|
let potential_gateways: Vec<String> = DOCKER_GATEWAY_CANDIDATES
|
|
.iter()
|
|
.map(|suffix| {
|
|
format!(
|
|
"{}.{}.{}{}",
|
|
ip_parts[0], ip_parts[1], ip_parts[2], suffix
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
for gateway in potential_gateways {
|
|
trace!(
|
|
"Testing potential gateway {} for {}",
|
|
gateway, interface_name
|
|
);
|
|
|
|
// Check if gateway is reachable
|
|
let route_test = Command::new("ip")
|
|
.args(["route", "get", &gateway, "dev", interface_name])
|
|
.output()
|
|
.await;
|
|
|
|
if let Ok(output) = route_test
|
|
&& output.status.success()
|
|
{
|
|
trace!(
|
|
"Gateway {} is reachable via {}",
|
|
gateway, interface_name
|
|
);
|
|
return Ok(gateway.to_string());
|
|
}
|
|
|
|
// Fallback: assume .1 is gateway (Docker standard)
|
|
if gateway.ends_with(".1") {
|
|
trace!(
|
|
"Assuming Docker gateway {} for {}",
|
|
gateway, interface_name
|
|
);
|
|
return Ok(gateway.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Err(anyhow!(
|
|
"Could not determine gateway for interface '{}' in network '{}'. \
|
|
Ensure the interface is properly configured with a valid gateway",
|
|
interface_name,
|
|
ip_cidr
|
|
))
|
|
}
|
|
|
|
/// Set default gateway to use specified interface
|
|
async fn set_default_gateway(
|
|
gateway: &str,
|
|
interface_name: &str,
|
|
) -> anyhow::Result<()> {
|
|
trace!(
|
|
"Setting default gateway to {} via {}",
|
|
gateway, interface_name
|
|
);
|
|
|
|
// Check if we have network privileges
|
|
if !check_network_privileges().await {
|
|
warn!(
|
|
"⚠️ Container lacks network privileges (NET_ADMIN capability required)"
|
|
);
|
|
warn!(
|
|
"Add 'cap_add: [\"NET_ADMIN\"]' to your docker-compose.yaml"
|
|
);
|
|
return Err(anyhow!(
|
|
"Insufficient network privileges to modify routing table. \
|
|
Container needs NET_ADMIN capability to configure network interfaces"
|
|
));
|
|
}
|
|
|
|
// Remove existing default routes
|
|
let remove_default = Command::new("sh")
|
|
.args(["-c", "ip route del default 2>/dev/null || true"])
|
|
.output()
|
|
.await;
|
|
|
|
if let Ok(output) = remove_default
|
|
&& output.status.success()
|
|
{
|
|
trace!("Removed existing default routes");
|
|
}
|
|
|
|
// Add new default route
|
|
let add_default_cmd = format!(
|
|
"ip route add default via {gateway} dev {interface_name}"
|
|
);
|
|
trace!("Adding default route: {}", add_default_cmd);
|
|
|
|
let add_default = Command::new("sh")
|
|
.args(["-c", &add_default_cmd])
|
|
.output()
|
|
.await
|
|
.context("Failed to add default route")?;
|
|
|
|
if !add_default.status.success() {
|
|
let error = String::from_utf8_lossy(&add_default.stderr)
|
|
.trim()
|
|
.to_string();
|
|
return Err(anyhow!(
|
|
"❌ Failed to set default gateway via '{}': {}. \
|
|
Verify interface configuration and network permissions",
|
|
interface_name,
|
|
error
|
|
));
|
|
}
|
|
|
|
trace!("Default gateway set to {} via {}", gateway, interface_name);
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if we have sufficient network privileges
|
|
async fn check_network_privileges() -> bool {
|
|
// Try to test NET_ADMIN capability with a harmless route operation
|
|
let capability_test = Command::new("sh")
|
|
.args(["-c", "ip route add 198.51.100.1/32 dev lo 2>/dev/null && ip route del 198.51.100.1/32 dev lo 2>/dev/null"])
|
|
.output()
|
|
.await;
|
|
|
|
matches!(capability_test, Ok(output) if output.status.success())
|
|
}
|