2024-02-15 16:08:29 +00:00
package server
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
cmd "codeberg.org/codeberg/pages/cli"
"codeberg.org/codeberg/pages/config"
"codeberg.org/codeberg/pages/server/acme"
"codeberg.org/codeberg/pages/server/cache"
"codeberg.org/codeberg/pages/server/certificates"
"codeberg.org/codeberg/pages/server/gitea"
"codeberg.org/codeberg/pages/server/handler"
)
// Serve sets up and starts the web server.
func Serve ( ctx * cli . Context ) error {
// initialize logger with Trace, overridden later with actual level
log . Logger = zerolog . New ( zerolog . ConsoleWriter { Out : os . Stderr } ) . With ( ) . Timestamp ( ) . Logger ( ) . Level ( zerolog . TraceLevel )
cfg , err := config . ReadConfig ( ctx )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "could not read config" )
}
config . MergeConfig ( ctx , cfg )
// Initialize the logger.
logLevel , err := zerolog . ParseLevel ( cfg . LogLevel )
if err != nil {
return err
}
log . Logger = zerolog . New ( zerolog . ConsoleWriter { Out : os . Stderr } ) . With ( ) . Timestamp ( ) . Logger ( ) . Level ( logLevel )
listeningSSLAddress := fmt . Sprintf ( "%s:%d" , cfg . Server . Host , cfg . Server . Port )
listeningHTTPAddress := fmt . Sprintf ( "%s:%d" , cfg . Server . Host , cfg . Server . HttpPort )
if cfg . Server . RawDomain != "" {
cfg . Server . AllowedCorsDomains = append ( cfg . Server . AllowedCorsDomains , cfg . Server . RawDomain )
}
// Make sure MainDomain has a leading dot
if ! strings . HasPrefix ( cfg . Server . MainDomain , "." ) {
// TODO make this better
cfg . Server . MainDomain = "." + cfg . Server . MainDomain
}
if len ( cfg . Server . PagesBranches ) == 0 {
return fmt . Errorf ( "no default branches set (PAGES_BRANCHES)" )
}
// Init ssl cert database
certDB , closeFn , err := cmd . OpenCertDB ( ctx )
if err != nil {
return err
}
defer closeFn ( )
challengeCache := cache . NewInMemoryCache ( )
// canonicalDomainCache stores canonical domains
canonicalDomainCache := cache . NewInMemoryCache ( )
// redirectsCache stores redirects in _redirects files
redirectsCache := cache . NewInMemoryCache ( )
// clientResponseCache stores responses from the Gitea server
clientResponseCache := cache . NewInMemoryCache ( )
2024-05-26 14:45:03 +00:00
giteaClient , err := gitea . NewClient ( cfg . Forge , clientResponseCache )
2024-02-15 16:08:29 +00:00
if err != nil {
return fmt . Errorf ( "could not create new gitea client: %v" , err )
}
2024-08-14 16:29:04 +03:00
var listener net . Listener
if cfg . Server . HttpOnlyMode {
2024-08-17 22:40:55 +03:00
log . Warn ( ) . Msgf ( "Starting pages-server in HTTP-only mode. Some features like encrypted connections and custom domains are not available in this mode. It is advisable to run behind a reverse-proxy." )
2024-08-14 16:29:04 +03:00
log . Info ( ) . Msgf ( "Create TCP listener on %s" , listeningHTTPAddress )
listener_ , err := net . Listen ( "tcp" , listeningHTTPAddress )
if err != nil {
return fmt . Errorf ( "couldn't create listener: %v" , err )
}
listener = listener_
} else {
acmeClient , err := acme . CreateAcmeClient ( cfg . ACME , cfg . Server . HttpServerEnabled , challengeCache )
if err != nil {
return err
}
if err := certificates . SetupMainDomainCertificates ( cfg . Server . MainDomain , acmeClient , certDB ) ; err != nil {
return err
}
// Create listener for SSL connections
log . Info ( ) . Msgf ( "Create TCP listener for SSL on %s" , listeningSSLAddress )
listener , err := net . Listen ( "tcp" , listeningSSLAddress )
if err != nil {
return fmt . Errorf ( "couldn't create listener: %v" , err )
}
// Setup listener for SSL connections
listener = tls . NewListener ( listener , certificates . TLSConfig (
cfg . Server . MainDomain ,
giteaClient ,
acmeClient ,
cfg . Server . PagesBranches [ 0 ] ,
challengeCache , canonicalDomainCache ,
certDB ,
cfg . ACME . NoDNS01 ,
cfg . Server . RawDomain ,
) )
interval := 12 * time . Hour
certMaintainCtx , cancelCertMaintain := context . WithCancel ( context . Background ( ) )
defer cancelCertMaintain ( )
go certificates . MaintainCertDB ( certMaintainCtx , interval , acmeClient , cfg . Server . MainDomain , certDB )
if cfg . Server . HttpServerEnabled {
// Create handler for http->https redirect and http acme challenges
httpHandler := certificates . SetupHTTPACMEChallengeServer ( challengeCache , uint ( cfg . Server . Port ) )
// Create listener for http and start listening
go func ( ) {
log . Info ( ) . Msgf ( "Start HTTP server listening on %s" , listeningHTTPAddress )
err := http . ListenAndServe ( listeningHTTPAddress , httpHandler )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Couldn't start HTTP server" )
}
} ( )
}
2024-02-15 16:08:29 +00:00
}
2024-04-30 19:50:03 +00:00
if ctx . IsSet ( "enable-profiling" ) {
StartProfilingServer ( ctx . String ( "profiling-address" ) )
}
2024-02-15 16:08:29 +00:00
// Create ssl handler based on settings
2024-05-26 20:05:46 +00:00
sslHandler := handler . Handler ( cfg . Server , giteaClient , canonicalDomainCache , redirectsCache )
2024-02-15 16:08:29 +00:00
// Start the ssl listener
2024-08-14 16:29:04 +03:00
log . Info ( ) . Msgf ( "Start main server using TCP listener on %s" , listener . Addr ( ) )
2024-02-15 16:08:29 +00:00
return http . Serve ( listener , sslHandler )
}