upaas/internal/server/server.go
sneak 3f9d83c436 Initial commit with server startup infrastructure
Core infrastructure:
- Uber fx dependency injection
- Chi router with middleware stack
- SQLite database with embedded migrations
- Embedded templates and static assets
- Structured logging with slog

Features implemented:
- Authentication (login, logout, session management, argon2id hashing)
- App management (create, edit, delete, list)
- Deployment pipeline (clone, build, deploy, health check)
- Webhook processing for Gitea
- Notifications (ntfy, Slack)
- Environment variables, labels, volumes per app
- SSH key generation for deploy keys

Server startup:
- Server.Run() starts HTTP server on configured port
- Server.Shutdown() for graceful shutdown
- SetupRoutes() wires all handlers with chi router
2025-12-29 15:46:03 +07:00

122 lines
2.7 KiB
Go

// Package server provides the HTTP server.
package server
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"go.uber.org/fx"
"git.eeqj.de/sneak/upaas/internal/config"
"git.eeqj.de/sneak/upaas/internal/globals"
"git.eeqj.de/sneak/upaas/internal/handlers"
"git.eeqj.de/sneak/upaas/internal/logger"
"git.eeqj.de/sneak/upaas/internal/middleware"
)
// Params contains dependencies for Server.
type Params struct {
fx.In
Logger *logger.Logger
Globals *globals.Globals
Config *config.Config
Middleware *middleware.Middleware
Handlers *handlers.Handlers
}
// shutdownTimeout is how long to wait for graceful shutdown.
const shutdownTimeout = 30 * time.Second
// readHeaderTimeout is the maximum duration for reading request headers.
const readHeaderTimeout = 10 * time.Second
// Server is the HTTP server.
type Server struct {
startupTime time.Time
port int
log *slog.Logger
router *chi.Mux
httpServer *http.Server
params Params
mw *middleware.Middleware
handlers *handlers.Handlers
}
// New creates a new Server instance.
func New(lifecycle fx.Lifecycle, params Params) (*Server, error) {
srv := &Server{
port: params.Config.Port,
log: params.Logger.Get(),
params: params,
mw: params.Middleware,
handlers: params.Handlers,
}
lifecycle.Append(fx.Hook{
OnStart: func(_ context.Context) error {
srv.startupTime = time.Now()
go srv.Run()
return nil
},
OnStop: func(ctx context.Context) error {
return srv.Shutdown(ctx)
},
})
return srv, nil
}
// Run starts the HTTP server.
func (s *Server) Run() {
s.SetupRoutes()
listenAddr := fmt.Sprintf(":%d", s.port)
s.httpServer = &http.Server{
Addr: listenAddr,
Handler: s,
ReadHeaderTimeout: readHeaderTimeout,
}
s.log.Info("http server starting", "addr", listenAddr)
err := s.httpServer.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
s.log.Error("http server error", "error", err)
}
}
// Shutdown gracefully shuts down the server.
func (s *Server) Shutdown(ctx context.Context) error {
if s.httpServer == nil {
return nil
}
s.log.Info("shutting down http server")
shutdownCtx, cancel := context.WithTimeout(ctx, shutdownTimeout)
defer cancel()
err := s.httpServer.Shutdown(shutdownCtx)
if err != nil {
s.log.Error("http server shutdown error", "error", err)
return fmt.Errorf("shutting down http server: %w", err)
}
s.log.Info("http server stopped")
return nil
}
// ServeHTTP implements http.Handler.
func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
s.router.ServeHTTP(writer, request)
}