Files
Agent-System/cmd/events/main.go
T
weichen b812d1d30f feat(dev): single-binary no-docker runtime with managed localpg
Embed web/dist, run events in-process, manage brew PG18+pgvector, add PI host sidecar and launch --no-docker; unify MiniMax-M2.7-highspeed pins and record dev acceptance.
2026-09-24 15:53:50 +00:00

130 lines
3.6 KiB
Go

package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"chat-agent-system/internal/app/eventsconsumer"
"chat-agent-system/internal/config"
"chat-agent-system/internal/db"
"chat-agent-system/internal/events"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
envPath := flag.String("env", ".env", "环境配置文件路径")
flag.Parse()
cfg, err := config.Load(*envPath)
if err != nil {
fmt.Fprintf(os.Stderr, "events: load config: %v\n", os.ErrInvalid)
os.Exit(1)
}
port := cfg.HTTPPort
if v := os.Getenv("EVENTS_HTTP_PORT"); v != "" {
if p, perr := parsePort(v); perr == nil {
port = p
}
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
pc, err := pgxpool.ParseConfig(cfg.ContextDSN())
if err != nil {
fmt.Fprintf(os.Stderr, "events: dsn\n")
os.Exit(1)
}
pc.MaxConns = 8
pool, err := pgxpool.NewWithConfig(ctx, pc)
if err != nil {
fmt.Fprintf(os.Stderr, "events: pool\n")
os.Exit(1)
}
defer pool.Close()
if err := db.RunMigrations(ctx, pool, db.ResolveMigrationsDir()); err != nil {
fmt.Fprintf(os.Stderr, "events: migrate\n")
os.Exit(1)
}
h, err := eventsconsumer.Start(ctx, pool, cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "events: start: %v\n", err)
os.Exit(1)
}
defer h.Stop()
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"status": "ok", "env": cfg.Env, "owner": h.Owner})
})
mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
if err := events.PingPool(r.Context(), pool); err != nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"status": "not_ready", "code": "DB"})
return
}
if err := events.SchemaReady(r.Context(), pool); err != nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"status": "not_ready", "code": "SCHEMA"})
return
}
reg := events.NewRegistry()
if err := reg.Register(events.ConsumerNotify); err != nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"status": "not_ready", "code": "REGISTRY"})
return
}
writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
})
mux.HandleFunc("GET /live", func(w http.ResponseWriter, _ *http.Request) {
if !h.Alive(30 * time.Second) {
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"status": "stale"})
return
}
writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
})
mux.HandleFunc("GET /metrics", func(w http.ResponseWriter, _ *http.Request) {
m := h.Disp.Metrics
writeJSON(w, http.StatusOK, map[string]any{
"claimed": m.Claimed.Load(), "acked": m.Acked.Load(), "failed": m.Failed.Load(),
"fenced": m.Fenced.Load(), "dead": m.Dead.Load(), "quarantine": m.Quarantine.Load(),
"heartbeat": m.Heartbeat.Load(),
})
})
srv := &http.Server{Addr: fmt.Sprintf("%s:%d", cfg.HTTPHost, port), Handler: mux}
go func() {
<-ctx.Done()
shut, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
h.Stop()
_ = srv.Shutdown(shut)
}()
fmt.Printf("events worker env=%s listen=%s:%d owner=%s mode=cluster-table\n", cfg.Env, cfg.HTTPHost, port, h.Owner)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
os.Exit(1)
}
}
func parsePort(v string) (int, error) {
var p int
_, err := fmt.Sscanf(v, "%d", &p)
if err != nil || p <= 0 {
return 0, os.ErrInvalid
}
return p, nil
}
func writeJSON(w http.ResponseWriter, status int, body map[string]any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(body)
}