113 lines
3.8 KiB
Go
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))
|
|
})
|
|
}
|