All checks were successful
Build and Release / build-and-release (push) Successful in 2m36s
Update the auto connection type logic to try local network connections before falling back to Tailscale. This provides better performance by using faster local connections when available while maintaining Tailscale as a reliable fallback. Changes: - Auto connection priority: local → tailscale → hostname (was tailscale → local) - Fallback retry order updated to match new priority - Supports omitting IP field in config for hosts without static local IP
150 lines
4.6 KiB
Rust
150 lines
4.6 KiB
Rust
use anyhow::Result;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::Path;
|
|
|
|
/// Main dashboard configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DashboardConfig {
|
|
pub zmq: ZmqConfig,
|
|
pub hosts: std::collections::HashMap<String, HostDetails>,
|
|
pub system: SystemConfig,
|
|
pub ssh: SshConfig,
|
|
pub service_logs: std::collections::HashMap<String, Vec<ServiceLogConfig>>,
|
|
}
|
|
|
|
/// ZMQ consumer configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ZmqConfig {
|
|
pub subscriber_ports: Vec<u16>,
|
|
/// Heartbeat timeout in seconds - hosts considered offline if no heartbeat received within this time
|
|
#[serde(default = "default_heartbeat_timeout_seconds")]
|
|
pub heartbeat_timeout_seconds: u64,
|
|
}
|
|
|
|
fn default_heartbeat_timeout_seconds() -> u64 {
|
|
10 // Default to 10 seconds - allows for multiple missed heartbeats
|
|
}
|
|
|
|
/// Individual host configuration details
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HostDetails {
|
|
pub mac_address: Option<String>,
|
|
/// Primary IP address (local network)
|
|
pub ip: Option<String>,
|
|
/// Tailscale network IP address
|
|
pub tailscale_ip: Option<String>,
|
|
/// Preferred connection type: "local", "tailscale", or "auto" (fallback)
|
|
#[serde(default = "default_connection_type")]
|
|
pub connection_type: String,
|
|
}
|
|
|
|
fn default_connection_type() -> String {
|
|
"auto".to_string()
|
|
}
|
|
|
|
impl HostDetails {
|
|
/// Get the preferred IP address for connection based on connection_type
|
|
pub fn get_connection_ip(&self, hostname: &str) -> String {
|
|
match self.connection_type.as_str() {
|
|
"tailscale" => {
|
|
if let Some(ref ts_ip) = self.tailscale_ip {
|
|
ts_ip.clone()
|
|
} else {
|
|
// Fallback to local IP or hostname
|
|
self.ip.as_ref().unwrap_or(&hostname.to_string()).clone()
|
|
}
|
|
}
|
|
"local" => {
|
|
if let Some(ref local_ip) = self.ip {
|
|
local_ip.clone()
|
|
} else {
|
|
hostname.to_string()
|
|
}
|
|
}
|
|
"auto" | _ => {
|
|
// Try local first, then tailscale, then hostname
|
|
if let Some(ref local_ip) = self.ip {
|
|
local_ip.clone()
|
|
} else if let Some(ref ts_ip) = self.tailscale_ip {
|
|
ts_ip.clone()
|
|
} else {
|
|
hostname.to_string()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get fallback IP addresses for connection retry
|
|
pub fn get_fallback_ips(&self, hostname: &str) -> Vec<String> {
|
|
let mut fallbacks = Vec::new();
|
|
|
|
// Add all available IPs except the primary one
|
|
let primary = self.get_connection_ip(hostname);
|
|
|
|
// Add fallbacks in priority order: local first, then tailscale
|
|
if let Some(ref local_ip) = self.ip {
|
|
if local_ip != &primary {
|
|
fallbacks.push(local_ip.clone());
|
|
}
|
|
}
|
|
|
|
if let Some(ref ts_ip) = self.tailscale_ip {
|
|
if ts_ip != &primary {
|
|
fallbacks.push(ts_ip.clone());
|
|
}
|
|
}
|
|
|
|
// Always include hostname as final fallback if not already primary
|
|
if hostname != primary {
|
|
fallbacks.push(hostname.to_string());
|
|
}
|
|
|
|
fallbacks
|
|
}
|
|
}
|
|
|
|
/// System configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SystemConfig {
|
|
pub nixos_config_git_url: String,
|
|
pub nixos_config_branch: String,
|
|
pub nixos_config_working_dir: String,
|
|
pub nixos_config_api_key_file: Option<String>,
|
|
}
|
|
|
|
/// SSH configuration for rebuild operations
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SshConfig {
|
|
pub rebuild_user: String,
|
|
pub rebuild_alias: String,
|
|
}
|
|
|
|
/// Service log file configuration per host
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServiceLogConfig {
|
|
pub service_name: String,
|
|
pub log_file_path: String,
|
|
}
|
|
|
|
impl DashboardConfig {
|
|
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
let path = path.as_ref();
|
|
let content = std::fs::read_to_string(path)?;
|
|
let config: DashboardConfig = toml::from_str(&content)?;
|
|
Ok(config)
|
|
}
|
|
}
|
|
|
|
impl Default for DashboardConfig {
|
|
fn default() -> Self {
|
|
panic!("Dashboard configuration must be loaded from file - no hardcoded defaults allowed")
|
|
}
|
|
}
|
|
|
|
impl Default for ZmqConfig {
|
|
fn default() -> Self {
|
|
panic!("Dashboard configuration must be loaded from file - no hardcoded defaults allowed")
|
|
}
|
|
}
|
|
|