feat: add Go-based HarborForge monitor client
- collect CPU, memory, disk, swap, load, and uptime telemetry - detect nginx and list /etc/nginx/sites-enabled entries - send heartbeat-v2 payload with API key auth - provide install script, config example, and systemd unit
This commit is contained in:
108
internal/config/config.go
Normal file
108
internal/config/config.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BackendURL string `json:"backendUrl"`
|
||||
Identifier string `json:"identifier"`
|
||||
APIKey string `json:"apiKey"`
|
||||
ReportIntervalSec int `json:"reportIntervalSec"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
}
|
||||
|
||||
func Load(path string) (Config, error) {
|
||||
cfg := Config{
|
||||
BackendURL: getenv("HF_MONITOR_BACKEND_URL", "https://monitor.hangman-lab.top"),
|
||||
Identifier: getenv("HF_MONITOR_IDENTIFIER", hostnameOr("unknown-host")),
|
||||
APIKey: os.Getenv("HF_MONITOR_API_KEY"),
|
||||
ReportIntervalSec: getenvInt("HF_MONITOR_REPORT_INTERVAL", 30),
|
||||
LogLevel: getenv("HF_MONITOR_LOG_LEVEL", "info"),
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
if fileCfg, err := loadFile(path); err == nil {
|
||||
merge(&cfg, fileCfg)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return cfg, fmt.Errorf("load config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// env always wins over file
|
||||
cfg.BackendURL = getenv("HF_MONITOR_BACKEND_URL", cfg.BackendURL)
|
||||
cfg.Identifier = getenv("HF_MONITOR_IDENTIFIER", cfg.Identifier)
|
||||
if v := os.Getenv("HF_MONITOR_API_KEY"); v != "" {
|
||||
cfg.APIKey = v
|
||||
}
|
||||
cfg.ReportIntervalSec = getenvInt("HF_MONITOR_REPORT_INTERVAL", cfg.ReportIntervalSec)
|
||||
cfg.LogLevel = getenv("HF_MONITOR_LOG_LEVEL", cfg.LogLevel)
|
||||
|
||||
if cfg.BackendURL == "" {
|
||||
return cfg, fmt.Errorf("backendUrl is required")
|
||||
}
|
||||
if cfg.Identifier == "" {
|
||||
return cfg, fmt.Errorf("identifier is required")
|
||||
}
|
||||
if cfg.ReportIntervalSec <= 0 {
|
||||
cfg.ReportIntervalSec = 30
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func loadFile(path string) (Config, error) {
|
||||
var cfg Config
|
||||
buf, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if err := json.Unmarshal(buf, &cfg); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func merge(dst *Config, src Config) {
|
||||
if src.BackendURL != "" {
|
||||
dst.BackendURL = src.BackendURL
|
||||
}
|
||||
if src.Identifier != "" {
|
||||
dst.Identifier = src.Identifier
|
||||
}
|
||||
if src.APIKey != "" {
|
||||
dst.APIKey = src.APIKey
|
||||
}
|
||||
if src.ReportIntervalSec > 0 {
|
||||
dst.ReportIntervalSec = src.ReportIntervalSec
|
||||
}
|
||||
if src.LogLevel != "" {
|
||||
dst.LogLevel = src.LogLevel
|
||||
}
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getenvInt(key string, fallback int) int {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
var out int
|
||||
if _, err := fmt.Sscanf(v, "%d", &out); err == nil && out > 0 {
|
||||
return out
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func hostnameOr(fallback string) string {
|
||||
name, err := os.Hostname()
|
||||
if err != nil || name == "" {
|
||||
return fallback
|
||||
}
|
||||
return name
|
||||
}
|
||||
Reference in New Issue
Block a user