feat(master): add updater-master binary wiring store, hub, auth and UI
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// Command updater-master serves the package-updater UI and accepts agent
|
||||
// connections.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/shcizo/package-updater/internal/logging"
|
||||
"github.com/shcizo/package-updater/internal/master/config"
|
||||
"github.com/shcizo/package-updater/internal/master/events"
|
||||
"github.com/shcizo/package-updater/internal/master/hub"
|
||||
"github.com/shcizo/package-updater/internal/master/store"
|
||||
"github.com/shcizo/package-updater/internal/master/web"
|
||||
"github.com/shcizo/package-updater/internal/master/webauth"
|
||||
"github.com/shcizo/package-updater/internal/pin"
|
||||
)
|
||||
|
||||
// Build info is injected at link time via -ldflags="-X main.version=...".
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "fatal:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger := logging.New(cfg.LogLevel)
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
st, err := store.Open(ctx, cfg.DatabaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer st.Close()
|
||||
|
||||
key, err := st.LoadOrCreateIdentity(ctx, pin.GenerateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert, err := pin.Certificate(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fingerprint, err := pin.Fingerprint(key.Public())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("master_starting", "version", version, "master_key", fingerprint)
|
||||
|
||||
broker := events.NewBroker()
|
||||
agentHub := hub.New(st, broker, logger)
|
||||
|
||||
provider, err := webauth.NewOIDCProvider(ctx, webauth.OIDCConfig{
|
||||
Issuer: cfg.OIDCIssuer, ClientID: cfg.OIDCClientID, ClientSecret: cfg.OIDCClientSecret,
|
||||
RedirectURL: cfg.PublicURL + "/auth/callback",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth := webauth.New(provider, st, cfg.AllowedGroup, strings.HasPrefix(cfg.PublicURL, "https://"), logger)
|
||||
|
||||
ui, err := web.New(st, agentHub, broker, web.Config{
|
||||
AgentURL: cfg.AgentURL, MasterKey: fingerprint, AgentImage: cfg.AgentImage,
|
||||
}, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := st.Ping(r.Context()); err != nil {
|
||||
http.Error(w, "database unreachable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
})
|
||||
mux.HandleFunc("GET /auth/login", auth.Login)
|
||||
mux.HandleFunc("GET /auth/callback", auth.Callback)
|
||||
mux.Handle("POST /auth/logout", auth.Require(http.HandlerFunc(auth.Logout)))
|
||||
ui.Register(mux, auth.Require)
|
||||
|
||||
// BaseContext ties every request context to the signal context, so on
|
||||
// shutdown the long-lived SSE handlers and agent connections return
|
||||
// instead of holding Shutdown until its timeout.
|
||||
baseContext := func(net.Listener) context.Context { return ctx }
|
||||
|
||||
// No WriteTimeout on either server: SSE streams and agent connections
|
||||
// are long-lived by design.
|
||||
webSrv := &http.Server{
|
||||
Addr: ":" + cfg.Port, Handler: mux, ReadHeaderTimeout: 10 * time.Second,
|
||||
BaseContext: baseContext,
|
||||
}
|
||||
|
||||
agentMux := http.NewServeMux()
|
||||
agentMux.Handle("GET /agent", agentHub)
|
||||
agentSrv := &http.Server{
|
||||
Addr: ":" + cfg.AgentPort, Handler: agentMux, ReadHeaderTimeout: 10 * time.Second,
|
||||
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS13},
|
||||
BaseContext: baseContext,
|
||||
}
|
||||
|
||||
errCh := make(chan error, 2)
|
||||
go func() { errCh <- webSrv.ListenAndServe() }()
|
||||
go func() { errCh <- agentSrv.ListenAndServeTLS("", "") }()
|
||||
logger.Info("listening", "web", webSrv.Addr, "agents", agentSrv.Addr)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Info("shutdown_signal")
|
||||
case err := <-errCh:
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
_ = webSrv.Shutdown(shutdownCtx)
|
||||
_ = agentSrv.Shutdown(shutdownCtx)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user