Files

113 lines
3.8 KiB
Go

package server
import (
"context"
"fmt"
"log"
"net/http"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/panelhosting/panel/internal/authsvc"
"github.com/panelhosting/panel/internal/bootstrap"
"github.com/panelhosting/panel/internal/config"
"github.com/panelhosting/panel/internal/handler"
"github.com/panelhosting/panel/internal/middleware"
"github.com/panelhosting/panel/internal/nginx"
"github.com/panelhosting/panel/internal/repository"
"github.com/panelhosting/panel/internal/setup"
"github.com/panelhosting/panel/internal/sitesvc"
"github.com/panelhosting/panel/internal/ssl"
"github.com/panelhosting/panel/internal/sslsvc"
)
type Server struct {
httpServer *http.Server
}
func New(pool *pgxpool.Pool, cfg *config.Config) (*Server, error) {
users := repository.NewUserRepository(pool)
servers := repository.NewServerRepository(pool)
sessions := repository.NewSessionRepository(pool)
sites := repository.NewSiteRepository(pool)
phpVers := repository.NewPHPVersionRepository(pool)
sslRepo := repository.NewSSLRepository(pool)
challengeStore := ssl.NewChallengeStore()
issuer := ssl.NewIssuer(cfg.ACMEStaging, cfg.SSLStoragePath, cfg.ACMEHTTPAddr, challengeStore)
ngx := nginx.NewManager(cfg.NginxSitesDir, cfg.NginxReloadCmd, cfg.SSLStoragePath)
sslService := sslsvc.NewService(sslRepo, sites, issuer, ngx, cfg)
setupSvc := setup.NewService(users, servers, cfg)
authSvc := authsvc.NewService(users, sessions, cfg.SessionTTLHours)
siteSvc := sitesvc.NewService(sites, servers, phpVers, sslService)
if cfg.ACMEEmail == "" {
log.Println("ssl: ACME_EMAIL not set globally, use per-site email for Let's Encrypt")
}
setupHandler := handler.NewSetupHandler(setupSvc)
authHandler := handler.NewAuthHandler(authSvc, cfg)
siteHandler := handler.NewSiteHandler(siteSvc)
acmeHandler := handler.NewACMEHandler(challengeStore)
authMW := middleware.NewAuth(authSvc, cfg)
setupRequired, err := setupSvc.Status(context.Background())
if err != nil {
return nil, fmt.Errorf("check setup status: %w", err)
}
if setupRequired {
log.Println("setup required: open / to create first admin")
}
if err := bootstrap.EnsureDefaultServer(context.Background(), servers, cfg); err != nil {
return nil, fmt.Errorf("bootstrap server: %w", err)
}
mux := http.NewServeMux()
mux.HandleFunc("GET /health", handler.Health)
mux.HandleFunc("GET /.well-known/acme-challenge/{token}", acmeHandler.Challenge)
mux.HandleFunc("GET /api/v1/setup/status", setupHandler.Status)
mux.HandleFunc("POST /api/v1/setup/register", setupHandler.Register)
mux.HandleFunc("POST /api/v1/auth/login", authHandler.Login)
mux.HandleFunc("POST /api/v1/auth/logout", authMW.Require(authHandler.Logout))
mux.HandleFunc("GET /api/v1/auth/me", authMW.Require(authHandler.Me))
mux.HandleFunc("GET /api/v1/php-versions", siteHandler.PHPVersions)
mux.HandleFunc("GET /api/v1/sites", authMW.Require(siteHandler.List))
mux.HandleFunc("POST /api/v1/sites", authMW.Require(siteHandler.Create))
mux.HandleFunc("POST /api/v1/sites/{id}/ssl/issue", authMW.Require(siteHandler.ReissueSSL))
mux.HandleFunc("GET /{$}", authMW.Optional(handler.IndexPage(setupSvc)))
addr := fmt.Sprintf(":%s", cfg.HTTPPort)
return &Server{
httpServer: &http.Server{
Addr: addr,
Handler: loggingMiddleware(mux),
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
},
}, nil
}
func (s *Server) Start() error {
log.Printf("panel listening on %s", s.httpServer.Addr)
return s.httpServer.ListenAndServe()
}
func (s *Server) Shutdown(ctx context.Context) error {
return s.httpServer.Shutdown(ctx)
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond))
})
}