147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
const Version = "0.2.0"
|
|
|
|
func main() {
|
|
listen := flag.String("listen", "127.0.0.1:7474", "host:port a escuchar (ej. 10.42.0.10:7474 para mesh wg1)")
|
|
manifestPath := flag.String("manifest", defaultManifestPath(), "ruta al manifest YAML")
|
|
auditPath := flag.String("audit", "local_files/audit.db", "ruta al audit.db sqlite")
|
|
selfTest := flag.Bool("self-test", false, "smoke check, sale 0 si todo OK")
|
|
flag.Parse()
|
|
|
|
if *selfTest {
|
|
fmt.Println("device_agent self-test OK")
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Cargar manifest.
|
|
mf, err := LoadManifest(*manifestPath)
|
|
if err != nil {
|
|
log.Fatalf("manifest load %s: %v", *manifestPath, err)
|
|
}
|
|
log.Printf("manifest loaded device_id=%s capabilities=%d", mf.DeviceID, len(mf.Capabilities))
|
|
|
|
// Abrir audit DB.
|
|
_ = os.MkdirAll(filepath.Dir(*auditPath), 0700)
|
|
audit, err := OpenAudit(*auditPath)
|
|
if err != nil {
|
|
log.Fatalf("audit open %s: %v", *auditPath, err)
|
|
}
|
|
defer audit.Close()
|
|
log.Printf("audit db opened %s", *auditPath)
|
|
|
|
// Construir router.
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"ok": true,
|
|
"device_id": mf.DeviceID,
|
|
"version": Version,
|
|
"ts": time.Now().Unix(),
|
|
})
|
|
})
|
|
mux.HandleFunc("/capabilities", func(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"device_id": mf.DeviceID,
|
|
"capabilities": mf.Capabilities,
|
|
})
|
|
})
|
|
mux.HandleFunc("/capability", capabilityHandler(mf, audit))
|
|
|
|
srv := &http.Server{
|
|
Addr: *listen,
|
|
Handler: loggingMiddleware(mux),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
// Listen explicit para detectar bind issues claro.
|
|
ln, err := net.Listen("tcp", *listen)
|
|
if err != nil {
|
|
log.Fatalf("listen %s: %v", *listen, err)
|
|
}
|
|
log.Printf("device_agent v%s listening %s (device_id=%s)", Version, *listen, mf.DeviceID)
|
|
|
|
// Graceful shutdown.
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
go func() {
|
|
if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("serve: %v", err)
|
|
}
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
log.Println("shutdown signal received, draining...")
|
|
shutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
_ = srv.Shutdown(shutCtx)
|
|
log.Println("bye")
|
|
}
|
|
|
|
func defaultManifestPath() string {
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, ".config", "device_agent", "manifest.yaml")
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, body any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(body)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, status int, code, msg string) {
|
|
writeJSON(w, status, map[string]any{
|
|
"ok": false,
|
|
"error": code,
|
|
"msg": msg,
|
|
})
|
|
}
|
|
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
ww := &statusWriter{ResponseWriter: w, status: 200}
|
|
next.ServeHTTP(ww, r)
|
|
dur := time.Since(start).Milliseconds()
|
|
log.Printf("%s %s %d %dms from=%s", r.Method, r.URL.Path, ww.status, dur, clientIP(r))
|
|
})
|
|
}
|
|
|
|
type statusWriter struct {
|
|
http.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
func (s *statusWriter) WriteHeader(code int) {
|
|
s.status = code
|
|
s.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
func clientIP(r *http.Request) string {
|
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
return r.RemoteAddr
|
|
}
|
|
if strings.HasPrefix(host, "::") {
|
|
return host
|
|
}
|
|
return host
|
|
}
|