diff --git a/cmd/flags.go b/cmd/flags.go index 258307f..209228e 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -1,37 +1,72 @@ package cmd import ( - "codeberg.org/codeberg/pages/server" "github.com/urfave/cli/v2" ) -// GiteaRoot specifies the root URL of the Gitea instance, without a trailing slash. -var GiteaRoot = []byte(server.EnvOr("GITEA_ROOT", "https://codeberg.org")) - -var GiteaApiToken = server.EnvOr("GITEA_API_TOKEN", "") - -// RawDomain specifies the domain from which raw repository content shall be served in the following format: -// https://{RawDomain}/{owner}/{repo}[/{branch|tag|commit}/{version}]/{filepath...} -// (set to []byte(nil) to disable raw content hosting) -var RawDomain = []byte(server.EnvOr("RAW_DOMAIN", "raw.codeberg.org")) - -// RawInfoPage will be shown (with a redirect) when trying to access RawDomain directly (or without owner/repo/path). -var RawInfoPage = server.EnvOr("REDIRECT_RAW_INFO", "https://docs.codeberg.org/pages/raw-content/") - var ServeFlags = []cli.Flag{ // MainDomainSuffix specifies the main domain (starting with a dot) for which subdomains shall be served as static // pages, or used for comparison in CNAME lookups. Static pages can be accessed through // https://{owner}.{MainDomain}[/{repo}], with repo defaulting to "pages". - // var MainDomainSuffix = []byte("." + server.EnvOr("PAGES_DOMAIN", "codeberg.page")) &cli.StringFlag{ - Name: "main-domain-suffix", - Aliases: nil, - Usage: "specifies the main domain (starting with a dot) for which subdomains shall be served as static pages", - EnvVars: []string{"PAGES_DOMAIN"}, - FilePath: "", - Required: false, - Hidden: false, - TakesFile: false, - Value: "codeberg.page", + Name: "main-domain-suffix", + Usage: "specifies the main domain (starting with a dot) for which subdomains shall be served as static pages", + EnvVars: []string{"PAGES_DOMAIN"}, + Value: "codeberg.page", + }, + // GiteaRoot specifies the root URL of the Gitea instance, without a trailing slash. + &cli.StringFlag{ + Name: "gitea-root", + Usage: "specifies the root URL of the Gitea instance, without a trailing slash.", + EnvVars: []string{"GITEA_ROOT"}, + Value: "https://codeberg.org", + }, + // GiteaApiToken specifies an api token for the Gitea instance + &cli.StringFlag{ + Name: "gitea-api-token", + Usage: "specifies an api token for the Gitea instance", + EnvVars: []string{"GITEA_API_TOKEN"}, + Value: "", + }, + // RawDomain specifies the domain from which raw repository content shall be served in the following format: + // https://{RawDomain}/{owner}/{repo}[/{branch|tag|commit}/{version}]/{filepath...} + // (set to []byte(nil) to disable raw content hosting) + &cli.StringFlag{ + Name: "raw-domain", + Usage: "specifies the domain from which raw repository content shall be served, not set disable raw content hosting", + EnvVars: []string{"RAW_DOMAIN"}, + Value: "raw.codeberg.org", + }, + // RawInfoPage will be shown (with a redirect) when trying to access RawDomain directly (or without owner/repo/path). + &cli.StringFlag{ + Name: "raw-info-page", + Usage: "will be shown (with a redirect) when trying to access $RAW_DOMAIN directly (or without owner/repo/path)", + EnvVars: []string{"REDIRECT_RAW_INFO"}, + Value: "https://docs.codeberg.org/pages/raw-content/", + }, + + &cli.StringFlag{ + Name: "host", + Usage: "specifies host of listening address", + EnvVars: []string{"HOST"}, + Value: "[::]", + }, + &cli.StringFlag{ + Name: "port", + Usage: "specifies port of listening address", + EnvVars: []string{"PORT"}, + Value: "443", + }, + + // ACME_API + &cli.StringFlag{ + Name: "acme-api", + EnvVars: []string{"ACME_API"}, + Value: "https://acme-v02.api.letsencrypt.org/directory", + }, + &cli.StringFlag{ + Name: "acme-email", + EnvVars: []string{"ACME_EMAIL"}, + Value: "noreply@example.email", }, } diff --git a/cmd/main.go b/cmd/main.go index 66f9e7b..a46fdbc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,12 +4,13 @@ import ( "bytes" "crypto/tls" "fmt" - "log" "net" "net/http" "os" + "strings" "time" + "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" "github.com/valyala/fasthttp" @@ -17,8 +18,8 @@ import ( ) // AllowedCorsDomains lists the domains for which Cross-Origin Resource Sharing is allowed. +// TODO: make it a flag var AllowedCorsDomains = [][]byte{ - RawDomain, []byte("fonts.codeberg.org"), []byte("design.codeberg.org"), } @@ -30,20 +31,26 @@ var BlacklistedPaths = [][]byte{ // Serve sets up and starts the web server. func Serve(ctx *cli.Context) error { + giteaRoot := strings.TrimSuffix(ctx.String("gitea-root"), "/") + giteaAPIToken := ctx.String("gitea-api-token") + rawDomain := ctx.String("raw-domain") mainDomainSuffix := []byte(ctx.String("main-domain-suffix")) + rawInfoPage := ctx.String("raw-info-page") + listeningAddress := fmt.Sprintf("%s:%s", ctx.String("host"), ctx.String("port")) + acmeAPI := ctx.String("acme-api") + acmeMail := ctx.String("acme-email") + allowedCorsDomains := AllowedCorsDomains + if len(rawDomain) != 0 { + allowedCorsDomains = append(allowedCorsDomains, []byte(rawDomain)) + } + // Make sure MainDomain has a trailing dot, and GiteaRoot has no trailing slash if !bytes.HasPrefix(mainDomainSuffix, []byte{'.'}) { mainDomainSuffix = append([]byte{'.'}, mainDomainSuffix...) } - GiteaRoot = bytes.TrimSuffix(GiteaRoot, []byte{'/'}) - - // Use HOST and PORT environment variables to determine listening address - address := fmt.Sprintf("%s:%s", server.EnvOr("HOST", "[::]"), server.EnvOr("PORT", "443")) - log.Printf("Listening on https://%s", address) - // Create handler based on settings - handler := server.Handler(mainDomainSuffix, RawDomain, GiteaRoot, RawInfoPage, GiteaApiToken, BlacklistedPaths, AllowedCorsDomains) + handler := server.Handler(mainDomainSuffix, []byte(rawDomain), giteaRoot, rawInfoPage, giteaAPIToken, BlacklistedPaths, allowedCorsDomains) // Enable compression by wrapping the handler with the compression function provided by FastHTTP compressedHandler := fasthttp.CompressHandlerBrotliLevel(handler, fasthttp.CompressBrotliBestSpeed, fasthttp.CompressBestSpeed) @@ -60,13 +67,14 @@ func Serve(ctx *cli.Context) error { } // Setup listener and TLS - listener, err := net.Listen("tcp", address) + log.Info().Msgf("Listening on https://%s", listeningAddress) + listener, err := net.Listen("tcp", listeningAddress) if err != nil { - log.Fatalf("Couldn't create listener: %s", err) + return fmt.Errorf("couldn't create listener: %s", err) } - listener = tls.NewListener(listener, server.TlsConfig(mainDomainSuffix, string(GiteaRoot), GiteaApiToken)) + listener = tls.NewListener(listener, server.TlsConfig(mainDomainSuffix, giteaRoot, giteaAPIToken)) - server.SetupCertificates(mainDomainSuffix) + server.SetupCertificates(mainDomainSuffix, acmeAPI, acmeMail) if os.Getenv("ENABLE_HTTP_SERVER") == "true" { go (func() { challengePath := []byte("/.well-known/acme-challenge/") @@ -83,7 +91,7 @@ func Serve(ctx *cli.Context) error { } }) if err != nil { - log.Fatalf("Couldn't start HTTP fastServer: %s", err) + log.Fatal().Err(err).Msg("Couldn't start HTTP fastServer") } })() } @@ -91,7 +99,7 @@ func Serve(ctx *cli.Context) error { // Start the web fastServer err = fastServer.Serve(listener) if err != nil { - log.Fatalf("Couldn't start fastServer: %s", err) + log.Fatal().Err(err).Msg("Couldn't start fastServer") } return nil diff --git a/server/certificates.go b/server/certificates.go index c330439..7b3e9d9 100644 --- a/server/certificates.go +++ b/server/certificates.go @@ -399,7 +399,7 @@ func mockCert(domain, msg, mainDomainSuffix string) tls.Certificate { return tlsCertificate } -func SetupCertificates(mainDomainSuffix []byte) { +func SetupCertificates(mainDomainSuffix []byte, acmeAPI, acmeMail string) { if KeyDatabaseErr != nil { panic(KeyDatabaseErr) } @@ -425,7 +425,7 @@ func SetupCertificates(mainDomainSuffix []byte) { panic(err) } myAcmeConfig = lego.NewConfig(&myAcmeAccount) - myAcmeConfig.CADirURL = EnvOr("ACME_API", "https://acme-v02.api.letsencrypt.org/directory") + myAcmeConfig.CADirURL = acmeAPI myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048 _, err := lego.NewClient(myAcmeConfig) if err != nil { @@ -437,12 +437,12 @@ func SetupCertificates(mainDomainSuffix []byte) { panic(err) } myAcmeAccount = AcmeAccount{ - Email: EnvOr("ACME_EMAIL", "noreply@example.email"), + Email: acmeMail, Key: privateKey, KeyPEM: string(certcrypto.PEMEncode(privateKey)), } myAcmeConfig = lego.NewConfig(&myAcmeAccount) - myAcmeConfig.CADirURL = EnvOr("ACME_API", "https://acme-v02.api.letsencrypt.org/directory") + myAcmeConfig.CADirURL = acmeAPI myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048 tempClient, err := lego.NewClient(myAcmeConfig) if err != nil { diff --git a/server/handler.go b/server/handler.go index f24227b..ca2872f 100644 --- a/server/handler.go +++ b/server/handler.go @@ -19,7 +19,7 @@ import ( ) // Handler handles a single HTTP request to the web server. -func Handler(mainDomainSuffix, rawDomain, giteaRoot []byte, rawInfoPage, giteaApiToken string, blacklistedPaths, allowedCorsDomains [][]byte) func(ctx *fasthttp.RequestCtx) { +func Handler(mainDomainSuffix, rawDomain []byte, giteaRoot, rawInfoPage, giteaApiToken string, blacklistedPaths, allowedCorsDomains [][]byte) func(ctx *fasthttp.RequestCtx) { return func(ctx *fasthttp.RequestCtx) { log := log.With().Str("Handler", string(ctx.Request.Header.RequestURI())).Logger() @@ -86,7 +86,7 @@ func Handler(mainDomainSuffix, rawDomain, giteaRoot []byte, rawInfoPage, giteaAp } // Check if the branch exists, otherwise treat it as a file path - branchTimestampResult := getBranchTimestamp(targetOwner, repo, branch, string(giteaRoot), giteaApiToken) + branchTimestampResult := getBranchTimestamp(targetOwner, repo, branch, giteaRoot, giteaApiToken) if branchTimestampResult == nil { // branch doesn't exist return false @@ -115,7 +115,7 @@ func Handler(mainDomainSuffix, rawDomain, giteaRoot []byte, rawInfoPage, giteaAp var tryUpstream = func() { // check if a canonical domain exists on a request on MainDomain if bytes.HasSuffix(trimmedHost, mainDomainSuffix) { - canonicalDomain, _ := checkCanonicalDomain(targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), string(giteaRoot), giteaApiToken) + canonicalDomain, _ := checkCanonicalDomain(targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), giteaRoot, giteaApiToken) if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) { canonicalPath := string(ctx.RequestURI()) if targetRepo != "pages" { @@ -127,7 +127,7 @@ func Handler(mainDomainSuffix, rawDomain, giteaRoot []byte, rawInfoPage, giteaAp } // Try to request the file from the Gitea API - if !upstream(ctx, targetOwner, targetRepo, targetBranch, targetPath, string(giteaRoot), giteaApiToken, targetOptions) { + if !upstream(ctx, targetOwner, targetRepo, targetBranch, targetPath, giteaRoot, giteaApiToken, targetOptions) { returnErrorPage(ctx, ctx.Response.StatusCode()) } } @@ -155,7 +155,7 @@ func Handler(mainDomainSuffix, rawDomain, giteaRoot []byte, rawInfoPage, giteaAp if len(pathElements) > 2 && strings.HasPrefix(pathElements[2], "@") { log.Debug().Msg("raw domain preparations, now trying with specified branch") if tryBranch(targetRepo, pathElements[2][1:], pathElements[3:], - string(giteaRoot)+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p", + giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p", ) { log.Debug().Msg("tryBranch, now trying upstream") tryUpstream() @@ -167,7 +167,7 @@ func Handler(mainDomainSuffix, rawDomain, giteaRoot []byte, rawInfoPage, giteaAp } else { log.Debug().Msg("raw domain preparations, now trying with default branch") tryBranch(targetRepo, "", pathElements[2:], - string(giteaRoot)+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p", + giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p", ) log.Debug().Msg("tryBranch, now trying upstream") tryUpstream() @@ -266,7 +266,7 @@ func Handler(mainDomainSuffix, rawDomain, giteaRoot []byte, rawInfoPage, giteaAp // Try to use the given repo on the given branch or the default branch log.Debug().Msg("custom domain preparations, now trying with details from DNS") if tryBranch(targetRepo, targetBranch, pathElements, canonicalLink) { - canonicalDomain, valid := checkCanonicalDomain(targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), string(giteaRoot), giteaApiToken) + canonicalDomain, valid := checkCanonicalDomain(targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), giteaRoot, giteaApiToken) if !valid { returnErrorPage(ctx, fasthttp.StatusMisdirectedRequest) return diff --git a/server/helpers.go b/server/helpers.go index 354f15e..ecb4bf8 100644 --- a/server/helpers.go +++ b/server/helpers.go @@ -3,8 +3,6 @@ package server import ( "bytes" "encoding/gob" - "os" - "github.com/akrylysov/pogreb" ) @@ -56,12 +54,3 @@ func PogrebGet(db *pogreb.DB, name []byte, obj interface{}) bool { } return true } - -// EnvOr reads an environment variable and returns a default value if it's empty. -// TODO: to helpers.go or use CLI framework -func EnvOr(env string, or string) string { - if v := os.Getenv(env); v != "" { - return v - } - return or -}