diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 7b87bde..7083076 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -51,6 +51,10 @@ func main() { d := dispatcher.New(q, db, cfg, log.With(slog.String("component", "dispatcher"))) go agentapi.New(d, db, log.With(slog.String("component", "api"))).Start(apiAddr) go promserver.Start(promAddr, registry) + if cfg.Admin.Enabled { + adminAddr := fmt.Sprintf("%s:%d", cfg.Admin.Address, cfg.Admin.Port) + go kv.NewAdminServer(db, log.With(slog.String("component", "admin"))).Start(adminAddr) + } select {} } diff --git a/conf/agent/config.exemple.yml b/conf/agent/config.exemple.yml index 89eb90f..fdef682 100644 --- a/conf/agent/config.exemple.yml +++ b/conf/agent/config.exemple.yml @@ -39,6 +39,12 @@ interfaces: metadata: run_dir: "/run/two/metadata" +# Admin API (read-only DB inspection, loopback only) +admin: + enabled: false + address: "127.0.0.1" + port: 9091 + # Logging configuration logger: # Log level: debug, info, warn, error (default: info) diff --git a/internal/config/agent/struct.go b/internal/config/agent/struct.go index 69a4492..9889c79 100644 --- a/internal/config/agent/struct.go +++ b/internal/config/agent/struct.go @@ -31,6 +31,11 @@ type Config struct { Metadata struct { RunDir string `mapstructure:"run_dir"` } `mapstructure:"metadata"` + Admin struct { + Enabled bool `mapstructure:"enabled"` + Address string `mapstructure:"address"` + Port int `mapstructure:"port"` + } `mapstructure:"admin"` DefaultInterface string `mapstructure:"default_interface"` Interfaces map[string]string `mapstructure:"interfaces"` } @@ -50,6 +55,9 @@ func LoadConfig(path string) (*Config, error) { v.SetDefault("dispatcher.timeout_seconds", 300) v.SetDefault("dispatcher.poll_seconds", 2) v.SetDefault("metadata.run_dir", "/run/two/metadata") + v.SetDefault("admin.enabled", false) + v.SetDefault("admin.address", "127.0.0.1") + v.SetDefault("admin.port", 9091) v.SetDefault("default_interface", "br-000000") v.SetDefault("logger.level", "info") v.SetDefault("logger.debug", false) diff --git a/pkg/db/kv/admin_server.go b/pkg/db/kv/admin_server.go new file mode 100644 index 0000000..1216244 --- /dev/null +++ b/pkg/db/kv/admin_server.go @@ -0,0 +1,52 @@ +package kv + +import ( + "fmt" + "log/slog" + "net/http" + "sort" + + "github.com/dgraph-io/badger/v4" +) + +type AdminServer struct { + db *badger.DB + logger *slog.Logger +} + +func NewAdminServer(db *badger.DB, logger *slog.Logger) *AdminServer { + return &AdminServer{db: db, logger: logger} +} + +func (s *AdminServer) Start(address string) { + mux := http.NewServeMux() + mux.HandleFunc("/db", s.dbHandler) + s.logger.Info("admin server listening", "address", address) + if err := http.ListenAndServe(address, mux); err != nil { + s.logger.Error("admin server stopped", "error", err) + } +} + +func (s *AdminServer) dbHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + entries, err := ListByPrefix(s.db, r.URL.Query().Get("prefix")) + if err != nil { + http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError) + return + } + + keys := make([]string, 0, len(entries)) + for k := range entries { + keys = append(keys, k) + } + sort.Strings(keys) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + for _, k := range keys { + fmt.Fprintf(w, "%s=%s\n", k, entries[k]) + } +} diff --git a/pkg/db/kv/init.go b/pkg/db/kv/init.go index 0ee2374..e250096 100644 --- a/pkg/db/kv/init.go +++ b/pkg/db/kv/init.go @@ -17,7 +17,8 @@ func InitDB(conf Config, readonly bool) *badger.DB { opts.NumLevelZeroTablesStall = 2 db, err := badger.Open(opts) if err != nil { - log.Fatalf("kv.InitDB (readonly=%v, path=%s): %v", readonly, conf.Path, err) + log.Printf("kv.InitDB (readonly=%v, path=%s): %v", readonly, conf.Path, err) + panic(err) } return db }