Architecture change to modules started.

- Added module-interface (module.go).
- Rewritten main function, added Registry (main.go)
This commit is contained in:
Fabian Gasper
2026-07-14 15:50:56 +02:00
parent ed3ffd1d9c
commit 641df9eaab
2 changed files with 82 additions and 81 deletions
+41 -79
View File
@@ -1,121 +1,83 @@
package main package main
import ( import (
"context"
"log" "log"
"net/http" "net/http"
"os" "os"
"strings"
"time"
"context"
"os/signal" "os/signal"
"sync"
"syscall" "syscall"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/time/rate"
) )
func noListing(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if strings.HasSuffix(req.URL.Path, "/") {
http.NotFound(w, req)
return
}
next.ServeHTTP(w, req)
})
}
func securityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
next.ServeHTTP(w, req)
})
}
var webhookLimiter = rate.NewLimiter(rate.Every(10*time.Second), 1)
func rateLimit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if !webhookLimiter.Allow() {
log.Println("webhook rate limit exceeded")
w.WriteHeader(http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, req)
})
}
func wkdHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Access-Control-Allow-Origin", "*")
next.ServeHTTP(w, req)
})
}
func healthHandler(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
func main() { func main() {
cfg := loadConfig() cfg := loadConfig()
if cfg.EnableWebhook { modules := []Module {
cloneOrOpen(cfg) // Module werden hier registriert, sobald sie existieren, zB.:
// &webserver.Module{},
// &keyserver.Module{},
// &webhook.Module{},
// &resourcemonitor.Module{},
} }
contentFS := http.FileServer(http.Dir(cfg.ContentDir))
keysFS := http.FileServer(http.Dir(cfg.KeysDir))
r := mux.NewRouter() r := mux.NewRouter()
r.Use(securityHeaders) r.Use(securityHeaders)
var active []Module
api := r.PathPrefix("/api/").Subrouter() for _, m := range modules {
api.HandleFunc("/healthz", healthHandler).Methods("GET", "HEAD") log.Printf("module %s: disabled, skipping", m.Name())
continue
if cfg.EnableWebhook {
r.Handle("/webhook", rateLimit(webhookHandler(cfg))).Methods("POST")
} }
// Eigener Bereich für .well-known: kein Listing, WKD-Header ctx, cancel := context.WithCancel(context.Background())
wkd := r.PathPrefix("/.well-known/").Subrouter()
wkd.Use(noListing)
wkd.Use(wkdHeaders)
wkd.PathPrefix("/").Handler(keysFS)
// Alles andere: normaler Fileserver, Listing erlaubt, hier kommt später zB /downloads/ rein var wg sync.WaitGroup
r.PathPrefix("/").Handler(contentFS) for :, m := range active {
wg.Add(1)
go func(m Module) {
defer wg.Done()
if err := m.Start(ctx, cfg); err != nil {
log.Printf("module %s: exited with error: %v" m.Name(), err)
}
}(m)
}
srv := &http.Server { srv := &http.Server {
Addr: ":" + cfg.Port, Addr: ":" + cfg.Port,
Handler: r, Handler: r,
} }
// Server in eigener Goroutine starten, damit main() weiterlaufen kann
go func() { go func() {
log.Printf("Listening on :%s, content=%s keys=%s (webhook: %v)", cfg.Port, cfg.ContentDir, cfg.KeysDir, cfg.EnableWebhook) log.Printf("Listening on :%s, cfg.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server error: %v", err) log.Fatalf("server error: %v", err)
} }
}() }()
// Auf SIGTERM/SIGINT warten
stop := make(chan os.Signal, 1) stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT) signal.Notify(stop, syscall.SIGTERM, syscall.SIGINIT)
<-stop <-stop
log.Println("Shutdown signal received, draining connections...") log.Pritln("Shutdown siganl received, draining connections...")
cancel() // signals all moduke Start() goroutines to stop via ctx
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer shutdownCancel()
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("graceful shutdown failed: %v", err) log.Printf("graceful shutdown failed %v", err)
} else { }
for _, m := range active {
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("module %s: stop error: %v", m.Name(), err)
}
}
wg.Wait()
log.Println("Server shut down cleanly") log.Println("Server shut down cleanly")
} }
}
+39
View File
@@ -0,0 +1,39 @@
package main
import (
"context"
"net/http"
"github.com/gorilla/mux"
)
// Module represents a self-contained feature of Varde.
// Each module is independently enabled/disabled via config and
// runs in the same process, sharing the HTTP router where relevant.
type Module interface {
// Name returns a short, unique identifier used in logs and config.
Name()
// Enabled reports whether this module should be started
// based on the loaded configuration.
Enabled(cfg *Config) bool
// RegisterRoutes attaches this module's HTTP routes to the shared router, if any.
// Modules without HTTP routes should leave this empty.
RegisterRoutes(r *musx.Router, cfg *Config)
// Start runs any background work for this module (eg. periodic checks, repo cloning).
// Should block until ctx is cancelled, or return immediately if the module has no background work.
Start(ctx context.Context, cfg *Config) error
// Stop performs graceful cleanup. Called druing shutdown for every module that was successfully started.
Stop(ctx context.Context) error
}
// baseModule provides no-op defaults so modules only need to implement the methods relevant to them.
type baseModule struct{}
func (baseModule) RegisterRoutes(r *mux.Router, cfg *Config){}
func (baseModule) Start(cxt context.Context, cfg *Config) error {return nil}
var _ = http.MethodGet // placeholder import guard, remove once a module uses net/http directly here