freeleaps-ops/apps/gitea-webhook-ambassador/cmd/server/main.go
zhenyus db590f3f27 refactor: update gitea-webhook-ambassador Dockerfile and configuration
- Changed the build process to include a web UI build stage using Node.js.
- Updated Go build stage to copy web UI files to the correct location.
- Removed the main.go file as it is no longer needed.
- Added SQLite database configuration to example config.
- Updated dependencies in go.mod and go.sum, including new packages for JWT and SQLite.
- Modified .gitignore to include new database and configuration files.

Signed-off-by: zhenyus <zhenyus@mathmast.com>
2025-06-10 16:00:52 +08:00

266 lines
7.2 KiB
Go

package main
import (
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"freeleaps.com/gitea-webhook-ambassador/internal/auth"
"freeleaps.com/gitea-webhook-ambassador/internal/config"
"freeleaps.com/gitea-webhook-ambassador/internal/database"
"freeleaps.com/gitea-webhook-ambassador/internal/handler"
"freeleaps.com/gitea-webhook-ambassador/internal/jenkins"
"freeleaps.com/gitea-webhook-ambassador/internal/logger"
"freeleaps.com/gitea-webhook-ambassador/internal/web"
webhandler "freeleaps.com/gitea-webhook-ambassador/internal/web/handler"
"freeleaps.com/gitea-webhook-ambassador/internal/worker"
)
var (
configFile = flag.String("config", "config.yaml", "Path to configuration file")
)
func main() {
flag.Parse()
// Initialize logger with default configuration
logger.Configure(logger.Config{
Level: "info",
Format: "text",
})
// Load initial configuration
if err := config.Load(*configFile); err != nil {
logger.Error("Failed to load configuration: %v", err)
os.Exit(1)
}
// Setup application
app, err := setupApplication()
if err != nil {
logger.Error("Failed to setup application: %v", err)
os.Exit(1)
}
defer app.cleanup()
// Start HTTP server
go app.startServer()
// Handle graceful shutdown
app.handleShutdown()
}
type application struct {
server *http.Server
workerPool *worker.Pool
db *database.DB
watcher *config.Watcher
}
func setupApplication() (*application, error) {
cfg := config.Get()
// Configure logger based on configuration
logger.Configure(logger.Config{
Level: cfg.Logging.Level,
Format: cfg.Logging.Format,
File: cfg.Logging.File,
})
// Ensure database directory exists
dbDir := filepath.Dir(cfg.Database.Path)
if err := os.MkdirAll(dbDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create database directory: %v", err)
}
// Initialize database
db, err := setupDatabase(cfg)
if err != nil {
return nil, fmt.Errorf("failed to setup database: %v", err)
}
// Create Jenkins client
jenkinsClient := jenkins.New(jenkins.Config{
URL: cfg.Jenkins.URL,
Username: cfg.Jenkins.Username,
Token: cfg.Jenkins.Token,
Timeout: time.Duration(cfg.Jenkins.Timeout) * time.Second,
})
// Create worker pool
workerPool, err := setupWorkerPool(cfg, jenkinsClient, db)
if err != nil {
return nil, fmt.Errorf("failed to setup worker pool: %v", err)
}
// Setup config watcher
watcher, err := setupConfigWatcher(*configFile)
if err != nil {
return nil, fmt.Errorf("failed to setup config watcher: %v", err)
}
if err := watcher.Start(); err != nil {
return nil, fmt.Errorf("failed to start config watcher: %v", err)
}
// Create HTTP server
server := setupHTTPServer(cfg, workerPool, db)
return &application{
server: server,
workerPool: workerPool,
db: db,
watcher: watcher,
}, nil
}
func setupDatabase(cfg config.Configuration) (*database.DB, error) {
return database.New(database.Config{
Path: cfg.Database.Path,
})
}
func setupWorkerPool(cfg config.Configuration, jenkinsClient *jenkins.Client, db *database.DB) (*worker.Pool, error) {
pool, err := worker.New(worker.Config{
PoolSize: cfg.Worker.PoolSize,
QueueSize: cfg.Worker.QueueSize,
MaxRetries: cfg.Worker.MaxRetries,
RetryBackoff: time.Duration(cfg.Worker.RetryBackoff) * time.Second,
Client: jenkinsClient,
DB: db,
})
if err != nil {
return nil, err
}
// Start event cleanup
go worker.CleanupEvents(time.Duration(cfg.EventCleanup.ExpireAfter) * time.Second)
return pool, nil
}
func setupConfigWatcher(configPath string) (*config.Watcher, error) {
return config.NewWatcher(configPath, func() error {
if err := config.Load(configPath); err != nil {
return err
}
newCfg := config.Get()
// Update logger configuration
logger.Configure(logger.Config{
Level: newCfg.Logging.Level,
Format: newCfg.Logging.Format,
File: newCfg.Logging.File,
})
logger.Info("Configuration reloaded successfully")
return nil
})
}
func setupHTTPServer(cfg config.Configuration, workerPool *worker.Pool, db *database.DB) *http.Server {
// Create handlers
webhookHandler := handler.NewWebhookHandler(workerPool, db, &cfg)
healthHandler := handler.NewHealthHandler(workerPool, &cfg)
adminHandler := handler.NewAdminHandler(db, &cfg)
projectHandler := handler.NewProjectHandler(db, &cfg)
logsHandler := handler.NewLogsHandler(db, &cfg)
// Create auth middleware
authMiddleware := auth.NewMiddleware(cfg.Server.SecretKey)
// Create dashboard handler
dashboardHandler, err := webhandler.NewDashboardHandler(
web.WebAssets,
projectHandler,
adminHandler,
logsHandler,
healthHandler,
)
if err != nil {
logger.Error("Failed to create dashboard handler: %v", err)
os.Exit(1)
}
// Setup HTTP routes
mux := http.NewServeMux()
// Static file handlers (not protected by auth)
mux.HandleFunc("/css/", dashboardHandler.ServeHTTP)
mux.HandleFunc("/js/", dashboardHandler.ServeHTTP)
mux.HandleFunc("/img/", dashboardHandler.ServeHTTP)
// Webhook endpoint (not protected by auth, uses its own validation)
mux.HandleFunc(cfg.Server.WebhookPath, webhookHandler.HandleWebhook)
// Login routes - must be defined before protected routes
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
dashboardHandler.ServeHTTP(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
mux.HandleFunc("/api/auth/login", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
authMiddleware.HandleLogin(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
// Protected routes
mux.Handle("/", authMiddleware.Authenticate(dashboardHandler))
mux.Handle("/dashboard", authMiddleware.Authenticate(dashboardHandler))
// Protected API routes
mux.Handle("/api/projects", authMiddleware.Authenticate(http.HandlerFunc(projectHandler.HandleGetProjectMapping)))
mux.Handle("/api/admin/api-keys", authMiddleware.Authenticate(http.HandlerFunc(adminHandler.HandleListAPIKeys)))
mux.Handle("/api/admin/api-keys/delete", authMiddleware.Authenticate(http.HandlerFunc(adminHandler.HandleDeleteAPIKey)))
mux.Handle("/api/logs", authMiddleware.Authenticate(http.HandlerFunc(logsHandler.HandleGetTriggerLogs)))
mux.Handle("/api/health", authMiddleware.Authenticate(http.HandlerFunc(healthHandler.HandleHealth)))
return &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Server.Port),
Handler: mux,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
}
func (app *application) startServer() {
logger.Info("Server listening on %s", app.server.Addr)
if err := app.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("HTTP server error: %v", err)
os.Exit(1)
}
}
func (app *application) handleShutdown() {
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
logger.Info("Shutting down server...")
app.cleanup()
logger.Info("Server shutdown complete")
}
func (app *application) cleanup() {
if app.workerPool != nil {
app.workerPool.Release()
}
if app.db != nil {
app.db.Close()
}
if app.watcher != nil {
app.watcher.Stop()
}
}