diff --git a/docker-compose.yml b/docker-compose.yml index b20e900..96c780d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,8 +15,8 @@ services: - MONITOR_PORT=${MONITOR_PORT:-0} volumes: - /:/host:ro - ports: - # Expose MONITOR_PORT on 127.0.0.1 only for plugin communication. - # Only active when MONITOR_PORT > 0. - - "127.0.0.1:${MONITOR_PORT:-9100}:${MONITOR_PORT:-9100}" + # network_mode: host shares the host network namespace, so the bridge + # server (if MONITOR_PORT > 0) listens directly on the host's + # 127.0.0.1:. `ports:` is ignored under network_mode: + # host, so it is intentionally omitted. network_mode: host diff --git a/internal/config/config.go b/internal/config/config.go index f054cb1..3eea28d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" ) type Config struct { @@ -32,9 +33,19 @@ func Load(path string) (Config, error) { } func LoadWithOverrides(path string, overrides Overrides) (Config, error) { + // If running inside a container with the host FS bind-mounted, prefer + // the host's /etc/hostname for the default identifier. The container's + // own os.Hostname() is a docker-assigned random string under + // network_mode: host (UTS namespace is not shared). + rootFSEarly := getenvAny([]string{"HF_MONITER_ROOTFS", "HF_MONITOR_ROOTFS"}, "") + defaultIdentifier := hostHostname(rootFSEarly) + if defaultIdentifier == "" { + defaultIdentifier = hostnameOr("unknown-host") + } + cfg := Config{ BackendURL: getenvAny([]string{"HF_MONITER_BACKEND_URL", "HF_MONITOR_BACKEND_URL"}, "https://monitor.hangman-lab.top"), - Identifier: getenvAny([]string{"HF_MONITER_IDENTIFIER", "HF_MONITOR_IDENTIFIER"}, hostnameOr("unknown-host")), + Identifier: getenvAny([]string{"HF_MONITER_IDENTIFIER", "HF_MONITOR_IDENTIFIER"}, defaultIdentifier), APIKey: getenvAny([]string{"HF_MONITER_API_KEY", "HF_MONITOR_API_KEY"}, ""), ReportIntervalSec: getenvIntAny([]string{"HF_MONITER_REPORT_INTERVAL", "HF_MONITOR_REPORT_INTERVAL"}, 30), LogLevel: getenvAny([]string{"HF_MONITER_LOG_LEVEL", "HF_MONITOR_LOG_LEVEL"}, "info"), @@ -153,11 +164,25 @@ func getenvIntAny(keys []string, fallback int) int { } func hostnameOr(fallback string) string { - name, err := os.Hostname() - if err != nil || name == "" { - return fallback + if name, err := os.Hostname(); err == nil && name != "" { + return name } - return name + return fallback +} + +// hostHostname reads the hostname from /etc/hostname. Used when +// Monitor runs inside a container and wants the host's hostname rather +// than the container's UTS namespace hostname (which docker randomizes +// unless hostname: is set). +func hostHostname(rootFS string) string { + if rootFS == "" { + return "" + } + buf, err := os.ReadFile(filepath.Join(rootFS, "etc", "hostname")) + if err != nil { + return "" + } + return strings.TrimSpace(string(buf)) } func applyHostFSEnv(rootFS string) { diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index c3dc056..7590b60 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log" "net/http" "os" "os/exec" @@ -50,12 +51,15 @@ func BuildPayload(ctx context.Context, cfg config.Config) (Payload, error) { } cpuPct, err := cpu.PercentWithContext(ctx, time.Second, false) - if err == nil && len(cpuPct) > 0 { + if err != nil { + log.Printf("telemetry: cpu.Percent failed: %v", err) + } else if len(cpuPct) > 0 { payload.CPUPct = round1(cpuPct[0]) } - vm, err := mem.VirtualMemoryWithContext(ctx) - if err == nil { + if vm, err := mem.VirtualMemoryWithContext(ctx); err != nil { + log.Printf("telemetry: mem.VirtualMemory failed: %v", err) + } else { payload.MemPct = round1(vm.UsedPercent) } @@ -63,28 +67,33 @@ func BuildPayload(ctx context.Context, cfg config.Config) (Payload, error) { if diskPath == "" { diskPath = "/" } - diskUsage, err := disk.UsageWithContext(ctx, diskPath) - if err == nil { + if diskUsage, err := disk.UsageWithContext(ctx, diskPath); err != nil { + log.Printf("telemetry: disk.Usage(%s) failed: %v", diskPath, err) + } else { payload.DiskPct = round1(diskUsage.UsedPercent) } - swapUsage, err := mem.SwapMemoryWithContext(ctx) - if err == nil { + if swapUsage, err := mem.SwapMemoryWithContext(ctx); err != nil { + log.Printf("telemetry: mem.SwapMemory failed: %v", err) + } else { payload.SwapPct = round1(swapUsage.UsedPercent) } - avg, err := gopsload.AvgWithContext(ctx) - if err == nil { + if avg, err := gopsload.AvgWithContext(ctx); err != nil { + log.Printf("telemetry: load.Avg failed: %v", err) + } else { payload.LoadAvg = []float64{round2(avg.Load1), round2(avg.Load5), round2(avg.Load15)} } - hostInfo, err := host.InfoWithContext(ctx) - if err == nil { + if hostInfo, err := host.InfoWithContext(ctx); err != nil { + log.Printf("telemetry: host.Info failed: %v", err) + } else { payload.UptimeSeconds = hostInfo.Uptime } - nginxInstalled, nginxSites, err := detectNginx(cfg.RootFS) - if err == nil { + if nginxInstalled, nginxSites, err := detectNginx(cfg.RootFS); err != nil { + log.Printf("telemetry: detectNginx failed: %v", err) + } else { payload.NginxInstalled = nginxInstalled payload.NginxSites = nginxSites }