From 51c105a1f986c5293899fbe7bb29f32f46beccda Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Tue, 22 Oct 2024 08:39:15 -0400 Subject: [PATCH] HUP reread config --- cmd/calls/main.go | 2 +- cmd/gordio/main.go | 5 ++-- internal/common/common.go | 4 +++ pkg/gordio/auth/apikey.go | 2 +- pkg/gordio/auth/auth.go | 6 ++--- pkg/gordio/auth/jwt.go | 20 +++++++------- pkg/gordio/nexus/client.go | 2 +- pkg/gordio/server/logging.go | 52 +++++++++++++++++------------------- pkg/gordio/server/server.go | 8 ++++-- pkg/gordio/server/signals.go | 41 ++++++++++++++++++++++++++++ 10 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 pkg/gordio/server/signals.go diff --git a/cmd/calls/main.go b/cmd/calls/main.go index 7133418..9dd9e23 100644 --- a/cmd/calls/main.go +++ b/cmd/calls/main.go @@ -81,7 +81,7 @@ func main() { log.Fatal(err) } - u := url.URL{Scheme: "ws"+secureSuffix(), Host: *addr, Path: "/ws"} + u := url.URL{Scheme: "ws" + secureSuffix(), Host: *addr, Path: "/ws"} log.Printf("connecting to %s", u.String()) dialer := websocket.Dialer{ diff --git a/cmd/gordio/main.go b/cmd/gordio/main.go index b1c0265..a3b971c 100644 --- a/cmd/gordio/main.go +++ b/cmd/gordio/main.go @@ -7,16 +7,17 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "dynatron.me/x/stillbox/internal/common" + "dynatron.me/x/stillbox/internal/version" "dynatron.me/x/stillbox/pkg/gordio" "dynatron.me/x/stillbox/pkg/gordio/admin" "dynatron.me/x/stillbox/pkg/gordio/config" - "dynatron.me/x/stillbox/internal/version" "github.com/spf13/cobra" ) func main() { - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: common.TimeFormat}) rootCmd := &cobra.Command{ Use: gordio.AppName, diff --git a/internal/common/common.go b/internal/common/common.go index fffb96d..345fdd9 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -4,6 +4,10 @@ import ( "github.com/spf13/cobra" ) +const ( + TimeFormat = "Jan 2 15:04:05" +) + type cmdOptions interface { Options(*cobra.Command, []string) error Execute() error diff --git a/pkg/gordio/auth/apikey.go b/pkg/gordio/auth/apikey.go index 876b35a..70cfae6 100644 --- a/pkg/gordio/auth/apikey.go +++ b/pkg/gordio/auth/apikey.go @@ -18,7 +18,7 @@ type apiKeyAuth interface { CheckAPIKey(ctx context.Context, key string) (*UserID, error) } -func (a *authenticator) CheckAPIKey(ctx context.Context, key string) (*UserID, error) { +func (a *Auth) CheckAPIKey(ctx context.Context, key string) (*UserID, error) { keyUuid, err := uuid.Parse(key) if err != nil { log.Error().Str("apikey", key).Msg("cannot parse key") diff --git a/pkg/gordio/auth/auth.go b/pkg/gordio/auth/auth.go index 26c9b11..5cc9397 100644 --- a/pkg/gordio/auth/auth.go +++ b/pkg/gordio/auth/auth.go @@ -26,14 +26,14 @@ type Authenticator interface { apiKeyAuth } -type authenticator struct { +type Auth struct { jwt *jwtauth.JWTAuth cfg config.Auth } // NewAuthenticator creates a new Authenticator with the provided config. -func NewAuthenticator(cfg config.Auth) Authenticator { - return &authenticator{ +func NewAuthenticator(cfg config.Auth) *Auth { + return &Auth{ jwt: jwtauth.New("HS256", []byte(cfg.JWTSecret), nil), cfg: cfg, } diff --git a/pkg/gordio/auth/jwt.go b/pkg/gordio/auth/jwt.go index 508fbcd..40a924b 100644 --- a/pkg/gordio/auth/jwt.go +++ b/pkg/gordio/auth/jwt.go @@ -39,21 +39,21 @@ type jwtAuth interface { type claims map[string]interface{} -func (a *authenticator) Authenticated(r *http.Request) (claims, bool) { +func (a *Auth) Authenticated(r *http.Request) (claims, bool) { // TODO: check IP against ACL, or conf.Public, and against map of routes tok, cl, err := jwtauth.FromContext(r.Context()) return cl, err != nil && tok != nil } -func (a *authenticator) VerifyMiddleware() func(http.Handler) http.Handler { +func (a *Auth) VerifyMiddleware() func(http.Handler) http.Handler { return jwtauth.Verifier(a.jwt) } -func (a *authenticator) AuthMiddleware() func(http.Handler) http.Handler { +func (a *Auth) AuthMiddleware() func(http.Handler) http.Handler { return jwtauth.Authenticator(a.jwt) } -func (a *authenticator) Login(ctx context.Context, username, password string) (token string, err error) { +func (a *Auth) Login(ctx context.Context, username, password string) (token string, err error) { q := database.New(database.FromCtx(ctx)) users, err := q.GetUsers(ctx) if err != nil { @@ -82,7 +82,7 @@ func (a *authenticator) Login(ctx context.Context, username, password string) (t return a.newToken(found.ID), nil } -func (a *authenticator) newToken(uid int32) string { +func (a *Auth) newToken(uid int32) string { claims := claims{ "sub": strconv.Itoa(int(uid)), } @@ -94,21 +94,21 @@ func (a *authenticator) newToken(uid int32) string { return tokenString } -func (a *authenticator) PublicRoutes(r chi.Router) { +func (a *Auth) PublicRoutes(r chi.Router) { r.Post("/login", a.routeAuth) } -func (a *authenticator) PrivateRoutes(r chi.Router) { +func (a *Auth) PrivateRoutes(r chi.Router) { r.Get("/refresh", a.routeRefresh) } -func (a *authenticator) allowInsecureCookie(r *http.Request) bool { +func (a *Auth) allowInsecureCookie(r *http.Request) bool { host := strings.Split(r.Host, ":") v, has := a.cfg.AllowInsecure[host[0]] return has && v } -func (a *authenticator) routeRefresh(w http.ResponseWriter, r *http.Request) { +func (a *Auth) routeRefresh(w http.ResponseWriter, r *http.Request) { jwToken, _, err := jwtauth.FromContext(r.Context()) if err != nil { http.Error(w, "Invalid token", http.StatusBadRequest) @@ -155,7 +155,7 @@ func (a *authenticator) routeRefresh(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &jr) } -func (a *authenticator) routeAuth(w http.ResponseWriter, r *http.Request) { +func (a *Auth) routeAuth(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) diff --git a/pkg/gordio/nexus/client.go b/pkg/gordio/nexus/client.go index 475a72b..239b0d6 100644 --- a/pkg/gordio/nexus/client.go +++ b/pkg/gordio/nexus/client.go @@ -7,9 +7,9 @@ import ( "runtime" "sync" + "dynatron.me/x/stillbox/internal/version" "dynatron.me/x/stillbox/pkg/calls" "dynatron.me/x/stillbox/pkg/gordio/database" - "dynatron.me/x/stillbox/internal/version" "dynatron.me/x/stillbox/pkg/pb" "github.com/rs/zerolog/log" diff --git a/pkg/gordio/server/logging.go b/pkg/gordio/server/logging.go index 8ee84c9..4c8502e 100644 --- a/pkg/gordio/server/logging.go +++ b/pkg/gordio/server/logging.go @@ -6,11 +6,10 @@ import ( "io/fs" "net/http" "os" - "os/signal" "runtime/debug" - "syscall" "time" + "dynatron.me/x/stillbox/internal/common" "dynatron.me/x/stillbox/pkg/gordio/config" "github.com/go-chi/chi/v5/middleware" @@ -25,41 +24,25 @@ const ( type Logger struct { console io.Writer writers []io.Writer - hup chan os.Signal + cfg []config.Logger lastFieldName string noColor bool } -func NewLogger(cfg *config.Config) (*Logger, error) { - l := &Logger{} +func NewLogger(cfg []config.Logger) (*Logger, error) { + l := &Logger{ + cfg: cfg, + } cw := &zerolog.ConsoleWriter{ Out: os.Stderr, - TimeFormat: "Jan 2 15:04:05", + TimeFormat: common.TimeFormat, FormatFieldName: l.fieldNameFormat, FormatFieldValue: l.fieldValueFormat, } l.console = cw - l.hup = make(chan os.Signal, 1) - go func() { - for sig := range l.hup { - log.Logger = log.Output(l.console) - log.Info().Msgf("received %s, closing and reopening logfiles", sig) - l.Close() - err := l.OpenLogs(cfg) - if err != nil { - log.Error().Err(err).Msg("error reopening logs") - continue - } - - l.Install() - } - }() - - signal.Notify(l.hup, syscall.SIGHUP) - err := l.OpenLogs(cfg) if err != nil { return nil, err @@ -70,6 +53,21 @@ func NewLogger(cfg *config.Config) (*Logger, error) { return l, nil } +func (l *Logger) HUP(cfg *config.Config) { + l.cfg = cfg.Log + + log.Logger = log.Output(l.console) + log.Info().Msg("closing and reopening logfiles") + l.Close() + err := l.OpenLogs(l.cfg) + if err != nil { + log.Error().Err(err).Msg("error reopening logs") + return + } + + l.Install() +} + func (l *Logger) Install() { log.Logger = log.Output(zerolog.MultiLevelWriter(l.writers...)) } @@ -91,9 +89,9 @@ func (l *Logger) Close() { l.writers = nil } -func (l *Logger) OpenLogs(cfg *config.Config) error { - l.writers = make([]io.Writer, 0, len(cfg.Log)) - for _, lc := range cfg.Log { +func (l *Logger) OpenLogs(cfg []config.Logger) error { + l.writers = make([]io.Writer, 0, len(cfg)) + for _, lc := range cfg { level := zerolog.TraceLevel if lc.Level != nil { var err error diff --git a/pkg/gordio/server/server.go b/pkg/gordio/server/server.go index 2868839..7f14f30 100644 --- a/pkg/gordio/server/server.go +++ b/pkg/gordio/server/server.go @@ -3,6 +3,7 @@ package server import ( "context" "net/http" + "os" "time" "dynatron.me/x/stillbox/pkg/gordio/auth" @@ -20,7 +21,7 @@ import ( const shutdownTimeout = 5 * time.Second type Server struct { - auth auth.Authenticator + auth *auth.Auth conf *config.Config db *database.DB r *chi.Mux @@ -28,10 +29,11 @@ type Server struct { sinks sinks.Sinks nex *nexus.Nexus logger *Logger + hup chan os.Signal } func New(ctx context.Context, cfg *config.Config) (*Server, error) { - logger, err := NewLogger(cfg) + logger, err := NewLogger(cfg.Log) if err != nil { return nil, err } @@ -75,6 +77,8 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) { func (s *Server) Go(ctx context.Context) error { defer s.db.Close() + s.installHupHandler() + ctx = database.CtxWithDB(ctx, s.db) httpSrv := &http.Server{ diff --git a/pkg/gordio/server/signals.go b/pkg/gordio/server/signals.go new file mode 100644 index 0000000..872a9d5 --- /dev/null +++ b/pkg/gordio/server/signals.go @@ -0,0 +1,41 @@ +package server + +import ( + "os" + "os/signal" + "syscall" + + "dynatron.me/x/stillbox/pkg/gordio/config" + "github.com/rs/zerolog/log" +) + +type hupper interface { + HUP(*config.Config) +} + +func (s *Server) huppers() []hupper { + return []hupper{ + s.logger, + } +} + +func (s *Server) installHupHandler() { + s.hup = make(chan os.Signal, 1) + go func() { + for sig := range s.hup { + log.Info().Msgf("received %s", sig) + err := s.conf.ReadConfig() + if err != nil { + log.Error().Err(err).Msg("cannot read config") + continue + } + + hs := s.huppers() + for _, h := range hs { + h.HUP(s.conf) + } + } + }() + + signal.Notify(s.hup, syscall.SIGHUP) +}