fix network interface information fetch from OS
This commit is contained in:
Binary file not shown.
@@ -270,6 +270,8 @@ func NewRouter(cfg *config.Config, db *database.DB, log *logger.Logger) *gin.Eng
|
||||
systemGroup.GET("/services/:name/logs", systemHandler.GetServiceLogs)
|
||||
systemGroup.POST("/support-bundle", systemHandler.GenerateSupportBundle)
|
||||
systemGroup.GET("/interfaces", systemHandler.ListNetworkInterfaces)
|
||||
systemGroup.GET("/ntp", systemHandler.GetNTPSettings)
|
||||
systemGroup.POST("/ntp", systemHandler.SaveNTPSettings)
|
||||
}
|
||||
|
||||
// IAM routes - GetUser can be accessed by user viewing own profile or admin
|
||||
|
||||
@@ -131,3 +131,45 @@ func (h *Handler) ListNetworkInterfaces(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"interfaces": interfaces})
|
||||
}
|
||||
|
||||
// SaveNTPSettings saves NTP configuration to the OS
|
||||
func (h *Handler) SaveNTPSettings(c *gin.Context) {
|
||||
var settings NTPSettings
|
||||
if err := c.ShouldBindJSON(&settings); err != nil {
|
||||
h.logger.Error("Invalid request body", "error", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate timezone
|
||||
if settings.Timezone == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "timezone is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate NTP servers
|
||||
if len(settings.NTPServers) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "at least one NTP server is required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.SaveNTPSettings(c.Request.Context(), settings); err != nil {
|
||||
h.logger.Error("Failed to save NTP settings", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "NTP settings saved successfully"})
|
||||
}
|
||||
|
||||
// GetNTPSettings retrieves current NTP configuration
|
||||
func (h *Handler) GetNTPSettings(c *gin.Context) {
|
||||
settings, err := h.service.GetNTPSettings(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get NTP settings", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get NTP settings"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"settings": settings})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -11,6 +12,12 @@ import (
|
||||
"github.com/atlasos/calypso/internal/common/logger"
|
||||
)
|
||||
|
||||
// NTPSettings represents NTP configuration
|
||||
type NTPSettings struct {
|
||||
Timezone string `json:"timezone"`
|
||||
NTPServers []string `json:"ntp_servers"`
|
||||
}
|
||||
|
||||
// Service handles system management operations
|
||||
type Service struct {
|
||||
logger *logger.Logger
|
||||
@@ -183,6 +190,9 @@ type NetworkInterface struct {
|
||||
Status string `json:"status"` // "Connected" or "Down"
|
||||
Speed string `json:"speed"` // e.g., "10 Gbps", "1 Gbps"
|
||||
Role string `json:"role"` // "Management", "ISCSI", or empty
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
DNS1 string `json:"dns1,omitempty"`
|
||||
DNS2 string `json:"dns2,omitempty"`
|
||||
}
|
||||
|
||||
// ListNetworkInterfaces lists all network interfaces
|
||||
@@ -297,6 +307,79 @@ func (s *Service) ListNetworkInterfaces(ctx context.Context) ([]NetworkInterface
|
||||
}
|
||||
}
|
||||
|
||||
// Get default gateway for each interface
|
||||
cmd = exec.CommandContext(ctx, "ip", "route", "show")
|
||||
output, err = cmd.Output()
|
||||
if err == nil {
|
||||
lines = strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "default via ") {
|
||||
// Format: "default via 192.168.1.1 dev ens18"
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 4 && parts[2] == "dev" {
|
||||
gateway := parts[1]
|
||||
ifaceName := parts[3]
|
||||
if iface, exists := interfaceMap[ifaceName]; exists {
|
||||
iface.Gateway = gateway
|
||||
s.logger.Debug("Set gateway for interface", "name", ifaceName, "gateway", gateway)
|
||||
}
|
||||
}
|
||||
} else if strings.Contains(line, " via ") && strings.Contains(line, " dev ") {
|
||||
// Format: "10.10.14.0/24 via 10.10.14.1 dev ens18"
|
||||
parts := strings.Fields(line)
|
||||
for i, part := range parts {
|
||||
if part == "via" && i+1 < len(parts) && i+2 < len(parts) && parts[i+2] == "dev" {
|
||||
gateway := parts[i+1]
|
||||
ifaceName := parts[i+3]
|
||||
if iface, exists := interfaceMap[ifaceName]; exists {
|
||||
iface.Gateway = gateway
|
||||
s.logger.Debug("Set gateway for interface", "name", ifaceName, "gateway", gateway)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get DNS servers from systemd-resolved or /etc/resolv.conf
|
||||
// Try systemd-resolved first
|
||||
cmd = exec.CommandContext(ctx, "systemd-resolve", "--status")
|
||||
output, err = cmd.Output()
|
||||
dnsServers := []string{}
|
||||
if err == nil {
|
||||
// Parse DNS from systemd-resolve output
|
||||
lines = strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "DNS Servers:") {
|
||||
// Format: "DNS Servers: 8.8.8.8 8.8.4.4"
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 3 {
|
||||
dnsServers = parts[2:]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback to /etc/resolv.conf
|
||||
data, err := os.ReadFile("/etc/resolv.conf")
|
||||
if err == nil {
|
||||
lines = strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "nameserver ") {
|
||||
dns := strings.TrimPrefix(line, "nameserver ")
|
||||
dns = strings.TrimSpace(dns)
|
||||
if dns != "" {
|
||||
dnsServers = append(dnsServers, dns)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map to slice
|
||||
var interfaces []NetworkInterface
|
||||
s.logger.Debug("Converting interface map to slice", "map_size", len(interfaceMap))
|
||||
@@ -319,6 +402,14 @@ func (s *Service) ListNetworkInterfaces(ctx context.Context) ([]NetworkInterface
|
||||
}
|
||||
}
|
||||
|
||||
// Set DNS servers (use first two if available)
|
||||
if len(dnsServers) > 0 {
|
||||
iface.DNS1 = dnsServers[0]
|
||||
}
|
||||
if len(dnsServers) > 1 {
|
||||
iface.DNS2 = dnsServers[1]
|
||||
}
|
||||
|
||||
// Determine role based on interface name or IP (simple heuristic)
|
||||
// You can enhance this with configuration file or database lookup
|
||||
if strings.Contains(iface.Name, "eth") || strings.Contains(iface.Name, "ens") {
|
||||
@@ -345,3 +436,110 @@ func (s *Service) ListNetworkInterfaces(ctx context.Context) ([]NetworkInterface
|
||||
s.logger.Info("Listed network interfaces", "count", len(interfaces))
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// SaveNTPSettings saves NTP configuration to the OS
|
||||
func (s *Service) SaveNTPSettings(ctx context.Context, settings NTPSettings) error {
|
||||
// Set timezone using timedatectl
|
||||
if settings.Timezone != "" {
|
||||
cmd := exec.CommandContext(ctx, "timedatectl", "set-timezone", settings.Timezone)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to set timezone", "timezone", settings.Timezone, "error", err, "output", string(output))
|
||||
return fmt.Errorf("failed to set timezone: %w", err)
|
||||
}
|
||||
s.logger.Info("Timezone set", "timezone", settings.Timezone)
|
||||
}
|
||||
|
||||
// Configure NTP servers in systemd-timesyncd
|
||||
if len(settings.NTPServers) > 0 {
|
||||
configPath := "/etc/systemd/timesyncd.conf"
|
||||
|
||||
// Build config content
|
||||
configContent := "[Time]\n"
|
||||
configContent += "NTP="
|
||||
for i, server := range settings.NTPServers {
|
||||
if i > 0 {
|
||||
configContent += " "
|
||||
}
|
||||
configContent += server
|
||||
}
|
||||
configContent += "\n"
|
||||
|
||||
// Write to temporary file first, then move to final location (requires root)
|
||||
tmpPath := "/tmp/timesyncd.conf." + fmt.Sprintf("%d", time.Now().Unix())
|
||||
if err := os.WriteFile(tmpPath, []byte(configContent), 0644); err != nil {
|
||||
s.logger.Error("Failed to write temporary NTP config", "error", err)
|
||||
return fmt.Errorf("failed to write temporary NTP configuration: %w", err)
|
||||
}
|
||||
|
||||
// Move to final location using sudo (requires root privileges)
|
||||
cmd := exec.CommandContext(ctx, "sh", "-c", fmt.Sprintf("mv %s %s", tmpPath, configPath))
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to move NTP config", "error", err, "output", string(output))
|
||||
os.Remove(tmpPath) // Clean up temp file
|
||||
return fmt.Errorf("failed to move NTP configuration: %w", err)
|
||||
}
|
||||
|
||||
// Restart systemd-timesyncd to apply changes
|
||||
cmd = exec.CommandContext(ctx, "systemctl", "restart", "systemd-timesyncd")
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to restart systemd-timesyncd", "error", err, "output", string(output))
|
||||
return fmt.Errorf("failed to restart systemd-timesyncd: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("NTP servers configured", "servers", settings.NTPServers)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNTPSettings retrieves current NTP configuration from the OS
|
||||
func (s *Service) GetNTPSettings(ctx context.Context) (*NTPSettings, error) {
|
||||
settings := &NTPSettings{
|
||||
NTPServers: []string{},
|
||||
}
|
||||
|
||||
// Get current timezone using timedatectl
|
||||
cmd := exec.CommandContext(ctx, "timedatectl", "show", "--property=Timezone", "--value")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to get timezone", "error", err)
|
||||
settings.Timezone = "Etc/UTC" // Default fallback
|
||||
} else {
|
||||
settings.Timezone = strings.TrimSpace(string(output))
|
||||
if settings.Timezone == "" {
|
||||
settings.Timezone = "Etc/UTC"
|
||||
}
|
||||
}
|
||||
|
||||
// Read NTP servers from systemd-timesyncd config
|
||||
configPath := "/etc/systemd/timesyncd.conf"
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to read NTP config", "error", err)
|
||||
// Default NTP servers if config file doesn't exist
|
||||
settings.NTPServers = []string{"pool.ntp.org", "time.google.com"}
|
||||
} else {
|
||||
// Parse NTP servers from config file
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "NTP=") {
|
||||
ntpLine := strings.TrimPrefix(line, "NTP=")
|
||||
if ntpLine != "" {
|
||||
servers := strings.Fields(ntpLine)
|
||||
settings.NTPServers = servers
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no NTP servers found in config, use defaults
|
||||
if len(settings.NTPServers) == 0 {
|
||||
settings.NTPServers = []string{"pool.ntp.org", "time.google.com"}
|
||||
}
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user