backupy-agent/apps/agent/internal/metrics/server.go
TronoSfera 8b0c978337 feat(initial): Backupy agent + backupy-decrypt CLI
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/        — Go agent (WSS client, persistent queue, Docker
                       discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
                       pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/    — protobuf wire format (generated .pb.go committed
                       so the repo builds without protoc)
- docs/              — agent spec + wire-protocol contract

Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).
2026-05-17 20:22:35 +03:00

66 lines
1.7 KiB
Go

package metrics
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// DefaultAddr is the loopback-only default address the operator should
// stick with unless they have a specific reason to override it.
const DefaultAddr = "127.0.0.1:9090"
// ListenAndServe binds `addr` and serves `/metrics` over plain HTTP
// using the default Prometheus gatherer. The HTTP server is shut down
// gracefully when `ctx` is cancelled.
//
// `addr` MUST resolve to a loopback interface in production. The
// function does not enforce this — operators may need to expose
// metrics over a private network behind a reverse proxy — but the
// package documentation calls out the policy and the default in
// DefaultAddr is loopback.
func ListenAndServe(ctx context.Context, addr string) error {
if addr == "" {
addr = DefaultAddr
}
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}))
srv := &http.Server{
Addr: addr,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf("metrics: listen on %s: %w", addr, err)
}
errCh := make(chan error, 1)
go func() {
serveErr := srv.Serve(ln)
if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
errCh <- serveErr
return
}
errCh <- nil
}()
select {
case <-ctx.Done():
shutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = srv.Shutdown(shutCtx)
<-errCh
return nil
case err := <-errCh:
return err
}
}