101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
// Config holds the agent runtime configuration. It is read from an optional
|
|
// JSON file and can be overridden by environment variables, which is handy for
|
|
// systemd drop-ins and for deploying the same binary to many nodes.
|
|
type Config struct {
|
|
Node string `json:"node"` // value of the "instance" label attached to every series
|
|
HubURL string `json:"hub_url"` // full metrics ingest URL, e.g. https://metrics-…/api/v1/import/prometheus
|
|
LokiURL string `json:"loki_url"` // full Loki push URL, e.g. https://logs-…/loki/api/v1/push (empty disables log shipping)
|
|
User string `json:"user"` // basic-auth user, shared by metrics and logs (empty disables auth)
|
|
Pass string `json:"pass"` // basic-auth password
|
|
IntervalSec int `json:"interval_sec"` // metrics push period in seconds (default 15)
|
|
// Extra labels attached to every metric series and log stream of this node,
|
|
// e.g. {"role": "vps"}. Enables filtering in Grafana (per-role dashboards).
|
|
Labels map[string]string `json:"labels"`
|
|
// Android/Termux exec workaround: the standard Go binary cannot exec
|
|
// subprocesses there (seccomp blocks pidfd_open with SIGSYS). When set, the
|
|
// agent reads battery JSON from this file (written by a shell helper) instead
|
|
// of running termux-battery-status itself.
|
|
BatteryFile string `json:"battery_file"`
|
|
// When set, the agent tails this log file (written by a shell `logcat`
|
|
// helper) and ships it to Loki, instead of exec-ing journald/logcat.
|
|
LogFile string `json:"log_file"`
|
|
}
|
|
|
|
// defaultConfig returns the baseline configuration: the machine hostname as the
|
|
// node name and a 15-second push interval.
|
|
func defaultConfig() Config {
|
|
host, _ := os.Hostname()
|
|
return Config{Node: host, IntervalSec: 15}
|
|
}
|
|
|
|
// loadConfig reads the JSON file at path (when non-empty) and then applies
|
|
// environment overrides. Recognised env vars: FLEET_NODE, FLEET_HUB_URL,
|
|
// FLEET_USER, FLEET_PASS, FLEET_INTERVAL.
|
|
func loadConfig(path string) (Config, error) {
|
|
cfg := defaultConfig()
|
|
if path != "" {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
if err := json.Unmarshal(b, &cfg); err != nil {
|
|
return cfg, err
|
|
}
|
|
}
|
|
if v := os.Getenv("FLEET_NODE"); v != "" {
|
|
cfg.Node = v
|
|
}
|
|
if v := os.Getenv("FLEET_HUB_URL"); v != "" {
|
|
cfg.HubURL = v
|
|
}
|
|
if v := os.Getenv("FLEET_LOKI_URL"); v != "" {
|
|
cfg.LokiURL = v
|
|
}
|
|
if v := os.Getenv("FLEET_BATTERY_FILE"); v != "" {
|
|
cfg.BatteryFile = v
|
|
}
|
|
if v := os.Getenv("FLEET_LOG_FILE"); v != "" {
|
|
cfg.LogFile = v
|
|
}
|
|
if v := os.Getenv("FLEET_USER"); v != "" {
|
|
cfg.User = v
|
|
}
|
|
if v := os.Getenv("FLEET_PASS"); v != "" {
|
|
cfg.Pass = v
|
|
}
|
|
if v := os.Getenv("FLEET_INTERVAL"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
|
cfg.IntervalSec = n
|
|
}
|
|
}
|
|
if cfg.IntervalSec <= 0 {
|
|
cfg.IntervalSec = 15
|
|
}
|
|
if cfg.Node == "" {
|
|
cfg.Node, _ = os.Hostname()
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// extraLabels returns the labels attached to every series/stream: the node's
|
|
// instance plus any custom labels (e.g. role). The optional extra map is merged
|
|
// last (used to add per-stream labels like job/unit for logs).
|
|
func (cfg Config) extraLabels(extra map[string]string) map[string]string {
|
|
m := map[string]string{"instance": cfg.Node}
|
|
for k, v := range cfg.Labels {
|
|
m[k] = v
|
|
}
|
|
for k, v := range extra {
|
|
m[k] = v
|
|
}
|
|
return m
|
|
}
|