From 4267d54a6302f1acb92ec2e4d382b2ddaae3a520 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Wed, 20 Apr 2022 23:42:01 +0200
Subject: [PATCH 001/196] refactor (2) (#34)

move forward with refactoring:
 - initial implementation of a smal "gitea client for fasthttp"
 - move constant into const.go

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/34
Reviewed-by: Otto Richter <otto@codeberg.org>
---
 server/upstream/const.go    |  2 ++
 server/upstream/domains.go  | 22 ++++++------
 server/upstream/gitea.go    | 67 +++++++++++++++++++++++++++++++++++++
 server/upstream/helper.go   | 30 +++++++----------
 server/upstream/upstream.go | 19 ++++++-----
 5 files changed, 103 insertions(+), 37 deletions(-)
 create mode 100644 server/upstream/gitea.go

diff --git a/server/upstream/const.go b/server/upstream/const.go
index 77f64dd..ce76e21 100644
--- a/server/upstream/const.go
+++ b/server/upstream/const.go
@@ -19,3 +19,5 @@ var fileCacheSizeLimit = 1024 * 1024
 
 // canonicalDomainCacheTimeout specifies the timeout for the canonical domain cache.
 var canonicalDomainCacheTimeout = 15 * time.Minute
+
+const canonicalDomainConfig = ".domains"
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 47a5564..28a2d9c 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -3,15 +3,19 @@ package upstream
 import (
 	"strings"
 
-	"github.com/valyala/fasthttp"
-
 	"codeberg.org/codeberg/pages/server/cache"
 )
 
 // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file).
 func CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaAPIToken string, canonicalDomainCache cache.SetGetKey) (string, bool) {
-	domains := []string{}
-	valid := false
+	return checkCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaAPIToken, canonicalDomainCache)
+}
+
+func checkCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaAPIToken string, canonicalDomainCache cache.SetGetKey) (string, bool) {
+	var (
+		domains []string
+		valid   bool
+	)
 	if cachedValue, ok := canonicalDomainCache.Get(targetOwner + "/" + targetRepo + "/" + targetBranch); ok {
 		domains = cachedValue.([]string)
 		for _, domain := range domains {
@@ -21,13 +25,9 @@ func CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, m
 			}
 		}
 	} else {
-		req := fasthttp.AcquireRequest()
-		req.SetRequestURI(giteaRoot + "/api/v1/repos/" + targetOwner + "/" + targetRepo + "/raw/" + targetBranch + "/.domains" + "?access_token=" + giteaAPIToken)
-		res := fasthttp.AcquireResponse()
-
-		err := client.Do(req, res)
-		if err == nil && res.StatusCode() == fasthttp.StatusOK {
-			for _, domain := range strings.Split(string(res.Body()), "\n") {
+		body, err := giteaRawContent(giteaRoot, targetRepo, targetBranch, giteaRoot, giteaAPIToken, canonicalDomainConfig)
+		if err == nil {
+			for _, domain := range strings.Split(string(body), "\n") {
 				domain = strings.ToLower(domain)
 				domain = strings.TrimSpace(domain)
 				domain = strings.TrimPrefix(domain, "http://")
diff --git a/server/upstream/gitea.go b/server/upstream/gitea.go
new file mode 100644
index 0000000..eeeb0a6
--- /dev/null
+++ b/server/upstream/gitea.go
@@ -0,0 +1,67 @@
+package upstream
+
+import (
+	"fmt"
+	"net/url"
+	"path"
+	"time"
+
+	"github.com/valyala/fasthttp"
+	"github.com/valyala/fastjson"
+)
+
+const giteaAPIRepos = "/api/v1/repos/"
+
+// TODOs:
+// * own client to store token & giteaRoot
+// * handle 404 -> page will show 500 atm
+
+func giteaRawContent(targetOwner, targetRepo, ref, giteaRoot, giteaAPIToken, resource string) ([]byte, error) {
+	req := fasthttp.AcquireRequest()
+
+	req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "raw", resource+"?ref="+url.QueryEscape(ref)))
+	req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
+	res := fasthttp.AcquireResponse()
+
+	if err := getFastHTTPClient(10*time.Second).Do(req, res); err != nil {
+		return nil, err
+	}
+	if res.StatusCode() != fasthttp.StatusOK {
+		return nil, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	}
+	return res.Body(), nil
+}
+
+func giteaGetRepoBranchTimestamp(giteaRoot, repoOwner, repoName, branchName, giteaAPIToken string) (time.Time, error) {
+	client := getFastHTTPClient(5 * time.Second)
+
+	req := fasthttp.AcquireRequest()
+	req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, repoOwner, repoName, "branches", branchName))
+	req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
+	res := fasthttp.AcquireResponse()
+
+	if err := client.Do(req, res); err != nil {
+		return time.Time{}, err
+	}
+	if res.StatusCode() != fasthttp.StatusOK {
+		return time.Time{}, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	}
+	return time.Parse(time.RFC3339, fastjson.GetString(res.Body(), "commit", "timestamp"))
+}
+
+func giteaGetRepoDefaultBranch(giteaRoot, repoOwner, repoName, giteaAPIToken string) (string, error) {
+	client := getFastHTTPClient(5 * time.Second)
+
+	req := fasthttp.AcquireRequest()
+	req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, repoOwner, repoName))
+	req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
+	res := fasthttp.AcquireResponse()
+
+	if err := client.Do(req, res); err != nil {
+		return "", err
+	}
+	if res.StatusCode() != fasthttp.StatusOK {
+		return "", fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	}
+	return fastjson.GetString(res.Body(), "default_branch"), nil
+}
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index f61d746..3b51479 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -3,9 +3,6 @@ package upstream
 import (
 	"time"
 
-	"github.com/valyala/fasthttp"
-	"github.com/valyala/fastjson"
-
 	"codeberg.org/codeberg/pages/server/cache"
 )
 
@@ -16,34 +13,31 @@ type branchTimestamp struct {
 
 // GetBranchTimestamp finds the default branch (if branch is "") and returns the last modification time of the branch
 // (or nil if the branch doesn't exist)
-func GetBranchTimestamp(owner, repo, branch, giteaRoot, giteaApiToken string, branchTimestampCache cache.SetGetKey) *branchTimestamp {
+func GetBranchTimestamp(owner, repo, branch, giteaRoot, giteaAPIToken string, branchTimestampCache cache.SetGetKey) *branchTimestamp {
 	if result, ok := branchTimestampCache.Get(owner + "/" + repo + "/" + branch); ok {
 		if result == nil {
 			return nil
 		}
 		return result.(*branchTimestamp)
 	}
-	result := &branchTimestamp{}
-	result.Branch = branch
-	if branch == "" {
+	result := &branchTimestamp{
+		Branch: branch,
+	}
+	if len(branch) == 0 {
 		// Get default branch
-		body := make([]byte, 0)
-		// TODO: use header for API key?
-		status, body, err := fasthttp.GetTimeout(body, giteaRoot+"/api/v1/repos/"+owner+"/"+repo+"?access_token="+giteaApiToken, 5*time.Second)
-		if err != nil || status != 200 {
-			_ = branchTimestampCache.Set(owner+"/"+repo+"/"+branch, nil, defaultBranchCacheTimeout)
+		defaultBranch, err := giteaGetRepoDefaultBranch(giteaRoot, owner, repo, giteaAPIToken)
+		if err != nil {
+			_ = branchTimestampCache.Set(owner+"/"+repo+"/", nil, defaultBranchCacheTimeout)
 			return nil
 		}
-		result.Branch = fastjson.GetString(body, "default_branch")
+		result.Branch = defaultBranch
 	}
 
-	body := make([]byte, 0)
-	status, body, err := fasthttp.GetTimeout(body, giteaRoot+"/api/v1/repos/"+owner+"/"+repo+"/branches/"+branch+"?access_token="+giteaApiToken, 5*time.Second)
-	if err != nil || status != 200 {
+	timestamp, err := giteaGetRepoBranchTimestamp(giteaRoot, owner, repo, branch, giteaAPIToken)
+	if err != nil {
 		return nil
 	}
-
-	result.Timestamp, _ = time.Parse(time.RFC3339, fastjson.GetString(body, "commit", "timestamp"))
+	result.Timestamp = timestamp
 	_ = branchTimestampCache.Set(owner+"/"+repo+"/"+branch, result, branchExistenceCacheTimeout)
 	return result
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 88d3471..33c80e9 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -38,11 +38,13 @@ type Options struct {
 	redirectIfExists    string
 }
 
-var client = fasthttp.Client{
-	ReadTimeout:        10 * time.Second,
-	MaxConnDuration:    60 * time.Second,
-	MaxConnWaitTimeout: 1000 * time.Millisecond,
-	MaxConnsPerHost:    128 * 16, // TODO: adjust bottlenecks for best performance with Gitea!
+func getFastHTTPClient(timeout time.Duration) *fasthttp.Client {
+	return &fasthttp.Client{
+		ReadTimeout:        timeout,
+		MaxConnDuration:    60 * time.Second,
+		MaxConnWaitTimeout: 1000 * time.Millisecond,
+		MaxConnsPerHost:    128 * 16, // TODO: adjust bottlenecks for best performance with Gitea!
+	}
 }
 
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
@@ -80,7 +82,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 	log.Debug().Msg("preparations")
 
 	// Make a GET request to the upstream URL
-	uri := o.TargetOwner + "/" + o.TargetRepo + "/raw/" + o.TargetBranch + "/" + o.TargetPath
+	uri := path.Join(o.TargetOwner, o.TargetRepo, "raw", o.TargetBranch, o.TargetPath)
 	var req *fasthttp.Request
 	var res *fasthttp.Response
 	var cachedResponse fileResponse
@@ -89,10 +91,11 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 		cachedResponse = cachedValue.(fileResponse)
 	} else {
 		req = fasthttp.AcquireRequest()
-		req.SetRequestURI(giteaRoot + "/api/v1/repos/" + uri + "?access_token=" + giteaAPIToken)
+		req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, uri))
+		req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
 		res = fasthttp.AcquireResponse()
 		res.SetBodyStream(&strings.Reader{}, -1)
-		err = client.Do(req, res)
+		err = getFastHTTPClient(10*time.Second).Do(req, res)
 	}
 	log.Debug().Msg("acquisition")
 

From 1724d9fb2e130eec7e15d0d203a3860502386ca8 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Tue, 10 May 2022 18:13:14 +0200
Subject: [PATCH 002/196] add "lint" to Justfile

---
 Justfile | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/Justfile b/Justfile
index 3d23175..fac93d1 100644
--- a/Justfile
+++ b/Justfile
@@ -10,3 +10,17 @@ dev:
 
 build:
     CGO_ENABLED=0 go build -ldflags '-s -w' -v -o build/codeberg-pages-server ./
+
+lint: tool-golangci tool-gofumpt
+    [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
+    golangci-lint run
+
+tool-golangci:
+    @hash golangci-lint> /dev/null 2>&1; if [ $? -ne 0 ]; then \
+    ggo install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
+    fi
+
+tool-gofumpt:
+    @hash gofumpt> /dev/null 2>&1; if [ $? -ne 0 ]; then \
+    go install mvdan.cc/gofumpt@latest; \
+    fi

From 2dbc66d052e50706be1d1ecdb8db55b701943a24 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Tue, 10 May 2022 18:14:28 +0200
Subject: [PATCH 003/196] let golangci-lint have 5m to check

---
 .woodpecker.yml | 2 +-
 Justfile        | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 76d9f3a..d6f3319 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -11,7 +11,7 @@ pipeline:
       - go version
       - go install mvdan.cc/gofumpt@latest
       - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
-      - golangci-lint run
+      - golangci-lint run --timeout 5m
 
   test:
     image: golang
diff --git a/Justfile b/Justfile
index fac93d1..8d31fa9 100644
--- a/Justfile
+++ b/Justfile
@@ -13,7 +13,7 @@ build:
 
 lint: tool-golangci tool-gofumpt
     [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
-    golangci-lint run
+    golangci-lint run --timeout 5m
 
 tool-golangci:
     @hash golangci-lint> /dev/null 2>&1; if [ $? -ne 0 ]; then \

From b2ca88805069700e59c7bca71e3aecd91c783e72 Mon Sep 17 00:00:00 2001
From: Moritz Marquardt <momar@noreply.codeberg.org>
Date: Sat, 14 May 2022 22:29:54 +0200
Subject: [PATCH 004/196] Change MaxConnsPerIP to 0 to fix too many connections
 from HAProxy (#77)

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/77
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Moritz Marquardt <momar@noreply.codeberg.org>
Co-committed-by: Moritz Marquardt <momar@noreply.codeberg.org>
---
 server/setup.go | 2 --
 1 file changed, 2 deletions(-)

diff --git a/server/setup.go b/server/setup.go
index 67c1c42..c186222 100644
--- a/server/setup.go
+++ b/server/setup.go
@@ -18,12 +18,10 @@ func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server {
 	return &fasthttp.Server{
 		Handler:                      compressedHandler,
 		DisablePreParseMultipartForm: true,
-		MaxRequestBodySize:           0,
 		NoDefaultServerHeader:        true,
 		NoDefaultDate:                true,
 		ReadTimeout:                  30 * time.Second, // needs to be this high for ACME certificates with ZeroSSL & HTTP-01 challenge
 		Concurrency:                  1024 * 32,        // TODO: adjust bottlenecks for best performance with Gitea!
-		MaxConnsPerIP:                100,
 	}
 }
 

From 6f12f2a8e46a4781dd1653be10addd340cfbb4e0 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sun, 15 May 2022 22:36:12 +0200
Subject: [PATCH 005/196] fix bug

---
 server/upstream/domains.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 28a2d9c..8669d08 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -25,7 +25,7 @@ func checkCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, m
 			}
 		}
 	} else {
-		body, err := giteaRawContent(giteaRoot, targetRepo, targetBranch, giteaRoot, giteaAPIToken, canonicalDomainConfig)
+		body, err := giteaRawContent(targetOwner, targetRepo, targetBranch, giteaRoot, giteaAPIToken, canonicalDomainConfig)
 		if err == nil {
 			for _, domain := range strings.Split(string(body), "\n") {
 				domain = strings.ToLower(domain)

From 57076a47d39aece27ddd7dbaa3fdb717f0d3076d Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Mon, 30 May 2022 23:55:37 +0200
Subject: [PATCH 006/196] Update 'Justfile'

---
 Justfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Justfile b/Justfile
index 8d31fa9..2c64574 100644
--- a/Justfile
+++ b/Justfile
@@ -17,7 +17,7 @@ lint: tool-golangci tool-gofumpt
 
 tool-golangci:
     @hash golangci-lint> /dev/null 2>&1; if [ $? -ne 0 ]; then \
-    ggo install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
+    go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
     fi
 
 tool-gofumpt:

From f2ba7eac6452f8ef79035240efbc279fcb79421e Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Fri, 10 Jun 2022 15:27:17 +0200
Subject: [PATCH 007/196] set golang to 1.18 (#84)

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/84
---
 .woodpecker.yml |   6 +--
 go.mod          | 113 +++++++++++++++++++++++++++++++++++++++++++++++-
 go.sum          |   1 -
 3 files changed, 115 insertions(+), 5 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index d6f3319..0440309 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,7 +1,7 @@
 pipeline:
   # use vendor to cache dependencies
   vendor:
-    image: golang
+    image: golang:1.18
     commands:
       - go mod vendor
 
@@ -14,11 +14,11 @@ pipeline:
       - golangci-lint run --timeout 5m
 
   test:
-    image: golang
+    image: golang:1.18
     commands:
       - go test ./...
 
   build:
-    image: golang
+    image: golang:1.18
     commands:
       - go build
diff --git a/go.mod b/go.mod
index 615bb12..38a289a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module codeberg.org/codeberg/pages
 
-go 1.16
+go 1.18
 
 require (
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
@@ -13,3 +13,114 @@ require (
 	github.com/valyala/fasthttp v1.31.0
 	github.com/valyala/fastjson v1.6.3
 )
+
+require (
+	cloud.google.com/go v0.54.0 // indirect
+	github.com/Azure/azure-sdk-for-go v32.4.0+incompatible // indirect
+	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
+	github.com/Azure/go-autorest/autorest v0.11.19 // indirect
+	github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
+	github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
+	github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
+	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
+	github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
+	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
+	github.com/Azure/go-autorest/logger v0.2.1 // indirect
+	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
+	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
+	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
+	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 // indirect
+	github.com/andybalholm/brotli v1.0.2 // indirect
+	github.com/aws/aws-sdk-go v1.39.0 // indirect
+	github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
+	github.com/cenkalti/backoff/v4 v4.1.1 // indirect
+	github.com/cloudflare/cloudflare-go v0.20.0 // indirect
+	github.com/cpu/goacmedns v0.1.1 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/deepmap/oapi-codegen v1.6.1 // indirect
+	github.com/dimchansky/utfbom v1.1.1 // indirect
+	github.com/dnsimple/dnsimple-go v0.70.1 // indirect
+	github.com/exoscale/egoscale v0.67.0 // indirect
+	github.com/fatih/structs v1.1.0 // indirect
+	github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
+	github.com/go-errors/errors v1.0.1 // indirect
+	github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
+	github.com/gofrs/uuid v3.2.0+incompatible // indirect
+	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/google/go-querystring v1.1.0 // indirect
+	github.com/google/uuid v1.1.1 // indirect
+	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
+	github.com/gophercloud/gophercloud v0.16.0 // indirect
+	github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
+	github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
+	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
+	github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
+	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
+	github.com/jarcoal/httpmock v1.0.6 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/json-iterator/go v1.1.7 // indirect
+	github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
+	github.com/klauspost/compress v1.13.4 // indirect
+	github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
+	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
+	github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
+	github.com/labbsr0x/goh v1.0.1 // indirect
+	github.com/linode/linodego v0.31.1 // indirect
+	github.com/liquidweb/go-lwApi v0.0.5 // indirect
+	github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
+	github.com/liquidweb/liquidweb-go v1.6.3 // indirect
+	github.com/mattn/go-isatty v0.0.12 // indirect
+	github.com/miekg/dns v1.1.43 // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
+	github.com/mitchellh/mapstructure v1.4.1 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.1 // indirect
+	github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
+	github.com/nrdcg/auroradns v1.0.1 // indirect
+	github.com/nrdcg/desec v0.6.0 // indirect
+	github.com/nrdcg/dnspod-go v0.4.0 // indirect
+	github.com/nrdcg/freemyip v0.2.0 // indirect
+	github.com/nrdcg/goinwx v0.8.1 // indirect
+	github.com/nrdcg/namesilo v0.2.1 // indirect
+	github.com/nrdcg/porkbun v0.1.1 // indirect
+	github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
+	github.com/ovh/go-ovh v1.1.0 // indirect
+	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/pquerna/otp v1.3.0 // indirect
+	github.com/russross/blackfriday/v2 v2.0.1 // indirect
+	github.com/sacloud/libsacloud v1.36.2 // indirect
+	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f // indirect
+	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
+	github.com/sirupsen/logrus v1.4.2 // indirect
+	github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
+	github.com/softlayer/softlayer-go v1.0.3 // indirect
+	github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
+	github.com/spf13/cast v1.3.1 // indirect
+	github.com/stretchr/objx v0.3.0 // indirect
+	github.com/transip/gotransip/v6 v6.6.1 // indirect
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
+	github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 // indirect
+	github.com/vultr/govultr/v2 v2.7.1 // indirect
+	go.opencensus.io v0.22.3 // indirect
+	go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect
+	golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
+	golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
+	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
+	golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
+	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
+	google.golang.org/api v0.20.0 // indirect
+	google.golang.org/appengine v1.6.5 // indirect
+	google.golang.org/genproto v0.0.0-20200305110556-506484158171 // indirect
+	google.golang.org/grpc v1.27.1 // indirect
+	google.golang.org/protobuf v1.26.0 // indirect
+	gopkg.in/ini.v1 v1.62.0 // indirect
+	gopkg.in/ns1/ns1-go.v2 v2.6.2 // indirect
+	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+)
diff --git a/go.sum b/go.sum
index d04f727..a443641 100644
--- a/go.sum
+++ b/go.sum
@@ -486,7 +486,6 @@ github.com/transip/gotransip/v6 v6.6.1 h1:nsCU1ErZS5G0FeOpgGXc4FsWvBff9GPswSMggs
 github.com/transip/gotransip/v6 v6.6.1/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
 github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
 github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
-github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=

From bb8eb32ee2be5d63dd31edc8d4367b6c21761867 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 10 Jun 2022 15:25:33 +0200
Subject: [PATCH 008/196] make debug messages unique

---
 server/handler.go | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/server/handler.go b/server/handler.go
index 8445850..0dc90ce 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -135,7 +135,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 				if tryBranch(targetRepo, pathElements[2][1:], pathElements[3:],
 					giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 				) {
-					log.Debug().Msg("tryBranch, now trying upstream")
+					log.Debug().Msg("tryBranch, now trying upstream 1")
 					tryUpstream(ctx, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 						giteaRoot, giteaAPIToken,
@@ -151,7 +151,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			tryBranch(targetRepo, "", pathElements[2:],
 				giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 			)
-			log.Debug().Msg("tryBranch, now trying upstream")
+			log.Debug().Msg("tryBranch, now trying upstream 2")
 			tryUpstream(ctx, mainDomainSuffix, trimmedHost,
 				targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 				giteaRoot, giteaAPIToken,
@@ -186,7 +186,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 				if tryBranch(pathElements[0], pathElements[1][1:], pathElements[2:],
 					"/"+pathElements[0]+"/%p",
 				) {
-					log.Debug().Msg("tryBranch, now trying upstream")
+					log.Debug().Msg("tryBranch, now trying upstream 3")
 					tryUpstream(ctx, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 						giteaRoot, giteaAPIToken,
@@ -202,7 +202,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			if strings.HasPrefix(pathElements[0], "@") {
 				log.Debug().Msg("main domain preparations, now trying with specified branch")
 				if tryBranch("pages", pathElements[0][1:], pathElements[1:], "/%p") {
-					log.Debug().Msg("tryBranch, now trying upstream")
+					log.Debug().Msg("tryBranch, now trying upstream 4")
 					tryUpstream(ctx, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 						giteaRoot, giteaAPIToken,
@@ -218,7 +218,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// example.codeberg.page/pages/... is not allowed here.
 			log.Debug().Msg("main domain preparations, now trying with specified repo")
 			if pathElements[0] != "pages" && tryBranch(pathElements[0], "pages", pathElements[1:], "") {
-				log.Debug().Msg("tryBranch, now trying upstream")
+				log.Debug().Msg("tryBranch, now trying upstream 5")
 				tryUpstream(ctx, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 					giteaRoot, giteaAPIToken,
@@ -230,7 +230,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// example.codeberg.page/index.html
 			log.Debug().Msg("main domain preparations, now trying with default repo/branch")
 			if tryBranch("pages", "", pathElements, "") {
-				log.Debug().Msg("tryBranch, now trying upstream")
+				log.Debug().Msg("tryBranch, now trying upstream 6")
 				tryUpstream(ctx, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 					giteaRoot, giteaAPIToken,
@@ -278,7 +278,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 					return
 				}
 
-				log.Debug().Msg("tryBranch, now trying upstream")
+				log.Debug().Msg("tryBranch, now trying upstream 7")
 				tryUpstream(ctx, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 					giteaRoot, giteaAPIToken,

From 659932521ce66acc16a8a7a80bed29c292532aee Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Fri, 10 Jun 2022 20:17:07 +0200
Subject: [PATCH 009/196] Add info how to test & debug the server (#85)

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/85
---
 README.md | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/README.md b/README.md
index b56239b..6543c90 100644
--- a/README.md
+++ b/README.md
@@ -97,3 +97,12 @@ After scanning the code, you should quickly be able to understand their function
 
 Again: Feel free to get in touch with us for any questions that might arise.
 Thank you very much.
+
+
+### Test Server
+
+run `just dev`
+now this pages should work:
+ - https://magiclike.localhost.mock.directory:4430/
+ - https://momar.localhost.mock.directory:4430/ci-testing/
+ - https://momar.localhost.mock.directory:4430/pag/@master/
\ No newline at end of file

From 02bd942b04033ab3d6c11b279b42440bac3c5739 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Sat, 11 Jun 2022 23:02:06 +0200
Subject: [PATCH 010/196] Move gitea api calls in own "client" package (#78)

continue #75
close #16
- fix regression (from #34) _thanks to @crystal_
- create own gitea client package
- more logging
- add mock impl of CertDB

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: crystal <crystal@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/78
Reviewed-by: crapStone <crapstone@noreply.codeberg.org>
---
 .woodpecker.yml                     |   2 +
 Justfile                            |   3 +
 cmd/certs.go                        |   2 +-
 cmd/main.go                         |  11 ++-
 server/certificates/certificates.go |  18 +++--
 server/certificates/mock_test.go    |  17 ++++
 server/database/interface.go        |   6 +-
 server/database/mock.go             |  55 +++++++++++++
 server/database/setup.go            |  16 ++--
 server/gitea/client.go              | 119 ++++++++++++++++++++++++++++
 server/gitea/client_test.go         |  23 ++++++
 server/gitea/fasthttp.go            |  15 ++++
 server/handler.go                   |  65 ++++++++-------
 server/handler_test.go              |  11 +--
 server/try.go                       |  10 +--
 server/upstream/domains.go          |   9 +--
 server/upstream/gitea.go            |  67 ----------------
 server/upstream/helper.go           |  37 +++++++--
 server/upstream/upstream.go         |  82 +++++++------------
 19 files changed, 374 insertions(+), 194 deletions(-)
 create mode 100644 server/certificates/mock_test.go
 create mode 100644 server/database/mock.go
 create mode 100644 server/gitea/client.go
 create mode 100644 server/gitea/client_test.go
 create mode 100644 server/gitea/fasthttp.go
 delete mode 100644 server/upstream/gitea.go

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 0440309..271aaca 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,3 +1,5 @@
+branches: main
+
 pipeline:
   # use vendor to cache dependencies
   vendor:
diff --git a/Justfile b/Justfile
index 2c64574..bab0a1e 100644
--- a/Justfile
+++ b/Justfile
@@ -15,6 +15,9 @@ lint: tool-golangci tool-gofumpt
     [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
     golangci-lint run --timeout 5m
 
+fmt: tool-gofumpt
+    gofumpt -w --extra .
+
 tool-golangci:
     @hash golangci-lint> /dev/null 2>&1; if [ $? -ne 0 ]; then \
     go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
diff --git a/cmd/certs.go b/cmd/certs.go
index 83f2ac5..d93fe13 100644
--- a/cmd/certs.go
+++ b/cmd/certs.go
@@ -61,7 +61,7 @@ func removeCert(ctx *cli.Context) error {
 
 	for _, domain := range domains {
 		fmt.Printf("Removing domain %s from the database...\n", domain)
-		if err := keyDatabase.Delete([]byte(domain)); err != nil {
+		if err := keyDatabase.Delete(domain); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/main.go b/cmd/main.go
index 6836cee..257b724 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -18,6 +18,7 @@ import (
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/certificates"
 	"codeberg.org/codeberg/pages/server/database"
+	"codeberg.org/codeberg/pages/server/gitea"
 )
 
 // AllowedCorsDomains lists the domains for which Cross-Origin Resource Sharing is allowed.
@@ -81,9 +82,12 @@ func Serve(ctx *cli.Context) error {
 	// TODO: make this an MRU cache with a size limit
 	fileResponseCache := cache.NewKeyValueCache()
 
+	giteaClient := gitea.NewClient(giteaRoot, giteaAPIToken)
+
 	// Create handler based on settings
 	handler := server.Handler(mainDomainSuffix, []byte(rawDomain),
-		giteaRoot, rawInfoPage, giteaAPIToken,
+		giteaClient,
+		giteaRoot, rawInfoPage,
 		BlacklistedPaths, allowedCorsDomains,
 		dnsLookupCache, canonicalDomainCache, branchTimestampCache, fileResponseCache)
 
@@ -105,7 +109,8 @@ func Serve(ctx *cli.Context) error {
 	defer certDB.Close() //nolint:errcheck    // database has no close ... sync behave like it
 
 	listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix,
-		giteaRoot, giteaAPIToken, dnsProvider,
+		giteaClient,
+		dnsProvider,
 		acmeUseRateLimits,
 		keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
 		certDB))
@@ -126,6 +131,7 @@ func Serve(ctx *cli.Context) error {
 
 	if enableHTTPServer {
 		go func() {
+			log.Info().Timestamp().Msg("Start listening on :80")
 			err := httpServer.ListenAndServe("[::]:80")
 			if err != nil {
 				log.Panic().Err(err).Msg("Couldn't start HTTP fastServer")
@@ -134,6 +140,7 @@ func Serve(ctx *cli.Context) error {
 	}
 
 	// Start the web fastServer
+	log.Info().Timestamp().Msgf("Start listening on %s", listener.Addr())
 	err = fastServer.Serve(listener)
 	if err != nil {
 		log.Panic().Err(err).Msg("Couldn't start fastServer")
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index a13215c..2684dfa 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -32,12 +32,14 @@ import (
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/database"
 	dnsutils "codeberg.org/codeberg/pages/server/dns"
+	"codeberg.org/codeberg/pages/server/gitea"
 	"codeberg.org/codeberg/pages/server/upstream"
 )
 
 // TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates.
 func TLSConfig(mainDomainSuffix []byte,
-	giteaRoot, giteaAPIToken, dnsProvider string,
+	giteaClient *gitea.Client,
+	dnsProvider string,
 	acmeUseRateLimits bool,
 	keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.SetGetKey,
 	certDB database.CertDB,
@@ -81,7 +83,7 @@ func TLSConfig(mainDomainSuffix []byte,
 					sni = string(sniBytes)
 				} else {
 					_, _ = targetRepo, targetBranch
-					_, valid := upstream.CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, sni, string(mainDomainSuffix), giteaRoot, giteaAPIToken, canonicalDomainCache)
+					_, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, sni, string(mainDomainSuffix), canonicalDomainCache)
 					if !valid {
 						sniBytes = mainDomainSuffix
 						sni = string(sniBytes)
@@ -193,7 +195,7 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
 
 func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) {
 	// parse certificate from database
-	res, err := certDB.Get(sni)
+	res, err := certDB.Get(string(sni))
 	if err != nil {
 		panic(err) // TODO: no panic
 	}
@@ -406,7 +408,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 
 func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error {
 	// getting main cert before ACME account so that we can fail here without hitting rate limits
-	mainCertBytes, err := certDB.Get(mainDomainSuffix)
+	mainCertBytes, err := certDB.Get(string(mainDomainSuffix))
 	if err != nil {
 		return fmt.Errorf("cert database is not working")
 	}
@@ -478,7 +480,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 
 				tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
 				if err != nil || !tlsCertificates[0].NotAfter.After(now) {
-					err := certDB.Delete(key)
+					err := certDB.Delete(string(key))
 					if err != nil {
 						log.Printf("[ERROR] Deleting expired certificate for %s failed: %s", string(key), err)
 					} else {
@@ -491,15 +493,15 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 		log.Printf("[INFO] Removed %d expired certificates from the database", expiredCertCount)
 
 		// compact the database
-		result, err := certDB.Compact()
+		msg, err := certDB.Compact()
 		if err != nil {
 			log.Printf("[ERROR] Compacting key database failed: %s", err)
 		} else {
-			log.Printf("[INFO] Compacted key database (%+v)", result)
+			log.Printf("[INFO] Compacted key database (%s)", msg)
 		}
 
 		// update main cert
-		res, err := certDB.Get(mainDomainSuffix)
+		res, err := certDB.Get(string(mainDomainSuffix))
 		if err != nil {
 			log.Err(err).Msgf("could not get cert for domain '%s'", mainDomainSuffix)
 		} else if res == nil {
diff --git a/server/certificates/mock_test.go b/server/certificates/mock_test.go
new file mode 100644
index 0000000..1cbd1f6
--- /dev/null
+++ b/server/certificates/mock_test.go
@@ -0,0 +1,17 @@
+package certificates
+
+import (
+	"testing"
+
+	"codeberg.org/codeberg/pages/server/database"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMockCert(t *testing.T) {
+	db, err := database.NewTmpDB()
+	assert.NoError(t, err)
+	cert := mockCert("example.com", "some error msg", "codeberg.page", db)
+	if assert.NotEmpty(t, cert) {
+		assert.NotEmpty(t, cert.Certificate)
+	}
+}
diff --git a/server/database/interface.go b/server/database/interface.go
index 01b9872..3ba3efc 100644
--- a/server/database/interface.go
+++ b/server/database/interface.go
@@ -8,8 +8,8 @@ import (
 type CertDB interface {
 	Close() error
 	Put(name string, cert *certificate.Resource) error
-	Get(name []byte) (*certificate.Resource, error)
-	Delete(key []byte) error
-	Compact() (pogreb.CompactionResult, error)
+	Get(name string) (*certificate.Resource, error)
+	Delete(key string) error
+	Compact() (string, error)
 	Items() *pogreb.ItemIterator
 }
diff --git a/server/database/mock.go b/server/database/mock.go
new file mode 100644
index 0000000..e6c1b5a
--- /dev/null
+++ b/server/database/mock.go
@@ -0,0 +1,55 @@
+package database
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/OrlovEvgeny/go-mcache"
+	"github.com/akrylysov/pogreb"
+	"github.com/go-acme/lego/v4/certificate"
+)
+
+var _ CertDB = tmpDB{}
+
+type tmpDB struct {
+	intern *mcache.CacheDriver
+	ttl    time.Duration
+}
+
+func (p tmpDB) Close() error {
+	_ = p.intern.Close()
+	return nil
+}
+
+func (p tmpDB) Put(name string, cert *certificate.Resource) error {
+	return p.intern.Set(name, cert, p.ttl)
+}
+
+func (p tmpDB) Get(name string) (*certificate.Resource, error) {
+	cert, has := p.intern.Get(name)
+	if !has {
+		return nil, fmt.Errorf("cert for '%s' not found", name)
+	}
+	return cert.(*certificate.Resource), nil
+}
+
+func (p tmpDB) Delete(key string) error {
+	p.intern.Remove(key)
+	return nil
+}
+
+func (p tmpDB) Compact() (string, error) {
+	p.intern.Truncate()
+	return "Truncate done", nil
+}
+
+func (p tmpDB) Items() *pogreb.ItemIterator {
+	panic("ItemIterator not implemented for tmpDB")
+}
+
+func NewTmpDB() (CertDB, error) {
+	return &tmpDB{
+		intern: mcache.New(),
+		ttl:    time.Minute,
+	}, nil
+}
diff --git a/server/database/setup.go b/server/database/setup.go
index e48b661..bbcf431 100644
--- a/server/database/setup.go
+++ b/server/database/setup.go
@@ -35,9 +35,9 @@ func (p aDB) Put(name string, cert *certificate.Resource) error {
 	return p.intern.Put([]byte(name), resGob.Bytes())
 }
 
-func (p aDB) Get(name []byte) (*certificate.Resource, error) {
+func (p aDB) Get(name string) (*certificate.Resource, error) {
 	cert := &certificate.Resource{}
-	resBytes, err := p.intern.Get(name)
+	resBytes, err := p.intern.Get([]byte(name))
 	if err != nil {
 		return nil, err
 	}
@@ -50,12 +50,16 @@ func (p aDB) Get(name []byte) (*certificate.Resource, error) {
 	return cert, nil
 }
 
-func (p aDB) Delete(key []byte) error {
-	return p.intern.Delete(key)
+func (p aDB) Delete(key string) error {
+	return p.intern.Delete([]byte(key))
 }
 
-func (p aDB) Compact() (pogreb.CompactionResult, error) {
-	return p.intern.Compact()
+func (p aDB) Compact() (string, error) {
+	result, err := p.intern.Compact()
+	if err != nil {
+		return "", err
+	}
+	return fmt.Sprintf("%+v", result), nil
 }
 
 func (p aDB) Items() *pogreb.ItemIterator {
diff --git a/server/gitea/client.go b/server/gitea/client.go
new file mode 100644
index 0000000..d4eb980
--- /dev/null
+++ b/server/gitea/client.go
@@ -0,0 +1,119 @@
+package gitea
+
+import (
+	"errors"
+	"fmt"
+	"net/url"
+	"path"
+	"time"
+
+	"github.com/valyala/fasthttp"
+	"github.com/valyala/fastjson"
+)
+
+const giteaAPIRepos = "/api/v1/repos/"
+
+var ErrorNotFound = errors.New("not found")
+
+type Client struct {
+	giteaRoot      string
+	giteaAPIToken  string
+	fastClient     *fasthttp.Client
+	infoTimeout    time.Duration
+	contentTimeout time.Duration
+}
+
+type FileResponse struct {
+	Exists   bool
+	MimeType string
+	Body     []byte
+}
+
+func joinURL(giteaRoot string, paths ...string) string { return giteaRoot + path.Join(paths...) }
+
+func (f FileResponse) IsEmpty() bool { return len(f.Body) != 0 }
+
+func NewClient(giteaRoot, giteaAPIToken string) *Client {
+	return &Client{
+		giteaRoot:      giteaRoot,
+		giteaAPIToken:  giteaAPIToken,
+		infoTimeout:    5 * time.Second,
+		contentTimeout: 10 * time.Second,
+		fastClient:     getFastHTTPClient(),
+	}
+}
+
+func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
+	url := joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "raw", resource+"?ref="+url.QueryEscape(ref))
+	res, err := client.do(client.contentTimeout, url)
+	if err != nil {
+		return nil, err
+	}
+
+	switch res.StatusCode() {
+	case fasthttp.StatusOK:
+		return res.Body(), nil
+	case fasthttp.StatusNotFound:
+		return nil, ErrorNotFound
+	default:
+		return nil, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	}
+}
+
+func (client *Client) ServeRawContent(uri string) (*fasthttp.Response, error) {
+	url := joinURL(client.giteaRoot, giteaAPIRepos, uri)
+	res, err := client.do(client.contentTimeout, url)
+	if err != nil {
+		return nil, err
+	}
+	// resp.SetBodyStream(&strings.Reader{}, -1)
+
+	if err != nil {
+		return nil, err
+	}
+
+	switch res.StatusCode() {
+	case fasthttp.StatusOK:
+		return res, nil
+	case fasthttp.StatusNotFound:
+		return nil, ErrorNotFound
+	default:
+		return nil, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	}
+}
+
+func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchName string) (time.Time, error) {
+	url := joinURL(client.giteaRoot, giteaAPIRepos, repoOwner, repoName, "branches", branchName)
+	res, err := client.do(client.infoTimeout, url)
+	if err != nil {
+		return time.Time{}, err
+	}
+	if res.StatusCode() != fasthttp.StatusOK {
+		return time.Time{}, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	}
+	return time.Parse(time.RFC3339, fastjson.GetString(res.Body(), "commit", "timestamp"))
+}
+
+func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (string, error) {
+	url := joinURL(client.giteaRoot, giteaAPIRepos, repoOwner, repoName)
+	res, err := client.do(client.infoTimeout, url)
+	if err != nil {
+		return "", err
+	}
+	if res.StatusCode() != fasthttp.StatusOK {
+		return "", fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	}
+	return fastjson.GetString(res.Body(), "default_branch"), nil
+}
+
+func (client *Client) do(timeout time.Duration, url string) (*fasthttp.Response, error) {
+	req := fasthttp.AcquireRequest()
+
+	req.SetRequestURI(url)
+	req.Header.Set(fasthttp.HeaderAuthorization, "token "+client.giteaAPIToken)
+	res := fasthttp.AcquireResponse()
+
+	err := client.fastClient.DoTimeout(req, res, timeout)
+
+	return res, err
+}
diff --git a/server/gitea/client_test.go b/server/gitea/client_test.go
new file mode 100644
index 0000000..bae9d4e
--- /dev/null
+++ b/server/gitea/client_test.go
@@ -0,0 +1,23 @@
+package gitea
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJoinURL(t *testing.T) {
+	url := joinURL("")
+	assert.EqualValues(t, "", url)
+
+	url = joinURL("", "", "")
+	assert.EqualValues(t, "", url)
+
+	url = joinURL("http://wwow.url.com", "a", "b/c/", "d")
+	// assert.EqualValues(t, "http://wwow.url.com/a/b/c/d", url)
+	assert.EqualValues(t, "http://wwow.url.coma/b/c/d", url)
+
+	url = joinURL("h:://wrong", "acdc")
+	// assert.EqualValues(t, "h:://wrong/acdc", url)
+	assert.EqualValues(t, "h:://wrongacdc", url)
+}
diff --git a/server/gitea/fasthttp.go b/server/gitea/fasthttp.go
new file mode 100644
index 0000000..4ff0f4a
--- /dev/null
+++ b/server/gitea/fasthttp.go
@@ -0,0 +1,15 @@
+package gitea
+
+import (
+	"time"
+
+	"github.com/valyala/fasthttp"
+)
+
+func getFastHTTPClient() *fasthttp.Client {
+	return &fasthttp.Client{
+		MaxConnDuration:    60 * time.Second,
+		MaxConnWaitTimeout: 1000 * time.Millisecond,
+		MaxConnsPerHost:    128 * 16, // TODO: adjust bottlenecks for best performance with Gitea!
+	}
+}
diff --git a/server/handler.go b/server/handler.go
index 0dc90ce..bda7bd0 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -4,19 +4,22 @@ import (
 	"bytes"
 	"strings"
 
+	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
 	"github.com/valyala/fasthttp"
 
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/dns"
+	"codeberg.org/codeberg/pages/server/gitea"
 	"codeberg.org/codeberg/pages/server/upstream"
 	"codeberg.org/codeberg/pages/server/utils"
 )
 
 // Handler handles a single HTTP request to the web server.
 func Handler(mainDomainSuffix, rawDomain []byte,
-	giteaRoot, rawInfoPage, giteaAPIToken string,
+	giteaClient *gitea.Client,
+	giteaRoot, rawInfoPage string,
 	blacklistedPaths, allowedCorsDomains [][]byte,
 	dnsLookupCache, canonicalDomainCache, branchTimestampCache, fileResponseCache cache.SetGetKey,
 ) func(ctx *fasthttp.RequestCtx) {
@@ -74,21 +77,21 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 		// Prepare request information to Gitea
 		var targetOwner, targetRepo, targetBranch, targetPath string
 		targetOptions := &upstream.Options{
-			ForbiddenMimeTypes: map[string]struct{}{},
-			TryIndexPages:      true,
+			TryIndexPages: true,
 		}
 
 		// tryBranch checks if a branch exists and populates the target variables. If canonicalLink is non-empty, it will
 		// also disallow search indexing and add a Link header to the canonical URL.
-		tryBranch := func(repo, branch string, path []string, canonicalLink string) bool {
+		tryBranch := func(log zerolog.Logger, repo, branch string, path []string, canonicalLink string) bool {
 			if repo == "" {
+				log.Debug().Msg("tryBranch: repo == ''")
 				return false
 			}
 
 			// Check if the branch exists, otherwise treat it as a file path
-			branchTimestampResult := upstream.GetBranchTimestamp(targetOwner, repo, branch, giteaRoot, giteaAPIToken, branchTimestampCache)
+			branchTimestampResult := upstream.GetBranchTimestamp(giteaClient, targetOwner, repo, branch, branchTimestampCache)
 			if branchTimestampResult == nil {
-				// branch doesn't exist
+				log.Debug().Msg("tryBranch: branch doesn't exist")
 				return false
 			}
 
@@ -108,6 +111,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 				)
 			}
 
+			log.Debug().Msg("tryBranch: true")
 			return true
 		}
 
@@ -117,7 +121,10 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			log.Debug().Msg("raw domain")
 
 			targetOptions.TryIndexPages = false
-			targetOptions.ForbiddenMimeTypes["text/html"] = struct{}{}
+			if targetOptions.ForbiddenMimeTypes == nil {
+				targetOptions.ForbiddenMimeTypes = make(map[string]bool)
+			}
+			targetOptions.ForbiddenMimeTypes["text/html"] = true
 			targetOptions.DefaultMimeType = "text/plain; charset=utf-8"
 
 			pathElements := strings.Split(string(bytes.Trim(ctx.Request.URI().Path(), "/")), "/")
@@ -132,13 +139,13 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// raw.codeberg.org/example/myrepo/@main/index.html
 			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:],
+				if tryBranch(log,
+					targetRepo, pathElements[2][1:], pathElements[3:],
 					giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 				) {
 					log.Debug().Msg("tryBranch, now trying upstream 1")
-					tryUpstream(ctx, mainDomainSuffix, trimmedHost,
+					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						giteaRoot, giteaAPIToken,
 						canonicalDomainCache, branchTimestampCache, fileResponseCache)
 					return
 				}
@@ -148,13 +155,13 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			}
 
 			log.Debug().Msg("raw domain preparations, now trying with default branch")
-			tryBranch(targetRepo, "", pathElements[2:],
+			tryBranch(log,
+				targetRepo, "", pathElements[2:],
 				giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 			)
 			log.Debug().Msg("tryBranch, now trying upstream 2")
-			tryUpstream(ctx, mainDomainSuffix, trimmedHost,
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 				targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-				giteaRoot, giteaAPIToken,
 				canonicalDomainCache, branchTimestampCache, fileResponseCache)
 			return
 
@@ -183,13 +190,13 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 				}
 
 				log.Debug().Msg("main domain preparations, now trying with specified repo & branch")
-				if tryBranch(pathElements[0], pathElements[1][1:], pathElements[2:],
+				if tryBranch(log,
+					pathElements[0], pathElements[1][1:], pathElements[2:],
 					"/"+pathElements[0]+"/%p",
 				) {
 					log.Debug().Msg("tryBranch, now trying upstream 3")
-					tryUpstream(ctx, mainDomainSuffix, trimmedHost,
+					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						giteaRoot, giteaAPIToken,
 						canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				} else {
 					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
@@ -201,11 +208,11 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// example.codeberg.page/@main/index.html
 			if strings.HasPrefix(pathElements[0], "@") {
 				log.Debug().Msg("main domain preparations, now trying with specified branch")
-				if tryBranch("pages", pathElements[0][1:], pathElements[1:], "/%p") {
+				if tryBranch(log,
+					"pages", pathElements[0][1:], pathElements[1:], "/%p") {
 					log.Debug().Msg("tryBranch, now trying upstream 4")
-					tryUpstream(ctx, mainDomainSuffix, trimmedHost,
+					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						giteaRoot, giteaAPIToken,
 						canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				} else {
 					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
@@ -217,11 +224,11 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// example.codeberg.page/myrepo/index.html
 			// example.codeberg.page/pages/... is not allowed here.
 			log.Debug().Msg("main domain preparations, now trying with specified repo")
-			if pathElements[0] != "pages" && tryBranch(pathElements[0], "pages", pathElements[1:], "") {
+			if pathElements[0] != "pages" && tryBranch(log,
+				pathElements[0], "pages", pathElements[1:], "") {
 				log.Debug().Msg("tryBranch, now trying upstream 5")
-				tryUpstream(ctx, mainDomainSuffix, trimmedHost,
+				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					giteaRoot, giteaAPIToken,
 					canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				return
 			}
@@ -229,11 +236,11 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// Try to use the "pages" repo on its default branch
 			// example.codeberg.page/index.html
 			log.Debug().Msg("main domain preparations, now trying with default repo/branch")
-			if tryBranch("pages", "", pathElements, "") {
+			if tryBranch(log,
+				"pages", "", pathElements, "") {
 				log.Debug().Msg("tryBranch, now trying upstream 6")
-				tryUpstream(ctx, mainDomainSuffix, trimmedHost,
+				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					giteaRoot, giteaAPIToken,
 					canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				return
 			}
@@ -261,8 +268,9 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 
 			// 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 := upstream.CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), giteaRoot, giteaAPIToken, canonicalDomainCache)
+			if tryBranch(log,
+				targetRepo, targetBranch, pathElements, canonicalLink) {
+				canonicalDomain, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), canonicalDomainCache)
 				if !valid {
 					html.ReturnErrorPage(ctx, fasthttp.StatusMisdirectedRequest)
 					return
@@ -279,9 +287,8 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 				}
 
 				log.Debug().Msg("tryBranch, now trying upstream 7")
-				tryUpstream(ctx, mainDomainSuffix, trimmedHost,
+				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					giteaRoot, giteaAPIToken,
 					canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				return
 			}
diff --git a/server/handler_test.go b/server/handler_test.go
index 3b4d21a..73002a2 100644
--- a/server/handler_test.go
+++ b/server/handler_test.go
@@ -8,15 +8,16 @@ import (
 	"github.com/valyala/fasthttp"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/gitea"
 )
 
 func TestHandlerPerformance(t *testing.T) {
+	giteaRoot := "https://codeberg.org"
+	giteaClient := gitea.NewClient(giteaRoot, "")
 	testHandler := Handler(
-		[]byte("codeberg.page"),
-		[]byte("raw.codeberg.org"),
-		"https://codeberg.org",
-		"https://docs.codeberg.org/pages/raw-content/",
-		"",
+		[]byte("codeberg.page"), []byte("raw.codeberg.org"),
+		giteaClient,
+		giteaRoot, "https://docs.codeberg.org/pages/raw-content/",
 		[][]byte{[]byte("/.well-known/acme-challenge/")},
 		[][]byte{[]byte("raw.codeberg.org"), []byte("fonts.codeberg.org"), []byte("design.codeberg.org")},
 		cache.NewKeyValueCache(),
diff --git a/server/try.go b/server/try.go
index 4eda5b2..254d3ec 100644
--- a/server/try.go
+++ b/server/try.go
@@ -8,22 +8,22 @@ import (
 
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/gitea"
 	"codeberg.org/codeberg/pages/server/upstream"
 )
 
 // tryUpstream forwards the target request to the Gitea API, and shows an error page on failure.
-func tryUpstream(ctx *fasthttp.RequestCtx,
+func tryUpstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	mainDomainSuffix, trimmedHost []byte,
 
 	targetOptions *upstream.Options,
-	targetOwner, targetRepo, targetBranch, targetPath,
+	targetOwner, targetRepo, targetBranch, targetPath string,
 
-	giteaRoot, giteaAPIToken string,
 	canonicalDomainCache, branchTimestampCache, fileResponseCache cache.SetGetKey,
 ) {
 	// check if a canonical domain exists on a request on MainDomain
 	if bytes.HasSuffix(trimmedHost, mainDomainSuffix) {
-		canonicalDomain, _ := upstream.CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), giteaRoot, giteaAPIToken, canonicalDomainCache)
+		canonicalDomain, _ := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), canonicalDomainCache)
 		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) {
 			canonicalPath := string(ctx.RequestURI())
 			if targetRepo != "pages" {
@@ -43,7 +43,7 @@ func tryUpstream(ctx *fasthttp.RequestCtx,
 	targetOptions.TargetPath = targetPath
 
 	// Try to request the file from the Gitea API
-	if !targetOptions.Upstream(ctx, giteaRoot, giteaAPIToken, branchTimestampCache, fileResponseCache) {
+	if !targetOptions.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
 		html.ReturnErrorPage(ctx, ctx.Response.StatusCode())
 	}
 }
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 8669d08..553c148 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -4,14 +4,11 @@ import (
 	"strings"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/gitea"
 )
 
 // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file).
-func CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaAPIToken string, canonicalDomainCache cache.SetGetKey) (string, bool) {
-	return checkCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaAPIToken, canonicalDomainCache)
-}
-
-func checkCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaAPIToken string, canonicalDomainCache cache.SetGetKey) (string, bool) {
+func CheckCanonicalDomain(giteaClient *gitea.Client, targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.SetGetKey) (string, bool) {
 	var (
 		domains []string
 		valid   bool
@@ -25,7 +22,7 @@ func checkCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, m
 			}
 		}
 	} else {
-		body, err := giteaRawContent(targetOwner, targetRepo, targetBranch, giteaRoot, giteaAPIToken, canonicalDomainConfig)
+		body, err := giteaClient.GiteaRawContent(targetOwner, targetRepo, targetBranch, canonicalDomainConfig)
 		if err == nil {
 			for _, domain := range strings.Split(string(body), "\n") {
 				domain = strings.ToLower(domain)
diff --git a/server/upstream/gitea.go b/server/upstream/gitea.go
deleted file mode 100644
index eeeb0a6..0000000
--- a/server/upstream/gitea.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package upstream
-
-import (
-	"fmt"
-	"net/url"
-	"path"
-	"time"
-
-	"github.com/valyala/fasthttp"
-	"github.com/valyala/fastjson"
-)
-
-const giteaAPIRepos = "/api/v1/repos/"
-
-// TODOs:
-// * own client to store token & giteaRoot
-// * handle 404 -> page will show 500 atm
-
-func giteaRawContent(targetOwner, targetRepo, ref, giteaRoot, giteaAPIToken, resource string) ([]byte, error) {
-	req := fasthttp.AcquireRequest()
-
-	req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "raw", resource+"?ref="+url.QueryEscape(ref)))
-	req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
-	res := fasthttp.AcquireResponse()
-
-	if err := getFastHTTPClient(10*time.Second).Do(req, res); err != nil {
-		return nil, err
-	}
-	if res.StatusCode() != fasthttp.StatusOK {
-		return nil, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
-	}
-	return res.Body(), nil
-}
-
-func giteaGetRepoBranchTimestamp(giteaRoot, repoOwner, repoName, branchName, giteaAPIToken string) (time.Time, error) {
-	client := getFastHTTPClient(5 * time.Second)
-
-	req := fasthttp.AcquireRequest()
-	req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, repoOwner, repoName, "branches", branchName))
-	req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
-	res := fasthttp.AcquireResponse()
-
-	if err := client.Do(req, res); err != nil {
-		return time.Time{}, err
-	}
-	if res.StatusCode() != fasthttp.StatusOK {
-		return time.Time{}, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
-	}
-	return time.Parse(time.RFC3339, fastjson.GetString(res.Body(), "commit", "timestamp"))
-}
-
-func giteaGetRepoDefaultBranch(giteaRoot, repoOwner, repoName, giteaAPIToken string) (string, error) {
-	client := getFastHTTPClient(5 * time.Second)
-
-	req := fasthttp.AcquireRequest()
-	req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, repoOwner, repoName))
-	req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
-	res := fasthttp.AcquireResponse()
-
-	if err := client.Do(req, res); err != nil {
-		return "", err
-	}
-	if res.StatusCode() != fasthttp.StatusOK {
-		return "", fmt.Errorf("unexpected status code '%d'", res.StatusCode())
-	}
-	return fastjson.GetString(res.Body(), "default_branch"), nil
-}
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index 3b51479..5bbe833 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -1,9 +1,14 @@
 package upstream
 
 import (
+	"mime"
+	"path"
+	"strconv"
+	"strings"
 	"time"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/gitea"
 )
 
 type branchTimestamp struct {
@@ -13,7 +18,7 @@ type branchTimestamp struct {
 
 // GetBranchTimestamp finds the default branch (if branch is "") and returns the last modification time of the branch
 // (or nil if the branch doesn't exist)
-func GetBranchTimestamp(owner, repo, branch, giteaRoot, giteaAPIToken string, branchTimestampCache cache.SetGetKey) *branchTimestamp {
+func GetBranchTimestamp(giteaClient *gitea.Client, owner, repo, branch string, branchTimestampCache cache.SetGetKey) *branchTimestamp {
 	if result, ok := branchTimestampCache.Get(owner + "/" + repo + "/" + branch); ok {
 		if result == nil {
 			return nil
@@ -25,7 +30,7 @@ func GetBranchTimestamp(owner, repo, branch, giteaRoot, giteaAPIToken string, br
 	}
 	if len(branch) == 0 {
 		// Get default branch
-		defaultBranch, err := giteaGetRepoDefaultBranch(giteaRoot, owner, repo, giteaAPIToken)
+		defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(owner, repo)
 		if err != nil {
 			_ = branchTimestampCache.Set(owner+"/"+repo+"/", nil, defaultBranchCacheTimeout)
 			return nil
@@ -33,7 +38,7 @@ func GetBranchTimestamp(owner, repo, branch, giteaRoot, giteaAPIToken string, br
 		result.Branch = defaultBranch
 	}
 
-	timestamp, err := giteaGetRepoBranchTimestamp(giteaRoot, owner, repo, branch, giteaAPIToken)
+	timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(owner, repo, result.Branch)
 	if err != nil {
 		return nil
 	}
@@ -42,8 +47,26 @@ func GetBranchTimestamp(owner, repo, branch, giteaRoot, giteaAPIToken string, br
 	return result
 }
 
-type fileResponse struct {
-	exists   bool
-	mimeType string
-	body     []byte
+func (o *Options) getMimeTypeByExtension() string {
+	if o.ForbiddenMimeTypes == nil {
+		o.ForbiddenMimeTypes = make(map[string]bool)
+	}
+	mimeType := mime.TypeByExtension(path.Ext(o.TargetPath))
+	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
+	if o.ForbiddenMimeTypes[mimeTypeSplit[0]] || mimeType == "" {
+		if o.DefaultMimeType != "" {
+			mimeType = o.DefaultMimeType
+		} else {
+			mimeType = "application/octet-stream"
+		}
+	}
+	return mimeType
+}
+
+func (o *Options) generateUri() string {
+	return path.Join(o.TargetOwner, o.TargetRepo, "raw", o.TargetBranch, o.TargetPath)
+}
+
+func (o *Options) timestamp() string {
+	return strconv.FormatInt(o.BranchTimestamp.Unix(), 10)
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 33c80e9..edf4f3f 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -2,11 +2,9 @@ package upstream
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"io"
-	"mime"
-	"path"
-	"strconv"
 	"strings"
 	"time"
 
@@ -15,6 +13,7 @@ import (
 
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/gitea"
 )
 
 // upstreamIndexPages lists pages that may be considered as index pages for directories.
@@ -30,7 +29,7 @@ type Options struct {
 	TargetPath,
 
 	DefaultMimeType string
-	ForbiddenMimeTypes map[string]struct{}
+	ForbiddenMimeTypes map[string]bool
 	TryIndexPages      bool
 	BranchTimestamp    time.Time
 	// internal
@@ -38,26 +37,13 @@ type Options struct {
 	redirectIfExists    string
 }
 
-func getFastHTTPClient(timeout time.Duration) *fasthttp.Client {
-	return &fasthttp.Client{
-		ReadTimeout:        timeout,
-		MaxConnDuration:    60 * time.Second,
-		MaxConnWaitTimeout: 1000 * time.Millisecond,
-		MaxConnsPerHost:    128 * 16, // TODO: adjust bottlenecks for best performance with Gitea!
-	}
-}
-
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
-func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken string, branchTimestampCache, fileResponseCache cache.SetGetKey) (final bool) {
+func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client, branchTimestampCache, fileResponseCache cache.SetGetKey) (final bool) {
 	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
 
-	if o.ForbiddenMimeTypes == nil {
-		o.ForbiddenMimeTypes = map[string]struct{}{}
-	}
-
 	// Check if the branch exists and when it was modified
 	if o.BranchTimestamp.IsZero() {
-		branch := GetBranchTimestamp(o.TargetOwner, o.TargetRepo, o.TargetBranch, giteaRoot, giteaAPIToken, branchTimestampCache)
+		branch := GetBranchTimestamp(giteaClient, o.TargetOwner, o.TargetRepo, o.TargetBranch, branchTimestampCache)
 
 		if branch == nil {
 			html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
@@ -82,25 +68,19 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 	log.Debug().Msg("preparations")
 
 	// Make a GET request to the upstream URL
-	uri := path.Join(o.TargetOwner, o.TargetRepo, "raw", o.TargetBranch, o.TargetPath)
-	var req *fasthttp.Request
+	uri := o.generateUri()
 	var res *fasthttp.Response
-	var cachedResponse fileResponse
+	var cachedResponse gitea.FileResponse
 	var err error
-	if cachedValue, ok := fileResponseCache.Get(uri + "?timestamp=" + strconv.FormatInt(o.BranchTimestamp.Unix(), 10)); ok && len(cachedValue.(fileResponse).body) > 0 {
-		cachedResponse = cachedValue.(fileResponse)
+	if cachedValue, ok := fileResponseCache.Get(uri + "?timestamp=" + o.timestamp()); ok && !cachedValue.(gitea.FileResponse).IsEmpty() {
+		cachedResponse = cachedValue.(gitea.FileResponse)
 	} else {
-		req = fasthttp.AcquireRequest()
-		req.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, uri))
-		req.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
-		res = fasthttp.AcquireResponse()
-		res.SetBodyStream(&strings.Reader{}, -1)
-		err = getFastHTTPClient(10*time.Second).Do(req, res)
+		res, err = giteaClient.ServeRawContent(uri)
 	}
 	log.Debug().Msg("acquisition")
 
 	// Handle errors
-	if (res == nil && !cachedResponse.exists) || (res != nil && res.StatusCode() == fasthttp.StatusNotFound) {
+	if (err != nil && errors.Is(err, gitea.ErrorNotFound)) || (res == nil && !cachedResponse.Exists) {
 		if o.TryIndexPages {
 			// copy the o struct & try if an index page exists
 			optionsForIndexPages := *o
@@ -108,9 +88,9 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 			optionsForIndexPages.appendTrailingSlash = true
 			for _, indexPage := range upstreamIndexPages {
 				optionsForIndexPages.TargetPath = strings.TrimSuffix(o.TargetPath, "/") + "/" + indexPage
-				if optionsForIndexPages.Upstream(ctx, giteaRoot, giteaAPIToken, branchTimestampCache, fileResponseCache) {
-					_ = fileResponseCache.Set(uri+"?timestamp="+strconv.FormatInt(o.BranchTimestamp.Unix(), 10), fileResponse{
-						exists: false,
+				if optionsForIndexPages.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
+					_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
+						Exists: false,
 					}, fileCacheTimeout)
 					return true
 				}
@@ -119,9 +99,9 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 			optionsForIndexPages.appendTrailingSlash = false
 			optionsForIndexPages.redirectIfExists = strings.TrimSuffix(string(ctx.Request.URI().Path()), "/") + ".html"
 			optionsForIndexPages.TargetPath = o.TargetPath + ".html"
-			if optionsForIndexPages.Upstream(ctx, giteaRoot, giteaAPIToken, branchTimestampCache, fileResponseCache) {
-				_ = fileResponseCache.Set(uri+"?timestamp="+strconv.FormatInt(o.BranchTimestamp.Unix(), 10), fileResponse{
-					exists: false,
+			if optionsForIndexPages.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
+				_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
+					Exists: false,
 				}, fileCacheTimeout)
 				return true
 			}
@@ -129,14 +109,14 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 		ctx.Response.SetStatusCode(fasthttp.StatusNotFound)
 		if res != nil {
 			// Update cache if the request is fresh
-			_ = fileResponseCache.Set(uri+"?timestamp="+strconv.FormatInt(o.BranchTimestamp.Unix(), 10), fileResponse{
-				exists: false,
+			_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
+				Exists: false,
 			}, fileCacheTimeout)
 		}
 		return false
 	}
 	if res != nil && (err != nil || res.StatusCode() != fasthttp.StatusOK) {
-		fmt.Printf("Couldn't fetch contents from \"%s\": %s (status code %d)\n", req.RequestURI(), err, res.StatusCode())
+		fmt.Printf("Couldn't fetch contents from \"%s\": %s (status code %d)\n", uri, err, res.StatusCode())
 		html.ReturnErrorPage(ctx, fasthttp.StatusInternalServerError)
 		return true
 	}
@@ -158,15 +138,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 	log.Debug().Msg("error handling")
 
 	// Set the MIME type
-	mimeType := mime.TypeByExtension(path.Ext(o.TargetPath))
-	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
-	if _, ok := o.ForbiddenMimeTypes[mimeTypeSplit[0]]; ok || mimeType == "" {
-		if o.DefaultMimeType != "" {
-			mimeType = o.DefaultMimeType
-		} else {
-			mimeType = "application/octet-stream"
-		}
-	}
+	mimeType := o.getMimeTypeByExtension()
 	ctx.Response.Header.SetContentType(mimeType)
 
 	// Everything's okay so far
@@ -185,20 +157,20 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 			err = res.BodyWriteTo(io.MultiWriter(ctx.Response.BodyWriter(), &cacheBodyWriter))
 		}
 	} else {
-		_, err = ctx.Write(cachedResponse.body)
+		_, err = ctx.Write(cachedResponse.Body)
 	}
 	if err != nil {
-		fmt.Printf("Couldn't write body for \"%s\": %s\n", req.RequestURI(), err)
+		fmt.Printf("Couldn't write body for \"%s\": %s\n", uri, err)
 		html.ReturnErrorPage(ctx, fasthttp.StatusInternalServerError)
 		return true
 	}
 	log.Debug().Msg("response")
 
 	if res != nil && ctx.Err() == nil {
-		cachedResponse.exists = true
-		cachedResponse.mimeType = mimeType
-		cachedResponse.body = cacheBodyWriter.Bytes()
-		_ = fileResponseCache.Set(uri+"?timestamp="+strconv.FormatInt(o.BranchTimestamp.Unix(), 10), cachedResponse, fileCacheTimeout)
+		cachedResponse.Exists = true
+		cachedResponse.MimeType = mimeType
+		cachedResponse.Body = cacheBodyWriter.Bytes()
+		_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), cachedResponse, fileCacheTimeout)
 	}
 
 	return true

From 35b35c5d67c83da87b6f4eeac5e6bd6242f2d807 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Sat, 11 Jun 2022 23:17:43 +0200
Subject: [PATCH 011/196] Add integration tests (#86)

close #82
close #32

make sure we dont get regressions again ... as we currently have in **main**

followups:
 - create a DNS subdomayn specific to redirect to mock url ...

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/86
Reviewed-by: crapStone <crapstone@noreply.codeberg.org>
---
 .woodpecker.yml          | 17 ++++++++--
 Justfile                 |  2 +-
 go.mod                   |  1 +
 go.sum                   |  2 ++
 integration/get_test.go  | 69 ++++++++++++++++++++++++++++++++++++++++
 integration/main_test.go | 62 ++++++++++++++++++++++++++++++++++++
 main.go                  |  1 +
 7 files changed, 150 insertions(+), 4 deletions(-)
 create mode 100644 integration/get_test.go
 create mode 100644 integration/main_test.go

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 271aaca..e7fa66a 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -8,19 +8,30 @@ pipeline:
       - go mod vendor
 
   lint:
-    image: golangci/golangci-lint:v1.45.2
+    image: golangci/golangci-lint:latest
+    pull: true
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest
       - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
-      - golangci-lint run --timeout 5m
+      - golangci-lint run --timeout 5m --build-tags integration
 
   test:
     image: golang:1.18
     commands:
-      - go test ./...
+      - go test -race codeberg.org/codeberg/pages/server/...
 
   build:
     image: golang:1.18
     commands:
       - go build
+
+  integration-tests:
+    image: golang:1.18
+    commands:
+      - go test -race -tags integration codeberg.org/codeberg/pages/integration/...
+    environment:
+      - ACME_API=https://acme.mock.directory
+      - PAGES_DOMAIN=localhost.mock.directory
+      - RAW_DOMAIN=raw.localhost.mock.directory
+      - PORT=4430
\ No newline at end of file
diff --git a/Justfile b/Justfile
index bab0a1e..c5c5a08 100644
--- a/Justfile
+++ b/Justfile
@@ -13,7 +13,7 @@ build:
 
 lint: tool-golangci tool-gofumpt
     [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
-    golangci-lint run --timeout 5m
+    golangci-lint run --timeout 5m --build-tags integration
 
 fmt: tool-gofumpt
     gofumpt -w --extra .
diff --git a/go.mod b/go.mod
index 38a289a..13df431 100644
--- a/go.mod
+++ b/go.mod
@@ -60,6 +60,7 @@ require (
 	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
 	github.com/jarcoal/httpmock v1.0.6 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/joho/godotenv v1.4.0 // indirect
 	github.com/json-iterator/go v1.1.7 // indirect
 	github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
 	github.com/klauspost/compress v1.13.4 // indirect
diff --git a/go.sum b/go.sum
index a443641..f009f5e 100644
--- a/go.sum
+++ b/go.sum
@@ -266,6 +266,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
+github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
diff --git a/integration/get_test.go b/integration/get_test.go
new file mode 100644
index 0000000..3f2048e
--- /dev/null
+++ b/integration/get_test.go
@@ -0,0 +1,69 @@
+//go:build integration
+// +build integration
+
+package integration
+
+import (
+	"bytes"
+	"crypto/tls"
+	"io"
+	"net/http"
+	"net/http/cookiejar"
+	"testing"
+
+	"github.com/rs/zerolog/log"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetRedirect(t *testing.T) {
+	log.Printf("== TestGetRedirect ==\n")
+	// test custom domain redirect
+	resp, err := getTestHTTPSClient().Get("https://calciumdibromid.localhost.mock.directory:4430")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusTemporaryRedirect, resp.StatusCode) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, "https://www.cabr2.de/", resp.Header["Location"][0])
+	assert.EqualValues(t, 0, getSize(resp.Body))
+}
+
+func TestGetContent(t *testing.T) {
+	log.Printf("== TestGetContent ==\n")
+	// test get image
+	resp, err := getTestHTTPSClient().Get("https://magiclike.localhost.mock.directory:4430/images/827679288a.jpg")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, "image/jpeg", resp.Header["Content-Type"][0])
+	assert.EqualValues(t, "124635", resp.Header["Content-Length"][0])
+	assert.EqualValues(t, 124635, getSize(resp.Body))
+
+	// specify branch
+	resp, err = getTestHTTPSClient().Get("https://momar.localhost.mock.directory:4430/pag/@master/")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header["Content-Type"][0])
+	assert.True(t, getSize(resp.Body) > 1000)
+}
+
+func getTestHTTPSClient() *http.Client {
+	cookieJar, _ := cookiejar.New(nil)
+	return &http.Client{
+		Jar: cookieJar,
+		CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
+			return http.ErrUseLastResponse
+		},
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+		},
+	}
+}
+
+func getSize(stream io.Reader) int {
+	buf := new(bytes.Buffer)
+	_, _ = buf.ReadFrom(stream)
+	return buf.Len()
+}
diff --git a/integration/main_test.go b/integration/main_test.go
new file mode 100644
index 0000000..43f6792
--- /dev/null
+++ b/integration/main_test.go
@@ -0,0 +1,62 @@
+//go:build integration
+// +build integration
+
+package integration
+
+import (
+	"context"
+	"os"
+	"testing"
+	"time"
+
+	"codeberg.org/codeberg/pages/cmd"
+
+	"github.com/rs/zerolog/log"
+	"github.com/urfave/cli/v2"
+)
+
+func TestMain(m *testing.M) {
+	log.Printf("=== TestMain: START Server ==\n")
+	serverCtx, serverCancel := context.WithCancel(context.Background())
+	if err := startServer(serverCtx); err != nil {
+		log.Fatal().Msgf("could not start server: %v", err)
+	}
+	defer func() {
+		serverCancel()
+		log.Printf("=== TestMain: Server STOPED ==\n")
+	}()
+
+	time.Sleep(20 * time.Second)
+
+	os.Exit(m.Run())
+}
+
+func startServer(ctx context.Context) error {
+	args := []string{
+		"--verbose",
+		"--acme-accept-terms", "true",
+	}
+	setEnvIfNotSet("ACME_API", "https://acme.mock.directory")
+	setEnvIfNotSet("PAGES_DOMAIN", "localhost.mock.directory")
+	setEnvIfNotSet("RAW_DOMAIN", "raw.localhost.mock.directory")
+	setEnvIfNotSet("PORT", "4430")
+
+	app := cli.NewApp()
+	app.Name = "pages-server"
+	app.Action = cmd.Serve
+	app.Flags = cmd.ServeFlags
+
+	go func() {
+		if err := app.RunContext(ctx, args); err != nil {
+			log.Fatal().Msgf("run server error: %v", err)
+		}
+	}()
+
+	return nil
+}
+
+func setEnvIfNotSet(key, value string) {
+	if _, set := os.LookupEnv(key); !set {
+		os.Setenv(key, value)
+	}
+}
diff --git a/main.go b/main.go
index 205ec93..2836b86 100644
--- a/main.go
+++ b/main.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 
+	_ "github.com/joho/godotenv/autoload"
 	"github.com/urfave/cli/v2"
 
 	"codeberg.org/codeberg/pages/cmd"

From 38fb28f84f5a2a9b3f993ef6b178472187655aa1 Mon Sep 17 00:00:00 2001
From: crystal <crystal@noreply.codeberg.org>
Date: Sun, 12 Jun 2022 03:50:00 +0200
Subject: [PATCH 012/196] implement custom 404 pages (#81)

solves #56.

- The expected filename is `404.html`, like GitHub Pages
- Each repo/branch can have one `404.html` file at it's root
- If a repo does not have a `pages` branch, the 404.html file from the `pages` repository is used
- You get status code 404 (unless you request /404.html which returns 200)
- The error page is cached

---
close #56

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/81
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: crystal <crystal@noreply.codeberg.org>
Co-committed-by: crystal <crystal@noreply.codeberg.org>
---
 integration/get_test.go     | 13 +++++++++++++
 server/upstream/upstream.go | 26 ++++++++++++++++++++++++--
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index 3f2048e..ef00531 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -49,6 +49,19 @@ func TestGetContent(t *testing.T) {
 	assert.True(t, getSize(resp.Body) > 1000)
 }
 
+func TestGetNotFound(t *testing.T) {
+	log.Printf("== TestGetNotFound ==\n")
+	// test custom not found pages
+	resp, err := getTestHTTPSClient().Get("https://crystal.localhost.mock.directory:4430/pages-404-demo/blah")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusNotFound, resp.StatusCode) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header["Content-Type"][0])
+	assert.EqualValues(t, "37", resp.Header["Content-Length"][0])
+	assert.EqualValues(t, 37, getSize(resp.Body))
+}
+
 func getTestHTTPSClient() *http.Client {
 	cookieJar, _ := cookiejar.New(nil)
 	return &http.Client{
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index edf4f3f..da97021 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -21,6 +21,11 @@ var upstreamIndexPages = []string{
 	"index.html",
 }
 
+// upstreamNotFoundPages lists pages that may be considered as custom 404 Not Found pages.
+var upstreamNotFoundPages = []string{
+	"404.html",
+}
+
 // Options provides various options for the upstream request.
 type Options struct {
 	TargetOwner,
@@ -107,6 +112,21 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 			}
 		}
 		ctx.Response.SetStatusCode(fasthttp.StatusNotFound)
+		if o.TryIndexPages {
+			// copy the o struct & try if a not found page exists
+			optionsForNotFoundPages := *o
+			optionsForNotFoundPages.TryIndexPages = false
+			optionsForNotFoundPages.appendTrailingSlash = false
+			for _, notFoundPage := range upstreamNotFoundPages {
+				optionsForNotFoundPages.TargetPath = "/" + notFoundPage
+				if optionsForNotFoundPages.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
+					_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
+						Exists: false,
+					}, fileCacheTimeout)
+					return true
+				}
+			}
+		}
 		if res != nil {
 			// Update cache if the request is fresh
 			_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
@@ -141,8 +161,10 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	mimeType := o.getMimeTypeByExtension()
 	ctx.Response.Header.SetContentType(mimeType)
 
-	// Everything's okay so far
-	ctx.Response.SetStatusCode(fasthttp.StatusOK)
+	if ctx.Response.StatusCode() != fasthttp.StatusNotFound {
+		// Everything's okay so far
+		ctx.Response.SetStatusCode(fasthttp.StatusOK)
+	}
 	ctx.Response.Header.SetLastModified(o.BranchTimestamp)
 
 	log.Debug().Msg("response preparations")

From 913f762eb0b4bce42e74b921dd2788ca8c0da7e9 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Mon, 13 Jun 2022 14:43:49 +0200
Subject: [PATCH 013/196] Add integration test for custom domain (#90)

and some nits

---
close #89

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/90
---
 Justfile                 |  6 ++++++
 integration/get_test.go  | 18 +++++++++++++++---
 integration/main_test.go |  4 ++--
 3 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/Justfile b/Justfile
index c5c5a08..720c0f1 100644
--- a/Justfile
+++ b/Justfile
@@ -27,3 +27,9 @@ tool-gofumpt:
     @hash gofumpt> /dev/null 2>&1; if [ $? -ne 0 ]; then \
     go install mvdan.cc/gofumpt@latest; \
     fi
+
+test:
+    go test -race codeberg.org/codeberg/pages/server/...
+
+integration:
+    go test -race -tags integration codeberg.org/codeberg/pages/integration/...
diff --git a/integration/get_test.go b/integration/get_test.go
index ef00531..973b049 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -16,7 +16,7 @@ import (
 )
 
 func TestGetRedirect(t *testing.T) {
-	log.Printf("== TestGetRedirect ==\n")
+	log.Printf("=== TestGetRedirect ===\n")
 	// test custom domain redirect
 	resp, err := getTestHTTPSClient().Get("https://calciumdibromid.localhost.mock.directory:4430")
 	assert.NoError(t, err)
@@ -28,7 +28,7 @@ func TestGetRedirect(t *testing.T) {
 }
 
 func TestGetContent(t *testing.T) {
-	log.Printf("== TestGetContent ==\n")
+	log.Printf("=== TestGetContent ===\n")
 	// test get image
 	resp, err := getTestHTTPSClient().Get("https://magiclike.localhost.mock.directory:4430/images/827679288a.jpg")
 	assert.NoError(t, err)
@@ -49,8 +49,20 @@ func TestGetContent(t *testing.T) {
 	assert.True(t, getSize(resp.Body) > 1000)
 }
 
+func TestCustomDomain(t *testing.T) {
+	log.Printf("=== TestCustomDomain ===\n")
+	resp, err := getTestHTTPSClient().Get("https://mock-pages.codeberg-test.org:4430/README.md")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, "text/markdown; charset=utf-8", resp.Header["Content-Type"][0])
+	assert.EqualValues(t, "106", resp.Header["Content-Length"][0])
+	assert.EqualValues(t, 106, getSize(resp.Body))
+}
+
 func TestGetNotFound(t *testing.T) {
-	log.Printf("== TestGetNotFound ==\n")
+	log.Printf("=== TestGetNotFound ===\n")
 	// test custom not found pages
 	resp, err := getTestHTTPSClient().Get("https://crystal.localhost.mock.directory:4430/pages-404-demo/blah")
 	assert.NoError(t, err)
diff --git a/integration/main_test.go b/integration/main_test.go
index 43f6792..8104413 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -16,14 +16,14 @@ import (
 )
 
 func TestMain(m *testing.M) {
-	log.Printf("=== TestMain: START Server ==\n")
+	log.Printf("=== TestMain: START Server ===\n")
 	serverCtx, serverCancel := context.WithCancel(context.Background())
 	if err := startServer(serverCtx); err != nil {
 		log.Fatal().Msgf("could not start server: %v", err)
 	}
 	defer func() {
 		serverCancel()
-		log.Printf("=== TestMain: Server STOPED ==\n")
+		log.Printf("=== TestMain: Server STOPED ===\n")
 	}()
 
 	time.Sleep(20 * time.Second)

From cc32bab31fa54e4614b539a712fa015dd8af4bfd Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Mon, 13 Jun 2022 20:07:32 +0200
Subject: [PATCH 014/196] Enhance joinURL and return error on gitea client on
 start instead while running (#88)

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/88
---
 cmd/main.go                 |  5 ++++-
 server/gitea/client.go      | 23 +++++++++++++++++++----
 server/gitea/client_test.go | 22 +++++++++++-----------
 server/handler_test.go      |  2 +-
 4 files changed, 35 insertions(+), 17 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go
index 257b724..80fb666 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -82,7 +82,10 @@ func Serve(ctx *cli.Context) error {
 	// TODO: make this an MRU cache with a size limit
 	fileResponseCache := cache.NewKeyValueCache()
 
-	giteaClient := gitea.NewClient(giteaRoot, giteaAPIToken)
+	giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken)
+	if err != nil {
+		return fmt.Errorf("could not create new gitea client: %v", err)
+	}
 
 	// Create handler based on settings
 	handler := server.Handler(mainDomainSuffix, []byte(rawDomain),
diff --git a/server/gitea/client.go b/server/gitea/client.go
index d4eb980..5410413 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"net/url"
-	"path"
+	"strings"
 	"time"
 
 	"github.com/valyala/fasthttp"
@@ -29,18 +29,33 @@ type FileResponse struct {
 	Body     []byte
 }
 
-func joinURL(giteaRoot string, paths ...string) string { return giteaRoot + path.Join(paths...) }
+// TODO: once golang v1.19 is min requirement, we can switch to 'JoinPath()' of 'net/url' package
+func joinURL(baseURL string, paths ...string) string {
+	p := make([]string, 0, len(paths))
+	for i := range paths {
+		path := strings.TrimSpace(paths[i])
+		path = strings.Trim(path, "/")
+		if len(path) != 0 {
+			p = append(p, path)
+		}
+	}
+
+	return baseURL + "/" + strings.Join(p, "/")
+}
 
 func (f FileResponse) IsEmpty() bool { return len(f.Body) != 0 }
 
-func NewClient(giteaRoot, giteaAPIToken string) *Client {
+func NewClient(giteaRoot, giteaAPIToken string) (*Client, error) {
+	rootURL, err := url.Parse(giteaRoot)
+	giteaRoot = strings.Trim(rootURL.String(), "/")
+
 	return &Client{
 		giteaRoot:      giteaRoot,
 		giteaAPIToken:  giteaAPIToken,
 		infoTimeout:    5 * time.Second,
 		contentTimeout: 10 * time.Second,
 		fastClient:     getFastHTTPClient(),
-	}
+	}, err
 }
 
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
diff --git a/server/gitea/client_test.go b/server/gitea/client_test.go
index bae9d4e..7dbad68 100644
--- a/server/gitea/client_test.go
+++ b/server/gitea/client_test.go
@@ -1,23 +1,23 @@
 package gitea
 
 import (
+	"net/url"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestJoinURL(t *testing.T) {
-	url := joinURL("")
-	assert.EqualValues(t, "", url)
+	baseURL := ""
+	assert.EqualValues(t, "/", joinURL(baseURL))
+	assert.EqualValues(t, "/", joinURL(baseURL, "", ""))
 
-	url = joinURL("", "", "")
-	assert.EqualValues(t, "", url)
+	baseURL = "http://wwow.url.com"
+	assert.EqualValues(t, "http://wwow.url.com/a/b/c/d", joinURL(baseURL, "a", "b/c/", "d"))
 
-	url = joinURL("http://wwow.url.com", "a", "b/c/", "d")
-	// assert.EqualValues(t, "http://wwow.url.com/a/b/c/d", url)
-	assert.EqualValues(t, "http://wwow.url.coma/b/c/d", url)
-
-	url = joinURL("h:://wrong", "acdc")
-	// assert.EqualValues(t, "h:://wrong/acdc", url)
-	assert.EqualValues(t, "h:://wrongacdc", url)
+	baseURL = "http://wow.url.com/subpath/2"
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/content.pdf", joinURL(baseURL, "/content.pdf"))
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/wonderful.jpg", joinURL(baseURL, "wonderful.jpg"))
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/raw/wonderful.jpg?ref=main", joinURL(baseURL, "raw", "wonderful.jpg"+"?ref="+url.QueryEscape("main")))
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/raw/wonderful.jpg%3Fref=main", joinURL(baseURL, "raw", "wonderful.jpg%3Fref=main"))
 }
diff --git a/server/handler_test.go b/server/handler_test.go
index 73002a2..23d9af5 100644
--- a/server/handler_test.go
+++ b/server/handler_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestHandlerPerformance(t *testing.T) {
 	giteaRoot := "https://codeberg.org"
-	giteaClient := gitea.NewClient(giteaRoot, "")
+	giteaClient, _ := gitea.NewClient(giteaRoot, "")
 	testHandler := Handler(
 		[]byte("codeberg.page"), []byte("raw.codeberg.org"),
 		giteaClient,

From 4c6164ef05d6f6ffe45a50527e24423776b49421 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Tue, 14 Jun 2022 18:23:34 +0200
Subject: [PATCH 015/196] Propagate ETag from gitea (#93)

close #15

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/93
---
 integration/get_test.go     | 18 ++++++++++--------
 server/gitea/client.go      |  1 +
 server/upstream/const.go    |  1 +
 server/upstream/upstream.go |  8 ++++++++
 4 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index 973b049..9cb66f5 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -23,7 +23,7 @@ func TestGetRedirect(t *testing.T) {
 	if !assert.EqualValues(t, http.StatusTemporaryRedirect, resp.StatusCode) {
 		t.FailNow()
 	}
-	assert.EqualValues(t, "https://www.cabr2.de/", resp.Header["Location"][0])
+	assert.EqualValues(t, "https://www.cabr2.de/", resp.Header.Get("Location"))
 	assert.EqualValues(t, 0, getSize(resp.Body))
 }
 
@@ -35,9 +35,10 @@ func TestGetContent(t *testing.T) {
 	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
 		t.FailNow()
 	}
-	assert.EqualValues(t, "image/jpeg", resp.Header["Content-Type"][0])
-	assert.EqualValues(t, "124635", resp.Header["Content-Length"][0])
+	assert.EqualValues(t, "image/jpeg", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "124635", resp.Header.Get("Content-Length"))
 	assert.EqualValues(t, 124635, getSize(resp.Body))
+	assert.Len(t, resp.Header.Get("ETag"), 42)
 
 	// specify branch
 	resp, err = getTestHTTPSClient().Get("https://momar.localhost.mock.directory:4430/pag/@master/")
@@ -45,8 +46,9 @@ func TestGetContent(t *testing.T) {
 	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
 		t.FailNow()
 	}
-	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header["Content-Type"][0])
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
 	assert.True(t, getSize(resp.Body) > 1000)
+	assert.Len(t, resp.Header.Get("ETag"), 42)
 }
 
 func TestCustomDomain(t *testing.T) {
@@ -56,8 +58,8 @@ func TestCustomDomain(t *testing.T) {
 	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
 		t.FailNow()
 	}
-	assert.EqualValues(t, "text/markdown; charset=utf-8", resp.Header["Content-Type"][0])
-	assert.EqualValues(t, "106", resp.Header["Content-Length"][0])
+	assert.EqualValues(t, "text/markdown; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "106", resp.Header.Get("Content-Length"))
 	assert.EqualValues(t, 106, getSize(resp.Body))
 }
 
@@ -69,8 +71,8 @@ func TestGetNotFound(t *testing.T) {
 	if !assert.EqualValues(t, http.StatusNotFound, resp.StatusCode) {
 		t.FailNow()
 	}
-	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header["Content-Type"][0])
-	assert.EqualValues(t, "37", resp.Header["Content-Length"][0])
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "37", resp.Header.Get("Content-Length"))
 	assert.EqualValues(t, 37, getSize(resp.Body))
 }
 
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 5410413..7b5d009 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -25,6 +25,7 @@ type Client struct {
 
 type FileResponse struct {
 	Exists   bool
+	ETag     []byte
 	MimeType string
 	Body     []byte
 }
diff --git a/server/upstream/const.go b/server/upstream/const.go
index ce76e21..247e1d1 100644
--- a/server/upstream/const.go
+++ b/server/upstream/const.go
@@ -12,6 +12,7 @@ var branchExistenceCacheTimeout = 5 * time.Minute
 
 // fileCacheTimeout specifies the timeout for the file content cache - you might want to make this quite long, depending
 // on your available memory.
+// TODO: move as option into cache interface
 var fileCacheTimeout = 5 * time.Minute
 
 // fileCacheSizeLimit limits the maximum file size that will be cached, and is set to 1 MB by default.
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index da97021..7c9a035 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -161,6 +161,14 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	mimeType := o.getMimeTypeByExtension()
 	ctx.Response.Header.SetContentType(mimeType)
 
+	// Set ETag
+	if cachedResponse.Exists {
+		ctx.Response.Header.SetBytesV(fasthttp.HeaderETag, cachedResponse.ETag)
+	} else if res != nil {
+		cachedResponse.ETag = res.Header.Peek(fasthttp.HeaderETag)
+		ctx.Response.Header.SetBytesV(fasthttp.HeaderETag, cachedResponse.ETag)
+	}
+
 	if ctx.Response.StatusCode() != fasthttp.StatusNotFound {
 		// Everything's okay so far
 		ctx.Response.SetStatusCode(fasthttp.StatusOK)

From 6dedd55eb38139742ffac62f3323355f1feb5068 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Tue, 14 Jun 2022 20:35:11 +0200
Subject: [PATCH 016/196] Release via CI (#94)

* release via CI
* general CI improvements

close #76, close #92

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/94
---
 .woodpecker.yml           | 52 +++++++++++++++++++++++++++++++--------
 Justfile                  |  9 +++++++
 integration/main_test.go  |  2 +-
 server/handler.go         |  3 ++-
 server/version/version.go |  3 +++
 5 files changed, 57 insertions(+), 12 deletions(-)
 create mode 100644 server/version/version.go

diff --git a/.woodpecker.yml b/.woodpecker.yml
index e7fa66a..081d131 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -9,6 +9,7 @@ pipeline:
 
   lint:
     image: golangci/golangci-lint:latest
+    group: compliant
     pull: true
     commands:
       - go version
@@ -16,22 +17,53 @@ pipeline:
       - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
       - golangci-lint run --timeout 5m --build-tags integration
 
-  test:
-    image: golang:1.18
-    commands:
-      - go test -race codeberg.org/codeberg/pages/server/...
-
   build:
-    image: golang:1.18
+    group: compliant
+    image: a6543/golang_just
     commands:
-      - go build
+      - go version
+      - just build
+    when:
+      event: [ "pull_request", "push" ]
+
+  build-tag:
+    group: compliant
+    image: a6543/golang_just
+    commands:
+      - go version
+      - just build-tag ${CI_COMMIT_TAG##v}
+    when:
+      event: [ "tag" ]
+
+  test:
+    image: a6543/golang_just
+    group: test
+    commands:
+      - just test
 
   integration-tests:
-    image: golang:1.18
+    image: a6543/golang_just
+    group: test
     commands:
-      - go test -race -tags integration codeberg.org/codeberg/pages/integration/...
+      - just integration
     environment:
       - ACME_API=https://acme.mock.directory
       - PAGES_DOMAIN=localhost.mock.directory
       - RAW_DOMAIN=raw.localhost.mock.directory
-      - PORT=4430
\ No newline at end of file
+      - PORT=4430
+
+  release:
+    image: plugins/gitea-release
+    settings:
+      base_url: https://codeberg.org
+      file_exists: overwrite
+      files: build/codeberg-pages-server
+      api_key:
+        from_secret: bot_token
+    environment:
+      - DRONE_REPO_OWNER=${CI_REPO_OWNER}
+      - DRONE_REPO_NAME=${CI_REPO_NAME}
+      - DRONE_BUILD_EVENT=${CI_BUILD_EVENT}
+      - DRONE_COMMIT_REF=${CI_COMMIT_REF}
+    when:
+      event: [ "tag" ]
diff --git a/Justfile b/Justfile
index 720c0f1..a908e7a 100644
--- a/Justfile
+++ b/Justfile
@@ -11,6 +11,9 @@ dev:
 build:
     CGO_ENABLED=0 go build -ldflags '-s -w' -v -o build/codeberg-pages-server ./
 
+build-tag VERSION:
+    CGO_ENABLED=0 go build -ldflags '-s -w -X "codeberg.org/codeberg/pages/server/version.Version={{VERSION}}"' -v -o build/codeberg-pages-server ./
+
 lint: tool-golangci tool-gofumpt
     [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
     golangci-lint run --timeout 5m --build-tags integration
@@ -31,5 +34,11 @@ tool-gofumpt:
 test:
     go test -race codeberg.org/codeberg/pages/server/...
 
+test-run TEST:
+    go test -race -run "^{{TEST}}$" codeberg.org/codeberg/pages/server/...
+
 integration:
     go test -race -tags integration codeberg.org/codeberg/pages/integration/...
+
+integration-run TEST:
+    go test -race -tags integration -run "^{{TEST}}$" codeberg.org/codeberg/pages/integration/...
\ No newline at end of file
diff --git a/integration/main_test.go b/integration/main_test.go
index 8104413..c8f524f 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -26,7 +26,7 @@ func TestMain(m *testing.M) {
 		log.Printf("=== TestMain: Server STOPED ===\n")
 	}()
 
-	time.Sleep(20 * time.Second)
+	time.Sleep(10 * time.Second)
 
 	os.Exit(m.Run())
 }
diff --git a/server/handler.go b/server/handler.go
index bda7bd0..3a2667d 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -14,6 +14,7 @@ import (
 	"codeberg.org/codeberg/pages/server/gitea"
 	"codeberg.org/codeberg/pages/server/upstream"
 	"codeberg.org/codeberg/pages/server/utils"
+	"codeberg.org/codeberg/pages/server/version"
 )
 
 // Handler handles a single HTTP request to the web server.
@@ -26,7 +27,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 	return func(ctx *fasthttp.RequestCtx) {
 		log := log.With().Str("Handler", string(ctx.Request.Header.RequestURI())).Logger()
 
-		ctx.Response.Header.Set("Server", "Codeberg Pages")
+		ctx.Response.Header.Set("Server", "CodebergPages/"+version.Version)
 
 		// Force new default from specification (since November 2020) - see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
 		ctx.Response.Header.Set("Referrer-Policy", "strict-origin-when-cross-origin")
diff --git a/server/version/version.go b/server/version/version.go
new file mode 100644
index 0000000..aa2cbb5
--- /dev/null
+++ b/server/version/version.go
@@ -0,0 +1,3 @@
+package version
+
+var Version string = "dev"

From 48a49f69a7dfe8faa584285a3f5fc0fa48fbd133 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Sun, 3 Jul 2022 13:20:02 +0200
Subject: [PATCH 017/196] Increase concurrent connections to default value 
 (#99)

Use the default value of `256 * 1024` for the concurrency limit, this will mean that the server will be able to handle more connections.

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/99
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/setup.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/server/setup.go b/server/setup.go
index c186222..027b3e1 100644
--- a/server/setup.go
+++ b/server/setup.go
@@ -21,7 +21,6 @@ func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server {
 		NoDefaultServerHeader:        true,
 		NoDefaultDate:                true,
 		ReadTimeout:                  30 * time.Second, // needs to be this high for ACME certificates with ZeroSSL & HTTP-01 challenge
-		Concurrency:                  1024 * 32,        // TODO: adjust bottlenecks for best performance with Gitea!
 	}
 }
 

From 9076bc3f75e05af80d2b812c7c1f72b69e1ce86f Mon Sep 17 00:00:00 2001
From: Gary Wang <blumia@noreply.codeberg.org>
Date: Fri, 8 Jul 2022 13:39:24 +0200
Subject: [PATCH 018/196] Support access branch that contains slash character
 (#102)

So we can access branch that contain slash like `branch/name` with `username.codeberg.page/repo/@branch~name/`.

Branch name cannot contain `~` character but it can be in a HTTP URL, so replace the `~` from URL to `/` could be a valid solution to me.

Resolve #101

Co-authored-by: Gary Wang <wzc782970009@gmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/102
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Gary Wang <blumia@noreply.codeberg.org>
Co-committed-by: Gary Wang <blumia@noreply.codeberg.org>
---
 README.md               |  3 ++-
 integration/get_test.go | 10 ++++++++++
 server/handler.go       |  4 ++++
 3 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 6543c90..700f279 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,8 @@ Mapping custom domains is not static anymore, but can be done with DNS:
 first line will be the canonical domain/URL; all other occurrences will be redirected to it.
 
 2) add a CNAME entry to your domain, pointing to `[[{branch}.]{repo}.]{owner}.codeberg.page` (repo defaults to
-"pages", "branch" defaults to the default branch if "repo" is "pages", or to "pages" if "repo" is something else):
+"pages", "branch" defaults to the default branch if "repo" is "pages", or to "pages" if "repo" is something else.
+If the branch name contains slash characters, you need to replace "/" in the branch name to "~"):
 	`www.example.org. IN CNAME main.pages.example.codeberg.page.`
 
 3) if a CNAME is set for "www.example.org", you can redirect there from the naked domain by adding an ALIAS record
diff --git a/integration/get_test.go b/integration/get_test.go
index 9cb66f5..1997010 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -49,6 +49,16 @@ func TestGetContent(t *testing.T) {
 	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
 	assert.True(t, getSize(resp.Body) > 1000)
 	assert.Len(t, resp.Header.Get("ETag"), 42)
+
+	// access branch name contains '/'
+	resp, err = getTestHTTPSClient().Get("https://blumia.localhost.mock.directory:4430/pages-server-integration-tests/@docs~main/")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.True(t, getSize(resp.Body) > 100)
+	assert.Len(t, resp.Header.Get("ETag"), 42)
 }
 
 func TestCustomDomain(t *testing.T) {
diff --git a/server/handler.go b/server/handler.go
index 3a2667d..11da0a5 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -89,6 +89,10 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 				return false
 			}
 
+			// Replace "~" to "/" so we can access branch that contains slash character
+			// Branch name cannot contain "~" so doing this is okay
+			branch = strings.ReplaceAll(branch, "~", "/")
+
 			// Check if the branch exists, otherwise treat it as a file path
 			branchTimestampResult := upstream.GetBranchTimestamp(giteaClient, targetOwner, repo, branch, branchTimestampCache)
 			if branchTimestampResult == nil {

From fd24b4a2bc7f6210bc18da09903accd2235697c2 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Tue, 12 Jul 2022 15:32:48 +0200
Subject: [PATCH 019/196] Pass logger to fasthttp (#98)

- Use a logger with `FASTHTTP` prefix as fasthttp's logger so it's easy to see what fasthttp is logging in console/journal.

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/98
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/setup.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/server/setup.go b/server/setup.go
index 027b3e1..ea96d1e 100644
--- a/server/setup.go
+++ b/server/setup.go
@@ -5,12 +5,20 @@ import (
 	"net/http"
 	"time"
 
+	"github.com/rs/zerolog/log"
+
 	"github.com/valyala/fasthttp"
 
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/utils"
 )
 
+type fasthttpLogger struct{}
+
+func (fasthttpLogger) Printf(format string, args ...interface{}) {
+	log.Printf("[FASTHTTP] "+format, args...)
+}
+
 func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server {
 	// Enable compression by wrapping the handler with the compression function provided by FastHTTP
 	compressedHandler := fasthttp.CompressHandlerBrotliLevel(handler, fasthttp.CompressBrotliBestSpeed, fasthttp.CompressBestSpeed)
@@ -21,6 +29,7 @@ func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server {
 		NoDefaultServerHeader:        true,
 		NoDefaultDate:                true,
 		ReadTimeout:                  30 * time.Second, // needs to be this high for ACME certificates with ZeroSSL & HTTP-01 challenge
+		Logger:                       fasthttpLogger{},
 	}
 }
 

From baf4e7e326baea1a08e539d89f7c73c2980152ee Mon Sep 17 00:00:00 2001
From: Jeremy <jtbx@noreply.codeberg.org>
Date: Fri, 15 Jul 2022 17:18:25 +0200
Subject: [PATCH 020/196] Make the 404 page more readable and natural (#104)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/104
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Jeremy <jtbx@noreply.codeberg.org>
Co-committed-by: Jeremy <jtbx@noreply.codeberg.org>
---
 html/404.html | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/html/404.html b/html/404.html
index 08ab16d..b7ec96e 100644
--- a/html/404.html
+++ b/html/404.html
@@ -23,11 +23,11 @@
 <body>
     <i class="fa fa-search text-primary" style="font-size: 96px;"></i>
     <h1 class="mb-0 text-primary">
-		This page was not found!
+		Page not found!
     </h1>
     <h5 class="text-center" style="max-width: 25em;">
-    	Sorry, this page doesn't exist or is inaccessible for other reasons (%status).<br/>
-		We hope this is not our fault ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
+    	Sorry, but this page couldn't be found or is inaccessible (%status).<br/>
+		We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
     </h5>
     <small class="text-muted">
         <img src="https://design.codeberg.org/logo-kit/icon.svg" class="align-top">

From 5411c96ef391e84cef358258272bf0e6fd406f59 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Fri, 15 Jul 2022 21:06:05 +0200
Subject: [PATCH 021/196] Tell fasthttp to not set "Content-Length: 0" on non
 cached content (#107)

fix #97

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/107
---
 integration/get_test.go     | 2 ++
 server/gitea/client.go      | 1 -
 server/upstream/upstream.go | 3 +++
 3 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index 1997010..8af40f0 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -59,6 +59,8 @@ func TestGetContent(t *testing.T) {
 	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
 	assert.True(t, getSize(resp.Body) > 100)
 	assert.Len(t, resp.Header.Get("ETag"), 42)
+
+	// TODO: test get of non cachable content (content size > fileCacheSizeLimit)
 }
 
 func TestCustomDomain(t *testing.T) {
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 7b5d009..3b9ad6f 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -82,7 +82,6 @@ func (client *Client) ServeRawContent(uri string) (*fasthttp.Response, error) {
 	if err != nil {
 		return nil, err
 	}
-	// resp.SetBodyStream(&strings.Reader{}, -1)
 
 	if err != nil {
 		return nil, err
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 7c9a035..b9fc2bc 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -181,6 +181,9 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	var cacheBodyWriter bytes.Buffer
 	if res != nil {
 		if res.Header.ContentLength() > fileCacheSizeLimit {
+			// fasthttp else will set "Content-Length: 0"
+			ctx.Response.SetBodyStream(&strings.Reader{}, -1)
+
 			err = res.BodyWriteTo(ctx.Response.BodyWriter())
 		} else {
 			// TODO: cache is half-empty if request is cancelled - does the ctx.Err() below do the trick?

From bcaceda71177d701f91c01eb04213e126de71ea0 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Fri, 15 Jul 2022 21:21:26 +0200
Subject: [PATCH 022/196] dont cache if ContentLength greater
 fileCacheSizeLimit (#108)

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/108
Reviewed-by: Otto <otto@codeberg.org>
---
 Justfile                    | 4 ++++
 server/upstream/upstream.go | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/Justfile b/Justfile
index a908e7a..c824efa 100644
--- a/Justfile
+++ b/Justfile
@@ -21,6 +21,10 @@ lint: tool-golangci tool-gofumpt
 fmt: tool-gofumpt
     gofumpt -w --extra .
 
+clean:
+    go clean ./...
+    rm -rf build/
+
 tool-golangci:
     @hash golangci-lint> /dev/null 2>&1; if [ $? -ne 0 ]; then \
     go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index b9fc2bc..5e888ac 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -199,7 +199,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	}
 	log.Debug().Msg("response")
 
-	if res != nil && ctx.Err() == nil {
+	if res != nil && res.Header.ContentLength() > fileCacheSizeLimit && ctx.Err() == nil {
 		cachedResponse.Exists = true
 		cachedResponse.MimeType = mimeType
 		cachedResponse.Body = cacheBodyWriter.Bytes()

From 8207586a4818b011649dbae8ca646196a3d74b21 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 15 Jul 2022 21:39:42 +0200
Subject: [PATCH 023/196] just fix bcaceda71177d701f91c01eb04213e126de71ea0

---
 server/upstream/upstream.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 5e888ac..9b7464e 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -199,7 +199,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	}
 	log.Debug().Msg("response")
 
-	if res != nil && res.Header.ContentLength() > fileCacheSizeLimit && ctx.Err() == nil {
+	if res != nil && res.Header.ContentLength() <= fileCacheSizeLimit && ctx.Err() == nil {
 		cachedResponse.Exists = true
 		cachedResponse.MimeType = mimeType
 		cachedResponse.Body = cacheBodyWriter.Bytes()

From 00e8a41c89486e8db3f49cbae7856471e9282f5a Mon Sep 17 00:00:00 2001
From: dorianim <mail@dorian.im>
Date: Sat, 16 Jul 2022 00:59:55 +0200
Subject: [PATCH 024/196] Add Dockerfile (#111)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/111
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: dorianim <mail@dorian.im>
Co-committed-by: dorianim <mail@dorian.im>
---
 .woodpecker.yml | 11 +++++++++++
 Dockerfile      | 15 +++++++++++++++
 2 files changed, 26 insertions(+)
 create mode 100644 Dockerfile

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 081d131..b83a4dd 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -26,6 +26,17 @@ pipeline:
     when:
       event: [ "pull_request", "push" ]
 
+  build-docker:
+    image: plugins/kaniko
+    settings:
+      dockerfile: Dockerfile
+      no_push: true
+      repo: Codeberg/pages-server
+      tags: latest
+    when:
+      event: [ "pull_request", "push" ]
+      path: Dockerfile
+
   build-tag:
     group: compliant
     image: a6543/golang_just
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..71dd236
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,15 @@
+FROM golang:alpine as build
+
+WORKDIR /workspace
+
+RUN apk add ca-certificates
+COPY . .
+RUN CGO_ENABLED=0 go build .
+
+FROM scratch
+COPY --from=build /workspace/pages /pages
+COPY --from=build \
+    /etc/ssl/certs/ca-certificates.crt \
+    /etc/ssl/certs/ca-certificates.crt
+    
+ENTRYPOINT ["/pages"]
\ No newline at end of file

From e06900d5e5a38c01efea25ca72340d1c0be45321 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 8 Aug 2022 15:25:31 +0200
Subject: [PATCH 025/196] fix lint issue

---
 server/certificates/certificates.go | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 2684dfa..8761c93 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -12,7 +12,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"strconv"
 	"strings"
@@ -329,7 +328,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 	var myAcmeAccount AcmeAccount
 	var myAcmeConfig *lego.Config
 
-	if account, err := ioutil.ReadFile(configFile); err == nil {
+	if account, err := os.ReadFile(configFile); err == nil {
 		if err := json.Unmarshal(account, &myAcmeAccount); err != nil {
 			return nil, err
 		}
@@ -395,9 +394,9 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 				log.Printf("[FAIL] Error during json.Marshal(myAcmeAccount), waiting for manual restart to avoid rate limits: %s", err)
 				select {}
 			}
-			err = ioutil.WriteFile(configFile, acmeAccountJSON, 0o600)
+			err = os.WriteFile(configFile, acmeAccountJSON, 0o600)
 			if err != nil {
-				log.Printf("[FAIL] Error during ioutil.WriteFile(\"acme-account.json\"), waiting for manual restart to avoid rate limits: %s", err)
+				log.Printf("[FAIL] Error during os.WriteFile(\"acme-account.json\"), waiting for manual restart to avoid rate limits: %s", err)
 				select {}
 			}
 		}

From 876a53d9a245c11c58b61617052f7f2281d16358 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Fri, 12 Aug 2022 05:06:26 +0200
Subject: [PATCH 026/196] Improve logging (#116)

- Actually log useful information at their respective log level.
- Add logs in hot-paths to be able to deep-dive and debug specific requests (see server/handler.go)
- Add more information to existing fields(e.g. the host that the user is visiting, this was noted by @fnetX).

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/116
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 cmd/flags.go                        | 12 +++----
 cmd/main.go                         | 15 ++++----
 go.mod                              |  9 ++---
 go.sum                              | 16 +++++----
 integration/get_test.go             | 10 +++---
 integration/main_test.go            | 10 +++---
 server/certificates/certificates.go | 54 ++++++++++++++---------------
 server/database/setup.go            |  2 +-
 server/handler.go                   | 40 ++++++++++++---------
 server/setup.go                     |  4 +--
 server/try.go                       |  1 +
 server/upstream/upstream.go         | 23 ++++++------
 12 files changed, 107 insertions(+), 89 deletions(-)

diff --git a/cmd/flags.go b/cmd/flags.go
index 78040be..3c1ac99 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -5,12 +5,6 @@ import (
 )
 
 var ServeFlags = []cli.Flag{
-	&cli.BoolFlag{
-		Name: "verbose",
-		// TODO: Usage
-		EnvVars: []string{"DEBUG"},
-	},
-
 	// 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".
@@ -69,6 +63,12 @@ var ServeFlags = []cli.Flag{
 		// TODO: desc
 		EnvVars: []string{"ENABLE_HTTP_SERVER"},
 	},
+	&cli.StringFlag{
+		Name:    "log-level",
+		Value:   "warn",
+		Usage:   "specify at which log level should be logged. Possible options: info, warn, error, fatal",
+		EnvVars: []string{"LOG_LEVEL"},
+	},
 
 	// ACME
 	&cli.StringFlag{
diff --git a/cmd/main.go b/cmd/main.go
index 80fb666..f57eb60 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"net"
+	"os"
 	"strings"
 	"time"
 
@@ -36,10 +37,12 @@ var BlacklistedPaths = [][]byte{
 
 // Serve sets up and starts the web server.
 func Serve(ctx *cli.Context) error {
-	verbose := ctx.Bool("verbose")
-	if !verbose {
-		zerolog.SetGlobalLevel(zerolog.InfoLevel)
+	// Initalize the logger.
+	logLevel, err := zerolog.ParseLevel(ctx.String("log-level"))
+	if err != nil {
+		return err
 	}
+	log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel)
 
 	giteaRoot := strings.TrimSuffix(ctx.String("gitea-root"), "/")
 	giteaAPIToken := ctx.String("gitea-api-token")
@@ -101,7 +104,7 @@ func Serve(ctx *cli.Context) error {
 	log.Info().Msgf("Listening on https://%s", listeningAddress)
 	listener, err := net.Listen("tcp", listeningAddress)
 	if err != nil {
-		return fmt.Errorf("couldn't create listener: %s", err)
+		return fmt.Errorf("couldn't create listener: %v", err)
 	}
 
 	// TODO: make "key-database.pogreb" set via flag
@@ -134,7 +137,7 @@ func Serve(ctx *cli.Context) error {
 
 	if enableHTTPServer {
 		go func() {
-			log.Info().Timestamp().Msg("Start listening on :80")
+			log.Info().Msg("Start HTTP server listening on :80")
 			err := httpServer.ListenAndServe("[::]:80")
 			if err != nil {
 				log.Panic().Err(err).Msg("Couldn't start HTTP fastServer")
@@ -143,7 +146,7 @@ func Serve(ctx *cli.Context) error {
 	}
 
 	// Start the web fastServer
-	log.Info().Timestamp().Msgf("Start listening on %s", listener.Addr())
+	log.Info().Msgf("Start listening on %s", listener.Addr())
 	err = fastServer.Serve(listener)
 	if err != nil {
 		log.Panic().Err(err).Msg("Couldn't start fastServer")
diff --git a/go.mod b/go.mod
index 13df431..64288de 100644
--- a/go.mod
+++ b/go.mod
@@ -6,8 +6,8 @@ require (
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
 	github.com/akrylysov/pogreb v0.10.1
 	github.com/go-acme/lego/v4 v4.5.3
+	github.com/joho/godotenv v1.4.0
 	github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
-	github.com/rs/zerolog v1.26.0
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli/v2 v2.3.0
 	github.com/valyala/fasthttp v1.31.0
@@ -60,7 +60,6 @@ require (
 	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
 	github.com/jarcoal/httpmock v1.0.6 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
-	github.com/joho/godotenv v1.4.0 // indirect
 	github.com/json-iterator/go v1.1.7 // indirect
 	github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
 	github.com/klauspost/compress v1.13.4 // indirect
@@ -72,7 +71,8 @@ require (
 	github.com/liquidweb/go-lwApi v0.0.5 // indirect
 	github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
 	github.com/liquidweb/liquidweb-go v1.6.3 // indirect
-	github.com/mattn/go-isatty v0.0.12 // indirect
+	github.com/mattn/go-colorable v0.1.12 // indirect
+	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/miekg/dns v1.1.43 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
@@ -92,6 +92,7 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pquerna/otp v1.3.0 // indirect
+	github.com/rs/zerolog v1.27.0 // indirect
 	github.com/russross/blackfriday/v2 v2.0.1 // indirect
 	github.com/sacloud/libsacloud v1.36.2 // indirect
 	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f // indirect
@@ -111,7 +112,7 @@ require (
 	golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
 	golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
-	golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
+	golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
 	golang.org/x/text v0.3.6 // indirect
 	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 	google.golang.org/api v0.20.0 // indirect
diff --git a/go.sum b/go.sum
index f009f5e..4ac6eb6 100644
--- a/go.sum
+++ b/go.sum
@@ -95,7 +95,7 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
 github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
@@ -321,12 +321,16 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
 github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
@@ -428,8 +432,8 @@ github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad/go.mod h1:h0+DiDRe
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
-github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
+github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
+github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -510,7 +514,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -569,7 +572,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -668,8 +670,11 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -723,7 +728,6 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/integration/get_test.go b/integration/get_test.go
index 8af40f0..191fa3f 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -7,16 +7,16 @@ import (
 	"bytes"
 	"crypto/tls"
 	"io"
+	"log"
 	"net/http"
 	"net/http/cookiejar"
 	"testing"
 
-	"github.com/rs/zerolog/log"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestGetRedirect(t *testing.T) {
-	log.Printf("=== TestGetRedirect ===\n")
+	log.Println("=== TestGetRedirect ===")
 	// test custom domain redirect
 	resp, err := getTestHTTPSClient().Get("https://calciumdibromid.localhost.mock.directory:4430")
 	assert.NoError(t, err)
@@ -28,7 +28,7 @@ func TestGetRedirect(t *testing.T) {
 }
 
 func TestGetContent(t *testing.T) {
-	log.Printf("=== TestGetContent ===\n")
+	log.Println("=== TestGetContent ===")
 	// test get image
 	resp, err := getTestHTTPSClient().Get("https://magiclike.localhost.mock.directory:4430/images/827679288a.jpg")
 	assert.NoError(t, err)
@@ -64,7 +64,7 @@ func TestGetContent(t *testing.T) {
 }
 
 func TestCustomDomain(t *testing.T) {
-	log.Printf("=== TestCustomDomain ===\n")
+	log.Println("=== TestCustomDomain ===")
 	resp, err := getTestHTTPSClient().Get("https://mock-pages.codeberg-test.org:4430/README.md")
 	assert.NoError(t, err)
 	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
@@ -76,7 +76,7 @@ func TestCustomDomain(t *testing.T) {
 }
 
 func TestGetNotFound(t *testing.T) {
-	log.Printf("=== TestGetNotFound ===\n")
+	log.Println("=== TestGetNotFound ===")
 	// test custom not found pages
 	resp, err := getTestHTTPSClient().Get("https://crystal.localhost.mock.directory:4430/pages-404-demo/blah")
 	assert.NoError(t, err)
diff --git a/integration/main_test.go b/integration/main_test.go
index c8f524f..06d553f 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -5,25 +5,25 @@ package integration
 
 import (
 	"context"
+	"log"
 	"os"
 	"testing"
 	"time"
 
 	"codeberg.org/codeberg/pages/cmd"
 
-	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
 )
 
 func TestMain(m *testing.M) {
-	log.Printf("=== TestMain: START Server ===\n")
+	log.Println("=== TestMain: START Server ===")
 	serverCtx, serverCancel := context.WithCancel(context.Background())
 	if err := startServer(serverCtx); err != nil {
-		log.Fatal().Msgf("could not start server: %v", err)
+		log.Fatalf("could not start server: %v", err)
 	}
 	defer func() {
 		serverCancel()
-		log.Printf("=== TestMain: Server STOPED ===\n")
+		log.Println("=== TestMain: Server STOPED ===")
 	}()
 
 	time.Sleep(10 * time.Second)
@@ -48,7 +48,7 @@ func startServer(ctx context.Context) error {
 
 	go func() {
 		if err := app.RunContext(ctx, args); err != nil {
-			log.Fatal().Msgf("run server error: %v", err)
+			log.Fatalf("run server error: %v", err)
 		}
 	}()
 
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 8761c93..8944468 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -228,7 +228,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUs
 				res.CSR = nil // acme client doesn't like CSR to be set
 				tlsCertificate, err = obtainCert(acmeClient, []string{string(sni)}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 				if err != nil {
-					log.Printf("Couldn't renew certificate for %s: %s", sni, err)
+					log.Error().Msgf("Couldn't renew certificate for %s: %v", string(sni), err)
 				}
 			})()
 		}
@@ -271,10 +271,10 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 		if acmeUseRateLimits {
 			acmeClientRequestLimit.Take()
 		}
-		log.Printf("Renewing certificate for %v", domains)
+		log.Debug().Msgf("Renewing certificate for: %v", domains)
 		res, err = acmeClient.Certificate.Renew(*renew, true, false, "")
 		if err != nil {
-			log.Printf("Couldn't renew certificate for %v, trying to request a new one: %s", domains, err)
+			log.Error().Err(err).Msgf("Couldn't renew certificate for %v, trying to request a new one", domains)
 			res = nil
 		}
 	}
@@ -289,7 +289,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 			acmeClientOrderLimit.Take()
 			acmeClientRequestLimit.Take()
 		}
-		log.Printf("Requesting new certificate for %v", domains)
+		log.Debug().Msgf("Re-requesting new certificate for %v", domains)
 		res, err = acmeClient.Certificate.Obtain(certificate.ObtainRequest{
 			Domains:    domains,
 			Bundle:     true,
@@ -297,7 +297,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 		})
 	}
 	if err != nil {
-		log.Printf("Couldn't obtain certificate for %v: %s", domains, err)
+		log.Error().Err(err).Msgf("Couldn't obtain again a certificate or %v", domains)
 		if renew != nil && renew.CertURL != "" {
 			tlsCertificate, err := tls.X509KeyPair(renew.Certificate, renew.PrivateKey)
 			if err == nil && tlsCertificate.Leaf.NotAfter.After(time.Now()) {
@@ -311,7 +311,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 		}
 		return mockCert(domains[0], err.Error(), string(mainDomainSuffix), keyDatabase), err
 	}
-	log.Printf("Obtained certificate for %v", domains)
+	log.Debug().Msgf("Obtained certificate for %v", domains)
 
 	if err := keyDatabase.Put(name, res); err != nil {
 		return tls.Certificate{}, err
@@ -344,7 +344,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 		_, err := lego.NewClient(myAcmeConfig)
 		if err != nil {
 			// TODO: should we fail hard instead?
-			log.Printf("[ERROR] Can't create ACME client, continuing with mock certs only: %s", err)
+			log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 		}
 		return myAcmeConfig, nil
 	} else if !os.IsNotExist(err) {
@@ -365,13 +365,13 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 	myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
 	tempClient, err := lego.NewClient(myAcmeConfig)
 	if err != nil {
-		log.Printf("[ERROR] Can't create ACME client, continuing with mock certs only: %s", err)
+		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 	} else {
 		// accept terms & log in to EAB
 		if acmeEabKID == "" || acmeEabHmac == "" {
 			reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: acmeAcceptTerms})
 			if err != nil {
-				log.Printf("[ERROR] Can't register ACME account, continuing with mock certs only: %s", err)
+				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
 			} else {
 				myAcmeAccount.Registration = reg
 			}
@@ -382,7 +382,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 				HmacEncoded:          acmeEabHmac,
 			})
 			if err != nil {
-				log.Printf("[ERROR] Can't register ACME account, continuing with mock certs only: %s", err)
+				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
 			} else {
 				myAcmeAccount.Registration = reg
 			}
@@ -391,12 +391,12 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 		if myAcmeAccount.Registration != nil {
 			acmeAccountJSON, err := json.Marshal(myAcmeAccount)
 			if err != nil {
-				log.Printf("[FAIL] Error during json.Marshal(myAcmeAccount), waiting for manual restart to avoid rate limits: %s", err)
+				log.Error().Err(err).Msg("json.Marshalfailed, waiting for manual restart to avoid rate limits")
 				select {}
 			}
 			err = os.WriteFile(configFile, acmeAccountJSON, 0o600)
 			if err != nil {
-				log.Printf("[FAIL] Error during os.WriteFile(\"acme-account.json\"), waiting for manual restart to avoid rate limits: %s", err)
+				log.Error().Err(err).Msg("os.WriteFile failed, waiting for manual restart to avoid rate limits")
 				select {}
 			}
 		}
@@ -414,38 +414,38 @@ func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *
 
 	acmeClient, err = lego.NewClient(acmeConfig)
 	if err != nil {
-		log.Printf("[ERROR] Can't create ACME client, continuing with mock certs only: %s", err)
+		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 	} else {
 		err = acmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
 		if err != nil {
-			log.Printf("[ERROR] Can't create TLS-ALPN-01 provider: %s", err)
+			log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
 		}
 		if enableHTTPServer {
 			err = acmeClient.Challenge.SetHTTP01Provider(AcmeHTTPChallengeProvider{challengeCache})
 			if err != nil {
-				log.Printf("[ERROR] Can't create HTTP-01 provider: %s", err)
+				log.Error().Err(err).Msg("Can't create HTTP-01 provider")
 			}
 		}
 	}
 
 	mainDomainAcmeClient, err = lego.NewClient(acmeConfig)
 	if err != nil {
-		log.Printf("[ERROR] Can't create ACME client, continuing with mock certs only: %s", err)
+		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 	} else {
 		if dnsProvider == "" {
 			// using mock server, don't use wildcard certs
 			err := mainDomainAcmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
 			if err != nil {
-				log.Printf("[ERROR] Can't create TLS-ALPN-01 provider: %s", err)
+				log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
 			}
 		} else {
 			provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
 			if err != nil {
-				log.Printf("[ERROR] Can't create DNS Challenge provider: %s", err)
+				log.Error().Err(err).Msg("Can't create DNS Challenge provider")
 			}
 			err = mainDomainAcmeClient.Challenge.SetDNS01Provider(provider)
 			if err != nil {
-				log.Printf("[ERROR] Can't create DNS-01 provider: %s", err)
+				log.Error().Err(err).Msg("Can't create DNS-01 provider")
 			}
 		}
 	}
@@ -453,7 +453,7 @@ func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *
 	if mainCertBytes == nil {
 		_, err = obtainCert(mainDomainAcmeClient, []string{"*" + string(mainDomainSuffix), string(mainDomainSuffix[1:])}, nil, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 		if err != nil {
-			log.Printf("[ERROR] Couldn't renew main domain certificate, continuing with mock certs only: %s", err)
+			log.Error().Err(err).Msg("Couldn't renew main domain certificate, continuing with mock certs only")
 		}
 	}
 
@@ -481,7 +481,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 				if err != nil || !tlsCertificates[0].NotAfter.After(now) {
 					err := certDB.Delete(string(key))
 					if err != nil {
-						log.Printf("[ERROR] Deleting expired certificate for %s failed: %s", string(key), err)
+						log.Error().Err(err).Msgf("Deleting expired certificate for %q failed", string(key))
 					} else {
 						expiredCertCount++
 					}
@@ -489,22 +489,22 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 			}
 			key, resBytes, err = keyDatabaseIterator.Next()
 		}
-		log.Printf("[INFO] Removed %d expired certificates from the database", expiredCertCount)
+		log.Debug().Msgf("Removed %d expired certificates from the database", expiredCertCount)
 
 		// compact the database
 		msg, err := certDB.Compact()
 		if err != nil {
-			log.Printf("[ERROR] Compacting key database failed: %s", err)
+			log.Error().Err(err).Msg("Compacting key database failed")
 		} else {
-			log.Printf("[INFO] Compacted key database (%s)", msg)
+			log.Debug().Msgf("Compacted key database: %s", msg)
 		}
 
 		// update main cert
 		res, err := certDB.Get(string(mainDomainSuffix))
 		if err != nil {
-			log.Err(err).Msgf("could not get cert for domain '%s'", mainDomainSuffix)
+			log.Error().Msgf("Couldn't get cert for domain %q", mainDomainSuffix)
 		} else if res == nil {
-			log.Error().Msgf("Couldn't renew certificate for main domain: %s", "expected main domain cert to exist, but it's missing - seems like the database is corrupted")
+			log.Error().Msgf("Couldn't renew certificate for main domain %q expected main domain cert to exist, but it's missing - seems like the database is corrupted", string(mainDomainSuffix))
 		} else {
 			tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
 
@@ -513,7 +513,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 				go (func() {
 					_, err = obtainCert(mainDomainAcmeClient, []string{"*" + string(mainDomainSuffix), string(mainDomainSuffix[1:])}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 					if err != nil {
-						log.Printf("[ERROR] Couldn't renew certificate for main domain: %s", err)
+						log.Error().Err(err).Msg("Couldn't renew certificate for main domain")
 					}
 				})()
 			}
diff --git a/server/database/setup.go b/server/database/setup.go
index bbcf431..1c5a0af 100644
--- a/server/database/setup.go
+++ b/server/database/setup.go
@@ -72,7 +72,7 @@ func (p aDB) sync() {
 	for {
 		err := p.intern.Sync()
 		if err != nil {
-			log.Err(err).Msg("Syncing cert database failed")
+			log.Error().Err(err).Msg("Syncing cert database failed")
 		}
 		select {
 		case <-p.ctx.Done():
diff --git a/server/handler.go b/server/handler.go
index 11da0a5..50fbcc5 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -85,7 +85,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 		// also disallow search indexing and add a Link header to the canonical URL.
 		tryBranch := func(log zerolog.Logger, repo, branch string, path []string, canonicalLink string) bool {
 			if repo == "" {
-				log.Debug().Msg("tryBranch: repo == ''")
+				log.Debug().Msg("tryBranch: repo is empty")
 				return false
 			}
 
@@ -120,10 +120,10 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			return true
 		}
 
-		log.Debug().Msg("preparations")
+		log.Debug().Msg("Preparing")
 		if rawDomain != nil && bytes.Equal(trimmedHost, rawDomain) {
 			// Serve raw content from RawDomain
-			log.Debug().Msg("raw domain")
+			log.Debug().Msg("Serving raw domain")
 
 			targetOptions.TryIndexPages = false
 			if targetOptions.ForbiddenMimeTypes == nil {
@@ -143,28 +143,28 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 
 			// raw.codeberg.org/example/myrepo/@main/index.html
 			if len(pathElements) > 2 && strings.HasPrefix(pathElements[2], "@") {
-				log.Debug().Msg("raw domain preparations, now trying with specified branch")
+				log.Debug().Msg("Preparing raw domain, now trying with specified branch")
 				if tryBranch(log,
 					targetRepo, pathElements[2][1:], pathElements[3:],
 					giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 				) {
-					log.Debug().Msg("tryBranch, now trying upstream 1")
+					log.Info().Msg("tryBranch, now trying upstream 1")
 					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 						canonicalDomainCache, branchTimestampCache, fileResponseCache)
 					return
 				}
-				log.Debug().Msg("missing branch")
+				log.Warn().Msg("Path missed a branch")
 				html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
 				return
 			}
 
-			log.Debug().Msg("raw domain preparations, now trying with default branch")
+			log.Debug().Msg("Preparing raw domain, now trying with default branch")
 			tryBranch(log,
 				targetRepo, "", pathElements[2:],
 				giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 			)
-			log.Debug().Msg("tryBranch, now trying upstream 2")
+			log.Info().Msg("tryBranch, now trying upstream 2")
 			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 				targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 				canonicalDomainCache, branchTimestampCache, fileResponseCache)
@@ -172,7 +172,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 
 		} else if bytes.HasSuffix(trimmedHost, mainDomainSuffix) {
 			// Serve pages from subdomains of MainDomainSuffix
-			log.Debug().Msg("main domain suffix")
+			log.Info().Msg("Serve pages from main domain suffix")
 
 			pathElements := strings.Split(string(bytes.Trim(ctx.Request.URI().Path(), "/")), "/")
 			targetOwner = string(bytes.TrimSuffix(trimmedHost, mainDomainSuffix))
@@ -194,16 +194,17 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 					return
 				}
 
-				log.Debug().Msg("main domain preparations, now trying with specified repo & branch")
+				log.Debug().Msg("Preparing main domain, now trying with specified repo & branch")
 				if tryBranch(log,
 					pathElements[0], pathElements[1][1:], pathElements[2:],
 					"/"+pathElements[0]+"/%p",
 				) {
-					log.Debug().Msg("tryBranch, now trying upstream 3")
+					log.Info().Msg("tryBranch, now trying upstream 3")
 					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 						canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				} else {
+					log.Warn().Msg("tryBranch: upstream 3 failed")
 					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
 				}
 				return
@@ -212,14 +213,15 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// Check if the first directory is a branch for the "pages" repo
 			// example.codeberg.page/@main/index.html
 			if strings.HasPrefix(pathElements[0], "@") {
-				log.Debug().Msg("main domain preparations, now trying with specified branch")
+				log.Debug().Msg("Preparing main domain, now trying with specified branch")
 				if tryBranch(log,
 					"pages", pathElements[0][1:], pathElements[1:], "/%p") {
-					log.Debug().Msg("tryBranch, now trying upstream 4")
+					log.Info().Msg("tryBranch, now trying upstream 4")
 					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 						canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				} else {
+					log.Warn().Msg("tryBranch: upstream 4 failed")
 					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
 				}
 				return
@@ -231,7 +233,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			log.Debug().Msg("main domain preparations, now trying with specified repo")
 			if pathElements[0] != "pages" && tryBranch(log,
 				pathElements[0], "pages", pathElements[1:], "") {
-				log.Debug().Msg("tryBranch, now trying upstream 5")
+				log.Info().Msg("tryBranch, now trying upstream 5")
 				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 					canonicalDomainCache, branchTimestampCache, fileResponseCache)
@@ -243,7 +245,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			log.Debug().Msg("main domain preparations, now trying with default repo/branch")
 			if tryBranch(log,
 				"pages", "", pathElements, "") {
-				log.Debug().Msg("tryBranch, now trying upstream 6")
+				log.Info().Msg("tryBranch, now trying upstream 6")
 				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 					canonicalDomainCache, branchTimestampCache, fileResponseCache)
@@ -251,6 +253,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			}
 
 			// Couldn't find a valid repo/branch
+
 			html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
 			return
 		} else {
@@ -272,11 +275,12 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			}
 
 			// 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")
+			log.Debug().Msg("Preparing custom domain, now trying with details from DNS")
 			if tryBranch(log,
 				targetRepo, targetBranch, pathElements, canonicalLink) {
 				canonicalDomain, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), canonicalDomainCache)
 				if !valid {
+					log.Warn().Msg("Custom domains, domain from DNS isn't valid/canonical")
 					html.ReturnErrorPage(ctx, fasthttp.StatusMisdirectedRequest)
 					return
 				} else if canonicalDomain != trimmedHostStr {
@@ -287,17 +291,19 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 						return
 					}
 
+					log.Warn().Msg("Custom domains, targetOwner from DNS is empty")
 					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
 					return
 				}
 
-				log.Debug().Msg("tryBranch, now trying upstream 7")
+				log.Info().Msg("tryBranch, now trying upstream 7 %s")
 				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 					canonicalDomainCache, branchTimestampCache, fileResponseCache)
 				return
 			}
 
+			log.Warn().Msg("Couldn't handle request, none of the options succeed")
 			html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
 			return
 		}
diff --git a/server/setup.go b/server/setup.go
index ea96d1e..176bb42 100644
--- a/server/setup.go
+++ b/server/setup.go
@@ -2,11 +2,11 @@ package server
 
 import (
 	"bytes"
+	"fmt"
 	"net/http"
 	"time"
 
 	"github.com/rs/zerolog/log"
-
 	"github.com/valyala/fasthttp"
 
 	"codeberg.org/codeberg/pages/server/cache"
@@ -16,7 +16,7 @@ import (
 type fasthttpLogger struct{}
 
 func (fasthttpLogger) Printf(format string, args ...interface{}) {
-	log.Printf("[FASTHTTP] "+format, args...)
+	log.Printf("FastHTTP: %s", fmt.Sprintf(format, args...))
 }
 
 func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server {
diff --git a/server/try.go b/server/try.go
index 254d3ec..24831c4 100644
--- a/server/try.go
+++ b/server/try.go
@@ -41,6 +41,7 @@ func tryUpstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	targetOptions.TargetRepo = targetRepo
 	targetOptions.TargetBranch = targetBranch
 	targetOptions.TargetPath = targetPath
+	targetOptions.Host = string(trimmedHost)
 
 	// Try to request the file from the Gitea API
 	if !targetOptions.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 9b7464e..4371e88 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -3,7 +3,6 @@ package upstream
 import (
 	"bytes"
 	"errors"
-	"fmt"
 	"io"
 	"strings"
 	"time"
@@ -33,7 +32,10 @@ type Options struct {
 	TargetBranch,
 	TargetPath,
 
-	DefaultMimeType string
+	// Used for debugging purposes.
+	Host string
+
+	DefaultMimeType    string
 	ForbiddenMimeTypes map[string]bool
 	TryIndexPages      bool
 	BranchTimestamp    time.Time
@@ -44,7 +46,7 @@ type Options struct {
 
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
 func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client, branchTimestampCache, fileResponseCache cache.SetGetKey) (final bool) {
-	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
+	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath, o.Host}).Logger()
 
 	// Check if the branch exists and when it was modified
 	if o.BranchTimestamp.IsZero() {
@@ -70,7 +72,8 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 			return true
 		}
 	}
-	log.Debug().Msg("preparations")
+
+	log.Debug().Msg("Preparing")
 
 	// Make a GET request to the upstream URL
 	uri := o.generateUri()
@@ -82,7 +85,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	} else {
 		res, err = giteaClient.ServeRawContent(uri)
 	}
-	log.Debug().Msg("acquisition")
+	log.Debug().Msg("Aquisting")
 
 	// Handle errors
 	if (err != nil && errors.Is(err, gitea.ErrorNotFound)) || (res == nil && !cachedResponse.Exists) {
@@ -136,7 +139,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 		return false
 	}
 	if res != nil && (err != nil || res.StatusCode() != fasthttp.StatusOK) {
-		fmt.Printf("Couldn't fetch contents from \"%s\": %s (status code %d)\n", uri, err, res.StatusCode())
+		log.Warn().Msgf("Couldn't fetch contents from %q: %v (status code %d)", uri, err, res.StatusCode())
 		html.ReturnErrorPage(ctx, fasthttp.StatusInternalServerError)
 		return true
 	}
@@ -155,7 +158,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 		ctx.Redirect(o.redirectIfExists, fasthttp.StatusTemporaryRedirect)
 		return true
 	}
-	log.Debug().Msg("error handling")
+	log.Debug().Msg("Handling error")
 
 	// Set the MIME type
 	mimeType := o.getMimeTypeByExtension()
@@ -175,7 +178,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	}
 	ctx.Response.Header.SetLastModified(o.BranchTimestamp)
 
-	log.Debug().Msg("response preparations")
+	log.Debug().Msg("Prepare response")
 
 	// Write the response body to the original request
 	var cacheBodyWriter bytes.Buffer
@@ -193,11 +196,11 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 		_, err = ctx.Write(cachedResponse.Body)
 	}
 	if err != nil {
-		fmt.Printf("Couldn't write body for \"%s\": %s\n", uri, err)
+		log.Error().Err(err).Msgf("Couldn't write body for %q", uri)
 		html.ReturnErrorPage(ctx, fasthttp.StatusInternalServerError)
 		return true
 	}
-	log.Debug().Msg("response")
+	log.Debug().Msg("Sending response")
 
 	if res != nil && res.Header.ContentLength() <= fileCacheSizeLimit && ctx.Err() == nil {
 		cachedResponse.Exists = true

From f72bbfd85fd8035a2a4dc216f6aeb9bc2fe19d0c Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Fri, 12 Aug 2022 05:24:05 +0200
Subject: [PATCH 027/196] Fix `just dev` (#121)

- Use the correct log level command, since 876a53d9a245c11c58b61617052f7f2281d16358

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/121
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 Justfile | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/Justfile b/Justfile
index c824efa..03a7436 100644
--- a/Justfile
+++ b/Justfile
@@ -6,7 +6,8 @@ dev:
     export PAGES_DOMAIN=localhost.mock.directory
     export RAW_DOMAIN=raw.localhost.mock.directory
     export PORT=4430
-    go run . --verbose
+    export LOG_LEVEL=trace
+    go run .
 
 build:
     CGO_ENABLED=0 go build -ldflags '-s -w' -v -o build/codeberg-pages-server ./
@@ -45,4 +46,4 @@ integration:
     go test -race -tags integration codeberg.org/codeberg/pages/integration/...
 
 integration-run TEST:
-    go test -race -tags integration -run "^{{TEST}}$" codeberg.org/codeberg/pages/integration/...
\ No newline at end of file
+    go test -race -tags integration -run "^{{TEST}}$" codeberg.org/codeberg/pages/integration/...

From 519259f459395073cca05f653c1d489f0da5d497 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Fri, 12 Aug 2022 06:14:39 +0200
Subject: [PATCH 028/196] publish docker images on tag and push to main (#122)

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/122
---
 .woodpecker.yml | 35 ++++++++++++++++++++++++++++++++---
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index b83a4dd..777c2af 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -26,7 +26,8 @@ pipeline:
     when:
       event: [ "pull_request", "push" ]
 
-  build-docker:
+  docker-dryrun:
+    group: compliant
     image: plugins/kaniko
     settings:
       dockerfile: Dockerfile
@@ -47,14 +48,14 @@ pipeline:
       event: [ "tag" ]
 
   test:
-    image: a6543/golang_just
     group: test
+    image: a6543/golang_just
     commands:
       - just test
 
   integration-tests:
-    image: a6543/golang_just
     group: test
+    image: a6543/golang_just
     commands:
       - just integration
     environment:
@@ -78,3 +79,31 @@ pipeline:
       - DRONE_COMMIT_REF=${CI_COMMIT_REF}
     when:
       event: [ "tag" ]
+
+  docker-next:
+    image: plugins/kaniko
+    settings:
+      registry: codeberg.org
+      dockerfile: Dockerfile
+      repo: Codeberg/pages-server
+      tags: next
+      username:
+        from_secret: bot_user
+      password:
+        from_secret: bot_token
+    when:
+      event: [ "push" ]
+
+  docker-tag:
+    image: plugins/kaniko
+    settings:
+      registry: codeberg.org
+      dockerfile: Dockerfile
+      repo: Codeberg/pages-server
+      tag: [ latest, "${CI_COMMIT_TAG}" ]
+      username:
+        from_secret: bot_user
+      password:
+        from_secret: bot_token
+    when:
+      event: [ "tag" ]

From dc41a4caf4d6d078ee96626ae2174fe62af26cf4 Mon Sep 17 00:00:00 2001
From: 6543 <6543@noreply.codeberg.org>
Date: Fri, 12 Aug 2022 06:40:12 +0200
Subject: [PATCH 029/196] Add Support to Follow Symlinks and LFS (#114)

close #79
close #80
close #91

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/114
---
 cmd/flags.go                | 13 ++++++++
 cmd/main.go                 |  2 +-
 go.mod                      |  2 +-
 go.sum                      |  3 --
 integration/get_test.go     | 35 +++++++++++++++++++++
 server/gitea/cache.go       | 12 +++++++
 server/gitea/client.go      | 62 +++++++++++++++++++++----------------
 server/handler_test.go      |  2 +-
 server/upstream/helper.go   |  4 +++
 server/upstream/upstream.go |  2 +-
 10 files changed, 103 insertions(+), 34 deletions(-)
 create mode 100644 server/gitea/cache.go

diff --git a/cmd/flags.go b/cmd/flags.go
index 3c1ac99..8ac09ec 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -63,6 +63,19 @@ var ServeFlags = []cli.Flag{
 		// TODO: desc
 		EnvVars: []string{"ENABLE_HTTP_SERVER"},
 	},
+	// Server Options
+	&cli.BoolFlag{
+		Name:    "enable-lfs-support",
+		Usage:   "enable lfs support, require gitea v1.17.0 as backend",
+		EnvVars: []string{"ENABLE_LFS_SUPPORT"},
+		Value:   true,
+	},
+	&cli.BoolFlag{
+		Name:    "enable-symlink-support",
+		Usage:   "follow symlinks if enabled, require gitea v1.18.0 as backend",
+		EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
+		Value:   true,
+	},
 	&cli.StringFlag{
 		Name:    "log-level",
 		Value:   "warn",
diff --git a/cmd/main.go b/cmd/main.go
index f57eb60..41809cb 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -85,7 +85,7 @@ func Serve(ctx *cli.Context) error {
 	// TODO: make this an MRU cache with a size limit
 	fileResponseCache := cache.NewKeyValueCache()
 
-	giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken)
+	giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken, ctx.Bool("enable-symlink-support"), ctx.Bool("enable-lfs-support"))
 	if err != nil {
 		return fmt.Errorf("could not create new gitea client: %v", err)
 	}
diff --git a/go.mod b/go.mod
index 64288de..479c328 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
 	github.com/go-acme/lego/v4 v4.5.3
 	github.com/joho/godotenv v1.4.0
 	github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
+	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli/v2 v2.3.0
 	github.com/valyala/fasthttp v1.31.0
@@ -92,7 +93,6 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pquerna/otp v1.3.0 // indirect
-	github.com/rs/zerolog v1.27.0 // indirect
 	github.com/russross/blackfriday/v2 v2.0.1 // indirect
 	github.com/sacloud/libsacloud v1.36.2 // indirect
 	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f // indirect
diff --git a/go.sum b/go.sum
index 4ac6eb6..23a58bc 100644
--- a/go.sum
+++ b/go.sum
@@ -327,7 +327,6 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
-github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -671,8 +670,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
diff --git a/integration/get_test.go b/integration/get_test.go
index 191fa3f..6054e17 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -10,6 +10,7 @@ import (
 	"log"
 	"net/http"
 	"net/http/cookiejar"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -88,6 +89,34 @@ func TestGetNotFound(t *testing.T) {
 	assert.EqualValues(t, 37, getSize(resp.Body))
 }
 
+func TestFollowSymlink(t *testing.T) {
+	log.Printf("=== TestFollowSymlink ===\n")
+
+	resp, err := getTestHTTPSClient().Get("https://6543.localhost.mock.directory:4430/tests_for_pages-server/@main/link")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, "application/octet-stream", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "4", resp.Header.Get("Content-Length"))
+	body := getBytes(resp.Body)
+	assert.EqualValues(t, 4, len(body))
+	assert.EqualValues(t, "abc\n", string(body))
+}
+
+func TestLFSSupport(t *testing.T) {
+	log.Printf("=== TestLFSSupport ===\n")
+
+	resp, err := getTestHTTPSClient().Get("https://6543.localhost.mock.directory:4430/tests_for_pages-server/@main/lfs.txt")
+	assert.NoError(t, err)
+	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+		t.FailNow()
+	}
+	body := strings.TrimSpace(string(getBytes(resp.Body)))
+	assert.EqualValues(t, 12, len(body))
+	assert.EqualValues(t, "actual value", body)
+}
+
 func getTestHTTPSClient() *http.Client {
 	cookieJar, _ := cookiejar.New(nil)
 	return &http.Client{
@@ -101,6 +130,12 @@ func getTestHTTPSClient() *http.Client {
 	}
 }
 
+func getBytes(stream io.Reader) []byte {
+	buf := new(bytes.Buffer)
+	_, _ = buf.ReadFrom(stream)
+	return buf.Bytes()
+}
+
 func getSize(stream io.Reader) int {
 	buf := new(bytes.Buffer)
 	_, _ = buf.ReadFrom(stream)
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
new file mode 100644
index 0000000..932ff3c
--- /dev/null
+++ b/server/gitea/cache.go
@@ -0,0 +1,12 @@
+package gitea
+
+type FileResponse struct {
+	Exists   bool
+	ETag     []byte
+	MimeType string
+	Body     []byte
+}
+
+func (f FileResponse) IsEmpty() bool {
+	return len(f.Body) != 0
+}
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 3b9ad6f..16cba84 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -7,11 +7,15 @@ import (
 	"strings"
 	"time"
 
+	"github.com/rs/zerolog/log"
 	"github.com/valyala/fasthttp"
 	"github.com/valyala/fastjson"
 )
 
-const giteaAPIRepos = "/api/v1/repos/"
+const (
+	giteaAPIRepos         = "/api/v1/repos/"
+	giteaObjectTypeHeader = "X-Gitea-Object-Type"
+)
 
 var ErrorNotFound = errors.New("not found")
 
@@ -21,13 +25,9 @@ type Client struct {
 	fastClient     *fasthttp.Client
 	infoTimeout    time.Duration
 	contentTimeout time.Duration
-}
 
-type FileResponse struct {
-	Exists   bool
-	ETag     []byte
-	MimeType string
-	Body     []byte
+	followSymlinks bool
+	supportLFS     bool
 }
 
 // TODO: once golang v1.19 is min requirement, we can switch to 'JoinPath()' of 'net/url' package
@@ -44,9 +44,7 @@ func joinURL(baseURL string, paths ...string) string {
 	return baseURL + "/" + strings.Join(p, "/")
 }
 
-func (f FileResponse) IsEmpty() bool { return len(f.Body) != 0 }
-
-func NewClient(giteaRoot, giteaAPIToken string) (*Client, error) {
+func NewClient(giteaRoot, giteaAPIToken string, followSymlinks, supportLFS bool) (*Client, error) {
 	rootURL, err := url.Parse(giteaRoot)
 	giteaRoot = strings.Trim(rootURL.String(), "/")
 
@@ -56,29 +54,28 @@ func NewClient(giteaRoot, giteaAPIToken string) (*Client, error) {
 		infoTimeout:    5 * time.Second,
 		contentTimeout: 10 * time.Second,
 		fastClient:     getFastHTTPClient(),
+
+		followSymlinks: followSymlinks,
+		supportLFS:     supportLFS,
 	}, err
 }
 
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
-	url := joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "raw", resource+"?ref="+url.QueryEscape(ref))
-	res, err := client.do(client.contentTimeout, url)
+	resp, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
 	if err != nil {
 		return nil, err
 	}
-
-	switch res.StatusCode() {
-	case fasthttp.StatusOK:
-		return res.Body(), nil
-	case fasthttp.StatusNotFound:
-		return nil, ErrorNotFound
-	default:
-		return nil, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
-	}
+	return resp.Body(), nil
 }
 
-func (client *Client) ServeRawContent(uri string) (*fasthttp.Response, error) {
-	url := joinURL(client.giteaRoot, giteaAPIRepos, uri)
-	res, err := client.do(client.contentTimeout, url)
+func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (*fasthttp.Response, error) {
+	var apiURL string
+	if client.supportLFS {
+		apiURL = joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "media", resource+"?ref="+url.QueryEscape(ref))
+	} else {
+		apiURL = joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "raw", resource+"?ref="+url.QueryEscape(ref))
+	}
+	resp, err := client.do(client.contentTimeout, apiURL)
 	if err != nil {
 		return nil, err
 	}
@@ -87,13 +84,24 @@ func (client *Client) ServeRawContent(uri string) (*fasthttp.Response, error) {
 		return nil, err
 	}
 
-	switch res.StatusCode() {
+	switch resp.StatusCode() {
 	case fasthttp.StatusOK:
-		return res, nil
+		objType := string(resp.Header.Peek(giteaObjectTypeHeader))
+		log.Trace().Msgf("server raw content object: %s", objType)
+		if client.followSymlinks && objType == "symlink" {
+			// TODO: limit to 1000 chars if we switched to std
+			linkDest := strings.TrimSpace(string(resp.Body()))
+			log.Debug().Msgf("follow symlink from '%s' to '%s'", resource, linkDest)
+			return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+		}
+
+		return resp, nil
+
 	case fasthttp.StatusNotFound:
 		return nil, ErrorNotFound
+
 	default:
-		return nil, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+		return nil, fmt.Errorf("unexpected status code '%d'", resp.StatusCode())
 	}
 }
 
diff --git a/server/handler_test.go b/server/handler_test.go
index 23d9af5..f9a721a 100644
--- a/server/handler_test.go
+++ b/server/handler_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestHandlerPerformance(t *testing.T) {
 	giteaRoot := "https://codeberg.org"
-	giteaClient, _ := gitea.NewClient(giteaRoot, "")
+	giteaClient, _ := gitea.NewClient(giteaRoot, "", false, false)
 	testHandler := Handler(
 		[]byte("codeberg.page"), []byte("raw.codeberg.org"),
 		giteaClient,
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index 5bbe833..0714dcd 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -67,6 +67,10 @@ func (o *Options) generateUri() string {
 	return path.Join(o.TargetOwner, o.TargetRepo, "raw", o.TargetBranch, o.TargetPath)
 }
 
+func (o *Options) generateUriClientArgs() (targetOwner, targetRepo, ref, resource string) {
+	return o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath
+}
+
 func (o *Options) timestamp() string {
 	return strconv.FormatInt(o.BranchTimestamp.Unix(), 10)
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 4371e88..0e27727 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -83,7 +83,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	if cachedValue, ok := fileResponseCache.Get(uri + "?timestamp=" + o.timestamp()); ok && !cachedValue.(gitea.FileResponse).IsEmpty() {
 		cachedResponse = cachedValue.(gitea.FileResponse)
 	} else {
-		res, err = giteaClient.ServeRawContent(uri)
+		res, err = giteaClient.ServeRawContent(o.generateUriClientArgs())
 	}
 	log.Debug().Msg("Aquisting")
 

From 88a217fbe6285039f77b903a9fdd1d53cd2dcbff Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 12 Aug 2022 06:55:35 +0200
Subject: [PATCH 030/196] docker images must be lowercase

---
 .woodpecker.yml | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 777c2af..c9a9dbe 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -32,7 +32,6 @@ pipeline:
     settings:
       dockerfile: Dockerfile
       no_push: true
-      repo: Codeberg/pages-server
       tags: latest
     when:
       event: [ "pull_request", "push" ]
@@ -85,7 +84,7 @@ pipeline:
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
-      repo: Codeberg/pages-server
+      repo: codeberg/pages-server
       tags: next
       username:
         from_secret: bot_user
@@ -99,7 +98,7 @@ pipeline:
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
-      repo: Codeberg/pages-server
+      repo: codeberg/pages-server
       tag: [ latest, "${CI_COMMIT_TAG}" ]
       username:
         from_secret: bot_user

From 392c6ae45290de0ff278416f5f7cfead7d13e0a2 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 12 Aug 2022 07:02:24 +0200
Subject: [PATCH 031/196] full-name

---
 .woodpecker.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index c9a9dbe..2674e9b 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -84,7 +84,7 @@ pipeline:
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
-      repo: codeberg/pages-server
+      repo: codeberg.org/codeberg/pages-server
       tags: next
       username:
         from_secret: bot_user
@@ -98,7 +98,7 @@ pipeline:
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
-      repo: codeberg/pages-server
+      repo: codeberg.org/codeberg/pages-server
       tag: [ latest, "${CI_COMMIT_TAG}" ]
       username:
         from_secret: bot_user

From 1ae50735a1d50c01cd2149334c9fd756f5390fd1 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Sat, 13 Aug 2022 18:03:31 +0200
Subject: [PATCH 032/196] Add host to handler logging (#123)

- Add the host to the Handler's logging fields, so you don't just see the path, but also which domain was being requested.

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/123
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/handler.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/handler.go b/server/handler.go
index 50fbcc5..cd67aa7 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -25,7 +25,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 	dnsLookupCache, canonicalDomainCache, branchTimestampCache, fileResponseCache cache.SetGetKey,
 ) func(ctx *fasthttp.RequestCtx) {
 	return func(ctx *fasthttp.RequestCtx) {
-		log := log.With().Str("Handler", string(ctx.Request.Header.RequestURI())).Logger()
+		log := log.With().Strs("Handler", []string{string(ctx.Request.Host()), string(ctx.Request.Header.RequestURI())}).Logger()
 
 		ctx.Response.Header.Set("Server", "CodebergPages/"+version.Version)
 

From 8f2699407d5c452e04bd0de8bbb367a60b4a88b8 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Tue, 13 Sep 2022 23:06:31 +0200
Subject: [PATCH 033/196] Make verbose checks in tryBranch (#127)

- It's likely that the tryBranch is returning false when it should be returning true, make these logs more verbose so they show up on production logs.

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/127
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/handler.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/handler.go b/server/handler.go
index cd67aa7..ed06659 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -85,7 +85,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 		// also disallow search indexing and add a Link header to the canonical URL.
 		tryBranch := func(log zerolog.Logger, repo, branch string, path []string, canonicalLink string) bool {
 			if repo == "" {
-				log.Debug().Msg("tryBranch: repo is empty")
+				log.Warn().Msg("tryBranch: repo is empty")
 				return false
 			}
 
@@ -96,7 +96,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// Check if the branch exists, otherwise treat it as a file path
 			branchTimestampResult := upstream.GetBranchTimestamp(giteaClient, targetOwner, repo, branch, branchTimestampCache)
 			if branchTimestampResult == nil {
-				log.Debug().Msg("tryBranch: branch doesn't exist")
+				log.Warn().Msg("tryBranch: branch doesn't exist")
 				return false
 			}
 

From 2a730b2439cae5ebd17d90e38b4ff27e4154a651 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Tue, 13 Sep 2022 23:26:45 +0200
Subject: [PATCH 034/196] Update README (#128)

- Update readme accordingly to the https://codeberg.org/Codeberg/pages-server/commit/876a53d9a245c11c58b61617052f7f2281d16358

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/128
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 700f279..c4758ac 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ and especially have a look at [this section of the haproxy.cfg](https://codeberg
 - `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
 - `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard.  
   See https://go-acme.github.io/lego/dns/ for available values & additional environment variables.
-- `DEBUG` (default: false): Set this to true to enable debug logging.
+- `LOG_LEVEL` (default: warn): Set this to specify the level of logging.
 
 
 ## Contributing to the development
@@ -106,4 +106,4 @@ run `just dev`
 now this pages should work:
  - https://magiclike.localhost.mock.directory:4430/
  - https://momar.localhost.mock.directory:4430/ci-testing/
- - https://momar.localhost.mock.directory:4430/pag/@master/
\ No newline at end of file
+ - https://momar.localhost.mock.directory:4430/pag/@master/

From 091e6c8ed9f737b3b84a3610c5836953677d122c Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Sun, 18 Sep 2022 16:13:27 +0200
Subject: [PATCH 035/196] Add explicit logging in `GetBranchTimestamp` (#130)

- Logs are currently indicating that it's returning `nil` in valid
scenarios, therefor this patch adds extra logging in this code to
better understand what it is doing in this function.

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/130
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/handler.go         | 2 +-
 server/upstream/helper.go | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/server/handler.go b/server/handler.go
index ed06659..fb8b419 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -296,7 +296,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 					return
 				}
 
-				log.Info().Msg("tryBranch, now trying upstream 7 %s")
+				log.Info().Msg("tryBranch, now trying upstream 7")
 				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
 					canonicalDomainCache, branchTimestampCache, fileResponseCache)
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index 0714dcd..28f4474 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -9,6 +9,7 @@ import (
 
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/gitea"
+	"github.com/rs/zerolog/log"
 )
 
 type branchTimestamp struct {
@@ -19,10 +20,13 @@ type branchTimestamp struct {
 // GetBranchTimestamp finds the default branch (if branch is "") and returns the last modification time of the branch
 // (or nil if the branch doesn't exist)
 func GetBranchTimestamp(giteaClient *gitea.Client, owner, repo, branch string, branchTimestampCache cache.SetGetKey) *branchTimestamp {
+	log := log.With().Strs("BranchInfo", []string{owner, repo, branch}).Logger()
 	if result, ok := branchTimestampCache.Get(owner + "/" + repo + "/" + branch); ok {
 		if result == nil {
+			log.Debug().Msg("branchTimestampCache found item, but result is empty")
 			return nil
 		}
+		log.Debug().Msg("branchTimestampCache found item, returning result")
 		return result.(*branchTimestamp)
 	}
 	result := &branchTimestamp{
@@ -32,16 +36,20 @@ func GetBranchTimestamp(giteaClient *gitea.Client, owner, repo, branch string, b
 		// Get default branch
 		defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(owner, repo)
 		if err != nil {
+			log.Err(err).Msg("Could't fetch default branch from repository")
 			_ = branchTimestampCache.Set(owner+"/"+repo+"/", nil, defaultBranchCacheTimeout)
 			return nil
 		}
+		log.Debug().Msg("Succesfully fetched default branch from Gitea")
 		result.Branch = defaultBranch
 	}
 
 	timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(owner, repo, result.Branch)
 	if err != nil {
+		log.Err(err).Msg("Could not get latest commit's timestamp from branch")
 		return nil
 	}
+	log.Debug().Msg("Succesfully fetched latest commit's timestamp from branch, adding to cache")
 	result.Timestamp = timestamp
 	_ = branchTimestampCache.Set(owner+"/"+repo+"/"+branch, result, branchExistenceCacheTimeout)
 	return result

From df2228b6d59b175df87b8849391b4a8d317e7eb3 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 10 Oct 2022 23:25:21 +0200
Subject: [PATCH 036/196] ci: let tag run pipeline

---
 .woodpecker.yml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 2674e9b..198648f 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,5 +1,3 @@
-branches: main
-
 pipeline:
   # use vendor to cache dependencies
   vendor:

From b9e9f1420954d1a352f7e9e7e799b160bd224eb1 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 10 Oct 2022 23:27:33 +0200
Subject: [PATCH 037/196] use codeberg.org/6543/docker-images/golang_just

Signed-off-by: 6543 <6543@obermui.de>
---
 .woodpecker.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 198648f..db51eba 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -17,7 +17,7 @@ pipeline:
 
   build:
     group: compliant
-    image: a6543/golang_just
+    image: codeberg.org/6543/docker-images/golang_just
     commands:
       - go version
       - just build
@@ -37,7 +37,7 @@ pipeline:
 
   build-tag:
     group: compliant
-    image: a6543/golang_just
+    image: codeberg.org/6543/docker-images/golang_just
     commands:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
@@ -46,13 +46,13 @@ pipeline:
 
   test:
     group: test
-    image: a6543/golang_just
+    image: codeberg.org/6543/docker-images/golang_just
     commands:
       - just test
 
   integration-tests:
     group: test
-    image: a6543/golang_just
+    image: codeberg.org/6543/docker-images/golang_just
     commands:
       - just integration
     environment:

From bf9a08e1fd4107667dc6816e1df0d0ec2c0acbe9 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Mon, 7 Nov 2022 16:27:37 +0100
Subject: [PATCH 038/196] Fatal on ACME Client creation failure (#133)

- For production(*cough* Codeberg *cough*), it's important to not use
mock certs. So fail right from the start if this is the case and not try
to "handle it gracefully", as it would break production.
- Resolves #131

CC @6543

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/133
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/certificates/certificates.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 8944468..2f59fb4 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -414,7 +414,7 @@ func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *
 
 	acmeClient, err = lego.NewClient(acmeConfig)
 	if err != nil {
-		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
+		log.Fatal().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 	} else {
 		err = acmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
 		if err != nil {

From 91b54bef29ca9e7d3394fad1e44fa4b6f795035c Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 7 Nov 2022 23:09:41 +0100
Subject: [PATCH 039/196] add newline

---
 server/upstream/upstream.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 0e27727..61c90de 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -85,6 +85,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	} else {
 		res, err = giteaClient.ServeRawContent(o.generateUriClientArgs())
 	}
+
 	log.Debug().Msg("Aquisting")
 
 	// Handle errors
@@ -158,6 +159,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 		ctx.Redirect(o.redirectIfExists, fasthttp.StatusTemporaryRedirect)
 		return true
 	}
+
 	log.Debug().Msg("Handling error")
 
 	// Set the MIME type
@@ -200,6 +202,7 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 		html.ReturnErrorPage(ctx, fasthttp.StatusInternalServerError)
 		return true
 	}
+
 	log.Debug().Msg("Sending response")
 
 	if res != nil && res.Header.ContentLength() <= fileCacheSizeLimit && ctx.Err() == nil {

From 8e67d28c4fddfc0b4f4f0dd5db1e60e4d80cf56a Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 11 Nov 2022 23:51:45 +0100
Subject: [PATCH 040/196] Add editorconfig, fix files and lint via ci

---
 .ecrc                          |  9 +++++++++
 .editorconfig                  | 17 +++++++++++++++++
 .woodpecker.yml                |  4 ++++
 Dockerfile                     |  4 ++--
 Justfile                       |  1 +
 README.md                      |  8 +++-----
 haproxy-sni/dhparam.pem        |  2 +-
 haproxy-sni/docker-compose.yml |  2 +-
 html/404.html                  |  6 +++---
 9 files changed, 41 insertions(+), 12 deletions(-)
 create mode 100644 .ecrc
 create mode 100644 .editorconfig

diff --git a/.ecrc b/.ecrc
new file mode 100644
index 0000000..d9ee788
--- /dev/null
+++ b/.ecrc
@@ -0,0 +1,9 @@
+{
+  "Exclude": [
+    ".git",
+    "go.mod", "go.sum",
+    "vendor",
+    "LICENSE",
+    "_test.go"
+  ]
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..354a828
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+tab_width = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.go]
+indent_style = tab
+
+[*.md]
+trim_trailing_whitespace = false
+indent_size = 1
diff --git a/.woodpecker.yml b/.woodpecker.yml
index db51eba..5c9a7fd 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -15,6 +15,10 @@ pipeline:
       - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
       - golangci-lint run --timeout 5m --build-tags integration
 
+  editor-config:
+    group: compliant
+    image: mstruebing/editorconfig-checker
+
   build:
     group: compliant
     image: codeberg.org/6543/docker-images/golang_just
diff --git a/Dockerfile b/Dockerfile
index 71dd236..904d6f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,5 +11,5 @@ COPY --from=build /workspace/pages /pages
 COPY --from=build \
     /etc/ssl/certs/ca-certificates.crt \
     /etc/ssl/certs/ca-certificates.crt
-    
-ENTRYPOINT ["/pages"]
\ No newline at end of file
+
+ENTRYPOINT ["/pages"]
diff --git a/Justfile b/Justfile
index 03a7436..f7ea414 100644
--- a/Justfile
+++ b/Justfile
@@ -18,6 +18,7 @@ build-tag VERSION:
 lint: tool-golangci tool-gofumpt
     [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
     golangci-lint run --timeout 5m --build-tags integration
+    # TODO: run editorconfig-checker
 
 fmt: tool-gofumpt
     gofumpt -w --extra .
diff --git a/README.md b/README.md
index c4758ac..50cdc3c 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,6 @@ It is suitable to be deployed by other Gitea instances, too, to offer static pag
 **End user documentation** can mainly be found at the [Wiki](https://codeberg.org/Codeberg/pages-server/wiki/Overview)
 and the [Codeberg Documentation](https://docs.codeberg.org/codeberg-pages/).
 
-
 ## Quickstart
 
 This is the new Codeberg Pages server, a solution for serving static pages from Gitea repositories.
@@ -20,17 +19,16 @@ first line will be the canonical domain/URL; all other occurrences will be redir
 2) add a CNAME entry to your domain, pointing to `[[{branch}.]{repo}.]{owner}.codeberg.page` (repo defaults to
 "pages", "branch" defaults to the default branch if "repo" is "pages", or to "pages" if "repo" is something else.
 If the branch name contains slash characters, you need to replace "/" in the branch name to "~"):
-	`www.example.org. IN CNAME main.pages.example.codeberg.page.`
+  `www.example.org. IN CNAME main.pages.example.codeberg.page.`
 
 3) if a CNAME is set for "www.example.org", you can redirect there from the naked domain by adding an ALIAS record
 for "example.org" (if your provider allows ALIAS or similar records, otherwise use A/AAAA), together with a TXT
 record that points to your repo (just like the CNAME record):
-	`example.org IN ALIAS codeberg.page.`
-	`example.org IN TXT main.pages.example.codeberg.page.`
+  `example.org IN ALIAS codeberg.page.`
+  `example.org IN TXT main.pages.example.codeberg.page.`
 
 Certificates are generated, updated and cleaned up automatically via Let's Encrypt through a TLS challenge.
 
-
 ## Deployment
 
 **Warning: Some Caveats Apply**  
diff --git a/haproxy-sni/dhparam.pem b/haproxy-sni/dhparam.pem
index 088f967..9b182b7 100644
--- a/haproxy-sni/dhparam.pem
+++ b/haproxy-sni/dhparam.pem
@@ -5,4 +5,4 @@ MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
 YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
 ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
------END DH PARAMETERS-----
\ No newline at end of file
+-----END DH PARAMETERS-----
diff --git a/haproxy-sni/docker-compose.yml b/haproxy-sni/docker-compose.yml
index 4dd8677..66ff52b 100644
--- a/haproxy-sni/docker-compose.yml
+++ b/haproxy-sni/docker-compose.yml
@@ -19,4 +19,4 @@ services:
     volumes:
     - ./pages-www:/srv:ro
     - ./pages.Caddyfile:/etc/caddy/Caddyfile:ro
-    
+
diff --git a/html/404.html b/html/404.html
index b7ec96e..21d968e 100644
--- a/html/404.html
+++ b/html/404.html
@@ -23,11 +23,11 @@
 <body>
     <i class="fa fa-search text-primary" style="font-size: 96px;"></i>
     <h1 class="mb-0 text-primary">
-		Page not found!
+      Page not found!
     </h1>
     <h5 class="text-center" style="max-width: 25em;">
-    	Sorry, but this page couldn't be found or is inaccessible (%status).<br/>
-		We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
+      Sorry, but this page couldn't be found or is inaccessible (%status).<br/>
+      We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
     </h5>
     <small class="text-muted">
         <img src="https://design.codeberg.org/logo-kit/icon.svg" class="align-top">

From 69eabb248a26e33b65732604267c486fd0daee84 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 12 Nov 2022 00:23:45 +0100
Subject: [PATCH 041/196] CI publish next only on default branch

---
 .woodpecker.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 5c9a7fd..20254fe 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -94,6 +94,7 @@ pipeline:
         from_secret: bot_token
     when:
       event: [ "push" ]
+      branch: ${CI_REPO_DEFAULT_BRANCH}
 
   docker-tag:
     image: plugins/kaniko

From b9966487f6f7a9fc88ae285c7350917e455f27a0 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 12 Nov 2022 20:37:20 +0100
Subject: [PATCH 042/196] switch to std http implementation instead of fasthttp
 (#106)

close #100
close #109
close #113
close #28
close #63

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/106
---
 cmd/main.go                         |  41 ++--
 go.mod                              |  17 +-
 go.sum                              |  33 +--
 html/404.html                       |   8 +-
 html/error.go                       |  47 +++--
 html/error.html                     |  38 ++++
 html/html.go                        |   5 +-
 integration/get_test.go             |  33 ++-
 server/certificates/certificates.go |  36 ++--
 server/context/context.go           |  62 ++++++
 server/database/mock.go             |   2 +-
 server/dns/const.go                 |   6 -
 server/dns/dns.go                   |   4 +
 server/gitea/cache.go               | 112 +++++++++-
 server/gitea/client.go              | 308 ++++++++++++++++++++--------
 server/gitea/client_test.go         |  23 ---
 server/gitea/fasthttp.go            |  15 --
 server/handler.go                   | 186 +++++++++--------
 server/handler_test.go              |  36 ++--
 server/helpers.go                   |   8 +-
 server/setup.go                     |  54 ++---
 server/try.go                       |  21 +-
 server/upstream/const.go            |  24 ---
 server/upstream/domains.go          |  10 +
 server/upstream/helper.go           |  74 ++-----
 server/upstream/upstream.go         | 194 +++++++++---------
 server/utils/utils.go               |   8 +-
 server/utils/utils_test.go          |   6 +-
 28 files changed, 827 insertions(+), 584 deletions(-)
 create mode 100644 html/error.html
 create mode 100644 server/context/context.go
 delete mode 100644 server/dns/const.go
 delete mode 100644 server/gitea/client_test.go
 delete mode 100644 server/gitea/fasthttp.go
 delete mode 100644 server/upstream/const.go

diff --git a/cmd/main.go b/cmd/main.go
index 41809cb..a3a61e1 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -1,12 +1,12 @@
 package cmd
 
 import (
-	"bytes"
 	"context"
 	"crypto/tls"
 	"errors"
 	"fmt"
 	"net"
+	"net/http"
 	"os"
 	"strings"
 	"time"
@@ -24,15 +24,15 @@ import (
 
 // AllowedCorsDomains lists the domains for which Cross-Origin Resource Sharing is allowed.
 // TODO: make it a flag
-var AllowedCorsDomains = [][]byte{
-	[]byte("fonts.codeberg.org"),
-	[]byte("design.codeberg.org"),
+var AllowedCorsDomains = []string{
+	"fonts.codeberg.org",
+	"design.codeberg.org",
 }
 
 // BlacklistedPaths specifies forbidden path prefixes for all Codeberg Pages.
 // TODO: Make it a flag too
-var BlacklistedPaths = [][]byte{
-	[]byte("/.well-known/acme-challenge/"),
+var BlacklistedPaths = []string{
+	"/.well-known/acme-challenge/",
 }
 
 // Serve sets up and starts the web server.
@@ -47,7 +47,7 @@ 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("pages-domain"))
+	mainDomainSuffix := ctx.String("pages-domain")
 	rawInfoPage := ctx.String("raw-info-page")
 	listeningAddress := fmt.Sprintf("%s:%s", ctx.String("host"), ctx.String("port"))
 	enableHTTPServer := ctx.Bool("enable-http-server")
@@ -65,12 +65,12 @@ func Serve(ctx *cli.Context) error {
 
 	allowedCorsDomains := AllowedCorsDomains
 	if len(rawDomain) != 0 {
-		allowedCorsDomains = append(allowedCorsDomains, []byte(rawDomain))
+		allowedCorsDomains = append(allowedCorsDomains, rawDomain)
 	}
 
 	// Make sure MainDomain has a trailing dot, and GiteaRoot has no trailing slash
-	if !bytes.HasPrefix(mainDomainSuffix, []byte{'.'}) {
-		mainDomainSuffix = append([]byte{'.'}, mainDomainSuffix...)
+	if !strings.HasPrefix(mainDomainSuffix, ".") {
+		mainDomainSuffix = "." + mainDomainSuffix
 	}
 
 	keyCache := cache.NewKeyValueCache()
@@ -79,26 +79,22 @@ func Serve(ctx *cli.Context) error {
 	canonicalDomainCache := cache.NewKeyValueCache()
 	// dnsLookupCache stores DNS lookups for custom domains
 	dnsLookupCache := cache.NewKeyValueCache()
-	// branchTimestampCache stores branch timestamps for faster cache checking
-	branchTimestampCache := cache.NewKeyValueCache()
-	// fileResponseCache stores responses from the Gitea server
-	// TODO: make this an MRU cache with a size limit
-	fileResponseCache := cache.NewKeyValueCache()
+	// clientResponseCache stores responses from the Gitea server
+	clientResponseCache := cache.NewKeyValueCache()
 
-	giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken, ctx.Bool("enable-symlink-support"), ctx.Bool("enable-lfs-support"))
+	giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken, clientResponseCache, ctx.Bool("enable-symlink-support"), ctx.Bool("enable-lfs-support"))
 	if err != nil {
 		return fmt.Errorf("could not create new gitea client: %v", err)
 	}
 
 	// Create handler based on settings
-	handler := server.Handler(mainDomainSuffix, []byte(rawDomain),
+	httpsHandler := server.Handler(mainDomainSuffix, rawDomain,
 		giteaClient,
 		giteaRoot, rawInfoPage,
 		BlacklistedPaths, allowedCorsDomains,
-		dnsLookupCache, canonicalDomainCache, branchTimestampCache, fileResponseCache)
+		dnsLookupCache, canonicalDomainCache)
 
-	fastServer := server.SetupServer(handler)
-	httpServer := server.SetupHTTPACMEChallengeServer(challengeCache)
+	httpHandler := server.SetupHTTPACMEChallengeServer(challengeCache)
 
 	// Setup listener and TLS
 	log.Info().Msgf("Listening on https://%s", listeningAddress)
@@ -138,7 +134,7 @@ func Serve(ctx *cli.Context) error {
 	if enableHTTPServer {
 		go func() {
 			log.Info().Msg("Start HTTP server listening on :80")
-			err := httpServer.ListenAndServe("[::]:80")
+			err := http.ListenAndServe("[::]:80", httpHandler)
 			if err != nil {
 				log.Panic().Err(err).Msg("Couldn't start HTTP fastServer")
 			}
@@ -147,8 +143,7 @@ func Serve(ctx *cli.Context) error {
 
 	// Start the web fastServer
 	log.Info().Msgf("Start listening on %s", listener.Addr())
-	err = fastServer.Serve(listener)
-	if err != nil {
+	if err := http.Serve(listener, httpsHandler); err != nil {
 		log.Panic().Err(err).Msg("Couldn't start fastServer")
 	}
 
diff --git a/go.mod b/go.mod
index 479c328..77ed762 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,9 @@
 module codeberg.org/codeberg/pages
 
-go 1.18
+go 1.19
 
 require (
+	code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
 	github.com/akrylysov/pogreb v0.10.1
 	github.com/go-acme/lego/v4 v4.5.3
@@ -11,8 +12,6 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli/v2 v2.3.0
-	github.com/valyala/fasthttp v1.31.0
-	github.com/valyala/fastjson v1.6.3
 )
 
 require (
@@ -31,7 +30,6 @@ require (
 	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
 	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
 	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 // indirect
-	github.com/andybalholm/brotli v1.0.2 // indirect
 	github.com/aws/aws-sdk-go v1.39.0 // indirect
 	github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
 	github.com/cenkalti/backoff/v4 v4.1.1 // indirect
@@ -39,6 +37,7 @@ require (
 	github.com/cpu/goacmedns v0.1.1 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/davidmz/go-pageant v1.0.2 // indirect
 	github.com/deepmap/oapi-codegen v1.6.1 // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
 	github.com/dnsimple/dnsimple-go v0.70.1 // indirect
@@ -46,6 +45,7 @@ require (
 	github.com/fatih/structs v1.1.0 // indirect
 	github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
 	github.com/go-errors/errors v1.0.1 // indirect
+	github.com/go-fed/httpsig v1.1.0 // indirect
 	github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
 	github.com/gofrs/uuid v3.2.0+incompatible // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
@@ -57,13 +57,13 @@ require (
 	github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
 	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
+	github.com/hashicorp/go-version v1.6.0 // indirect
 	github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
 	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
 	github.com/jarcoal/httpmock v1.0.6 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/json-iterator/go v1.1.7 // indirect
 	github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
-	github.com/klauspost/compress v1.13.4 // indirect
 	github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
 	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
 	github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
@@ -104,15 +104,14 @@ require (
 	github.com/spf13/cast v1.3.1 // indirect
 	github.com/stretchr/objx v0.3.0 // indirect
 	github.com/transip/gotransip/v6 v6.6.1 // indirect
-	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 // indirect
 	github.com/vultr/govultr/v2 v2.7.1 // indirect
 	go.opencensus.io v0.22.3 // indirect
 	go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect
-	golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
-	golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
+	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
+	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
-	golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
+	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
 	golang.org/x/text v0.3.6 // indirect
 	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 	google.golang.org/api v0.20.0 // indirect
diff --git a/go.sum b/go.sum
index 23a58bc..a44001c 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa h1:OVwgYrY6vr6gWZvgnmevFhtL0GVA4HKaFOhD+joPoNk=
+code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/azure-sdk-for-go v32.4.0+incompatible h1:1JP8SKfroEakYiQU2ZyPDosh8w2Tg9UopKt88VyQPt4=
 github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -66,8 +68,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 h1:dkj8/dxOQ4L1XpwCzRLqukvUBbxuNdz3FeyvHFnRjmo=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
-github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
-github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@@ -106,6 +106,8 @@ github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
+github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpAMecnw=
 github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -136,6 +138,8 @@ github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJ
 github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
 github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
+github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -182,7 +186,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -243,6 +246,9 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -282,8 +288,6 @@ github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcM
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s=
-github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc=
 github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -494,15 +498,9 @@ github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
-github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE=
-github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
-github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
-github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
 github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 h1:TFXGGMHmml4rs29PdPisC/aaCzOxUu1Vsh9on/IpUfE=
 github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
 github.com/vultr/govultr/v2 v2.7.1 h1:uF9ERet++Gb+7Cqs3p1P6b6yebeaZqVd7t5P2uZCaJU=
@@ -539,8 +537,10 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -604,8 +604,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
-golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -667,12 +667,13 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/html/404.html b/html/404.html
index 21d968e..7c721b5 100644
--- a/html/404.html
+++ b/html/404.html
@@ -3,7 +3,7 @@
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width">
-    <title>%status</title>
+    <title>%status%</title>
 
     <link rel="stylesheet" href="https://design.codeberg.org/design-kit/codeberg.css" />
     <link href="https://fonts.codeberg.org/dist/inter/Inter%20Web/inter.css" rel="stylesheet" />
@@ -23,11 +23,11 @@
 <body>
     <i class="fa fa-search text-primary" style="font-size: 96px;"></i>
     <h1 class="mb-0 text-primary">
-      Page not found!
+        Page not found!
     </h1>
     <h5 class="text-center" style="max-width: 25em;">
-      Sorry, but this page couldn't be found or is inaccessible (%status).<br/>
-      We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
+        Sorry, but this page couldn't be found or is inaccessible (%status%).<br/>
+        We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
     </h5>
     <small class="text-muted">
         <img src="https://design.codeberg.org/logo-kit/icon.svg" class="align-top">
diff --git a/html/error.go b/html/error.go
index 325dada..826c42b 100644
--- a/html/error.go
+++ b/html/error.go
@@ -1,24 +1,45 @@
 package html
 
 import (
-	"bytes"
+	"net/http"
 	"strconv"
+	"strings"
 
-	"github.com/valyala/fasthttp"
+	"codeberg.org/codeberg/pages/server/context"
 )
 
-// ReturnErrorPage sets the response status code and writes NotFoundPage to the response body, with "%status" replaced
-// with the provided status code.
-func ReturnErrorPage(ctx *fasthttp.RequestCtx, code int) {
-	ctx.Response.SetStatusCode(code)
-	ctx.Response.Header.SetContentType("text/html; charset=utf-8")
-	message := fasthttp.StatusMessage(code)
-	if code == fasthttp.StatusMisdirectedRequest {
-		message += " - domain not specified in <code>.domains</code> file"
+// ReturnErrorPage sets the response status code and writes NotFoundPage to the response body,
+// with "%status%" and %message% replaced with the provided statusCode and msg
+func ReturnErrorPage(ctx *context.Context, msg string, statusCode int) {
+	ctx.RespWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
+	ctx.RespWriter.WriteHeader(statusCode)
+
+	if msg == "" {
+		msg = errorBody(statusCode)
+	} else {
+		// TODO: use template engine
+		msg = strings.ReplaceAll(strings.ReplaceAll(ErrorPage, "%message%", msg), "%status%", http.StatusText(statusCode))
 	}
-	if code == fasthttp.StatusFailedDependency {
+
+	_, _ = ctx.RespWriter.Write([]byte(msg))
+}
+
+func errorMessage(statusCode int) string {
+	message := http.StatusText(statusCode)
+
+	switch statusCode {
+	case http.StatusMisdirectedRequest:
+		message += " - domain not specified in <code>.domains</code> file"
+	case http.StatusFailedDependency:
 		message += " - target repo/branch doesn't exist or is private"
 	}
-	// TODO: use template engine?
-	ctx.Response.SetBody(bytes.ReplaceAll(NotFoundPage, []byte("%status"), []byte(strconv.Itoa(code)+" "+message)))
+
+	return message
+}
+
+// TODO: use template engine
+func errorBody(statusCode int) string {
+	return strings.ReplaceAll(NotFoundPage,
+		"%status%",
+		strconv.Itoa(statusCode)+" "+errorMessage(statusCode))
 }
diff --git a/html/error.html b/html/error.html
new file mode 100644
index 0000000..f1975f7
--- /dev/null
+++ b/html/error.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html class="codeberg-design">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width">
+    <title>%status%</title>
+
+    <link rel="stylesheet" href="https://design.codeberg.org/design-kit/codeberg.css" />
+    <link href="https://fonts.codeberg.org/dist/inter/Inter%20Web/inter.css" rel="stylesheet" />
+    <link href="https://fonts.codeberg.org/dist/fontawesome5/css/all.min.css" rel="stylesheet" />
+
+    <style>
+        body {
+            margin: 0; padding: 1rem; box-sizing: border-box;
+            width: 100%; min-height: 100vh;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+        }
+    </style>
+</head>
+<body>
+    <i class="fa fa-search text-primary" style="font-size: 96px;"></i>
+    <h1 class="mb-0 text-primary">
+        %status%!
+    </h1>
+    <h5 class="text-center" style="max-width: 25em;">
+        Sorry, but this page couldn't be served.<br/>
+        We got an <b>"%message%"</b><br/>
+        We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
+    </h5>
+    <small class="text-muted">
+        <img src="https://design.codeberg.org/logo-kit/icon.svg" class="align-top">
+        Static pages made easy - <a href="https://codeberg.page">Codeberg Pages</a>
+    </small>
+</body>
+</html>
diff --git a/html/html.go b/html/html.go
index d223e15..a76ce59 100644
--- a/html/html.go
+++ b/html/html.go
@@ -3,4 +3,7 @@ package html
 import _ "embed"
 
 //go:embed 404.html
-var NotFoundPage []byte
+var NotFoundPage string
+
+//go:embed error.html
+var ErrorPage string
diff --git a/integration/get_test.go b/integration/get_test.go
index 6054e17..8794651 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -25,7 +25,7 @@ func TestGetRedirect(t *testing.T) {
 		t.FailNow()
 	}
 	assert.EqualValues(t, "https://www.cabr2.de/", resp.Header.Get("Location"))
-	assert.EqualValues(t, 0, getSize(resp.Body))
+	assert.EqualValues(t, `<a href="https://www.cabr2.de/">Temporary Redirect</a>.`, strings.TrimSpace(string(getBytes(resp.Body))))
 }
 
 func TestGetContent(t *testing.T) {
@@ -44,12 +44,13 @@ func TestGetContent(t *testing.T) {
 	// specify branch
 	resp, err = getTestHTTPSClient().Get("https://momar.localhost.mock.directory:4430/pag/@master/")
 	assert.NoError(t, err)
-	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+	if !assert.NotNil(t, resp) {
 		t.FailNow()
 	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
 	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
 	assert.True(t, getSize(resp.Body) > 1000)
-	assert.Len(t, resp.Header.Get("ETag"), 42)
+	assert.Len(t, resp.Header.Get("ETag"), 44)
 
 	// access branch name contains '/'
 	resp, err = getTestHTTPSClient().Get("https://blumia.localhost.mock.directory:4430/pages-server-integration-tests/@docs~main/")
@@ -59,7 +60,7 @@ func TestGetContent(t *testing.T) {
 	}
 	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
 	assert.True(t, getSize(resp.Body) > 100)
-	assert.Len(t, resp.Header.Get("ETag"), 42)
+	assert.Len(t, resp.Header.Get("ETag"), 44)
 
 	// TODO: test get of non cachable content (content size > fileCacheSizeLimit)
 }
@@ -68,9 +69,10 @@ func TestCustomDomain(t *testing.T) {
 	log.Println("=== TestCustomDomain ===")
 	resp, err := getTestHTTPSClient().Get("https://mock-pages.codeberg-test.org:4430/README.md")
 	assert.NoError(t, err)
-	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+	if !assert.NotNil(t, resp) {
 		t.FailNow()
 	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
 	assert.EqualValues(t, "text/markdown; charset=utf-8", resp.Header.Get("Content-Type"))
 	assert.EqualValues(t, "106", resp.Header.Get("Content-Length"))
 	assert.EqualValues(t, 106, getSize(resp.Body))
@@ -81,9 +83,10 @@ func TestGetNotFound(t *testing.T) {
 	// test custom not found pages
 	resp, err := getTestHTTPSClient().Get("https://crystal.localhost.mock.directory:4430/pages-404-demo/blah")
 	assert.NoError(t, err)
-	if !assert.EqualValues(t, http.StatusNotFound, resp.StatusCode) {
+	if !assert.NotNil(t, resp) {
 		t.FailNow()
 	}
+	assert.EqualValues(t, http.StatusNotFound, resp.StatusCode)
 	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
 	assert.EqualValues(t, "37", resp.Header.Get("Content-Length"))
 	assert.EqualValues(t, 37, getSize(resp.Body))
@@ -94,9 +97,10 @@ func TestFollowSymlink(t *testing.T) {
 
 	resp, err := getTestHTTPSClient().Get("https://6543.localhost.mock.directory:4430/tests_for_pages-server/@main/link")
 	assert.NoError(t, err)
-	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+	if !assert.NotNil(t, resp) {
 		t.FailNow()
 	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
 	assert.EqualValues(t, "application/octet-stream", resp.Header.Get("Content-Type"))
 	assert.EqualValues(t, "4", resp.Header.Get("Content-Length"))
 	body := getBytes(resp.Body)
@@ -109,14 +113,27 @@ func TestLFSSupport(t *testing.T) {
 
 	resp, err := getTestHTTPSClient().Get("https://6543.localhost.mock.directory:4430/tests_for_pages-server/@main/lfs.txt")
 	assert.NoError(t, err)
-	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
+	if !assert.NotNil(t, resp) {
 		t.FailNow()
 	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
 	body := strings.TrimSpace(string(getBytes(resp.Body)))
 	assert.EqualValues(t, 12, len(body))
 	assert.EqualValues(t, "actual value", body)
 }
 
+func TestGetOptions(t *testing.T) {
+	log.Println("=== TestGetOptions ===")
+	req, _ := http.NewRequest(http.MethodOptions, "https://mock-pages.codeberg-test.org:4430/README.md", nil)
+	resp, err := getTestHTTPSClient().Do(req)
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusNoContent, resp.StatusCode)
+	assert.EqualValues(t, "GET, HEAD, OPTIONS", resp.Header.Get("Allow"))
+}
+
 func getTestHTTPSClient() *http.Client {
 	cookieJar, _ := cookiejar.New(nil)
 	return &http.Client{
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 2f59fb4..429ab23 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -36,7 +36,7 @@ import (
 )
 
 // TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates.
-func TLSConfig(mainDomainSuffix []byte,
+func TLSConfig(mainDomainSuffix string,
 	giteaClient *gitea.Client,
 	dnsProvider string,
 	acmeUseRateLimits bool,
@@ -47,7 +47,6 @@ func TLSConfig(mainDomainSuffix []byte,
 		// check DNS name & get certificate from Let's Encrypt
 		GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
 			sni := strings.ToLower(strings.TrimSpace(info.ServerName))
-			sniBytes := []byte(sni)
 			if len(sni) < 1 {
 				return nil, errors.New("missing sni")
 			}
@@ -69,23 +68,20 @@ func TLSConfig(mainDomainSuffix []byte,
 			}
 
 			targetOwner := ""
-			if bytes.HasSuffix(sniBytes, mainDomainSuffix) || bytes.Equal(sniBytes, mainDomainSuffix[1:]) {
+			if strings.HasSuffix(sni, mainDomainSuffix) || strings.EqualFold(sni, mainDomainSuffix[1:]) {
 				// deliver default certificate for the main domain (*.codeberg.page)
-				sniBytes = mainDomainSuffix
-				sni = string(sniBytes)
+				sni = mainDomainSuffix
 			} else {
 				var targetRepo, targetBranch string
-				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(sni, string(mainDomainSuffix), dnsLookupCache)
+				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(sni, mainDomainSuffix, dnsLookupCache)
 				if targetOwner == "" {
 					// DNS not set up, return main certificate to redirect to the docs
-					sniBytes = mainDomainSuffix
-					sni = string(sniBytes)
+					sni = mainDomainSuffix
 				} else {
 					_, _ = targetRepo, targetBranch
-					_, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, sni, string(mainDomainSuffix), canonicalDomainCache)
+					_, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, sni, mainDomainSuffix, canonicalDomainCache)
 					if !valid {
-						sniBytes = mainDomainSuffix
-						sni = string(sniBytes)
+						sni = mainDomainSuffix
 					}
 				}
 			}
@@ -98,9 +94,9 @@ func TLSConfig(mainDomainSuffix []byte,
 			var tlsCertificate tls.Certificate
 			var err error
 			var ok bool
-			if tlsCertificate, ok = retrieveCertFromDB(sniBytes, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); !ok {
+			if tlsCertificate, ok = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); !ok {
 				// request a new certificate
-				if bytes.Equal(sniBytes, mainDomainSuffix) {
+				if strings.EqualFold(sni, mainDomainSuffix) {
 					return nil, errors.New("won't request certificate for main domain, something really bad has happened")
 				}
 
@@ -192,7 +188,7 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
 	return nil
 }
 
-func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) {
+func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) {
 	// parse certificate from database
 	res, err := certDB.Get(string(sni))
 	if err != nil {
@@ -208,7 +204,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUs
 	}
 
 	// TODO: document & put into own function
-	if !bytes.Equal(sni, mainDomainSuffix) {
+	if !strings.EqualFold(sni, mainDomainSuffix) {
 		tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
 		if err != nil {
 			panic(err)
@@ -239,7 +235,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUs
 
 var obtainLocks = sync.Map{}
 
-func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider string, mainDomainSuffix []byte, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, error) {
+func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, error) {
 	name := strings.TrimPrefix(domains[0], "*")
 	if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' {
 		domains = domains[1:]
@@ -252,7 +248,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 			time.Sleep(100 * time.Millisecond)
 			_, working = obtainLocks.Load(name)
 		}
-		cert, ok := retrieveCertFromDB([]byte(name), mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
+		cert, ok := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
 		if !ok {
 			return tls.Certificate{}, errors.New("certificate failed in synchronous request")
 		}
@@ -405,7 +401,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 	return myAcmeConfig, nil
 }
 
-func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error {
+func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error {
 	// getting main cert before ACME account so that we can fail here without hitting rate limits
 	mainCertBytes, err := certDB.Get(string(mainDomainSuffix))
 	if err != nil {
@@ -460,7 +456,7 @@ func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *
 	return nil
 }
 
-func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix []byte, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) {
+func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) {
 	for {
 		// clean up expired certs
 		now := time.Now()
@@ -468,7 +464,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 		keyDatabaseIterator := certDB.Items()
 		key, resBytes, err := keyDatabaseIterator.Next()
 		for err == nil {
-			if !bytes.Equal(key, mainDomainSuffix) {
+			if !strings.EqualFold(string(key), mainDomainSuffix) {
 				resGob := bytes.NewBuffer(resBytes)
 				resDec := gob.NewDecoder(resGob)
 				res := &certificate.Resource{}
diff --git a/server/context/context.go b/server/context/context.go
new file mode 100644
index 0000000..be01df0
--- /dev/null
+++ b/server/context/context.go
@@ -0,0 +1,62 @@
+package context
+
+import (
+	stdContext "context"
+	"net/http"
+)
+
+type Context struct {
+	RespWriter http.ResponseWriter
+	Req        *http.Request
+	StatusCode int
+}
+
+func New(w http.ResponseWriter, r *http.Request) *Context {
+	return &Context{
+		RespWriter: w,
+		Req:        r,
+		StatusCode: http.StatusOK,
+	}
+}
+
+func (c *Context) Context() stdContext.Context {
+	if c.Req != nil {
+		return c.Req.Context()
+	}
+	return stdContext.Background()
+}
+
+func (c *Context) Response() *http.Response {
+	if c.Req != nil && c.Req.Response != nil {
+		return c.Req.Response
+	}
+	return nil
+}
+
+func (c *Context) String(raw string, status ...int) {
+	code := http.StatusOK
+	if len(status) != 0 {
+		code = status[0]
+	}
+	c.RespWriter.WriteHeader(code)
+	_, _ = c.RespWriter.Write([]byte(raw))
+}
+
+func (c *Context) IsMethod(m string) bool {
+	return c.Req.Method == m
+}
+
+func (c *Context) Redirect(uri string, statusCode int) {
+	http.Redirect(c.RespWriter, c.Req, uri, statusCode)
+}
+
+// Path returns requested path.
+//
+// The returned bytes are valid until your request handler returns.
+func (c *Context) Path() string {
+	return c.Req.URL.Path
+}
+
+func (c *Context) Host() string {
+	return c.Req.URL.Host
+}
diff --git a/server/database/mock.go b/server/database/mock.go
index e6c1b5a..dfe2316 100644
--- a/server/database/mock.go
+++ b/server/database/mock.go
@@ -28,7 +28,7 @@ func (p tmpDB) Put(name string, cert *certificate.Resource) error {
 func (p tmpDB) Get(name string) (*certificate.Resource, error) {
 	cert, has := p.intern.Get(name)
 	if !has {
-		return nil, fmt.Errorf("cert for '%s' not found", name)
+		return nil, fmt.Errorf("cert for %q not found", name)
 	}
 	return cert.(*certificate.Resource), nil
 }
diff --git a/server/dns/const.go b/server/dns/const.go
deleted file mode 100644
index bb2413b..0000000
--- a/server/dns/const.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package dns
-
-import "time"
-
-// lookupCacheTimeout specifies the timeout for the DNS lookup cache.
-var lookupCacheTimeout = 15 * time.Minute
diff --git a/server/dns/dns.go b/server/dns/dns.go
index dc759b0..818e29a 100644
--- a/server/dns/dns.go
+++ b/server/dns/dns.go
@@ -3,10 +3,14 @@ package dns
 import (
 	"net"
 	"strings"
+	"time"
 
 	"codeberg.org/codeberg/pages/server/cache"
 )
 
+// lookupCacheTimeout specifies the timeout for the DNS lookup cache.
+var lookupCacheTimeout = 15 * time.Minute
+
 // GetTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix.
 // If everything is fine, it returns the target data.
 func GetTargetFromDNS(domain, mainDomainSuffix string, dnsLookupCache cache.SetGetKey) (targetOwner, targetRepo, targetBranch string) {
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index 932ff3c..b11a370 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -1,12 +1,116 @@
 package gitea
 
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net/http"
+	"time"
+
+	"github.com/rs/zerolog/log"
+
+	"codeberg.org/codeberg/pages/server/cache"
+)
+
+const (
+	// defaultBranchCacheTimeout specifies the timeout for the default branch cache. It can be quite long.
+	defaultBranchCacheTimeout = 15 * time.Minute
+
+	// branchExistenceCacheTimeout specifies the timeout for the branch timestamp & existence cache. It should be shorter
+	// than fileCacheTimeout, as that gets invalidated if the branch timestamp has changed. That way, repo changes will be
+	// picked up faster, while still allowing the content to be cached longer if nothing changes.
+	branchExistenceCacheTimeout = 5 * time.Minute
+
+	// fileCacheTimeout specifies the timeout for the file content cache - you might want to make this quite long, depending
+	// on your available memory.
+	// TODO: move as option into cache interface
+	fileCacheTimeout = 5 * time.Minute
+
+	// fileCacheSizeLimit limits the maximum file size that will be cached, and is set to 1 MB by default.
+	fileCacheSizeLimit = int64(1000 * 1000)
+)
+
 type FileResponse struct {
-	Exists   bool
-	ETag     []byte
-	MimeType string
-	Body     []byte
+	Exists    bool
+	IsSymlink bool
+	ETag      string
+	MimeType  string
+	Body      []byte
 }
 
 func (f FileResponse) IsEmpty() bool {
 	return len(f.Body) != 0
 }
+
+func (f FileResponse) createHttpResponse(cacheKey string) (http.Header, int) {
+	header := make(http.Header)
+	var statusCode int
+
+	if f.Exists {
+		statusCode = http.StatusOK
+	} else {
+		statusCode = http.StatusNotFound
+	}
+
+	if f.IsSymlink {
+		header.Set(giteaObjectTypeHeader, objTypeSymlink)
+	}
+	header.Set(ETagHeader, f.ETag)
+	header.Set(ContentTypeHeader, f.MimeType)
+	header.Set(ContentLengthHeader, fmt.Sprintf("%d", len(f.Body)))
+	header.Set(PagesCacheIndicatorHeader, "true")
+
+	log.Trace().Msgf("fileCache for %q used", cacheKey)
+	return header, statusCode
+}
+
+type BranchTimestamp struct {
+	Branch    string
+	Timestamp time.Time
+	notFound  bool
+}
+
+type writeCacheReader struct {
+	originalReader io.ReadCloser
+	buffer         *bytes.Buffer
+	rileResponse   *FileResponse
+	cacheKey       string
+	cache          cache.SetGetKey
+	hasError       bool
+}
+
+func (t *writeCacheReader) Read(p []byte) (n int, err error) {
+	n, err = t.originalReader.Read(p)
+	if err != nil {
+		log.Trace().Err(err).Msgf("[cache] original reader for %q has returned an error", t.cacheKey)
+		t.hasError = true
+	} else if n > 0 {
+		_, _ = t.buffer.Write(p[:n])
+	}
+	return
+}
+
+func (t *writeCacheReader) Close() error {
+	if !t.hasError {
+		fc := *t.rileResponse
+		fc.Body = t.buffer.Bytes()
+		_ = t.cache.Set(t.cacheKey, fc, fileCacheTimeout)
+	}
+	log.Trace().Msgf("cacheReader for %q saved=%t closed", t.cacheKey, !t.hasError)
+	return t.originalReader.Close()
+}
+
+func (f FileResponse) CreateCacheReader(r io.ReadCloser, cache cache.SetGetKey, cacheKey string) io.ReadCloser {
+	if r == nil || cache == nil || cacheKey == "" {
+		log.Error().Msg("could not create CacheReader")
+		return nil
+	}
+
+	return &writeCacheReader{
+		originalReader: r,
+		buffer:         bytes.NewBuffer(make([]byte, 0)),
+		rileResponse:   &f,
+		cache:          cache,
+		cacheKey:       cacheKey,
+	}
+}
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 16cba84..c63ee21 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -1,142 +1,276 @@
 package gitea
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
+	"io"
+	"mime"
+	"net/http"
 	"net/url"
+	"path"
+	"strconv"
 	"strings"
 	"time"
 
+	"code.gitea.io/sdk/gitea"
 	"github.com/rs/zerolog/log"
-	"github.com/valyala/fasthttp"
-	"github.com/valyala/fastjson"
-)
 
-const (
-	giteaAPIRepos         = "/api/v1/repos/"
-	giteaObjectTypeHeader = "X-Gitea-Object-Type"
+	"codeberg.org/codeberg/pages/server/cache"
 )
 
 var ErrorNotFound = errors.New("not found")
 
+const (
+	// cache key prefixe
+	branchTimestampCacheKeyPrefix = "branchTime"
+	defaultBranchCacheKeyPrefix   = "defaultBranch"
+	rawContentCacheKeyPrefix      = "rawContent"
+
+	// pages server
+	PagesCacheIndicatorHeader = "X-Pages-Cache"
+	symlinkReadLimit          = 10000
+
+	// gitea
+	giteaObjectTypeHeader = "X-Gitea-Object-Type"
+	objTypeSymlink        = "symlink"
+
+	// std
+	ETagHeader          = "ETag"
+	ContentTypeHeader   = "Content-Type"
+	ContentLengthHeader = "Content-Length"
+)
+
 type Client struct {
-	giteaRoot      string
-	giteaAPIToken  string
-	fastClient     *fasthttp.Client
-	infoTimeout    time.Duration
-	contentTimeout time.Duration
+	sdkClient     *gitea.Client
+	responseCache cache.SetGetKey
 
 	followSymlinks bool
 	supportLFS     bool
+
+	forbiddenMimeTypes map[string]bool
+	defaultMimeType    string
 }
 
-// TODO: once golang v1.19 is min requirement, we can switch to 'JoinPath()' of 'net/url' package
-func joinURL(baseURL string, paths ...string) string {
-	p := make([]string, 0, len(paths))
-	for i := range paths {
-		path := strings.TrimSpace(paths[i])
-		path = strings.Trim(path, "/")
-		if len(path) != 0 {
-			p = append(p, path)
-		}
-	}
-
-	return baseURL + "/" + strings.Join(p, "/")
-}
-
-func NewClient(giteaRoot, giteaAPIToken string, followSymlinks, supportLFS bool) (*Client, error) {
+func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, followSymlinks, supportLFS bool) (*Client, error) {
 	rootURL, err := url.Parse(giteaRoot)
+	if err != nil {
+		return nil, err
+	}
 	giteaRoot = strings.Trim(rootURL.String(), "/")
 
+	stdClient := http.Client{Timeout: 10 * time.Second}
+
+	// TODO: pass down
+	var (
+		forbiddenMimeTypes map[string]bool
+		defaultMimeType    string
+	)
+
+	if forbiddenMimeTypes == nil {
+		forbiddenMimeTypes = make(map[string]bool)
+	}
+	if defaultMimeType == "" {
+		defaultMimeType = "application/octet-stream"
+	}
+
+	sdk, err := gitea.NewClient(giteaRoot, gitea.SetHTTPClient(&stdClient), gitea.SetToken(giteaAPIToken))
 	return &Client{
-		giteaRoot:      giteaRoot,
-		giteaAPIToken:  giteaAPIToken,
-		infoTimeout:    5 * time.Second,
-		contentTimeout: 10 * time.Second,
-		fastClient:     getFastHTTPClient(),
+		sdkClient:     sdk,
+		responseCache: respCache,
 
 		followSymlinks: followSymlinks,
 		supportLFS:     supportLFS,
+
+		forbiddenMimeTypes: forbiddenMimeTypes,
+		defaultMimeType:    defaultMimeType,
 	}, err
 }
 
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
-	resp, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
+	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
 	if err != nil {
 		return nil, err
 	}
-	return resp.Body(), nil
+	defer reader.Close()
+	return io.ReadAll(reader)
 }
 
-func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (*fasthttp.Response, error) {
-	var apiURL string
-	if client.supportLFS {
-		apiURL = joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "media", resource+"?ref="+url.QueryEscape(ref))
-	} else {
-		apiURL = joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "raw", resource+"?ref="+url.QueryEscape(ref))
-	}
-	resp, err := client.do(client.contentTimeout, apiURL)
-	if err != nil {
-		return nil, err
-	}
+func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (io.ReadCloser, http.Header, int, error) {
+	cacheKey := fmt.Sprintf("%s/%s/%s|%s|%s", rawContentCacheKeyPrefix, targetOwner, targetRepo, ref, resource)
+	log := log.With().Str("cache_key", cacheKey).Logger()
 
-	if err != nil {
-		return nil, err
-	}
-
-	switch resp.StatusCode() {
-	case fasthttp.StatusOK:
-		objType := string(resp.Header.Peek(giteaObjectTypeHeader))
-		log.Trace().Msgf("server raw content object: %s", objType)
-		if client.followSymlinks && objType == "symlink" {
-			// TODO: limit to 1000 chars if we switched to std
-			linkDest := strings.TrimSpace(string(resp.Body()))
-			log.Debug().Msgf("follow symlink from '%s' to '%s'", resource, linkDest)
-			return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+	// handle if cache entry exist
+	if cache, ok := client.responseCache.Get(cacheKey); ok {
+		cache := cache.(FileResponse)
+		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey)
+		// TODO: check against some timestamp missmatch?!?
+		if cache.Exists {
+			if cache.IsSymlink {
+				linkDest := string(cache.Body)
+				log.Debug().Msgf("[cache] follow symlink from %q to %q", resource, linkDest)
+				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+			} else {
+				log.Debug().Msg("[cache] return bytes")
+				return io.NopCloser(bytes.NewReader(cache.Body)), cachedHeader, cachedStatusCode, nil
+			}
+		} else {
+			return nil, cachedHeader, cachedStatusCode, ErrorNotFound
 		}
-
-		return resp, nil
-
-	case fasthttp.StatusNotFound:
-		return nil, ErrorNotFound
-
-	default:
-		return nil, fmt.Errorf("unexpected status code '%d'", resp.StatusCode())
 	}
+
+	// not in cache, open reader via gitea api
+	reader, resp, err := client.sdkClient.GetFileReader(targetOwner, targetRepo, ref, resource, client.supportLFS)
+	if resp != nil {
+		switch resp.StatusCode {
+		case http.StatusOK:
+			// first handle symlinks
+			{
+				objType := resp.Header.Get(giteaObjectTypeHeader)
+				log.Trace().Msgf("server raw content object %q", objType)
+				if client.followSymlinks && objType == objTypeSymlink {
+					defer reader.Close()
+					// read limited chars for symlink
+					linkDestBytes, err := io.ReadAll(io.LimitReader(reader, symlinkReadLimit))
+					if err != nil {
+						return nil, nil, http.StatusInternalServerError, err
+					}
+					linkDest := strings.TrimSpace(string(linkDestBytes))
+
+					// we store symlink not content to reduce duplicates in cache
+					if err := client.responseCache.Set(cacheKey, FileResponse{
+						Exists:    true,
+						IsSymlink: true,
+						Body:      []byte(linkDest),
+						ETag:      resp.Header.Get(ETagHeader),
+					}, fileCacheTimeout); err != nil {
+						log.Error().Err(err).Msg("[cache] error on cache write")
+					}
+
+					log.Debug().Msgf("follow symlink from %q to %q", resource, linkDest)
+					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+				}
+			}
+
+			// now we are sure it's content so set the MIME type
+			mimeType := client.getMimeTypeByExtension(resource)
+			resp.Response.Header.Set(ContentTypeHeader, mimeType)
+
+			if !shouldRespBeSavedToCache(resp.Response) {
+				return reader, resp.Response.Header, resp.StatusCode, err
+			}
+
+			// now we write to cache and respond at the sime time
+			fileResp := FileResponse{
+				Exists:   true,
+				ETag:     resp.Header.Get(ETagHeader),
+				MimeType: mimeType,
+			}
+			return fileResp.CreateCacheReader(reader, client.responseCache, cacheKey), resp.Response.Header, resp.StatusCode, nil
+
+		case http.StatusNotFound:
+			if err := client.responseCache.Set(cacheKey, FileResponse{
+				Exists: false,
+				ETag:   resp.Header.Get(ETagHeader),
+			}, fileCacheTimeout); err != nil {
+				log.Error().Err(err).Msg("[cache] error on cache write")
+			}
+
+			return nil, resp.Response.Header, http.StatusNotFound, ErrorNotFound
+		default:
+			return nil, resp.Response.Header, resp.StatusCode, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
+		}
+	}
+	return nil, nil, http.StatusInternalServerError, err
 }
 
-func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchName string) (time.Time, error) {
-	url := joinURL(client.giteaRoot, giteaAPIRepos, repoOwner, repoName, "branches", branchName)
-	res, err := client.do(client.infoTimeout, url)
+func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchName string) (*BranchTimestamp, error) {
+	cacheKey := fmt.Sprintf("%s/%s/%s/%s", branchTimestampCacheKeyPrefix, repoOwner, repoName, branchName)
+
+	if stamp, ok := client.responseCache.Get(cacheKey); ok && stamp != nil {
+		branchTimeStamp := stamp.(*BranchTimestamp)
+		if branchTimeStamp.notFound {
+			log.Trace().Msgf("[cache] use branch %q not found", branchName)
+			return &BranchTimestamp{}, ErrorNotFound
+		}
+		log.Trace().Msgf("[cache] use branch %q exist", branchName)
+		return branchTimeStamp, nil
+	}
+
+	branch, resp, err := client.sdkClient.GetRepoBranch(repoOwner, repoName, branchName)
 	if err != nil {
-		return time.Time{}, err
+		if resp != nil && resp.StatusCode == http.StatusNotFound {
+			log.Trace().Msgf("[cache] set cache branch %q not found", branchName)
+			if err := client.responseCache.Set(cacheKey, &BranchTimestamp{Branch: branchName, notFound: true}, branchExistenceCacheTimeout); err != nil {
+				log.Error().Err(err).Msg("[cache] error on cache write")
+			}
+			return &BranchTimestamp{}, ErrorNotFound
+		}
+		return &BranchTimestamp{}, err
 	}
-	if res.StatusCode() != fasthttp.StatusOK {
-		return time.Time{}, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	if resp.StatusCode != http.StatusOK {
+		return &BranchTimestamp{}, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
 	}
-	return time.Parse(time.RFC3339, fastjson.GetString(res.Body(), "commit", "timestamp"))
+
+	stamp := &BranchTimestamp{
+		Branch:    branch.Name,
+		Timestamp: branch.Commit.Timestamp,
+	}
+
+	log.Trace().Msgf("set cache branch [%s] exist", branchName)
+	if err := client.responseCache.Set(cacheKey, stamp, branchExistenceCacheTimeout); err != nil {
+		log.Error().Err(err).Msg("[cache] error on cache write")
+	}
+	return stamp, nil
 }
 
 func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (string, error) {
-	url := joinURL(client.giteaRoot, giteaAPIRepos, repoOwner, repoName)
-	res, err := client.do(client.infoTimeout, url)
+	cacheKey := fmt.Sprintf("%s/%s/%s", defaultBranchCacheKeyPrefix, repoOwner, repoName)
+
+	if branch, ok := client.responseCache.Get(cacheKey); ok && branch != nil {
+		return branch.(string), nil
+	}
+
+	repo, resp, err := client.sdkClient.GetRepo(repoOwner, repoName)
 	if err != nil {
 		return "", err
 	}
-	if res.StatusCode() != fasthttp.StatusOK {
-		return "", fmt.Errorf("unexpected status code '%d'", res.StatusCode())
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
 	}
-	return fastjson.GetString(res.Body(), "default_branch"), nil
+
+	branch := repo.DefaultBranch
+	if err := client.responseCache.Set(cacheKey, branch, defaultBranchCacheTimeout); err != nil {
+		log.Error().Err(err).Msg("[cache] error on cache write")
+	}
+	return branch, nil
 }
 
-func (client *Client) do(timeout time.Duration, url string) (*fasthttp.Response, error) {
-	req := fasthttp.AcquireRequest()
-
-	req.SetRequestURI(url)
-	req.Header.Set(fasthttp.HeaderAuthorization, "token "+client.giteaAPIToken)
-	res := fasthttp.AcquireResponse()
-
-	err := client.fastClient.DoTimeout(req, res, timeout)
-
-	return res, err
+func (client *Client) getMimeTypeByExtension(resource string) string {
+	mimeType := mime.TypeByExtension(path.Ext(resource))
+	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
+	if client.forbiddenMimeTypes[mimeTypeSplit[0]] || mimeType == "" {
+		mimeType = client.defaultMimeType
+	}
+	log.Trace().Msgf("probe mime of %q is %q", resource, mimeType)
+	return mimeType
+}
+
+func shouldRespBeSavedToCache(resp *http.Response) bool {
+	if resp == nil {
+		return false
+	}
+
+	contentLengthRaw := resp.Header.Get(ContentLengthHeader)
+	if contentLengthRaw == "" {
+		return false
+	}
+
+	contentLeng, err := strconv.ParseInt(contentLengthRaw, 10, 64)
+	if err != nil {
+		log.Error().Err(err).Msg("could not parse content length")
+	}
+
+	// if content to big or could not be determined we not cache it
+	return contentLeng > 0 && contentLeng < fileCacheSizeLimit
 }
diff --git a/server/gitea/client_test.go b/server/gitea/client_test.go
deleted file mode 100644
index 7dbad68..0000000
--- a/server/gitea/client_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package gitea
-
-import (
-	"net/url"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestJoinURL(t *testing.T) {
-	baseURL := ""
-	assert.EqualValues(t, "/", joinURL(baseURL))
-	assert.EqualValues(t, "/", joinURL(baseURL, "", ""))
-
-	baseURL = "http://wwow.url.com"
-	assert.EqualValues(t, "http://wwow.url.com/a/b/c/d", joinURL(baseURL, "a", "b/c/", "d"))
-
-	baseURL = "http://wow.url.com/subpath/2"
-	assert.EqualValues(t, "http://wow.url.com/subpath/2/content.pdf", joinURL(baseURL, "/content.pdf"))
-	assert.EqualValues(t, "http://wow.url.com/subpath/2/wonderful.jpg", joinURL(baseURL, "wonderful.jpg"))
-	assert.EqualValues(t, "http://wow.url.com/subpath/2/raw/wonderful.jpg?ref=main", joinURL(baseURL, "raw", "wonderful.jpg"+"?ref="+url.QueryEscape("main")))
-	assert.EqualValues(t, "http://wow.url.com/subpath/2/raw/wonderful.jpg%3Fref=main", joinURL(baseURL, "raw", "wonderful.jpg%3Fref=main"))
-}
diff --git a/server/gitea/fasthttp.go b/server/gitea/fasthttp.go
deleted file mode 100644
index 4ff0f4a..0000000
--- a/server/gitea/fasthttp.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package gitea
-
-import (
-	"time"
-
-	"github.com/valyala/fasthttp"
-)
-
-func getFastHTTPClient() *fasthttp.Client {
-	return &fasthttp.Client{
-		MaxConnDuration:    60 * time.Second,
-		MaxConnWaitTimeout: 1000 * time.Millisecond,
-		MaxConnsPerHost:    128 * 16, // TODO: adjust bottlenecks for best performance with Gitea!
-	}
-}
diff --git a/server/handler.go b/server/handler.go
index fb8b419..894cd25 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -1,15 +1,17 @@
 package server
 
 import (
-	"bytes"
+	"fmt"
+	"net/http"
+	"path"
 	"strings"
 
 	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
-	"github.com/valyala/fasthttp"
 
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/dns"
 	"codeberg.org/codeberg/pages/server/gitea"
 	"codeberg.org/codeberg/pages/server/upstream"
@@ -17,42 +19,48 @@ import (
 	"codeberg.org/codeberg/pages/server/version"
 )
 
+const (
+	headerAccessControlAllowOrigin  = "Access-Control-Allow-Origin"
+	headerAccessControlAllowMethods = "Access-Control-Allow-Methods"
+)
+
 // Handler handles a single HTTP request to the web server.
-func Handler(mainDomainSuffix, rawDomain []byte,
+func Handler(mainDomainSuffix, rawDomain string,
 	giteaClient *gitea.Client,
 	giteaRoot, rawInfoPage string,
-	blacklistedPaths, allowedCorsDomains [][]byte,
-	dnsLookupCache, canonicalDomainCache, branchTimestampCache, fileResponseCache cache.SetGetKey,
-) func(ctx *fasthttp.RequestCtx) {
-	return func(ctx *fasthttp.RequestCtx) {
-		log := log.With().Strs("Handler", []string{string(ctx.Request.Host()), string(ctx.Request.Header.RequestURI())}).Logger()
+	blacklistedPaths, allowedCorsDomains []string,
+	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
+) http.HandlerFunc {
+	return func(w http.ResponseWriter, req *http.Request) {
+		log := log.With().Strs("Handler", []string{string(req.Host), req.RequestURI}).Logger()
+		ctx := context.New(w, req)
 
-		ctx.Response.Header.Set("Server", "CodebergPages/"+version.Version)
+		ctx.RespWriter.Header().Set("Server", "CodebergPages/"+version.Version)
 
 		// Force new default from specification (since November 2020) - see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
-		ctx.Response.Header.Set("Referrer-Policy", "strict-origin-when-cross-origin")
+		ctx.RespWriter.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
 
 		// Enable browser caching for up to 10 minutes
-		ctx.Response.Header.Set("Cache-Control", "public, max-age=600")
+		ctx.RespWriter.Header().Set("Cache-Control", "public, max-age=600")
 
-		trimmedHost := utils.TrimHostPort(ctx.Request.Host())
+		trimmedHost := utils.TrimHostPort(req.Host)
 
 		// Add HSTS for RawDomain and MainDomainSuffix
-		if hsts := GetHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" {
-			ctx.Response.Header.Set("Strict-Transport-Security", hsts)
+		if hsts := getHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" {
+			ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts)
 		}
 
 		// Block all methods not required for static pages
-		if !ctx.IsGet() && !ctx.IsHead() && !ctx.IsOptions() {
-			ctx.Response.Header.Set("Allow", "GET, HEAD, OPTIONS")
-			ctx.Error("Method not allowed", fasthttp.StatusMethodNotAllowed)
+		if !ctx.IsMethod(http.MethodGet) && !ctx.IsMethod(http.MethodHead) && !ctx.IsMethod(http.MethodOptions) {
+			ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions) // duplic 1
+			ctx.String("Method not allowed", http.StatusMethodNotAllowed)
 			return
 		}
 
 		// Block blacklisted paths (like ACME challenges)
 		for _, blacklistedPath := range blacklistedPaths {
-			if bytes.HasPrefix(ctx.Path(), blacklistedPath) {
-				html.ReturnErrorPage(ctx, fasthttp.StatusForbidden)
+			if strings.HasPrefix(ctx.Path(), blacklistedPath) {
+				html.ReturnErrorPage(ctx, "requested blacklisted path", http.StatusForbidden)
 				return
 			}
 		}
@@ -60,18 +68,19 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 		// Allow CORS for specified domains
 		allowCors := false
 		for _, allowedCorsDomain := range allowedCorsDomains {
-			if bytes.Equal(trimmedHost, allowedCorsDomain) {
+			if strings.EqualFold(trimmedHost, allowedCorsDomain) {
 				allowCors = true
 				break
 			}
 		}
 		if allowCors {
-			ctx.Response.Header.Set("Access-Control-Allow-Origin", "*")
-			ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET, HEAD")
+			ctx.RespWriter.Header().Set(headerAccessControlAllowOrigin, "*")
+			ctx.RespWriter.Header().Set(headerAccessControlAllowMethods, http.MethodGet+", "+http.MethodHead)
 		}
-		ctx.Response.Header.Set("Allow", "GET, HEAD, OPTIONS")
-		if ctx.IsOptions() {
-			ctx.Response.Header.SetStatusCode(fasthttp.StatusNoContent)
+
+		ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions) // duplic 1
+		if ctx.IsMethod(http.MethodOptions) {
+			ctx.RespWriter.WriteHeader(http.StatusNoContent)
 			return
 		}
 
@@ -83,9 +92,10 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 
 		// tryBranch checks if a branch exists and populates the target variables. If canonicalLink is non-empty, it will
 		// also disallow search indexing and add a Link header to the canonical URL.
-		tryBranch := func(log zerolog.Logger, repo, branch string, path []string, canonicalLink string) bool {
+		// TODO: move into external func to not alert vars indirectly
+		tryBranch := func(log zerolog.Logger, repo, branch string, _path []string, canonicalLink string) bool {
 			if repo == "" {
-				log.Warn().Msg("tryBranch: repo is empty")
+				log.Debug().Msg("tryBranch: repo is empty")
 				return false
 			}
 
@@ -94,23 +104,23 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			branch = strings.ReplaceAll(branch, "~", "/")
 
 			// Check if the branch exists, otherwise treat it as a file path
-			branchTimestampResult := upstream.GetBranchTimestamp(giteaClient, targetOwner, repo, branch, branchTimestampCache)
+			branchTimestampResult := upstream.GetBranchTimestamp(giteaClient, targetOwner, repo, branch)
 			if branchTimestampResult == nil {
-				log.Warn().Msg("tryBranch: branch doesn't exist")
+				log.Debug().Msg("tryBranch: branch doesn't exist")
 				return false
 			}
 
 			// Branch exists, use it
 			targetRepo = repo
-			targetPath = strings.Trim(strings.Join(path, "/"), "/")
+			targetPath = path.Join(_path...)
 			targetBranch = branchTimestampResult.Branch
 
 			targetOptions.BranchTimestamp = branchTimestampResult.Timestamp
 
 			if canonicalLink != "" {
 				// Hide from search machines & add canonical link
-				ctx.Response.Header.Set("X-Robots-Tag", "noarchive, noindex")
-				ctx.Response.Header.Set("Link",
+				ctx.RespWriter.Header().Set("X-Robots-Tag", "noarchive, noindex")
+				ctx.RespWriter.Header().Set("Link",
 					strings.NewReplacer("%b", targetBranch, "%p", targetPath).Replace(canonicalLink)+
 						"; rel=\"canonical\"",
 				)
@@ -120,22 +130,18 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			return true
 		}
 
-		log.Debug().Msg("Preparing")
-		if rawDomain != nil && bytes.Equal(trimmedHost, rawDomain) {
+		log.Debug().Msg("preparations")
+		if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) {
 			// Serve raw content from RawDomain
-			log.Debug().Msg("Serving raw domain")
+			log.Debug().Msg("raw domain")
 
 			targetOptions.TryIndexPages = false
-			if targetOptions.ForbiddenMimeTypes == nil {
-				targetOptions.ForbiddenMimeTypes = make(map[string]bool)
-			}
-			targetOptions.ForbiddenMimeTypes["text/html"] = true
-			targetOptions.DefaultMimeType = "text/plain; charset=utf-8"
+			targetOptions.ServeRaw = true
 
-			pathElements := strings.Split(string(bytes.Trim(ctx.Request.URI().Path(), "/")), "/")
+			pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
 			if len(pathElements) < 2 {
 				// https://{RawDomain}/{owner}/{repo}[/@{branch}]/{path} is required
-				ctx.Redirect(rawInfoPage, fasthttp.StatusTemporaryRedirect)
+				ctx.Redirect(rawInfoPage, http.StatusTemporaryRedirect)
 				return
 			}
 			targetOwner = pathElements[0]
@@ -143,45 +149,45 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 
 			// raw.codeberg.org/example/myrepo/@main/index.html
 			if len(pathElements) > 2 && strings.HasPrefix(pathElements[2], "@") {
-				log.Debug().Msg("Preparing raw domain, now trying with specified branch")
+				log.Debug().Msg("raw domain preparations, now trying with specified branch")
 				if tryBranch(log,
 					targetRepo, pathElements[2][1:], pathElements[3:],
 					giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 				) {
-					log.Info().Msg("tryBranch, now trying upstream 1")
+					log.Debug().Msg("tryBranch, now trying upstream 1")
 					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						canonicalDomainCache, branchTimestampCache, fileResponseCache)
+						canonicalDomainCache)
 					return
 				}
-				log.Warn().Msg("Path missed a branch")
-				html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+				log.Debug().Msg("missing branch info")
+				html.ReturnErrorPage(ctx, "missing branch info", http.StatusFailedDependency)
 				return
 			}
 
-			log.Debug().Msg("Preparing raw domain, now trying with default branch")
+			log.Debug().Msg("raw domain preparations, now trying with default branch")
 			tryBranch(log,
 				targetRepo, "", pathElements[2:],
 				giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
 			)
-			log.Info().Msg("tryBranch, now trying upstream 2")
+			log.Debug().Msg("tryBranch, now trying upstream 2")
 			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 				targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-				canonicalDomainCache, branchTimestampCache, fileResponseCache)
+				canonicalDomainCache)
 			return
 
-		} else if bytes.HasSuffix(trimmedHost, mainDomainSuffix) {
+		} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
 			// Serve pages from subdomains of MainDomainSuffix
-			log.Info().Msg("Serve pages from main domain suffix")
+			log.Debug().Msg("main domain suffix")
 
-			pathElements := strings.Split(string(bytes.Trim(ctx.Request.URI().Path(), "/")), "/")
-			targetOwner = string(bytes.TrimSuffix(trimmedHost, mainDomainSuffix))
+			pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
+			targetOwner = strings.TrimSuffix(trimmedHost, mainDomainSuffix)
 			targetRepo = pathElements[0]
 			targetPath = strings.Trim(strings.Join(pathElements[1:], "/"), "/")
 
 			if targetOwner == "www" {
 				// www.codeberg.page redirects to codeberg.page // TODO: rm hardcoded - use cname?
-				ctx.Redirect("https://"+string(mainDomainSuffix[1:])+string(ctx.Path()), fasthttp.StatusPermanentRedirect)
+				ctx.Redirect("https://"+string(mainDomainSuffix[1:])+string(ctx.Path()), http.StatusPermanentRedirect)
 				return
 			}
 
@@ -190,22 +196,24 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			if len(pathElements) > 1 && strings.HasPrefix(pathElements[1], "@") {
 				if targetRepo == "pages" {
 					// example.codeberg.org/pages/@... redirects to example.codeberg.org/@...
-					ctx.Redirect("/"+strings.Join(pathElements[1:], "/"), fasthttp.StatusTemporaryRedirect)
+					ctx.Redirect("/"+strings.Join(pathElements[1:], "/"), http.StatusTemporaryRedirect)
 					return
 				}
 
-				log.Debug().Msg("Preparing main domain, now trying with specified repo & branch")
+				log.Debug().Msg("main domain preparations, now trying with specified repo & branch")
+				branch := pathElements[1][1:]
 				if tryBranch(log,
-					pathElements[0], pathElements[1][1:], pathElements[2:],
+					pathElements[0], branch, pathElements[2:],
 					"/"+pathElements[0]+"/%p",
 				) {
-					log.Info().Msg("tryBranch, now trying upstream 3")
+					log.Debug().Msg("tryBranch, now trying upstream 3")
 					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						canonicalDomainCache, branchTimestampCache, fileResponseCache)
+						canonicalDomainCache)
 				} else {
-					log.Warn().Msg("tryBranch: upstream 3 failed")
-					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+					html.ReturnErrorPage(ctx,
+						fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", branch, targetOwner, targetRepo),
+						http.StatusFailedDependency)
 				}
 				return
 			}
@@ -213,16 +221,18 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			// Check if the first directory is a branch for the "pages" repo
 			// example.codeberg.page/@main/index.html
 			if strings.HasPrefix(pathElements[0], "@") {
-				log.Debug().Msg("Preparing main domain, now trying with specified branch")
+				log.Debug().Msg("main domain preparations, now trying with specified branch")
+				branch := pathElements[0][1:]
 				if tryBranch(log,
-					"pages", pathElements[0][1:], pathElements[1:], "/%p") {
-					log.Info().Msg("tryBranch, now trying upstream 4")
+					"pages", branch, pathElements[1:], "/%p") {
+					log.Debug().Msg("tryBranch, now trying upstream 4")
 					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						canonicalDomainCache, branchTimestampCache, fileResponseCache)
+						targetOptions, targetOwner, "pages", targetBranch, targetPath,
+						canonicalDomainCache)
 				} else {
-					log.Warn().Msg("tryBranch: upstream 4 failed")
-					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+					html.ReturnErrorPage(ctx,
+						fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", branch, targetOwner, "pages"),
+						http.StatusFailedDependency)
 				}
 				return
 			}
@@ -233,10 +243,10 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			log.Debug().Msg("main domain preparations, now trying with specified repo")
 			if pathElements[0] != "pages" && tryBranch(log,
 				pathElements[0], "pages", pathElements[1:], "") {
-				log.Info().Msg("tryBranch, now trying upstream 5")
+				log.Debug().Msg("tryBranch, now trying upstream 5")
 				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					canonicalDomainCache, branchTimestampCache, fileResponseCache)
+					canonicalDomainCache)
 				return
 			}
 
@@ -245,28 +255,31 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			log.Debug().Msg("main domain preparations, now trying with default repo/branch")
 			if tryBranch(log,
 				"pages", "", pathElements, "") {
-				log.Info().Msg("tryBranch, now trying upstream 6")
+				log.Debug().Msg("tryBranch, now trying upstream 6")
 				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					canonicalDomainCache, branchTimestampCache, fileResponseCache)
+					canonicalDomainCache)
 				return
 			}
 
 			// Couldn't find a valid repo/branch
-
-			html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+			html.ReturnErrorPage(ctx,
+				fmt.Sprintf("couldn't find a valid repo[%s]/branch[%s]", targetRepo, targetBranch),
+				http.StatusFailedDependency)
 			return
 		} else {
 			trimmedHostStr := string(trimmedHost)
 
-			// Serve pages from external domains
+			// Serve pages from custom domains
 			targetOwner, targetRepo, targetBranch = dns.GetTargetFromDNS(trimmedHostStr, string(mainDomainSuffix), dnsLookupCache)
 			if targetOwner == "" {
-				html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+				html.ReturnErrorPage(ctx,
+					"could not obtain repo owner from custom domain",
+					http.StatusFailedDependency)
 				return
 			}
 
-			pathElements := strings.Split(string(bytes.Trim(ctx.Request.URI().Path(), "/")), "/")
+			pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
 			canonicalLink := ""
 			if strings.HasPrefix(pathElements[0], "@") {
 				targetBranch = pathElements[0][1:]
@@ -275,36 +288,33 @@ func Handler(mainDomainSuffix, rawDomain []byte,
 			}
 
 			// Try to use the given repo on the given branch or the default branch
-			log.Debug().Msg("Preparing custom domain, now trying with details from DNS")
+			log.Debug().Msg("custom domain preparations, now trying with details from DNS")
 			if tryBranch(log,
 				targetRepo, targetBranch, pathElements, canonicalLink) {
 				canonicalDomain, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), canonicalDomainCache)
 				if !valid {
-					log.Warn().Msg("Custom domains, domain from DNS isn't valid/canonical")
-					html.ReturnErrorPage(ctx, fasthttp.StatusMisdirectedRequest)
+					html.ReturnErrorPage(ctx, "domain not specified in <code>.domains</code> file", http.StatusMisdirectedRequest)
 					return
 				} else if canonicalDomain != trimmedHostStr {
 					// only redirect if the target is also a codeberg page!
 					targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix), dnsLookupCache)
 					if targetOwner != "" {
-						ctx.Redirect("https://"+canonicalDomain+string(ctx.RequestURI()), fasthttp.StatusTemporaryRedirect)
+						ctx.Redirect("https://"+canonicalDomain+string(ctx.Path()), http.StatusTemporaryRedirect)
 						return
 					}
 
-					log.Warn().Msg("Custom domains, targetOwner from DNS is empty")
-					html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+					html.ReturnErrorPage(ctx, "target is no codeberg page", http.StatusFailedDependency)
 					return
 				}
 
-				log.Info().Msg("tryBranch, now trying upstream 7")
+				log.Debug().Msg("tryBranch, now trying upstream 7")
 				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
 					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					canonicalDomainCache, branchTimestampCache, fileResponseCache)
+					canonicalDomainCache)
 				return
 			}
 
-			log.Warn().Msg("Couldn't handle request, none of the options succeed")
-			html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+			html.ReturnErrorPage(ctx, "could not find target for custom domain", http.StatusFailedDependency)
 			return
 		}
 	}
diff --git a/server/handler_test.go b/server/handler_test.go
index f9a721a..c0aca14 100644
--- a/server/handler_test.go
+++ b/server/handler_test.go
@@ -1,44 +1,42 @@
 package server
 
 import (
-	"fmt"
+	"net/http/httptest"
 	"testing"
 	"time"
 
-	"github.com/valyala/fasthttp"
-
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/gitea"
+	"github.com/rs/zerolog/log"
 )
 
 func TestHandlerPerformance(t *testing.T) {
 	giteaRoot := "https://codeberg.org"
-	giteaClient, _ := gitea.NewClient(giteaRoot, "", false, false)
+	giteaClient, _ := gitea.NewClient(giteaRoot, "", cache.NewKeyValueCache(), false, false)
 	testHandler := Handler(
-		[]byte("codeberg.page"), []byte("raw.codeberg.org"),
+		"codeberg.page", "raw.codeberg.org",
 		giteaClient,
 		giteaRoot, "https://docs.codeberg.org/pages/raw-content/",
-		[][]byte{[]byte("/.well-known/acme-challenge/")},
-		[][]byte{[]byte("raw.codeberg.org"), []byte("fonts.codeberg.org"), []byte("design.codeberg.org")},
-		cache.NewKeyValueCache(),
-		cache.NewKeyValueCache(),
+		[]string{"/.well-known/acme-challenge/"},
+		[]string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
 		cache.NewKeyValueCache(),
 		cache.NewKeyValueCache(),
 	)
 
 	testCase := func(uri string, status int) {
-		ctx := &fasthttp.RequestCtx{
-			Request:  *fasthttp.AcquireRequest(),
-			Response: *fasthttp.AcquireResponse(),
-		}
-		ctx.Request.SetRequestURI(uri)
-		fmt.Printf("Start: %v\n", time.Now())
+		req := httptest.NewRequest("GET", uri, nil)
+		w := httptest.NewRecorder()
+
+		log.Printf("Start: %v\n", time.Now())
 		start := time.Now()
-		testHandler(ctx)
+		testHandler(w, req)
 		end := time.Now()
-		fmt.Printf("Done: %v\n", time.Now())
-		if ctx.Response.StatusCode() != status {
-			t.Errorf("request failed with status code %d", ctx.Response.StatusCode())
+		log.Printf("Done: %v\n", time.Now())
+
+		resp := w.Result()
+
+		if resp.StatusCode != status {
+			t.Errorf("request failed with status code %d", resp.StatusCode)
 		} else {
 			t.Logf("request took %d milliseconds", end.Sub(start).Milliseconds())
 		}
diff --git a/server/helpers.go b/server/helpers.go
index 6d55ddf..7c898cd 100644
--- a/server/helpers.go
+++ b/server/helpers.go
@@ -1,13 +1,13 @@
 package server
 
 import (
-	"bytes"
+	"strings"
 )
 
-// GetHSTSHeader returns a HSTS header with includeSubdomains & preload for MainDomainSuffix and RawDomain, or an empty
+// getHSTSHeader returns a HSTS header with includeSubdomains & preload for MainDomainSuffix and RawDomain, or an empty
 // string for custom domains.
-func GetHSTSHeader(host, mainDomainSuffix, rawDomain []byte) string {
-	if bytes.HasSuffix(host, mainDomainSuffix) || bytes.Equal(host, rawDomain) {
+func getHSTSHeader(host, mainDomainSuffix, rawDomain string) string {
+	if strings.HasSuffix(host, mainDomainSuffix) || strings.EqualFold(host, rawDomain) {
 		return "max-age=63072000; includeSubdomains; preload"
 	} else {
 		return ""
diff --git a/server/setup.go b/server/setup.go
index 176bb42..e7194ed 100644
--- a/server/setup.go
+++ b/server/setup.go
@@ -1,53 +1,27 @@
 package server
 
 import (
-	"bytes"
-	"fmt"
 	"net/http"
-	"time"
-
-	"github.com/rs/zerolog/log"
-	"github.com/valyala/fasthttp"
+	"strings"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/utils"
 )
 
-type fasthttpLogger struct{}
+func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) http.HandlerFunc {
+	challengePath := "/.well-known/acme-challenge/"
 
-func (fasthttpLogger) Printf(format string, args ...interface{}) {
-	log.Printf("FastHTTP: %s", fmt.Sprintf(format, args...))
-}
-
-func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server {
-	// Enable compression by wrapping the handler with the compression function provided by FastHTTP
-	compressedHandler := fasthttp.CompressHandlerBrotliLevel(handler, fasthttp.CompressBrotliBestSpeed, fasthttp.CompressBestSpeed)
-
-	return &fasthttp.Server{
-		Handler:                      compressedHandler,
-		DisablePreParseMultipartForm: true,
-		NoDefaultServerHeader:        true,
-		NoDefaultDate:                true,
-		ReadTimeout:                  30 * time.Second, // needs to be this high for ACME certificates with ZeroSSL & HTTP-01 challenge
-		Logger:                       fasthttpLogger{},
-	}
-}
-
-func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) *fasthttp.Server {
-	challengePath := []byte("/.well-known/acme-challenge/")
-
-	return &fasthttp.Server{
-		Handler: func(ctx *fasthttp.RequestCtx) {
-			if bytes.HasPrefix(ctx.Path(), challengePath) {
-				challenge, ok := challengeCache.Get(string(utils.TrimHostPort(ctx.Host())) + "/" + string(bytes.TrimPrefix(ctx.Path(), challengePath)))
-				if !ok || challenge == nil {
-					ctx.SetStatusCode(http.StatusNotFound)
-					ctx.SetBodyString("no challenge for this token")
-				}
-				ctx.SetBodyString(challenge.(string))
-			} else {
-				ctx.Redirect("https://"+string(ctx.Host())+string(ctx.RequestURI()), http.StatusMovedPermanently)
+	return func(w http.ResponseWriter, req *http.Request) {
+		ctx := context.New(w, req)
+		if strings.HasPrefix(ctx.Path(), challengePath) {
+			challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + string(strings.TrimPrefix(ctx.Path(), challengePath)))
+			if !ok || challenge == nil {
+				ctx.String("no challenge for this token", http.StatusNotFound)
 			}
-		},
+			ctx.String(challenge.(string))
+		} else {
+			ctx.Redirect("https://"+string(ctx.Host())+string(ctx.Path()), http.StatusMovedPermanently)
+		}
 	}
 }
diff --git a/server/try.go b/server/try.go
index 24831c4..135c1e0 100644
--- a/server/try.go
+++ b/server/try.go
@@ -1,38 +1,37 @@
 package server
 
 import (
-	"bytes"
+	"net/http"
 	"strings"
 
-	"github.com/valyala/fasthttp"
-
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/gitea"
 	"codeberg.org/codeberg/pages/server/upstream"
 )
 
 // tryUpstream forwards the target request to the Gitea API, and shows an error page on failure.
-func tryUpstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
-	mainDomainSuffix, trimmedHost []byte,
+func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
+	mainDomainSuffix, trimmedHost string,
 
 	targetOptions *upstream.Options,
 	targetOwner, targetRepo, targetBranch, targetPath string,
 
-	canonicalDomainCache, branchTimestampCache, fileResponseCache cache.SetGetKey,
+	canonicalDomainCache cache.SetGetKey,
 ) {
 	// check if a canonical domain exists on a request on MainDomain
-	if bytes.HasSuffix(trimmedHost, mainDomainSuffix) {
+	if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
 		canonicalDomain, _ := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), canonicalDomainCache)
 		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) {
-			canonicalPath := string(ctx.RequestURI())
+			canonicalPath := ctx.Req.RequestURI
 			if targetRepo != "pages" {
 				path := strings.SplitN(canonicalPath, "/", 3)
 				if len(path) >= 3 {
 					canonicalPath = "/" + path[2]
 				}
 			}
-			ctx.Redirect("https://"+canonicalDomain+canonicalPath, fasthttp.StatusTemporaryRedirect)
+			ctx.Redirect("https://"+canonicalDomain+canonicalPath, http.StatusTemporaryRedirect)
 			return
 		}
 	}
@@ -44,7 +43,7 @@ func tryUpstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 	targetOptions.Host = string(trimmedHost)
 
 	// Try to request the file from the Gitea API
-	if !targetOptions.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
-		html.ReturnErrorPage(ctx, ctx.Response.StatusCode())
+	if !targetOptions.Upstream(ctx, giteaClient) {
+		html.ReturnErrorPage(ctx, "", ctx.StatusCode)
 	}
 }
diff --git a/server/upstream/const.go b/server/upstream/const.go
deleted file mode 100644
index 247e1d1..0000000
--- a/server/upstream/const.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package upstream
-
-import "time"
-
-// defaultBranchCacheTimeout specifies the timeout for the default branch cache. It can be quite long.
-var defaultBranchCacheTimeout = 15 * time.Minute
-
-// branchExistenceCacheTimeout specifies the timeout for the branch timestamp & existence cache. It should be shorter
-// than fileCacheTimeout, as that gets invalidated if the branch timestamp has changed. That way, repo changes will be
-// picked up faster, while still allowing the content to be cached longer if nothing changes.
-var branchExistenceCacheTimeout = 5 * time.Minute
-
-// fileCacheTimeout specifies the timeout for the file content cache - you might want to make this quite long, depending
-// on your available memory.
-// TODO: move as option into cache interface
-var fileCacheTimeout = 5 * time.Minute
-
-// fileCacheSizeLimit limits the maximum file size that will be cached, and is set to 1 MB by default.
-var fileCacheSizeLimit = 1024 * 1024
-
-// canonicalDomainCacheTimeout specifies the timeout for the canonical domain cache.
-var canonicalDomainCacheTimeout = 15 * time.Minute
-
-const canonicalDomainConfig = ".domains"
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 553c148..6ad6506 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -2,11 +2,19 @@ package upstream
 
 import (
 	"strings"
+	"time"
+
+	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/gitea"
 )
 
+// canonicalDomainCacheTimeout specifies the timeout for the canonical domain cache.
+var canonicalDomainCacheTimeout = 15 * time.Minute
+
+const canonicalDomainConfig = ".domains"
+
 // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file).
 func CheckCanonicalDomain(giteaClient *gitea.Client, targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.SetGetKey) (string, bool) {
 	var (
@@ -36,6 +44,8 @@ func CheckCanonicalDomain(giteaClient *gitea.Client, targetOwner, targetRepo, ta
 					valid = true
 				}
 			}
+		} else {
+			log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, targetOwner, targetRepo)
 		}
 		domains = append(domains, targetOwner+mainDomainSuffix)
 		if domains[len(domains)-1] == actualDomain {
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index 28f4474..6bc23c8 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -1,84 +1,36 @@
 package upstream
 
 import (
-	"mime"
-	"path"
-	"strconv"
-	"strings"
-	"time"
+	"errors"
 
-	"codeberg.org/codeberg/pages/server/cache"
-	"codeberg.org/codeberg/pages/server/gitea"
 	"github.com/rs/zerolog/log"
-)
 
-type branchTimestamp struct {
-	Branch    string
-	Timestamp time.Time
-}
+	"codeberg.org/codeberg/pages/server/gitea"
+)
 
 // GetBranchTimestamp finds the default branch (if branch is "") and returns the last modification time of the branch
 // (or nil if the branch doesn't exist)
-func GetBranchTimestamp(giteaClient *gitea.Client, owner, repo, branch string, branchTimestampCache cache.SetGetKey) *branchTimestamp {
+func GetBranchTimestamp(giteaClient *gitea.Client, owner, repo, branch string) *gitea.BranchTimestamp {
 	log := log.With().Strs("BranchInfo", []string{owner, repo, branch}).Logger()
-	if result, ok := branchTimestampCache.Get(owner + "/" + repo + "/" + branch); ok {
-		if result == nil {
-			log.Debug().Msg("branchTimestampCache found item, but result is empty")
-			return nil
-		}
-		log.Debug().Msg("branchTimestampCache found item, returning result")
-		return result.(*branchTimestamp)
-	}
-	result := &branchTimestamp{
-		Branch: branch,
-	}
+
 	if len(branch) == 0 {
 		// Get default branch
 		defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(owner, repo)
 		if err != nil {
 			log.Err(err).Msg("Could't fetch default branch from repository")
-			_ = branchTimestampCache.Set(owner+"/"+repo+"/", nil, defaultBranchCacheTimeout)
 			return nil
 		}
-		log.Debug().Msg("Succesfully fetched default branch from Gitea")
-		result.Branch = defaultBranch
+		log.Debug().Msgf("Succesfully fetched default branch %q from Gitea", defaultBranch)
+		branch = defaultBranch
 	}
 
-	timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(owner, repo, result.Branch)
+	timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(owner, repo, branch)
 	if err != nil {
-		log.Err(err).Msg("Could not get latest commit's timestamp from branch")
+		if !errors.Is(err, gitea.ErrorNotFound) {
+			log.Error().Err(err).Msg("Could not get latest commit's timestamp from branch")
+		}
 		return nil
 	}
-	log.Debug().Msg("Succesfully fetched latest commit's timestamp from branch, adding to cache")
-	result.Timestamp = timestamp
-	_ = branchTimestampCache.Set(owner+"/"+repo+"/"+branch, result, branchExistenceCacheTimeout)
-	return result
-}
-
-func (o *Options) getMimeTypeByExtension() string {
-	if o.ForbiddenMimeTypes == nil {
-		o.ForbiddenMimeTypes = make(map[string]bool)
-	}
-	mimeType := mime.TypeByExtension(path.Ext(o.TargetPath))
-	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
-	if o.ForbiddenMimeTypes[mimeTypeSplit[0]] || mimeType == "" {
-		if o.DefaultMimeType != "" {
-			mimeType = o.DefaultMimeType
-		} else {
-			mimeType = "application/octet-stream"
-		}
-	}
-	return mimeType
-}
-
-func (o *Options) generateUri() string {
-	return path.Join(o.TargetOwner, o.TargetRepo, "raw", o.TargetBranch, o.TargetPath)
-}
-
-func (o *Options) generateUriClientArgs() (targetOwner, targetRepo, ref, resource string) {
-	return o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath
-}
-
-func (o *Options) timestamp() string {
-	return strconv.FormatInt(o.BranchTimestamp.Unix(), 10)
+	log.Debug().Msgf("Succesfully fetched latest commit's timestamp from branch: %#v", timestamp)
+	return timestamp
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 61c90de..d37c35e 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -1,20 +1,27 @@
 package upstream
 
 import (
-	"bytes"
 	"errors"
+	"fmt"
 	"io"
+	"net/http"
 	"strings"
 	"time"
 
 	"github.com/rs/zerolog/log"
-	"github.com/valyala/fasthttp"
 
 	"codeberg.org/codeberg/pages/html"
-	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/gitea"
 )
 
+const (
+	headerLastModified    = "Last-Modified"
+	headerIfModifiedSince = "If-Modified-Since"
+
+	rawMime = "text/plain; charset=utf-8"
+)
+
 // upstreamIndexPages lists pages that may be considered as index pages for directories.
 var upstreamIndexPages = []string{
 	"index.html",
@@ -35,61 +42,61 @@ type Options struct {
 	// Used for debugging purposes.
 	Host string
 
-	DefaultMimeType    string
-	ForbiddenMimeTypes map[string]bool
-	TryIndexPages      bool
-	BranchTimestamp    time.Time
+	TryIndexPages   bool
+	BranchTimestamp time.Time
 	// internal
 	appendTrailingSlash bool
 	redirectIfExists    string
+
+	ServeRaw bool
 }
 
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
-func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client, branchTimestampCache, fileResponseCache cache.SetGetKey) (final bool) {
-	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath, o.Host}).Logger()
+func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (final bool) {
+	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
+
+	if o.TargetOwner == "" || o.TargetRepo == "" {
+		html.ReturnErrorPage(ctx, "either repo owner or name info is missing", http.StatusBadRequest)
+		return true
+	}
 
 	// Check if the branch exists and when it was modified
 	if o.BranchTimestamp.IsZero() {
-		branch := GetBranchTimestamp(giteaClient, o.TargetOwner, o.TargetRepo, o.TargetBranch, branchTimestampCache)
+		branch := GetBranchTimestamp(giteaClient, o.TargetOwner, o.TargetRepo, o.TargetBranch)
 
-		if branch == nil {
-			html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
+		if branch == nil || branch.Branch == "" {
+			html.ReturnErrorPage(ctx,
+				fmt.Sprintf("could not get timestamp of branch %q", o.TargetBranch),
+				http.StatusFailedDependency)
 			return true
 		}
 		o.TargetBranch = branch.Branch
 		o.BranchTimestamp = branch.Timestamp
 	}
 
-	if o.TargetOwner == "" || o.TargetRepo == "" || o.TargetBranch == "" {
-		html.ReturnErrorPage(ctx, fasthttp.StatusBadRequest)
-		return true
-	}
-
 	// Check if the browser has a cached version
-	if ifModifiedSince, err := time.Parse(time.RFC1123, string(ctx.Request.Header.Peek("If-Modified-Since"))); err == nil {
-		if !ifModifiedSince.Before(o.BranchTimestamp) {
-			ctx.Response.SetStatusCode(fasthttp.StatusNotModified)
-			return true
+	if ctx.Response() != nil {
+		if ifModifiedSince, err := time.Parse(time.RFC1123, string(ctx.Response().Header.Get(headerIfModifiedSince))); err == nil {
+			if !ifModifiedSince.Before(o.BranchTimestamp) {
+				ctx.RespWriter.WriteHeader(http.StatusNotModified)
+				log.Trace().Msg("check response against last modified: valid")
+				return true
+			}
 		}
+		log.Trace().Msg("check response against last modified: outdated")
 	}
 
 	log.Debug().Msg("Preparing")
 
-	// Make a GET request to the upstream URL
-	uri := o.generateUri()
-	var res *fasthttp.Response
-	var cachedResponse gitea.FileResponse
-	var err error
-	if cachedValue, ok := fileResponseCache.Get(uri + "?timestamp=" + o.timestamp()); ok && !cachedValue.(gitea.FileResponse).IsEmpty() {
-		cachedResponse = cachedValue.(gitea.FileResponse)
-	} else {
-		res, err = giteaClient.ServeRawContent(o.generateUriClientArgs())
+	reader, header, statusCode, err := giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath)
+	if reader != nil {
+		defer reader.Close()
 	}
 
 	log.Debug().Msg("Aquisting")
 
-	// Handle errors
-	if (err != nil && errors.Is(err, gitea.ErrorNotFound)) || (res == nil && !cachedResponse.Exists) {
+	// Handle not found error
+	if err != nil && errors.Is(err, gitea.ErrorNotFound) {
 		if o.TryIndexPages {
 			// copy the o struct & try if an index page exists
 			optionsForIndexPages := *o
@@ -97,25 +104,20 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 			optionsForIndexPages.appendTrailingSlash = true
 			for _, indexPage := range upstreamIndexPages {
 				optionsForIndexPages.TargetPath = strings.TrimSuffix(o.TargetPath, "/") + "/" + indexPage
-				if optionsForIndexPages.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
-					_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
-						Exists: false,
-					}, fileCacheTimeout)
+				if optionsForIndexPages.Upstream(ctx, giteaClient) {
 					return true
 				}
 			}
 			// compatibility fix for GitHub Pages (/example → /example.html)
 			optionsForIndexPages.appendTrailingSlash = false
-			optionsForIndexPages.redirectIfExists = strings.TrimSuffix(string(ctx.Request.URI().Path()), "/") + ".html"
+			optionsForIndexPages.redirectIfExists = strings.TrimSuffix(ctx.Path(), "/") + ".html"
 			optionsForIndexPages.TargetPath = o.TargetPath + ".html"
-			if optionsForIndexPages.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
-				_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
-					Exists: false,
-				}, fileCacheTimeout)
+			if optionsForIndexPages.Upstream(ctx, giteaClient) {
 				return true
 			}
 		}
-		ctx.Response.SetStatusCode(fasthttp.StatusNotFound)
+
+		ctx.StatusCode = http.StatusNotFound
 		if o.TryIndexPages {
 			// copy the o struct & try if a not found page exists
 			optionsForNotFoundPages := *o
@@ -123,94 +125,84 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client,
 			optionsForNotFoundPages.appendTrailingSlash = false
 			for _, notFoundPage := range upstreamNotFoundPages {
 				optionsForNotFoundPages.TargetPath = "/" + notFoundPage
-				if optionsForNotFoundPages.Upstream(ctx, giteaClient, branchTimestampCache, fileResponseCache) {
-					_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
-						Exists: false,
-					}, fileCacheTimeout)
+				if optionsForNotFoundPages.Upstream(ctx, giteaClient) {
 					return true
 				}
 			}
 		}
-		if res != nil {
-			// Update cache if the request is fresh
-			_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
-				Exists: false,
-			}, fileCacheTimeout)
-		}
 		return false
 	}
-	if res != nil && (err != nil || res.StatusCode() != fasthttp.StatusOK) {
-		log.Warn().Msgf("Couldn't fetch contents from %q: %v (status code %d)", uri, err, res.StatusCode())
-		html.ReturnErrorPage(ctx, fasthttp.StatusInternalServerError)
+
+	// handle unexpected client errors
+	if err != nil || reader == nil || statusCode != http.StatusOK {
+		log.Debug().Msg("Handling error")
+		var msg string
+
+		if err != nil {
+			msg = "gitea client returned unexpected error"
+			log.Error().Err(err).Msg(msg)
+			msg = fmt.Sprintf("%s: %v", msg, err)
+		}
+		if reader == nil {
+			msg = "gitea client returned no reader"
+			log.Error().Msg(msg)
+		}
+		if statusCode != http.StatusOK {
+			msg = fmt.Sprintf("Couldn't fetch contents (status code %d)", statusCode)
+			log.Error().Msg(msg)
+		}
+
+		html.ReturnErrorPage(ctx, msg, http.StatusInternalServerError)
 		return true
 	}
 
 	// Append trailing slash if missing (for index files), and redirect to fix filenames in general
 	// o.appendTrailingSlash is only true when looking for index pages
-	if o.appendTrailingSlash && !bytes.HasSuffix(ctx.Request.URI().Path(), []byte{'/'}) {
-		ctx.Redirect(string(ctx.Request.URI().Path())+"/", fasthttp.StatusTemporaryRedirect)
+	if o.appendTrailingSlash && !strings.HasSuffix(ctx.Path(), "/") {
+		ctx.Redirect(ctx.Path()+"/", http.StatusTemporaryRedirect)
 		return true
 	}
-	if bytes.HasSuffix(ctx.Request.URI().Path(), []byte("/index.html")) {
-		ctx.Redirect(strings.TrimSuffix(string(ctx.Request.URI().Path()), "index.html"), fasthttp.StatusTemporaryRedirect)
+	if strings.HasSuffix(ctx.Path(), "/index.html") {
+		ctx.Redirect(strings.TrimSuffix(ctx.Path(), "index.html"), http.StatusTemporaryRedirect)
 		return true
 	}
 	if o.redirectIfExists != "" {
-		ctx.Redirect(o.redirectIfExists, fasthttp.StatusTemporaryRedirect)
+		ctx.Redirect(o.redirectIfExists, http.StatusTemporaryRedirect)
 		return true
 	}
 
-	log.Debug().Msg("Handling error")
-
-	// Set the MIME type
-	mimeType := o.getMimeTypeByExtension()
-	ctx.Response.Header.SetContentType(mimeType)
-
-	// Set ETag
-	if cachedResponse.Exists {
-		ctx.Response.Header.SetBytesV(fasthttp.HeaderETag, cachedResponse.ETag)
-	} else if res != nil {
-		cachedResponse.ETag = res.Header.Peek(fasthttp.HeaderETag)
-		ctx.Response.Header.SetBytesV(fasthttp.HeaderETag, cachedResponse.ETag)
+	// Set ETag & MIME
+	if eTag := header.Get(gitea.ETagHeader); eTag != "" {
+		ctx.RespWriter.Header().Set(gitea.ETagHeader, eTag)
 	}
-
-	if ctx.Response.StatusCode() != fasthttp.StatusNotFound {
-		// Everything's okay so far
-		ctx.Response.SetStatusCode(fasthttp.StatusOK)
+	if cacheIndicator := header.Get(gitea.PagesCacheIndicatorHeader); cacheIndicator != "" {
+		ctx.RespWriter.Header().Set(gitea.PagesCacheIndicatorHeader, cacheIndicator)
 	}
-	ctx.Response.Header.SetLastModified(o.BranchTimestamp)
+	if length := header.Get(gitea.ContentLengthHeader); length != "" {
+		ctx.RespWriter.Header().Set(gitea.ContentLengthHeader, length)
+	}
+	if mime := header.Get(gitea.ContentTypeHeader); mime == "" || o.ServeRaw {
+		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, rawMime)
+	} else {
+		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, mime)
+	}
+	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(time.RFC1123))
 
 	log.Debug().Msg("Prepare response")
 
-	// Write the response body to the original request
-	var cacheBodyWriter bytes.Buffer
-	if res != nil {
-		if res.Header.ContentLength() > fileCacheSizeLimit {
-			// fasthttp else will set "Content-Length: 0"
-			ctx.Response.SetBodyStream(&strings.Reader{}, -1)
+	ctx.RespWriter.WriteHeader(ctx.StatusCode)
 
-			err = res.BodyWriteTo(ctx.Response.BodyWriter())
-		} else {
-			// TODO: cache is half-empty if request is cancelled - does the ctx.Err() below do the trick?
-			err = res.BodyWriteTo(io.MultiWriter(ctx.Response.BodyWriter(), &cacheBodyWriter))
+	// Write the response body to the original request
+	if reader != nil {
+		_, err := io.Copy(ctx.RespWriter, reader)
+		if err != nil {
+			log.Error().Err(err).Msgf("Couldn't write body for %q", o.TargetPath)
+			html.ReturnErrorPage(ctx, "", http.StatusInternalServerError)
+			return true
 		}
-	} else {
-		_, err = ctx.Write(cachedResponse.Body)
-	}
-	if err != nil {
-		log.Error().Err(err).Msgf("Couldn't write body for %q", uri)
-		html.ReturnErrorPage(ctx, fasthttp.StatusInternalServerError)
-		return true
 	}
 
 	log.Debug().Msg("Sending response")
 
-	if res != nil && res.Header.ContentLength() <= fileCacheSizeLimit && ctx.Err() == nil {
-		cachedResponse.Exists = true
-		cachedResponse.MimeType = mimeType
-		cachedResponse.Body = cacheBodyWriter.Bytes()
-		_ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), cachedResponse, fileCacheTimeout)
-	}
-
 	return true
 }
diff --git a/server/utils/utils.go b/server/utils/utils.go
index 7be330f..30f948d 100644
--- a/server/utils/utils.go
+++ b/server/utils/utils.go
@@ -1,9 +1,11 @@
 package utils
 
-import "bytes"
+import (
+	"strings"
+)
 
-func TrimHostPort(host []byte) []byte {
-	i := bytes.IndexByte(host, ':')
+func TrimHostPort(host string) string {
+	i := strings.IndexByte(host, ':')
 	if i >= 0 {
 		return host[:i]
 	}
diff --git a/server/utils/utils_test.go b/server/utils/utils_test.go
index 3dc0632..2532392 100644
--- a/server/utils/utils_test.go
+++ b/server/utils/utils_test.go
@@ -7,7 +7,7 @@ import (
 )
 
 func TestTrimHostPort(t *testing.T) {
-	assert.EqualValues(t, "aa", TrimHostPort([]byte("aa")))
-	assert.EqualValues(t, "", TrimHostPort([]byte(":")))
-	assert.EqualValues(t, "example.com", TrimHostPort([]byte("example.com:80")))
+	assert.EqualValues(t, "aa", TrimHostPort("aa"))
+	assert.EqualValues(t, "", TrimHostPort(":"))
+	assert.EqualValues(t, "example.com", TrimHostPort("example.com:80"))
 }

From 6c63b66ce44535cb2fdc3543780e01fa9994e3d5 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 12 Nov 2022 20:43:44 +0100
Subject: [PATCH 043/196] Refactor split long functions (#135)

we have big functions that handle all stuff ... we should split this into smaler chuncks so we could test them seperate and make clear cuts in what happens where

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/135
---
 cmd/main.go                             |   5 +-
 server/certificates/certificates.go     |   8 +-
 server/context/context.go               |  10 +-
 server/gitea/client.go                  |   8 +
 server/handler.go                       | 321 ------------------------
 server/handler/handler.go               | 111 ++++++++
 server/handler/handler_custom_domain.go |  71 ++++++
 server/handler/handler_raw_domain.go    |  67 +++++
 server/handler/handler_sub_domain.go    | 120 +++++++++
 server/{ => handler}/handler_test.go    |   7 +-
 server/{helpers.go => handler/hsts.go}  |   2 +-
 server/handler/try.go                   |  76 ++++++
 server/try.go                           |  49 ----
 server/upstream/domains.go              |  16 +-
 server/upstream/header.go               |  28 +++
 server/upstream/helper.go               |  33 ++-
 server/upstream/upstream.go             |  42 ++--
 17 files changed, 547 insertions(+), 427 deletions(-)
 delete mode 100644 server/handler.go
 create mode 100644 server/handler/handler.go
 create mode 100644 server/handler/handler_custom_domain.go
 create mode 100644 server/handler/handler_raw_domain.go
 create mode 100644 server/handler/handler_sub_domain.go
 rename server/{ => handler}/handler_test.go (85%)
 rename server/{helpers.go => handler/hsts.go} (96%)
 create mode 100644 server/handler/try.go
 delete mode 100644 server/try.go
 create mode 100644 server/upstream/header.go

diff --git a/cmd/main.go b/cmd/main.go
index a3a61e1..6ad1aa8 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -20,6 +20,7 @@ import (
 	"codeberg.org/codeberg/pages/server/certificates"
 	"codeberg.org/codeberg/pages/server/database"
 	"codeberg.org/codeberg/pages/server/gitea"
+	"codeberg.org/codeberg/pages/server/handler"
 )
 
 // AllowedCorsDomains lists the domains for which Cross-Origin Resource Sharing is allowed.
@@ -88,9 +89,9 @@ func Serve(ctx *cli.Context) error {
 	}
 
 	// Create handler based on settings
-	httpsHandler := server.Handler(mainDomainSuffix, rawDomain,
+	httpsHandler := handler.Handler(mainDomainSuffix, rawDomain,
 		giteaClient,
-		giteaRoot, rawInfoPage,
+		rawInfoPage,
 		BlacklistedPaths, allowedCorsDomains,
 		dnsLookupCache, canonicalDomainCache)
 
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 429ab23..42620b1 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -78,8 +78,12 @@ func TLSConfig(mainDomainSuffix string,
 					// DNS not set up, return main certificate to redirect to the docs
 					sni = mainDomainSuffix
 				} else {
-					_, _ = targetRepo, targetBranch
-					_, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, sni, mainDomainSuffix, canonicalDomainCache)
+					targetOpt := &upstream.Options{
+						TargetOwner:  targetOwner,
+						TargetRepo:   targetRepo,
+						TargetBranch: targetBranch,
+					}
+					_, valid := targetOpt.CheckCanonicalDomain(giteaClient, sni, mainDomainSuffix, canonicalDomainCache)
 					if !valid {
 						sni = mainDomainSuffix
 					}
diff --git a/server/context/context.go b/server/context/context.go
index be01df0..481fee2 100644
--- a/server/context/context.go
+++ b/server/context/context.go
@@ -3,6 +3,8 @@ package context
 import (
 	stdContext "context"
 	"net/http"
+
+	"codeberg.org/codeberg/pages/server/utils"
 )
 
 type Context struct {
@@ -42,10 +44,6 @@ func (c *Context) String(raw string, status ...int) {
 	_, _ = c.RespWriter.Write([]byte(raw))
 }
 
-func (c *Context) IsMethod(m string) bool {
-	return c.Req.Method == m
-}
-
 func (c *Context) Redirect(uri string, statusCode int) {
 	http.Redirect(c.RespWriter, c.Req, uri, statusCode)
 }
@@ -60,3 +58,7 @@ func (c *Context) Path() string {
 func (c *Context) Host() string {
 	return c.Req.URL.Host
 }
+
+func (c *Context) TrimHostPort() string {
+	return utils.TrimHostPort(c.Req.Host)
+}
diff --git a/server/gitea/client.go b/server/gitea/client.go
index c63ee21..51647ba 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -45,6 +45,8 @@ type Client struct {
 	sdkClient     *gitea.Client
 	responseCache cache.SetGetKey
 
+	giteaRoot string
+
 	followSymlinks bool
 	supportLFS     bool
 
@@ -79,6 +81,8 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, follo
 		sdkClient:     sdk,
 		responseCache: respCache,
 
+		giteaRoot: giteaRoot,
+
 		followSymlinks: followSymlinks,
 		supportLFS:     supportLFS,
 
@@ -87,6 +91,10 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, follo
 	}, err
 }
 
+func (client *Client) ContentWebLink(targetOwner, targetRepo, branch, resource string) string {
+	return path.Join(client.giteaRoot, targetOwner, targetRepo, "src/branch", branch, resource)
+}
+
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
 	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
 	if err != nil {
diff --git a/server/handler.go b/server/handler.go
deleted file mode 100644
index 894cd25..0000000
--- a/server/handler.go
+++ /dev/null
@@ -1,321 +0,0 @@
-package server
-
-import (
-	"fmt"
-	"net/http"
-	"path"
-	"strings"
-
-	"github.com/rs/zerolog"
-	"github.com/rs/zerolog/log"
-
-	"codeberg.org/codeberg/pages/html"
-	"codeberg.org/codeberg/pages/server/cache"
-	"codeberg.org/codeberg/pages/server/context"
-	"codeberg.org/codeberg/pages/server/dns"
-	"codeberg.org/codeberg/pages/server/gitea"
-	"codeberg.org/codeberg/pages/server/upstream"
-	"codeberg.org/codeberg/pages/server/utils"
-	"codeberg.org/codeberg/pages/server/version"
-)
-
-const (
-	headerAccessControlAllowOrigin  = "Access-Control-Allow-Origin"
-	headerAccessControlAllowMethods = "Access-Control-Allow-Methods"
-)
-
-// Handler handles a single HTTP request to the web server.
-func Handler(mainDomainSuffix, rawDomain string,
-	giteaClient *gitea.Client,
-	giteaRoot, rawInfoPage string,
-	blacklistedPaths, allowedCorsDomains []string,
-	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
-) http.HandlerFunc {
-	return func(w http.ResponseWriter, req *http.Request) {
-		log := log.With().Strs("Handler", []string{string(req.Host), req.RequestURI}).Logger()
-		ctx := context.New(w, req)
-
-		ctx.RespWriter.Header().Set("Server", "CodebergPages/"+version.Version)
-
-		// Force new default from specification (since November 2020) - see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
-		ctx.RespWriter.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
-
-		// Enable browser caching for up to 10 minutes
-		ctx.RespWriter.Header().Set("Cache-Control", "public, max-age=600")
-
-		trimmedHost := utils.TrimHostPort(req.Host)
-
-		// Add HSTS for RawDomain and MainDomainSuffix
-		if hsts := getHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" {
-			ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts)
-		}
-
-		// Block all methods not required for static pages
-		if !ctx.IsMethod(http.MethodGet) && !ctx.IsMethod(http.MethodHead) && !ctx.IsMethod(http.MethodOptions) {
-			ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions) // duplic 1
-			ctx.String("Method not allowed", http.StatusMethodNotAllowed)
-			return
-		}
-
-		// Block blacklisted paths (like ACME challenges)
-		for _, blacklistedPath := range blacklistedPaths {
-			if strings.HasPrefix(ctx.Path(), blacklistedPath) {
-				html.ReturnErrorPage(ctx, "requested blacklisted path", http.StatusForbidden)
-				return
-			}
-		}
-
-		// Allow CORS for specified domains
-		allowCors := false
-		for _, allowedCorsDomain := range allowedCorsDomains {
-			if strings.EqualFold(trimmedHost, allowedCorsDomain) {
-				allowCors = true
-				break
-			}
-		}
-		if allowCors {
-			ctx.RespWriter.Header().Set(headerAccessControlAllowOrigin, "*")
-			ctx.RespWriter.Header().Set(headerAccessControlAllowMethods, http.MethodGet+", "+http.MethodHead)
-		}
-
-		ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions) // duplic 1
-		if ctx.IsMethod(http.MethodOptions) {
-			ctx.RespWriter.WriteHeader(http.StatusNoContent)
-			return
-		}
-
-		// Prepare request information to Gitea
-		var targetOwner, targetRepo, targetBranch, targetPath string
-		targetOptions := &upstream.Options{
-			TryIndexPages: true,
-		}
-
-		// tryBranch checks if a branch exists and populates the target variables. If canonicalLink is non-empty, it will
-		// also disallow search indexing and add a Link header to the canonical URL.
-		// TODO: move into external func to not alert vars indirectly
-		tryBranch := func(log zerolog.Logger, repo, branch string, _path []string, canonicalLink string) bool {
-			if repo == "" {
-				log.Debug().Msg("tryBranch: repo is empty")
-				return false
-			}
-
-			// Replace "~" to "/" so we can access branch that contains slash character
-			// Branch name cannot contain "~" so doing this is okay
-			branch = strings.ReplaceAll(branch, "~", "/")
-
-			// Check if the branch exists, otherwise treat it as a file path
-			branchTimestampResult := upstream.GetBranchTimestamp(giteaClient, targetOwner, repo, branch)
-			if branchTimestampResult == nil {
-				log.Debug().Msg("tryBranch: branch doesn't exist")
-				return false
-			}
-
-			// Branch exists, use it
-			targetRepo = repo
-			targetPath = path.Join(_path...)
-			targetBranch = branchTimestampResult.Branch
-
-			targetOptions.BranchTimestamp = branchTimestampResult.Timestamp
-
-			if canonicalLink != "" {
-				// Hide from search machines & add canonical link
-				ctx.RespWriter.Header().Set("X-Robots-Tag", "noarchive, noindex")
-				ctx.RespWriter.Header().Set("Link",
-					strings.NewReplacer("%b", targetBranch, "%p", targetPath).Replace(canonicalLink)+
-						"; rel=\"canonical\"",
-				)
-			}
-
-			log.Debug().Msg("tryBranch: true")
-			return true
-		}
-
-		log.Debug().Msg("preparations")
-		if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) {
-			// Serve raw content from RawDomain
-			log.Debug().Msg("raw domain")
-
-			targetOptions.TryIndexPages = false
-			targetOptions.ServeRaw = true
-
-			pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
-			if len(pathElements) < 2 {
-				// https://{RawDomain}/{owner}/{repo}[/@{branch}]/{path} is required
-				ctx.Redirect(rawInfoPage, http.StatusTemporaryRedirect)
-				return
-			}
-			targetOwner = pathElements[0]
-			targetRepo = pathElements[1]
-
-			// raw.codeberg.org/example/myrepo/@main/index.html
-			if len(pathElements) > 2 && strings.HasPrefix(pathElements[2], "@") {
-				log.Debug().Msg("raw domain preparations, now trying with specified branch")
-				if tryBranch(log,
-					targetRepo, pathElements[2][1:], pathElements[3:],
-					giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
-				) {
-					log.Debug().Msg("tryBranch, now trying upstream 1")
-					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						canonicalDomainCache)
-					return
-				}
-				log.Debug().Msg("missing branch info")
-				html.ReturnErrorPage(ctx, "missing branch info", http.StatusFailedDependency)
-				return
-			}
-
-			log.Debug().Msg("raw domain preparations, now trying with default branch")
-			tryBranch(log,
-				targetRepo, "", pathElements[2:],
-				giteaRoot+"/"+targetOwner+"/"+targetRepo+"/src/branch/%b/%p",
-			)
-			log.Debug().Msg("tryBranch, now trying upstream 2")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-				targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-				canonicalDomainCache)
-			return
-
-		} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
-			// Serve pages from subdomains of MainDomainSuffix
-			log.Debug().Msg("main domain suffix")
-
-			pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
-			targetOwner = strings.TrimSuffix(trimmedHost, mainDomainSuffix)
-			targetRepo = pathElements[0]
-			targetPath = strings.Trim(strings.Join(pathElements[1:], "/"), "/")
-
-			if targetOwner == "www" {
-				// www.codeberg.page redirects to codeberg.page // TODO: rm hardcoded - use cname?
-				ctx.Redirect("https://"+string(mainDomainSuffix[1:])+string(ctx.Path()), http.StatusPermanentRedirect)
-				return
-			}
-
-			// Check if the first directory is a repo with the second directory as a branch
-			// example.codeberg.page/myrepo/@main/index.html
-			if len(pathElements) > 1 && strings.HasPrefix(pathElements[1], "@") {
-				if targetRepo == "pages" {
-					// example.codeberg.org/pages/@... redirects to example.codeberg.org/@...
-					ctx.Redirect("/"+strings.Join(pathElements[1:], "/"), http.StatusTemporaryRedirect)
-					return
-				}
-
-				log.Debug().Msg("main domain preparations, now trying with specified repo & branch")
-				branch := pathElements[1][1:]
-				if tryBranch(log,
-					pathElements[0], branch, pathElements[2:],
-					"/"+pathElements[0]+"/%p",
-				) {
-					log.Debug().Msg("tryBranch, now trying upstream 3")
-					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-						targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-						canonicalDomainCache)
-				} else {
-					html.ReturnErrorPage(ctx,
-						fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", branch, targetOwner, targetRepo),
-						http.StatusFailedDependency)
-				}
-				return
-			}
-
-			// Check if the first directory is a branch for the "pages" repo
-			// example.codeberg.page/@main/index.html
-			if strings.HasPrefix(pathElements[0], "@") {
-				log.Debug().Msg("main domain preparations, now trying with specified branch")
-				branch := pathElements[0][1:]
-				if tryBranch(log,
-					"pages", branch, pathElements[1:], "/%p") {
-					log.Debug().Msg("tryBranch, now trying upstream 4")
-					tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-						targetOptions, targetOwner, "pages", targetBranch, targetPath,
-						canonicalDomainCache)
-				} else {
-					html.ReturnErrorPage(ctx,
-						fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", branch, targetOwner, "pages"),
-						http.StatusFailedDependency)
-				}
-				return
-			}
-
-			// Check if the first directory is a repo with a "pages" branch
-			// example.codeberg.page/myrepo/index.html
-			// example.codeberg.page/pages/... is not allowed here.
-			log.Debug().Msg("main domain preparations, now trying with specified repo")
-			if pathElements[0] != "pages" && tryBranch(log,
-				pathElements[0], "pages", pathElements[1:], "") {
-				log.Debug().Msg("tryBranch, now trying upstream 5")
-				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					canonicalDomainCache)
-				return
-			}
-
-			// Try to use the "pages" repo on its default branch
-			// example.codeberg.page/index.html
-			log.Debug().Msg("main domain preparations, now trying with default repo/branch")
-			if tryBranch(log,
-				"pages", "", pathElements, "") {
-				log.Debug().Msg("tryBranch, now trying upstream 6")
-				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					canonicalDomainCache)
-				return
-			}
-
-			// Couldn't find a valid repo/branch
-			html.ReturnErrorPage(ctx,
-				fmt.Sprintf("couldn't find a valid repo[%s]/branch[%s]", targetRepo, targetBranch),
-				http.StatusFailedDependency)
-			return
-		} else {
-			trimmedHostStr := string(trimmedHost)
-
-			// Serve pages from custom domains
-			targetOwner, targetRepo, targetBranch = dns.GetTargetFromDNS(trimmedHostStr, string(mainDomainSuffix), dnsLookupCache)
-			if targetOwner == "" {
-				html.ReturnErrorPage(ctx,
-					"could not obtain repo owner from custom domain",
-					http.StatusFailedDependency)
-				return
-			}
-
-			pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
-			canonicalLink := ""
-			if strings.HasPrefix(pathElements[0], "@") {
-				targetBranch = pathElements[0][1:]
-				pathElements = pathElements[1:]
-				canonicalLink = "/%p"
-			}
-
-			// 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(log,
-				targetRepo, targetBranch, pathElements, canonicalLink) {
-				canonicalDomain, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), canonicalDomainCache)
-				if !valid {
-					html.ReturnErrorPage(ctx, "domain not specified in <code>.domains</code> file", http.StatusMisdirectedRequest)
-					return
-				} else if canonicalDomain != trimmedHostStr {
-					// only redirect if the target is also a codeberg page!
-					targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix), dnsLookupCache)
-					if targetOwner != "" {
-						ctx.Redirect("https://"+canonicalDomain+string(ctx.Path()), http.StatusTemporaryRedirect)
-						return
-					}
-
-					html.ReturnErrorPage(ctx, "target is no codeberg page", http.StatusFailedDependency)
-					return
-				}
-
-				log.Debug().Msg("tryBranch, now trying upstream 7")
-				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost,
-					targetOptions, targetOwner, targetRepo, targetBranch, targetPath,
-					canonicalDomainCache)
-				return
-			}
-
-			html.ReturnErrorPage(ctx, "could not find target for custom domain", http.StatusFailedDependency)
-			return
-		}
-	}
-}
diff --git a/server/handler/handler.go b/server/handler/handler.go
new file mode 100644
index 0000000..b42751e
--- /dev/null
+++ b/server/handler/handler.go
@@ -0,0 +1,111 @@
+package handler
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/rs/zerolog/log"
+
+	"codeberg.org/codeberg/pages/html"
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/gitea"
+	"codeberg.org/codeberg/pages/server/version"
+)
+
+const (
+	headerAccessControlAllowOrigin  = "Access-Control-Allow-Origin"
+	headerAccessControlAllowMethods = "Access-Control-Allow-Methods"
+)
+
+// Handler handles a single HTTP request to the web server.
+func Handler(mainDomainSuffix, rawDomain string,
+	giteaClient *gitea.Client,
+	rawInfoPage string,
+	blacklistedPaths, allowedCorsDomains []string,
+	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
+) http.HandlerFunc {
+	return func(w http.ResponseWriter, req *http.Request) {
+		log := log.With().Strs("Handler", []string{string(req.Host), req.RequestURI}).Logger()
+		ctx := context.New(w, req)
+
+		ctx.RespWriter.Header().Set("Server", "CodebergPages/"+version.Version)
+
+		// Force new default from specification (since November 2020) - see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
+		ctx.RespWriter.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
+
+		// Enable browser caching for up to 10 minutes
+		ctx.RespWriter.Header().Set("Cache-Control", "public, max-age=600")
+
+		trimmedHost := ctx.TrimHostPort()
+
+		// Add HSTS for RawDomain and MainDomainSuffix
+		if hsts := getHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" {
+			ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts)
+		}
+
+		// Handle all http methods
+		ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions)
+		switch ctx.Req.Method {
+		case http.MethodOptions:
+			// return Allow header
+			ctx.RespWriter.WriteHeader(http.StatusNoContent)
+			return
+		case http.MethodGet,
+			http.MethodHead:
+			// end switch case and handle allowed requests
+			break
+		default:
+			// Block all methods not required for static pages
+			ctx.String("Method not allowed", http.StatusMethodNotAllowed)
+			return
+		}
+
+		// Block blacklisted paths (like ACME challenges)
+		for _, blacklistedPath := range blacklistedPaths {
+			if strings.HasPrefix(ctx.Path(), blacklistedPath) {
+				html.ReturnErrorPage(ctx, "requested blacklisted path", http.StatusForbidden)
+				return
+			}
+		}
+
+		// Allow CORS for specified domains
+		allowCors := false
+		for _, allowedCorsDomain := range allowedCorsDomains {
+			if strings.EqualFold(trimmedHost, allowedCorsDomain) {
+				allowCors = true
+				break
+			}
+		}
+		if allowCors {
+			ctx.RespWriter.Header().Set(headerAccessControlAllowOrigin, "*")
+			ctx.RespWriter.Header().Set(headerAccessControlAllowMethods, http.MethodGet+", "+http.MethodHead)
+		}
+
+		// Prepare request information to Gitea
+		pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
+
+		if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) {
+			log.Debug().Msg("raw domain request detecded")
+			handleRaw(log, ctx, giteaClient,
+				mainDomainSuffix, rawInfoPage,
+				trimmedHost,
+				pathElements,
+				canonicalDomainCache)
+		} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
+			log.Debug().Msg("subdomain request detecded")
+			handleSubDomain(log, ctx, giteaClient,
+				mainDomainSuffix,
+				trimmedHost,
+				pathElements,
+				canonicalDomainCache)
+		} else {
+			log.Debug().Msg("custom domain request detecded")
+			handleCustomDomain(log, ctx, giteaClient,
+				mainDomainSuffix,
+				trimmedHost,
+				pathElements,
+				dnsLookupCache, canonicalDomainCache)
+		}
+	}
+}
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
new file mode 100644
index 0000000..bec3b46
--- /dev/null
+++ b/server/handler/handler_custom_domain.go
@@ -0,0 +1,71 @@
+package handler
+
+import (
+	"net/http"
+	"path"
+	"strings"
+
+	"codeberg.org/codeberg/pages/html"
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/dns"
+	"codeberg.org/codeberg/pages/server/gitea"
+	"codeberg.org/codeberg/pages/server/upstream"
+	"github.com/rs/zerolog"
+)
+
+func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
+	mainDomainSuffix string,
+	trimmedHost string,
+	pathElements []string,
+	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
+) {
+	// Serve pages from custom domains
+	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, string(mainDomainSuffix), dnsLookupCache)
+	if targetOwner == "" {
+		html.ReturnErrorPage(ctx,
+			"could not obtain repo owner from custom domain",
+			http.StatusFailedDependency)
+		return
+	}
+
+	pathParts := pathElements
+	canonicalLink := false
+	if strings.HasPrefix(pathElements[0], "@") {
+		targetBranch = pathElements[0][1:]
+		pathParts = pathElements[1:]
+		canonicalLink = true
+	}
+
+	// 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 targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+		TryIndexPages: true,
+		TargetOwner:   targetOwner,
+		TargetRepo:    targetRepo,
+		TargetBranch:  targetBranch,
+		TargetPath:    path.Join(pathParts...),
+	}, canonicalLink); works {
+		canonicalDomain, valid := targetOpt.CheckCanonicalDomain(giteaClient, trimmedHost, string(mainDomainSuffix), canonicalDomainCache)
+		if !valid {
+			html.ReturnErrorPage(ctx, "domain not specified in <code>.domains</code> file", http.StatusMisdirectedRequest)
+			return
+		} else if canonicalDomain != trimmedHost {
+			// only redirect if the target is also a codeberg page!
+			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix), dnsLookupCache)
+			if targetOwner != "" {
+				ctx.Redirect("https://"+canonicalDomain+string(targetOpt.TargetPath), http.StatusTemporaryRedirect)
+				return
+			}
+
+			html.ReturnErrorPage(ctx, "target is no codeberg page", http.StatusFailedDependency)
+			return
+		}
+
+		log.Debug().Msg("tryBranch, now trying upstream 7")
+		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+		return
+	}
+
+	html.ReturnErrorPage(ctx, "could not find target for custom domain", http.StatusFailedDependency)
+}
diff --git a/server/handler/handler_raw_domain.go b/server/handler/handler_raw_domain.go
new file mode 100644
index 0000000..5e974da
--- /dev/null
+++ b/server/handler/handler_raw_domain.go
@@ -0,0 +1,67 @@
+package handler
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+	"strings"
+
+	"github.com/rs/zerolog"
+
+	"codeberg.org/codeberg/pages/html"
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/gitea"
+	"codeberg.org/codeberg/pages/server/upstream"
+)
+
+func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
+	mainDomainSuffix, rawInfoPage string,
+	trimmedHost string,
+	pathElements []string,
+	canonicalDomainCache cache.SetGetKey,
+) {
+	// Serve raw content from RawDomain
+	log.Debug().Msg("raw domain")
+
+	if len(pathElements) < 2 {
+		// https://{RawDomain}/{owner}/{repo}[/@{branch}]/{path} is required
+		ctx.Redirect(rawInfoPage, http.StatusTemporaryRedirect)
+		return
+	}
+
+	// raw.codeberg.org/example/myrepo/@main/index.html
+	if len(pathElements) > 2 && strings.HasPrefix(pathElements[2], "@") {
+		log.Debug().Msg("raw domain preparations, now trying with specified branch")
+		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+			ServeRaw:     true,
+			TargetOwner:  pathElements[0],
+			TargetRepo:   pathElements[1],
+			TargetBranch: pathElements[2][1:],
+			TargetPath:   path.Join(pathElements[3:]...),
+		}, true); works {
+			log.Trace().Msg("tryUpstream: serve raw domain with specified branch")
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+			return
+		}
+		log.Debug().Msg("missing branch info")
+		html.ReturnErrorPage(ctx, "missing branch info", http.StatusFailedDependency)
+		return
+	}
+
+	log.Debug().Msg("raw domain preparations, now trying with default branch")
+	if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+		TryIndexPages: false,
+		ServeRaw:      true,
+		TargetOwner:   pathElements[0],
+		TargetRepo:    pathElements[1],
+		TargetPath:    path.Join(pathElements[2:]...),
+	}, true); works {
+		log.Trace().Msg("tryUpstream: serve raw domain with default branch")
+		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+	} else {
+		html.ReturnErrorPage(ctx,
+			fmt.Sprintf("raw domain could not find repo '%s/%s' or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo),
+			http.StatusNotFound)
+	}
+}
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
new file mode 100644
index 0000000..df42d61
--- /dev/null
+++ b/server/handler/handler_sub_domain.go
@@ -0,0 +1,120 @@
+package handler
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+	"strings"
+
+	"github.com/rs/zerolog"
+
+	"codeberg.org/codeberg/pages/html"
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/gitea"
+	"codeberg.org/codeberg/pages/server/upstream"
+)
+
+func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
+	mainDomainSuffix string,
+	trimmedHost string,
+	pathElements []string,
+	canonicalDomainCache cache.SetGetKey,
+) {
+	// Serve pages from subdomains of MainDomainSuffix
+	log.Debug().Msg("main domain suffix")
+
+	targetOwner := strings.TrimSuffix(trimmedHost, mainDomainSuffix)
+	targetRepo := pathElements[0]
+
+	if targetOwner == "www" {
+		// www.codeberg.page redirects to codeberg.page // TODO: rm hardcoded - use cname?
+		ctx.Redirect("https://"+string(mainDomainSuffix[1:])+string(ctx.Path()), http.StatusPermanentRedirect)
+		return
+	}
+
+	// Check if the first directory is a repo with the second directory as a branch
+	// example.codeberg.page/myrepo/@main/index.html
+	if len(pathElements) > 1 && strings.HasPrefix(pathElements[1], "@") {
+		if targetRepo == "pages" {
+			// example.codeberg.org/pages/@... redirects to example.codeberg.org/@...
+			ctx.Redirect("/"+strings.Join(pathElements[1:], "/"), http.StatusTemporaryRedirect)
+			return
+		}
+
+		log.Debug().Msg("main domain preparations, now trying with specified repo & branch")
+		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+			TryIndexPages: true,
+			TargetOwner:   targetOwner,
+			TargetRepo:    pathElements[0],
+			TargetBranch:  pathElements[1][1:],
+			TargetPath:    path.Join(pathElements[2:]...),
+		}, true); works {
+			log.Trace().Msg("tryUpstream: serve with specified repo and branch")
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+		} else {
+			html.ReturnErrorPage(ctx,
+				fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
+				http.StatusFailedDependency)
+		}
+		return
+	}
+
+	// Check if the first directory is a branch for the "pages" repo
+	// example.codeberg.page/@main/index.html
+	if strings.HasPrefix(pathElements[0], "@") {
+		log.Debug().Msg("main domain preparations, now trying with specified branch")
+		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+			TryIndexPages: true,
+			TargetOwner:   targetOwner,
+			TargetRepo:    "pages",
+			TargetBranch:  pathElements[0][1:],
+			TargetPath:    path.Join(pathElements[1:]...),
+		}, true); works {
+			log.Trace().Msg("tryUpstream: serve default pages repo with specified branch")
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+		} else {
+			html.ReturnErrorPage(ctx,
+				fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
+				http.StatusFailedDependency)
+		}
+		return
+	}
+
+	// Check if the first directory is a repo with a "pages" branch
+	// example.codeberg.page/myrepo/index.html
+	// example.codeberg.page/pages/... is not allowed here.
+	log.Debug().Msg("main domain preparations, now trying with specified repo")
+	if pathElements[0] != "pages" {
+		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+			TryIndexPages: true,
+			TargetOwner:   targetOwner,
+			TargetRepo:    pathElements[0],
+			TargetBranch:  "pages",
+			TargetPath:    path.Join(pathElements[1:]...),
+		}, false); works {
+			log.Debug().Msg("tryBranch, now trying upstream 5")
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+			return
+		}
+	}
+
+	// Try to use the "pages" repo on its default branch
+	// example.codeberg.page/index.html
+	log.Debug().Msg("main domain preparations, now trying with default repo/branch")
+	if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+		TryIndexPages: true,
+		TargetOwner:   targetOwner,
+		TargetRepo:    "pages",
+		TargetPath:    path.Join(pathElements...),
+	}, false); works {
+		log.Debug().Msg("tryBranch, now trying upstream 6")
+		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+		return
+	}
+
+	// Couldn't find a valid repo/branch
+	html.ReturnErrorPage(ctx,
+		fmt.Sprintf("couldn't find a valid repo[%s]", targetRepo),
+		http.StatusFailedDependency)
+}
diff --git a/server/handler_test.go b/server/handler/handler_test.go
similarity index 85%
rename from server/handler_test.go
rename to server/handler/handler_test.go
index c0aca14..f5538c9 100644
--- a/server/handler_test.go
+++ b/server/handler/handler_test.go
@@ -1,4 +1,4 @@
-package server
+package handler
 
 import (
 	"net/http/httptest"
@@ -11,12 +11,11 @@ import (
 )
 
 func TestHandlerPerformance(t *testing.T) {
-	giteaRoot := "https://codeberg.org"
-	giteaClient, _ := gitea.NewClient(giteaRoot, "", cache.NewKeyValueCache(), false, false)
+	giteaClient, _ := gitea.NewClient("https://codeberg.org", "", cache.NewKeyValueCache(), false, false)
 	testHandler := Handler(
 		"codeberg.page", "raw.codeberg.org",
 		giteaClient,
-		giteaRoot, "https://docs.codeberg.org/pages/raw-content/",
+		"https://docs.codeberg.org/pages/raw-content/",
 		[]string{"/.well-known/acme-challenge/"},
 		[]string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
 		cache.NewKeyValueCache(),
diff --git a/server/helpers.go b/server/handler/hsts.go
similarity index 96%
rename from server/helpers.go
rename to server/handler/hsts.go
index 7c898cd..1ab73ae 100644
--- a/server/helpers.go
+++ b/server/handler/hsts.go
@@ -1,4 +1,4 @@
-package server
+package handler
 
 import (
 	"strings"
diff --git a/server/handler/try.go b/server/handler/try.go
new file mode 100644
index 0000000..b9adb7b
--- /dev/null
+++ b/server/handler/try.go
@@ -0,0 +1,76 @@
+package handler
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/rs/zerolog"
+
+	"codeberg.org/codeberg/pages/html"
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/gitea"
+	"codeberg.org/codeberg/pages/server/upstream"
+)
+
+// tryUpstream forwards the target request to the Gitea API, and shows an error page on failure.
+func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
+	mainDomainSuffix, trimmedHost string,
+	options *upstream.Options,
+	canonicalDomainCache cache.SetGetKey,
+) {
+	// check if a canonical domain exists on a request on MainDomain
+	if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
+		canonicalDomain, _ := options.CheckCanonicalDomain(giteaClient, "", string(mainDomainSuffix), canonicalDomainCache)
+		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) {
+			canonicalPath := ctx.Req.RequestURI
+			if options.TargetRepo != "pages" {
+				path := strings.SplitN(canonicalPath, "/", 3)
+				if len(path) >= 3 {
+					canonicalPath = "/" + path[2]
+				}
+			}
+			ctx.Redirect("https://"+canonicalDomain+canonicalPath, http.StatusTemporaryRedirect)
+			return
+		}
+	}
+
+	// add host for debugging
+	options.Host = string(trimmedHost)
+
+	// Try to request the file from the Gitea API
+	if !options.Upstream(ctx, giteaClient) {
+		html.ReturnErrorPage(ctx, "", ctx.StatusCode)
+	}
+}
+
+// tryBranch checks if a branch exists and populates the target variables. If canonicalLink is non-empty,
+// it will also disallow search indexing and add a Link header to the canonical URL.
+func tryBranch(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
+	targetOptions *upstream.Options, canonicalLink bool,
+) (*upstream.Options, bool) {
+	if targetOptions.TargetOwner == "" || targetOptions.TargetRepo == "" {
+		log.Debug().Msg("tryBranch: owner or repo is empty")
+		return nil, false
+	}
+
+	// Replace "~" to "/" so we can access branch that contains slash character
+	// Branch name cannot contain "~" so doing this is okay
+	targetOptions.TargetBranch = strings.ReplaceAll(targetOptions.TargetBranch, "~", "/")
+
+	// Check if the branch exists, otherwise treat it as a file path
+	branchExist, _ := targetOptions.GetBranchTimestamp(giteaClient)
+	if !branchExist {
+		log.Debug().Msg("tryBranch: branch doesn't exist")
+		return nil, false
+	}
+
+	if canonicalLink {
+		// Hide from search machines & add canonical link
+		ctx.RespWriter.Header().Set("X-Robots-Tag", "noarchive, noindex")
+		ctx.RespWriter.Header().Set("Link", targetOptions.ContentWebLink(giteaClient)+"; rel=\"canonical\"")
+	}
+
+	log.Debug().Msg("tryBranch: true")
+	return targetOptions, true
+}
diff --git a/server/try.go b/server/try.go
deleted file mode 100644
index 135c1e0..0000000
--- a/server/try.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package server
-
-import (
-	"net/http"
-	"strings"
-
-	"codeberg.org/codeberg/pages/html"
-	"codeberg.org/codeberg/pages/server/cache"
-	"codeberg.org/codeberg/pages/server/context"
-	"codeberg.org/codeberg/pages/server/gitea"
-	"codeberg.org/codeberg/pages/server/upstream"
-)
-
-// tryUpstream forwards the target request to the Gitea API, and shows an error page on failure.
-func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
-	mainDomainSuffix, trimmedHost string,
-
-	targetOptions *upstream.Options,
-	targetOwner, targetRepo, targetBranch, targetPath string,
-
-	canonicalDomainCache cache.SetGetKey,
-) {
-	// check if a canonical domain exists on a request on MainDomain
-	if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
-		canonicalDomain, _ := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), canonicalDomainCache)
-		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) {
-			canonicalPath := ctx.Req.RequestURI
-			if targetRepo != "pages" {
-				path := strings.SplitN(canonicalPath, "/", 3)
-				if len(path) >= 3 {
-					canonicalPath = "/" + path[2]
-				}
-			}
-			ctx.Redirect("https://"+canonicalDomain+canonicalPath, http.StatusTemporaryRedirect)
-			return
-		}
-	}
-
-	targetOptions.TargetOwner = targetOwner
-	targetOptions.TargetRepo = targetRepo
-	targetOptions.TargetBranch = targetBranch
-	targetOptions.TargetPath = targetPath
-	targetOptions.Host = string(trimmedHost)
-
-	// Try to request the file from the Gitea API
-	if !targetOptions.Upstream(ctx, giteaClient) {
-		html.ReturnErrorPage(ctx, "", ctx.StatusCode)
-	}
-}
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 6ad6506..0e29673 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -16,12 +16,12 @@ var canonicalDomainCacheTimeout = 15 * time.Minute
 const canonicalDomainConfig = ".domains"
 
 // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file).
-func CheckCanonicalDomain(giteaClient *gitea.Client, targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.SetGetKey) (string, bool) {
+func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.SetGetKey) (string, bool) {
 	var (
 		domains []string
 		valid   bool
 	)
-	if cachedValue, ok := canonicalDomainCache.Get(targetOwner + "/" + targetRepo + "/" + targetBranch); ok {
+	if cachedValue, ok := canonicalDomainCache.Get(o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch); ok {
 		domains = cachedValue.([]string)
 		for _, domain := range domains {
 			if domain == actualDomain {
@@ -30,7 +30,7 @@ func CheckCanonicalDomain(giteaClient *gitea.Client, targetOwner, targetRepo, ta
 			}
 		}
 	} else {
-		body, err := giteaClient.GiteaRawContent(targetOwner, targetRepo, targetBranch, canonicalDomainConfig)
+		body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, canonicalDomainConfig)
 		if err == nil {
 			for _, domain := range strings.Split(string(body), "\n") {
 				domain = strings.ToLower(domain)
@@ -45,16 +45,16 @@ func CheckCanonicalDomain(giteaClient *gitea.Client, targetOwner, targetRepo, ta
 				}
 			}
 		} else {
-			log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, targetOwner, targetRepo)
+			log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
 		}
-		domains = append(domains, targetOwner+mainDomainSuffix)
+		domains = append(domains, o.TargetOwner+mainDomainSuffix)
 		if domains[len(domains)-1] == actualDomain {
 			valid = true
 		}
-		if targetRepo != "" && targetRepo != "pages" {
-			domains[len(domains)-1] += "/" + targetRepo
+		if o.TargetRepo != "" && o.TargetRepo != "pages" {
+			domains[len(domains)-1] += "/" + o.TargetRepo
 		}
-		_ = canonicalDomainCache.Set(targetOwner+"/"+targetRepo+"/"+targetBranch, domains, canonicalDomainCacheTimeout)
+		_ = canonicalDomainCache.Set(o.TargetOwner+"/"+o.TargetRepo+"/"+o.TargetBranch, domains, canonicalDomainCacheTimeout)
 	}
 	return domains[0], valid
 }
diff --git a/server/upstream/header.go b/server/upstream/header.go
new file mode 100644
index 0000000..9575a3f
--- /dev/null
+++ b/server/upstream/header.go
@@ -0,0 +1,28 @@
+package upstream
+
+import (
+	"net/http"
+	"time"
+
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/gitea"
+)
+
+// setHeader set values to response header
+func (o *Options) setHeader(ctx *context.Context, header http.Header) {
+	if eTag := header.Get(gitea.ETagHeader); eTag != "" {
+		ctx.RespWriter.Header().Set(gitea.ETagHeader, eTag)
+	}
+	if cacheIndicator := header.Get(gitea.PagesCacheIndicatorHeader); cacheIndicator != "" {
+		ctx.RespWriter.Header().Set(gitea.PagesCacheIndicatorHeader, cacheIndicator)
+	}
+	if length := header.Get(gitea.ContentLengthHeader); length != "" {
+		ctx.RespWriter.Header().Set(gitea.ContentLengthHeader, length)
+	}
+	if mime := header.Get(gitea.ContentTypeHeader); mime == "" || o.ServeRaw {
+		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, rawMime)
+	} else {
+		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, mime)
+	}
+	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(time.RFC1123))
+}
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index 6bc23c8..428976b 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -2,35 +2,46 @@ package upstream
 
 import (
 	"errors"
+	"fmt"
 
 	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/gitea"
 )
 
-// GetBranchTimestamp finds the default branch (if branch is "") and returns the last modification time of the branch
-// (or nil if the branch doesn't exist)
-func GetBranchTimestamp(giteaClient *gitea.Client, owner, repo, branch string) *gitea.BranchTimestamp {
-	log := log.With().Strs("BranchInfo", []string{owner, repo, branch}).Logger()
+// GetBranchTimestamp finds the default branch (if branch is "") and save branch and it's last modification time to Options
+func (o *Options) GetBranchTimestamp(giteaClient *gitea.Client) (bool, error) {
+	log := log.With().Strs("BranchInfo", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch}).Logger()
 
-	if len(branch) == 0 {
+	if len(o.TargetBranch) == 0 {
 		// Get default branch
-		defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(owner, repo)
+		defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(o.TargetOwner, o.TargetRepo)
 		if err != nil {
 			log.Err(err).Msg("Could't fetch default branch from repository")
-			return nil
+			return false, err
 		}
 		log.Debug().Msgf("Succesfully fetched default branch %q from Gitea", defaultBranch)
-		branch = defaultBranch
+		o.TargetBranch = defaultBranch
 	}
 
-	timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(owner, repo, branch)
+	timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(o.TargetOwner, o.TargetRepo, o.TargetBranch)
 	if err != nil {
 		if !errors.Is(err, gitea.ErrorNotFound) {
 			log.Error().Err(err).Msg("Could not get latest commit's timestamp from branch")
 		}
-		return nil
+		return false, err
 	}
+
+	if timestamp == nil || timestamp.Branch == "" {
+		return false, fmt.Errorf("empty response")
+	}
+
 	log.Debug().Msgf("Succesfully fetched latest commit's timestamp from branch: %#v", timestamp)
-	return timestamp
+	o.BranchTimestamp = timestamp.Timestamp
+	o.TargetBranch = timestamp.Branch
+	return true, nil
+}
+
+func (o *Options) ContentWebLink(giteaClient *gitea.Client) string {
+	return giteaClient.ContentWebLink(o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath) + "; rel=\"canonical\""
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index d37c35e..b76b8e6 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -34,10 +34,10 @@ var upstreamNotFoundPages = []string{
 
 // Options provides various options for the upstream request.
 type Options struct {
-	TargetOwner,
-	TargetRepo,
-	TargetBranch,
-	TargetPath,
+	TargetOwner  string
+	TargetRepo   string
+	TargetBranch string
+	TargetPath   string
 
 	// Used for debugging purposes.
 	Host string
@@ -62,16 +62,22 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 
 	// Check if the branch exists and when it was modified
 	if o.BranchTimestamp.IsZero() {
-		branch := GetBranchTimestamp(giteaClient, o.TargetOwner, o.TargetRepo, o.TargetBranch)
-
-		if branch == nil || branch.Branch == "" {
+		branchExist, err := o.GetBranchTimestamp(giteaClient)
+		// handle 404
+		if err != nil && errors.Is(err, gitea.ErrorNotFound) || !branchExist {
 			html.ReturnErrorPage(ctx,
-				fmt.Sprintf("could not get timestamp of branch %q", o.TargetBranch),
+				fmt.Sprintf("branch %q for '%s/%s' not found", o.TargetBranch, o.TargetOwner, o.TargetRepo),
+				http.StatusNotFound)
+			return true
+		}
+
+		// handle unexpected errors
+		if err != nil {
+			html.ReturnErrorPage(ctx,
+				fmt.Sprintf("could not get timestamp of branch %q: %v", o.TargetBranch, err),
 				http.StatusFailedDependency)
 			return true
 		}
-		o.TargetBranch = branch.Branch
-		o.BranchTimestamp = branch.Timestamp
 	}
 
 	// Check if the browser has a cached version
@@ -172,21 +178,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 	}
 
 	// Set ETag & MIME
-	if eTag := header.Get(gitea.ETagHeader); eTag != "" {
-		ctx.RespWriter.Header().Set(gitea.ETagHeader, eTag)
-	}
-	if cacheIndicator := header.Get(gitea.PagesCacheIndicatorHeader); cacheIndicator != "" {
-		ctx.RespWriter.Header().Set(gitea.PagesCacheIndicatorHeader, cacheIndicator)
-	}
-	if length := header.Get(gitea.ContentLengthHeader); length != "" {
-		ctx.RespWriter.Header().Set(gitea.ContentLengthHeader, length)
-	}
-	if mime := header.Get(gitea.ContentTypeHeader); mime == "" || o.ServeRaw {
-		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, rawMime)
-	} else {
-		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, mime)
-	}
-	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(time.RFC1123))
+	o.setHeader(ctx, header)
 
 	log.Debug().Msg("Prepare response")
 

From b6d0a04b217f34705cb8cb6ba89107c76a87bfb1 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 12 Nov 2022 21:04:34 +0100
Subject: [PATCH 044/196] refactor: rm not needed type conversion

---
 server/handler/handler_custom_domain.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index bec3b46..5ea7649 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -21,7 +21,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
 ) {
 	// Serve pages from custom domains
-	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, string(mainDomainSuffix), dnsLookupCache)
+	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, dnsLookupCache)
 	if targetOwner == "" {
 		html.ReturnErrorPage(ctx,
 			"could not obtain repo owner from custom domain",
@@ -46,13 +46,13 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 		TargetBranch:  targetBranch,
 		TargetPath:    path.Join(pathParts...),
 	}, canonicalLink); works {
-		canonicalDomain, valid := targetOpt.CheckCanonicalDomain(giteaClient, trimmedHost, string(mainDomainSuffix), canonicalDomainCache)
+		canonicalDomain, valid := targetOpt.CheckCanonicalDomain(giteaClient, trimmedHost, mainDomainSuffix, canonicalDomainCache)
 		if !valid {
 			html.ReturnErrorPage(ctx, "domain not specified in <code>.domains</code> file", http.StatusMisdirectedRequest)
 			return
 		} else if canonicalDomain != trimmedHost {
 			// only redirect if the target is also a codeberg page!
-			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix), dnsLookupCache)
+			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, dnsLookupCache)
 			if targetOwner != "" {
 				ctx.Redirect("https://"+canonicalDomain+string(targetOpt.TargetPath), http.StatusTemporaryRedirect)
 				return

From aa90356f0a4d48556a749e4e3677aa5e7641c4d8 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 12 Nov 2022 21:10:16 +0100
Subject: [PATCH 045/196] use a const for defaultPagesRepo

---
 server/handler/handler_sub_domain.go | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index df42d61..8190cd9 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -15,6 +15,8 @@ import (
 	"codeberg.org/codeberg/pages/server/upstream"
 )
 
+const defaultPagesRepo = "pages"
+
 func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
 	mainDomainSuffix string,
 	trimmedHost string,
@@ -36,7 +38,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 	// Check if the first directory is a repo with the second directory as a branch
 	// example.codeberg.page/myrepo/@main/index.html
 	if len(pathElements) > 1 && strings.HasPrefix(pathElements[1], "@") {
-		if targetRepo == "pages" {
+		if targetRepo == defaultPagesRepo {
 			// example.codeberg.org/pages/@... redirects to example.codeberg.org/@...
 			ctx.Redirect("/"+strings.Join(pathElements[1:], "/"), http.StatusTemporaryRedirect)
 			return
@@ -60,14 +62,14 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		return
 	}
 
-	// Check if the first directory is a branch for the "pages" repo
+	// Check if the first directory is a branch for the defaultPagesRepo
 	// example.codeberg.page/@main/index.html
 	if strings.HasPrefix(pathElements[0], "@") {
 		log.Debug().Msg("main domain preparations, now trying with specified branch")
 		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
 			TryIndexPages: true,
 			TargetOwner:   targetOwner,
-			TargetRepo:    "pages",
+			TargetRepo:    defaultPagesRepo,
 			TargetBranch:  pathElements[0][1:],
 			TargetPath:    path.Join(pathElements[1:]...),
 		}, true); works {
@@ -81,16 +83,16 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		return
 	}
 
-	// Check if the first directory is a repo with a "pages" branch
+	// Check if the first directory is a repo with a defaultPagesRepo branch
 	// example.codeberg.page/myrepo/index.html
 	// example.codeberg.page/pages/... is not allowed here.
 	log.Debug().Msg("main domain preparations, now trying with specified repo")
-	if pathElements[0] != "pages" {
+	if pathElements[0] != defaultPagesRepo {
 		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
 			TryIndexPages: true,
 			TargetOwner:   targetOwner,
 			TargetRepo:    pathElements[0],
-			TargetBranch:  "pages",
+			TargetBranch:  defaultPagesRepo,
 			TargetPath:    path.Join(pathElements[1:]...),
 		}, false); works {
 			log.Debug().Msg("tryBranch, now trying upstream 5")
@@ -99,13 +101,13 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		}
 	}
 
-	// Try to use the "pages" repo on its default branch
+	// Try to use the defaultPagesRepo on its default branch
 	// example.codeberg.page/index.html
 	log.Debug().Msg("main domain preparations, now trying with default repo/branch")
 	if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
 		TryIndexPages: true,
 		TargetOwner:   targetOwner,
-		TargetRepo:    "pages",
+		TargetRepo:    defaultPagesRepo,
 		TargetPath:    path.Join(pathElements...),
 	}, false); works {
 		log.Debug().Msg("tryBranch, now trying upstream 6")

From c827a28dd829974af387665dc7e0db92283ed930 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 12 Nov 2022 21:13:13 +0100
Subject: [PATCH 046/196] defaultPagesBranch

---
 server/handler/handler_sub_domain.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 8190cd9..59d1bbc 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -16,6 +16,7 @@ import (
 )
 
 const defaultPagesRepo = "pages"
+const defaultPagesBranch = "pages"
 
 func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
 	mainDomainSuffix string,
@@ -92,7 +93,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			TryIndexPages: true,
 			TargetOwner:   targetOwner,
 			TargetRepo:    pathElements[0],
-			TargetBranch:  defaultPagesRepo,
+			TargetBranch:  defaultPagesBranch,
 			TargetPath:    path.Join(pathElements[1:]...),
 		}, false); works {
 			log.Debug().Msg("tryBranch, now trying upstream 5")

From 4565481643dfd9014a674cd5a182a6c42e820fe0 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 12 Nov 2022 21:16:11 +0100
Subject: [PATCH 047/196] refactor: finish use default const for
 defaultPagesBranch and defaultPagesRepo

---
 server/handler/handler.go            | 2 ++
 server/handler/handler_sub_domain.go | 3 ---
 server/handler/try.go                | 2 +-
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/server/handler/handler.go b/server/handler/handler.go
index b42751e..1a0a285 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -16,6 +16,8 @@ import (
 const (
 	headerAccessControlAllowOrigin  = "Access-Control-Allow-Origin"
 	headerAccessControlAllowMethods = "Access-Control-Allow-Methods"
+	defaultPagesRepo                = "pages"
+	defaultPagesBranch              = "pages"
 )
 
 // Handler handles a single HTTP request to the web server.
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 59d1bbc..73177b6 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -15,9 +15,6 @@ import (
 	"codeberg.org/codeberg/pages/server/upstream"
 )
 
-const defaultPagesRepo = "pages"
-const defaultPagesBranch = "pages"
-
 func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
 	mainDomainSuffix string,
 	trimmedHost string,
diff --git a/server/handler/try.go b/server/handler/try.go
index b9adb7b..bae3c44 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -24,7 +24,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 		canonicalDomain, _ := options.CheckCanonicalDomain(giteaClient, "", string(mainDomainSuffix), canonicalDomainCache)
 		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) {
 			canonicalPath := ctx.Req.RequestURI
-			if options.TargetRepo != "pages" {
+			if options.TargetRepo != defaultPagesRepo {
 				path := strings.SplitN(canonicalPath, "/", 3)
 				if len(path) >= 3 {
 					canonicalPath = "/" + path[2]

From 3c61a39864f087f1bb6305dbcd512bdd7ffb72fa Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Sat, 12 Nov 2022 22:25:20 +0100
Subject: [PATCH 048/196] Enable http/2 support (#137)

As per [the documentation](https://pkg.go.dev/net/http#Serve), it doesn't enable HTTP2 by-default, unless we enable it via the `NextProtos` option.

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/137
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/certificates/certificates.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 42620b1..11cf0df 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -117,6 +117,7 @@ func TLSConfig(mainDomainSuffix string,
 		},
 		PreferServerCipherSuites: true,
 		NextProtos: []string{
+			"h2",
 			"http/1.1",
 			tlsalpn01.ACMETLS1Protocol,
 		},

From f2f943c0d800c513025a2fecfb1a48729052fdc1 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Tue, 15 Nov 2022 16:15:11 +0100
Subject: [PATCH 049/196] Remove unnecessary conversion (#139)

- Remove unnecessary type conversion.
- Enforce via CI

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/139
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 .golangci.yml                           | 20 ++++++++++
 cmd/main.go                             |  2 +-
 integration/get_test.go                 |  2 +-
 integration/main_test.go                |  2 +-
 server/certificates/certificates.go     | 50 +++++++++++++------------
 server/database/setup.go                |  2 +-
 server/gitea/cache.go                   |  5 +--
 server/handler/handler.go               |  2 +-
 server/handler/handler_custom_domain.go |  2 +-
 server/handler/handler_sub_domain.go    |  2 +-
 server/handler/try.go                   |  8 ++--
 server/setup.go                         |  4 +-
 server/upstream/helper.go               |  2 +-
 server/upstream/upstream.go             |  4 +-
 14 files changed, 64 insertions(+), 43 deletions(-)
 create mode 100644 .golangci.yml

diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..6d9b95a
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,20 @@
+linters-settings:
+  gocritic:
+    enabled-tags:
+      - diagnostic
+      - experimental
+      - opinionated
+      - performance
+      - style
+    disabled-checks:
+      - importShadow
+      - ifElseChain
+      - hugeParam
+
+linters:
+  enable:
+    - unconvert
+    - gocritic
+
+run:
+  timeout: 5m
diff --git a/cmd/main.go b/cmd/main.go
index 6ad1aa8..b72013a 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -65,7 +65,7 @@ func Serve(ctx *cli.Context) error {
 	}
 
 	allowedCorsDomains := AllowedCorsDomains
-	if len(rawDomain) != 0 {
+	if rawDomain != "" {
 		allowedCorsDomains = append(allowedCorsDomains, rawDomain)
 	}
 
diff --git a/integration/get_test.go b/integration/get_test.go
index 8794651..81d8488 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -124,7 +124,7 @@ func TestLFSSupport(t *testing.T) {
 
 func TestGetOptions(t *testing.T) {
 	log.Println("=== TestGetOptions ===")
-	req, _ := http.NewRequest(http.MethodOptions, "https://mock-pages.codeberg-test.org:4430/README.md", nil)
+	req, _ := http.NewRequest(http.MethodOptions, "https://mock-pages.codeberg-test.org:4430/README.md", http.NoBody)
 	resp, err := getTestHTTPSClient().Do(req)
 	assert.NoError(t, err)
 	if !assert.NotNil(t, resp) {
diff --git a/integration/main_test.go b/integration/main_test.go
index 06d553f..406b33a 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -28,7 +28,7 @@ func TestMain(m *testing.M) {
 
 	time.Sleep(10 * time.Second)
 
-	os.Exit(m.Run())
+	m.Run()
 }
 
 func startServer(ctx context.Context) error {
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 11cf0df..8af4be5 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -53,17 +53,19 @@ func TLSConfig(mainDomainSuffix string,
 
 			if info.SupportedProtos != nil {
 				for _, proto := range info.SupportedProtos {
-					if proto == tlsalpn01.ACMETLS1Protocol {
-						challenge, ok := challengeCache.Get(sni)
-						if !ok {
-							return nil, errors.New("no challenge for this domain")
-						}
-						cert, err := tlsalpn01.ChallengeCert(sni, challenge.(string))
-						if err != nil {
-							return nil, err
-						}
-						return cert, nil
+					if proto != tlsalpn01.ACMETLS1Protocol {
+						continue
 					}
+
+					challenge, ok := challengeCache.Get(sni)
+					if !ok {
+						return nil, errors.New("no challenge for this domain")
+					}
+					cert, err := tlsalpn01.ChallengeCert(sni, challenge.(string))
+					if err != nil {
+						return nil, err
+					}
+					return cert, nil
 				}
 			}
 
@@ -195,7 +197,7 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
 
 func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) {
 	// parse certificate from database
-	res, err := certDB.Get(string(sni))
+	res, err := certDB.Get(sni)
 	if err != nil {
 		panic(err) // TODO: no panic
 	}
@@ -216,7 +218,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLi
 		}
 
 		// renew certificates 7 days before they expire
-		if !tlsCertificate.Leaf.NotAfter.After(time.Now().Add(7 * 24 * time.Hour)) {
+		if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
 			// TODO: add ValidUntil to custom res struct
 			if res.CSR != nil && len(res.CSR) > 0 {
 				// CSR stores the time when the renewal shall be tried again
@@ -227,9 +229,9 @@ func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLi
 			}
 			go (func() {
 				res.CSR = nil // acme client doesn't like CSR to be set
-				tlsCertificate, err = obtainCert(acmeClient, []string{string(sni)}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
+				tlsCertificate, err = obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 				if err != nil {
-					log.Error().Msgf("Couldn't renew certificate for %s: %v", string(sni), err)
+					log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
 				}
 			})()
 		}
@@ -262,7 +264,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 	defer obtainLocks.Delete(name)
 
 	if acmeClient == nil {
-		return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", string(mainDomainSuffix), keyDatabase), nil
+		return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase), nil
 	}
 
 	// request actual cert
@@ -305,12 +307,12 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 				// avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at
 				renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
 				if err := keyDatabase.Put(name, renew); err != nil {
-					return mockCert(domains[0], err.Error(), string(mainDomainSuffix), keyDatabase), err
+					return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
 				}
 				return tlsCertificate, nil
 			}
 		}
-		return mockCert(domains[0], err.Error(), string(mainDomainSuffix), keyDatabase), err
+		return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
 	}
 	log.Debug().Msgf("Obtained certificate for %v", domains)
 
@@ -408,7 +410,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 
 func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error {
 	// getting main cert before ACME account so that we can fail here without hitting rate limits
-	mainCertBytes, err := certDB.Get(string(mainDomainSuffix))
+	mainCertBytes, err := certDB.Get(mainDomainSuffix)
 	if err != nil {
 		return fmt.Errorf("cert database is not working")
 	}
@@ -452,7 +454,7 @@ func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Co
 	}
 
 	if mainCertBytes == nil {
-		_, err = obtainCert(mainDomainAcmeClient, []string{"*" + string(mainDomainSuffix), string(mainDomainSuffix[1:])}, nil, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
+		_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, nil, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 		if err != nil {
 			log.Error().Err(err).Msg("Couldn't renew main domain certificate, continuing with mock certs only")
 		}
@@ -479,7 +481,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 				}
 
 				tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
-				if err != nil || !tlsCertificates[0].NotAfter.After(now) {
+				if err != nil || tlsCertificates[0].NotAfter.Before(now) {
 					err := certDB.Delete(string(key))
 					if err != nil {
 						log.Error().Err(err).Msgf("Deleting expired certificate for %q failed", string(key))
@@ -501,18 +503,18 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 		}
 
 		// update main cert
-		res, err := certDB.Get(string(mainDomainSuffix))
+		res, err := certDB.Get(mainDomainSuffix)
 		if err != nil {
 			log.Error().Msgf("Couldn't get cert for domain %q", mainDomainSuffix)
 		} else if res == nil {
-			log.Error().Msgf("Couldn't renew certificate for main domain %q expected main domain cert to exist, but it's missing - seems like the database is corrupted", string(mainDomainSuffix))
+			log.Error().Msgf("Couldn't renew certificate for main domain %q expected main domain cert to exist, but it's missing - seems like the database is corrupted", mainDomainSuffix)
 		} else {
 			tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
 
 			// renew main certificate 30 days before it expires
-			if !tlsCertificates[0].NotAfter.After(time.Now().Add(30 * 24 * time.Hour)) {
+			if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
 				go (func() {
-					_, err = obtainCert(mainDomainAcmeClient, []string{"*" + string(mainDomainSuffix), string(mainDomainSuffix[1:])}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
+					_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 					if err != nil {
 						log.Error().Err(err).Msg("Couldn't renew certificate for main domain")
 					}
diff --git a/server/database/setup.go b/server/database/setup.go
index 1c5a0af..097c63e 100644
--- a/server/database/setup.go
+++ b/server/database/setup.go
@@ -44,7 +44,7 @@ func (p aDB) Get(name string) (*certificate.Resource, error) {
 	if resBytes == nil {
 		return nil, nil
 	}
-	if err = gob.NewDecoder(bytes.NewBuffer(resBytes)).Decode(cert); err != nil {
+	if err := gob.NewDecoder(bytes.NewBuffer(resBytes)).Decode(cert); err != nil {
 		return nil, err
 	}
 	return cert, nil
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index b11a370..85cbcde 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -42,9 +42,8 @@ func (f FileResponse) IsEmpty() bool {
 	return len(f.Body) != 0
 }
 
-func (f FileResponse) createHttpResponse(cacheKey string) (http.Header, int) {
-	header := make(http.Header)
-	var statusCode int
+func (f FileResponse) createHttpResponse(cacheKey string) (header http.Header, statusCode int) {
+	header = make(http.Header)
 
 	if f.Exists {
 		statusCode = http.StatusOK
diff --git a/server/handler/handler.go b/server/handler/handler.go
index 1a0a285..78301e9 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -28,7 +28,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
 ) http.HandlerFunc {
 	return func(w http.ResponseWriter, req *http.Request) {
-		log := log.With().Strs("Handler", []string{string(req.Host), req.RequestURI}).Logger()
+		log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
 		ctx := context.New(w, req)
 
 		ctx.RespWriter.Header().Set("Server", "CodebergPages/"+version.Version)
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 5ea7649..2f98085 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -54,7 +54,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 			// only redirect if the target is also a codeberg page!
 			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, dnsLookupCache)
 			if targetOwner != "" {
-				ctx.Redirect("https://"+canonicalDomain+string(targetOpt.TargetPath), http.StatusTemporaryRedirect)
+				ctx.Redirect("https://"+canonicalDomain+targetOpt.TargetPath, http.StatusTemporaryRedirect)
 				return
 			}
 
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 73177b6..1d769d4 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -29,7 +29,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 
 	if targetOwner == "www" {
 		// www.codeberg.page redirects to codeberg.page // TODO: rm hardcoded - use cname?
-		ctx.Redirect("https://"+string(mainDomainSuffix[1:])+string(ctx.Path()), http.StatusPermanentRedirect)
+		ctx.Redirect("https://"+mainDomainSuffix[1:]+ctx.Path(), http.StatusPermanentRedirect)
 		return
 	}
 
diff --git a/server/handler/try.go b/server/handler/try.go
index bae3c44..5a09b91 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -21,8 +21,8 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 ) {
 	// check if a canonical domain exists on a request on MainDomain
 	if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
-		canonicalDomain, _ := options.CheckCanonicalDomain(giteaClient, "", string(mainDomainSuffix), canonicalDomainCache)
-		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) {
+		canonicalDomain, _ := options.CheckCanonicalDomain(giteaClient, "", mainDomainSuffix, canonicalDomainCache)
+		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix) {
 			canonicalPath := ctx.Req.RequestURI
 			if options.TargetRepo != defaultPagesRepo {
 				path := strings.SplitN(canonicalPath, "/", 3)
@@ -35,8 +35,8 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 		}
 	}
 
-	// add host for debugging
-	options.Host = string(trimmedHost)
+	// Add host for debugging.
+	options.Host = trimmedHost
 
 	// Try to request the file from the Gitea API
 	if !options.Upstream(ctx, giteaClient) {
diff --git a/server/setup.go b/server/setup.go
index e7194ed..282e692 100644
--- a/server/setup.go
+++ b/server/setup.go
@@ -15,13 +15,13 @@ func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) http.HandlerFu
 	return func(w http.ResponseWriter, req *http.Request) {
 		ctx := context.New(w, req)
 		if strings.HasPrefix(ctx.Path(), challengePath) {
-			challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + string(strings.TrimPrefix(ctx.Path(), challengePath)))
+			challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + strings.TrimPrefix(ctx.Path(), challengePath))
 			if !ok || challenge == nil {
 				ctx.String("no challenge for this token", http.StatusNotFound)
 			}
 			ctx.String(challenge.(string))
 		} else {
-			ctx.Redirect("https://"+string(ctx.Host())+string(ctx.Path()), http.StatusMovedPermanently)
+			ctx.Redirect("https://"+ctx.Host()+ctx.Path(), http.StatusMovedPermanently)
 		}
 	}
 }
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index 428976b..a84d4f0 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -13,7 +13,7 @@ import (
 func (o *Options) GetBranchTimestamp(giteaClient *gitea.Client) (bool, error) {
 	log := log.With().Strs("BranchInfo", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch}).Logger()
 
-	if len(o.TargetBranch) == 0 {
+	if o.TargetBranch == "" {
 		// Get default branch
 		defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(o.TargetOwner, o.TargetRepo)
 		if err != nil {
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index b76b8e6..7c3c848 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -82,8 +82,8 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 
 	// Check if the browser has a cached version
 	if ctx.Response() != nil {
-		if ifModifiedSince, err := time.Parse(time.RFC1123, string(ctx.Response().Header.Get(headerIfModifiedSince))); err == nil {
-			if !ifModifiedSince.Before(o.BranchTimestamp) {
+		if ifModifiedSince, err := time.Parse(time.RFC1123, ctx.Response().Header.Get(headerIfModifiedSince)); err == nil {
+			if ifModifiedSince.After(o.BranchTimestamp) {
 				ctx.RespWriter.WriteHeader(http.StatusNotModified)
 				log.Trace().Msg("check response against last modified: valid")
 				return true

From caeb1a4acb0d35c78988fcd4e30d9ac51847f13a Mon Sep 17 00:00:00 2001
From: jklippel <jklippel@noreply.codeberg.org>
Date: Tue, 22 Nov 2022 21:26:10 +0000
Subject: [PATCH 050/196] Return a 404 if there is no repository (#141)

If no repository is found the user expects a 404 status code
instead of a dependency failed status code (as it was before).

Signed-off-by: Jan Klippel <c0d3b3rg@kl1pp3l.de>

Fixes: https://codeberg.org/Codeberg/Community/issues/809

Co-authored-by: Jan Klippel <c0d3b3rg@kl1pp3l.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/141
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: jklippel <jklippel@noreply.codeberg.org>
Co-committed-by: jklippel <jklippel@noreply.codeberg.org>
---
 server/handler/handler_sub_domain.go |  4 ++--
 server/handler/handler_test.go       | 35 ++++++++++++++--------------
 2 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 1d769d4..2a75e9f 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -115,6 +115,6 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 
 	// Couldn't find a valid repo/branch
 	html.ReturnErrorPage(ctx,
-		fmt.Sprintf("couldn't find a valid repo[%s]", targetRepo),
-		http.StatusFailedDependency)
+		fmt.Sprintf("could not find a valid repository[%s]", targetRepo),
+		http.StatusNotFound)
 }
diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index f5538c9..626564a 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -23,26 +23,27 @@ func TestHandlerPerformance(t *testing.T) {
 	)
 
 	testCase := func(uri string, status int) {
-		req := httptest.NewRequest("GET", uri, nil)
-		w := httptest.NewRecorder()
+		t.Run(uri, func(t *testing.T) {
+			req := httptest.NewRequest("GET", uri, nil)
+			w := httptest.NewRecorder()
 
-		log.Printf("Start: %v\n", time.Now())
-		start := time.Now()
-		testHandler(w, req)
-		end := time.Now()
-		log.Printf("Done: %v\n", time.Now())
+			log.Printf("Start: %v\n", time.Now())
+			start := time.Now()
+			testHandler(w, req)
+			end := time.Now()
+			log.Printf("Done: %v\n", time.Now())
 
-		resp := w.Result()
+			resp := w.Result()
 
-		if resp.StatusCode != status {
-			t.Errorf("request failed with status code %d", resp.StatusCode)
-		} else {
-			t.Logf("request took %d milliseconds", end.Sub(start).Milliseconds())
-		}
+			if resp.StatusCode != status {
+				t.Errorf("request failed with status code %d", resp.StatusCode)
+			} else {
+				t.Logf("request took %d milliseconds", end.Sub(start).Milliseconds())
+			}
+		})
 	}
 
-	testCase("https://mondstern.codeberg.page/", 424) // TODO: expect 200
-	testCase("https://mondstern.codeberg.page/", 424) // TODO: expect 200
-	testCase("https://example.momar.xyz/", 424)       // TODO: expect 200
-	testCase("https://codeberg.page/", 424)           // TODO: expect 200
+	testCase("https://mondstern.codeberg.page/", 404) // TODO: expect 200
+	testCase("https://codeberg.page/", 404)           // TODO: expect 200
+	testCase("https://example.momar.xyz/", 424)
 }

From 5e72753e916d1076cfd772d3d6c7f86474c6c87a Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Tue, 22 Nov 2022 22:30:53 +0100
Subject: [PATCH 051/196] ci: "docker-tag" use `tags`

---
 .woodpecker.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 20254fe..7e6d694 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -102,7 +102,7 @@ pipeline:
       registry: codeberg.org
       dockerfile: Dockerfile
       repo: codeberg.org/codeberg/pages-server
-      tag: [ latest, "${CI_COMMIT_TAG}" ]
+      tags: [ latest, "${CI_COMMIT_TAG}" ]
       username:
         from_secret: bot_user
       password:

From dcf03fc078228a286384757c3501ab5df5306062 Mon Sep 17 00:00:00 2001
From: crapStone <crapstone@noreply.codeberg.org>
Date: Fri, 2 Dec 2022 15:25:25 +0000
Subject: [PATCH 052/196] Fix error page (#144)

Co-authored-by: crapStone <crapstone01@gmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/144
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crapStone <crapstone@noreply.codeberg.org>
Co-committed-by: crapStone <crapstone@noreply.codeberg.org>
---
 html/error.go | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/html/error.go b/html/error.go
index 826c42b..ac222c4 100644
--- a/html/error.go
+++ b/html/error.go
@@ -1,6 +1,7 @@
 package html
 
 import (
+	"html/template"
 	"net/http"
 	"strconv"
 	"strings"
@@ -39,7 +40,8 @@ func errorMessage(statusCode int) string {
 
 // TODO: use template engine
 func errorBody(statusCode int) string {
-	return strings.ReplaceAll(NotFoundPage,
-		"%status%",
-		strconv.Itoa(statusCode)+" "+errorMessage(statusCode))
+	return template.HTMLEscapeString(
+		strings.ReplaceAll(NotFoundPage,
+			"%status%",
+			strconv.Itoa(statusCode)+" "+errorMessage(statusCode)))
 }

From 9d769aeee7bdbdce03bbb0f6fb213d4b7d45a701 Mon Sep 17 00:00:00 2001
From: crapStone <crapstone@noreply.codeberg.org>
Date: Sun, 4 Dec 2022 21:24:58 +0000
Subject: [PATCH 053/196] Fix error page generation (#145)

Co-authored-by: crapStone <crapstone01@gmail.com>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/145
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crapStone <crapstone@noreply.codeberg.org>
Co-committed-by: crapStone <crapstone@noreply.codeberg.org>
---
 Justfile           |  4 ++--
 html/error.go      | 31 +++++++++++++++++--------------
 html/error_test.go | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 57 insertions(+), 16 deletions(-)
 create mode 100644 html/error_test.go

diff --git a/Justfile b/Justfile
index f7ea414..7d72fe8 100644
--- a/Justfile
+++ b/Justfile
@@ -38,10 +38,10 @@ tool-gofumpt:
     fi
 
 test:
-    go test -race codeberg.org/codeberg/pages/server/...
+    go test -race codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
 
 test-run TEST:
-    go test -race -run "^{{TEST}}$" codeberg.org/codeberg/pages/server/...
+    go test -race -run "^{{TEST}}$" codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
 
 integration:
     go test -race -tags integration codeberg.org/codeberg/pages/integration/...
diff --git a/html/error.go b/html/error.go
index ac222c4..206b123 100644
--- a/html/error.go
+++ b/html/error.go
@@ -15,16 +15,27 @@ func ReturnErrorPage(ctx *context.Context, msg string, statusCode int) {
 	ctx.RespWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
 	ctx.RespWriter.WriteHeader(statusCode)
 
-	if msg == "" {
-		msg = errorBody(statusCode)
-	} else {
-		// TODO: use template engine
-		msg = strings.ReplaceAll(strings.ReplaceAll(ErrorPage, "%message%", msg), "%status%", http.StatusText(statusCode))
-	}
+	msg = generateResponse(msg, statusCode)
 
 	_, _ = ctx.RespWriter.Write([]byte(msg))
 }
 
+// TODO: use template engine
+func generateResponse(msg string, statusCode int) string {
+	if msg == "" {
+		msg = strings.ReplaceAll(NotFoundPage,
+			"%status%",
+			strconv.Itoa(statusCode)+" "+errorMessage(statusCode))
+	} else {
+		msg = strings.ReplaceAll(
+			strings.ReplaceAll(ErrorPage, "%message%", template.HTMLEscapeString(msg)),
+			"%status%",
+			http.StatusText(statusCode))
+	}
+
+	return msg
+}
+
 func errorMessage(statusCode int) string {
 	message := http.StatusText(statusCode)
 
@@ -37,11 +48,3 @@ func errorMessage(statusCode int) string {
 
 	return message
 }
-
-// TODO: use template engine
-func errorBody(statusCode int) string {
-	return template.HTMLEscapeString(
-		strings.ReplaceAll(NotFoundPage,
-			"%status%",
-			strconv.Itoa(statusCode)+" "+errorMessage(statusCode)))
-}
diff --git a/html/error_test.go b/html/error_test.go
new file mode 100644
index 0000000..f5da08c
--- /dev/null
+++ b/html/error_test.go
@@ -0,0 +1,38 @@
+package html
+
+import (
+	"net/http"
+	"strings"
+	"testing"
+)
+
+func TestValidMessage(t *testing.T) {
+	testString := "requested blacklisted path"
+	statusCode := http.StatusForbidden
+
+	expected := strings.ReplaceAll(
+		strings.ReplaceAll(ErrorPage, "%message%", testString),
+		"%status%",
+		http.StatusText(statusCode))
+	actual := generateResponse(testString, statusCode)
+
+	if expected != actual {
+		t.Errorf("generated response did not match: expected: '%s', got: '%s'", expected, actual)
+	}
+}
+
+func TestMessageWithHtml(t *testing.T) {
+	testString := `abc<img src=1 onerror=alert("xss");`
+	escapedString := "abc&lt;img src=1 onerror=alert(&#34;xss&#34;);"
+	statusCode := http.StatusNotFound
+
+	expected := strings.ReplaceAll(
+		strings.ReplaceAll(ErrorPage, "%message%", escapedString),
+		"%status%",
+		http.StatusText(statusCode))
+	actual := generateResponse(testString, statusCode)
+
+	if expected != actual {
+		t.Errorf("generated response did not match: expected: '%s', got: '%s'", expected, actual)
+	}
+}

From 98d198d4197f35af4a50dafab6ff6766eac06faf Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Wed, 4 Jan 2023 04:51:27 +0000
Subject: [PATCH 054/196] Safely get certificate's leaf (#150)

- It's not guaranteed that `tls.X509KeyPair` will set `c.Leaf`.
- This patch fixes this by using a wrapper that parses the leaf
certificate(in bytes) if `c.Leaf` wasn't set.
- Resolves #149

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/150
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/certificates/certificates.go | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 8af4be5..e3cdbfb 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -303,7 +303,11 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 		log.Error().Err(err).Msgf("Couldn't obtain again a certificate or %v", domains)
 		if renew != nil && renew.CertURL != "" {
 			tlsCertificate, err := tls.X509KeyPair(renew.Certificate, renew.PrivateKey)
-			if err == nil && tlsCertificate.Leaf.NotAfter.After(time.Now()) {
+			if err != nil {
+				return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
+			}
+			leaf, err := leaf(&tlsCertificate)
+			if err == nil && leaf.NotAfter.After(time.Now()) {
 				// avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at
 				renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
 				if err := keyDatabase.Put(name, renew); err != nil {
@@ -529,3 +533,12 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 		}
 	}
 }
+
+// leaf returns the parsed leaf certificate, either from c.leaf or by parsing
+// the corresponding c.Certificate[0].
+func leaf(c *tls.Certificate) (*x509.Certificate, error) {
+	if c.Leaf != nil {
+		return c.Leaf, nil
+	}
+	return x509.ParseCertificate(c.Certificate[0])
+}

From f7fad2a5ae0e4942848604a77199f4f1f8a1c3b4 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Wed, 4 Jan 2023 06:08:06 +0100
Subject: [PATCH 055/196] Integration Tests use
 https://codeberg.org/cb_pages_tests

---
 integration/get_test.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index 81d8488..f13ce8b 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -31,7 +31,7 @@ func TestGetRedirect(t *testing.T) {
 func TestGetContent(t *testing.T) {
 	log.Println("=== TestGetContent ===")
 	// test get image
-	resp, err := getTestHTTPSClient().Get("https://magiclike.localhost.mock.directory:4430/images/827679288a.jpg")
+	resp, err := getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg")
 	assert.NoError(t, err)
 	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
 		t.FailNow()
@@ -42,7 +42,7 @@ func TestGetContent(t *testing.T) {
 	assert.Len(t, resp.Header.Get("ETag"), 42)
 
 	// specify branch
-	resp, err = getTestHTTPSClient().Get("https://momar.localhost.mock.directory:4430/pag/@master/")
+	resp, err = getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/pag/@master/")
 	assert.NoError(t, err)
 	if !assert.NotNil(t, resp) {
 		t.FailNow()
@@ -53,7 +53,7 @@ func TestGetContent(t *testing.T) {
 	assert.Len(t, resp.Header.Get("ETag"), 44)
 
 	// access branch name contains '/'
-	resp, err = getTestHTTPSClient().Get("https://blumia.localhost.mock.directory:4430/pages-server-integration-tests/@docs~main/")
+	resp, err = getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/blumia/@docs~main/")
 	assert.NoError(t, err)
 	if !assert.EqualValues(t, http.StatusOK, resp.StatusCode) {
 		t.FailNow()
@@ -81,7 +81,7 @@ func TestCustomDomain(t *testing.T) {
 func TestGetNotFound(t *testing.T) {
 	log.Println("=== TestGetNotFound ===")
 	// test custom not found pages
-	resp, err := getTestHTTPSClient().Get("https://crystal.localhost.mock.directory:4430/pages-404-demo/blah")
+	resp, err := getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/pages-404-demo/blah")
 	assert.NoError(t, err)
 	if !assert.NotNil(t, resp) {
 		t.FailNow()
@@ -95,7 +95,7 @@ func TestGetNotFound(t *testing.T) {
 func TestFollowSymlink(t *testing.T) {
 	log.Printf("=== TestFollowSymlink ===\n")
 
-	resp, err := getTestHTTPSClient().Get("https://6543.localhost.mock.directory:4430/tests_for_pages-server/@main/link")
+	resp, err := getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/tests_for_pages-server/@main/link")
 	assert.NoError(t, err)
 	if !assert.NotNil(t, resp) {
 		t.FailNow()
@@ -111,7 +111,7 @@ func TestFollowSymlink(t *testing.T) {
 func TestLFSSupport(t *testing.T) {
 	log.Printf("=== TestLFSSupport ===\n")
 
-	resp, err := getTestHTTPSClient().Get("https://6543.localhost.mock.directory:4430/tests_for_pages-server/@main/lfs.txt")
+	resp, err := getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/tests_for_pages-server/@main/lfs.txt")
 	assert.NoError(t, err)
 	if !assert.NotNil(t, resp) {
 		t.FailNow()

From c286b3b1d0a21bc5cc447df7744da9894d857e6a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Leopoldo=20Sologuren=20Guti=C3=A9rrez?=
 <fsologureng@noreply.codeberg.org>
Date: Wed, 4 Jan 2023 05:26:14 +0000
Subject: [PATCH 056/196] Added TokenBucket to limit the rate of validation
 failures (#151)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Added new TockenBucket named `acmeClientFailLimit` to avoid being banned because of the [Failed validation limit](https://letsencrypt.org/docs/failed-validation-limit/) of Let's Encrypt.

The behaviour is similar to the other limiters blocking the `obtainCert` func ensuring rate under limit.

Co-authored-by: fsologureng <sologuren@estudiohum.cl>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/151
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Felipe Leopoldo Sologuren Gutiérrez <fsologureng@noreply.codeberg.org>
Co-committed-by: Felipe Leopoldo Sologuren Gutiérrez <fsologureng@noreply.codeberg.org>
---
 server/certificates/certificates.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index e3cdbfb..1aa90a0 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -163,6 +163,9 @@ var acmeClientOrderLimit = equalizer.NewTokenBucket(25, 15*time.Minute)
 // rate limit is 20 / second, we want 5 / second (especially as one cert takes at least two requests)
 var acmeClientRequestLimit = equalizer.NewTokenBucket(5, 1*time.Second)
 
+// rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/
+var acmeClientFailLimit = equalizer.NewTokenBucket(5, 1*time.Hour)
+
 type AcmeTLSChallengeProvider struct {
 	challengeCache cache.SetGetKey
 }
@@ -278,6 +281,9 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 		res, err = acmeClient.Certificate.Renew(*renew, true, false, "")
 		if err != nil {
 			log.Error().Err(err).Msgf("Couldn't renew certificate for %v, trying to request a new one", domains)
+			if acmeUseRateLimits {
+				acmeClientFailLimit.Take()
+			}
 			res = nil
 		}
 	}
@@ -298,6 +304,9 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 			Bundle:     true,
 			MustStaple: false,
 		})
+		if acmeUseRateLimits && err != nil {
+			acmeClientFailLimit.Take()
+		}
 	}
 	if err != nil {
 		log.Error().Err(err).Msgf("Couldn't obtain again a certificate or %v", domains)

From bd538abd372c41336c355a7d0890a289bad256a5 Mon Sep 17 00:00:00 2001
From: crapStone <crapstone@noreply.codeberg.org>
Date: Wed, 11 Jan 2023 00:00:37 +0000
Subject: [PATCH 057/196] Fix wrong redirect on custom domain with path (#154)

closes #153

Co-authored-by: crapStone <crapstone01@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/154
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crapStone <crapstone@noreply.codeberg.org>
Co-committed-by: crapStone <crapstone@noreply.codeberg.org>
---
 integration/get_test.go                 | 18 ++++++++++++++++++
 server/handler/handler_custom_domain.go |  2 +-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index f13ce8b..556ac53 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -78,6 +78,24 @@ func TestCustomDomain(t *testing.T) {
 	assert.EqualValues(t, 106, getSize(resp.Body))
 }
 
+func TestCustomDomainRedirects(t *testing.T) {
+	log.Println("=== TestCustomDomainRedirects ===")
+	// test redirect from default pages domain to custom domain
+	resp, err := getTestHTTPSClient().Get("https://6543.localhost.mock.directory:4430/test_pages-server_custom-mock-domain/@main/README.md")
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusTemporaryRedirect, resp.StatusCode)
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
+	// TODO: custom port is not evaluated (witch does hurt tests & dev env only)
+	assert.EqualValues(t, "https://mock-pages.codeberg-test.org/@main/README.md", resp.Header.Get("Location"))
+	assert.EqualValues(t, `https:/codeberg.org/6543/test_pages-server_custom-mock-domain/src/branch/main/README.md; rel="canonical"; rel="canonical"`, resp.Header.Get("Link"))
+
+	// TODO: test redirect from an custom domain to the primary custom domain (www.example.com -> example.com)
+	//       (cover bug https://codeberg.org/Codeberg/pages-server/issues/153)
+}
+
 func TestGetNotFound(t *testing.T) {
 	log.Println("=== TestGetNotFound ===")
 	// test custom not found pages
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 2f98085..a541b74 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -54,7 +54,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 			// only redirect if the target is also a codeberg page!
 			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, dnsLookupCache)
 			if targetOwner != "" {
-				ctx.Redirect("https://"+canonicalDomain+targetOpt.TargetPath, http.StatusTemporaryRedirect)
+				ctx.Redirect("https://"+canonicalDomain+"/"+targetOpt.TargetPath, http.StatusTemporaryRedirect)
 				return
 			}
 

From 513e79832ae5bd8e825eb4f5a2605b1c585eccd6 Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Sun, 22 Jan 2023 18:52:21 +0000
Subject: [PATCH 058/196] Use correct log level for `CheckCanonicalDomain`
 (#162)

- Currently any error generated by requesting the `.domains` file of a repository would be logged under the info log level, which isn't the correct log level when we exclude the not found error.
- Use warn log level if the error isn't the not found error.

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/162
Reviewed-by: Otto <otto@codeberg.org>
---
 server/upstream/domains.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 0e29673..ba1c494 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -45,7 +45,11 @@ func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain,
 				}
 			}
 		} else {
-			log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
+			if err != gitea.ErrorNotFound {
+				log.Error().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
+			} else {
+				log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
+			}
 		}
 		domains = append(domains, o.TargetOwner+mainDomainSuffix)
 		if domains[len(domains)-1] == actualDomain {

From 2c2087953d975c8cc9b57f6b7c94cb18a7894170 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Thu, 9 Feb 2023 17:30:06 +0000
Subject: [PATCH 059/196] Add Integration test for custom domain redirect to
 another custom domain (#172)

address #155

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/172
---
 integration/get_test.go | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index 556ac53..6dd57bc 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -89,11 +89,22 @@ func TestCustomDomainRedirects(t *testing.T) {
 	assert.EqualValues(t, http.StatusTemporaryRedirect, resp.StatusCode)
 	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
 	// TODO: custom port is not evaluated (witch does hurt tests & dev env only)
+	// assert.EqualValues(t, "https://mock-pages.codeberg-test.org:4430/@main/README.md", resp.Header.Get("Location"))
 	assert.EqualValues(t, "https://mock-pages.codeberg-test.org/@main/README.md", resp.Header.Get("Location"))
 	assert.EqualValues(t, `https:/codeberg.org/6543/test_pages-server_custom-mock-domain/src/branch/main/README.md; rel="canonical"; rel="canonical"`, resp.Header.Get("Link"))
 
-	// TODO: test redirect from an custom domain to the primary custom domain (www.example.com -> example.com)
-	//       (cover bug https://codeberg.org/Codeberg/pages-server/issues/153)
+	// test redirect from an custom domain to the primary custom domain (www.example.com -> example.com)
+	// regression test to https://codeberg.org/Codeberg/pages-server/issues/153
+	resp, err = getTestHTTPSClient().Get("https://mock-pages-redirect.codeberg-test.org:4430/README.md")
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusTemporaryRedirect, resp.StatusCode)
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
+	// TODO: custom port is not evaluated (witch does hurt tests & dev env only)
+	// assert.EqualValues(t, "https://mock-pages.codeberg-test.org:4430/README.md", resp.Header.Get("Location"))
+	assert.EqualValues(t, "https://mock-pages.codeberg-test.org/README.md", resp.Header.Get("Location"))
 }
 
 func TestGetNotFound(t *testing.T) {

From 8b1f497bc4699a76c8323a1466a93de4fc0f8b4d Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Fri, 10 Feb 2023 01:38:15 +0000
Subject: [PATCH 060/196] Allow to use certificate even if domain validation
 fails (#160)

- Currently if the canonical domain validations fails(either for
legitimate reasons or for bug reasons like the request to Gitea/Forgejo
failing) it will use main domain certificate, which in the case for
custom domains will warrant a security error as the certificate isn't
issued to the custom domain.
- This patch handles this situation more gracefully and instead only
disallow obtaining a certificate if the domain validation fails, so in
the case that a certificate still exists it can still be used even if
the canonical domain validation fails. There's a small side effect,
legitimate users that remove domains from `.domain` will still be able
to use the removed domain(as long as the DNS records exists) as long as
the certificate currently hold by pages-server isn't expired.
- Given the increased usage in custom domains that are resulting in
errors, I think it ways more than the side effect.
- In order to future-proof against future slowdowns of instances, add a retry mechanism to the domain validation function, such that it's more likely to succeed even if the instance is not responding.
- Refactor the code a bit and add some comments.

Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/160
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/certificates/certificates.go |  9 +++-
 server/upstream/domains.go          | 71 +++++++++++++++--------------
 2 files changed, 46 insertions(+), 34 deletions(-)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 1aa90a0..0bf5672 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -70,6 +70,7 @@ func TLSConfig(mainDomainSuffix string,
 			}
 
 			targetOwner := ""
+			mayObtainCert := true
 			if strings.HasSuffix(sni, mainDomainSuffix) || strings.EqualFold(sni, mainDomainSuffix[1:]) {
 				// deliver default certificate for the main domain (*.codeberg.page)
 				sni = mainDomainSuffix
@@ -87,7 +88,9 @@ func TLSConfig(mainDomainSuffix string,
 					}
 					_, valid := targetOpt.CheckCanonicalDomain(giteaClient, sni, mainDomainSuffix, canonicalDomainCache)
 					if !valid {
-						sni = mainDomainSuffix
+						// We shouldn't obtain a certificate when we cannot check if the
+						// repository has specified this domain in the `.domains` file.
+						mayObtainCert = false
 					}
 				}
 			}
@@ -106,6 +109,10 @@ func TLSConfig(mainDomainSuffix string,
 					return nil, errors.New("won't request certificate for main domain, something really bad has happened")
 				}
 
+				if !mayObtainCert {
+					return nil, fmt.Errorf("won't request certificate for %q", sni)
+				}
+
 				tlsCertificate, err = obtainCert(acmeClient, []string{sni}, nil, targetOwner, dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 				if err != nil {
 					return nil, err
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index ba1c494..5b274b6 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -16,49 +16,54 @@ var canonicalDomainCacheTimeout = 15 * time.Minute
 const canonicalDomainConfig = ".domains"
 
 // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file).
-func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.SetGetKey) (string, bool) {
-	var (
-		domains []string
-		valid   bool
-	)
+func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.SetGetKey) (domain string, valid bool) {
+	// Check if this request is cached.
 	if cachedValue, ok := canonicalDomainCache.Get(o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch); ok {
-		domains = cachedValue.([]string)
+		domains := cachedValue.([]string)
 		for _, domain := range domains {
 			if domain == actualDomain {
 				valid = true
 				break
 			}
 		}
-	} else {
-		body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, canonicalDomainConfig)
-		if err == nil {
-			for _, domain := range strings.Split(string(body), "\n") {
-				domain = strings.ToLower(domain)
-				domain = strings.TrimSpace(domain)
-				domain = strings.TrimPrefix(domain, "http://")
-				domain = strings.TrimPrefix(domain, "https://")
-				if len(domain) > 0 && !strings.HasPrefix(domain, "#") && !strings.ContainsAny(domain, "\t /") && strings.ContainsRune(domain, '.') {
-					domains = append(domains, domain)
-				}
-				if domain == actualDomain {
-					valid = true
-				}
-			}
-		} else {
-			if err != gitea.ErrorNotFound {
-				log.Error().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
-			} else {
-				log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
-			}
+		return domains[0], valid
+	}
+
+	body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, canonicalDomainConfig)
+	if err == nil || err == gitea.ErrorNotFound {
+		log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
+	}
+
+	var domains []string
+	for _, domain := range strings.Split(string(body), "\n") {
+		domain = strings.ToLower(domain)
+		domain = strings.TrimSpace(domain)
+		domain = strings.TrimPrefix(domain, "http://")
+		domain = strings.TrimPrefix(domain, "https://")
+		if len(domain) > 0 && !strings.HasPrefix(domain, "#") && !strings.ContainsAny(domain, "\t /") && strings.ContainsRune(domain, '.') {
+			domains = append(domains, domain)
 		}
-		domains = append(domains, o.TargetOwner+mainDomainSuffix)
-		if domains[len(domains)-1] == actualDomain {
+		if domain == actualDomain {
 			valid = true
 		}
-		if o.TargetRepo != "" && o.TargetRepo != "pages" {
-			domains[len(domains)-1] += "/" + o.TargetRepo
-		}
-		_ = canonicalDomainCache.Set(o.TargetOwner+"/"+o.TargetRepo+"/"+o.TargetBranch, domains, canonicalDomainCacheTimeout)
 	}
+
+	// Add [owner].[pages-domain] as valid domnain.
+	domains = append(domains, o.TargetOwner+mainDomainSuffix)
+	if domains[len(domains)-1] == actualDomain {
+		valid = true
+	}
+
+	// If the target repository isn't called pages, add `/[repository]` to the
+	// previous valid domain.
+	if o.TargetRepo != "" && o.TargetRepo != "pages" {
+		domains[len(domains)-1] += "/" + o.TargetRepo
+	}
+
+	// Add result to cache.
+	_ = canonicalDomainCache.Set(o.TargetOwner+"/"+o.TargetRepo+"/"+o.TargetBranch, domains, canonicalDomainCacheTimeout)
+
+	// Return the first domain from the list and return if any of the domains
+	// matched the requested domain.
 	return domains[0], valid
 }

From 7fce7cf68bcc35cd5d054b0434d6943d77f7053a Mon Sep 17 00:00:00 2001
From: foehammer <foehammer@noreply.codeberg.org>
Date: Fri, 10 Feb 2023 01:44:44 +0000
Subject: [PATCH 061/196] Added Whitespace Trimming TXT DNS Records (#152)

Solves https://codeberg.org/Codeberg/Community/issues/823 and https://codeberg.org/Codeberg/pages-server/issues/143

Co-authored-by: foehammer127 <foehammer127@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/152
Reviewed-by: Otto <otto@codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: foehammer <foehammer@noreply.codeberg.org>
Co-committed-by: foehammer <foehammer@noreply.codeberg.org>
---
 server/dns/dns.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/dns/dns.go b/server/dns/dns.go
index 818e29a..2719d4d 100644
--- a/server/dns/dns.go
+++ b/server/dns/dns.go
@@ -28,7 +28,7 @@ func GetTargetFromDNS(domain, mainDomainSuffix string, dnsLookupCache cache.SetG
 			names, err := net.LookupTXT(domain)
 			if err == nil {
 				for _, name := range names {
-					name = strings.TrimSuffix(name, ".")
+					name = strings.TrimSuffix(strings.TrimSpace(name), ".")
 					if strings.HasSuffix(name, mainDomainSuffix) {
 						cname = name
 						break

From 7b35a192bf4f87ca93c7d3a8a3d3dd2672c22fec Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 10 Feb 2023 03:00:14 +0000
Subject: [PATCH 062/196] Add cert store option based on sqlite3, mysql &
 postgres (#173)

Deprecate **pogreb**!

close #169

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/173
---
 .gitignore                              |   2 +
 .golangci.yml                           |  14 +
 .woodpecker.yml                         |  15 +-
 Dockerfile                              |   5 +-
 Justfile                                |  20 +-
 README.md                               |   3 +-
 cmd/certs.go                            |  97 ++++--
 cmd/flags.go                            | 259 ++++++++--------
 cmd/main.go                             |  17 +-
 cmd/setup.go                            |  45 +++
 go.mod                                  |  18 +-
 go.sum                                  | 377 +++++++++++++++++++++++-
 integration/main_test.go                |   3 +-
 main.go                                 |   2 +-
 server/certificates/certificates.go     | 133 +++++----
 server/certificates/mock.go             |  15 +-
 server/certificates/mock_test.go        |   3 +-
 server/database/interface.go            |  62 +++-
 server/database/mock.go                 |   5 +-
 server/database/{setup.go => pogreb.go} |  31 +-
 server/database/xorm.go                 | 121 ++++++++
 server/upstream/helper.go               |   8 +-
 22 files changed, 1000 insertions(+), 255 deletions(-)
 create mode 100644 cmd/setup.go
 rename server/database/{setup.go => pogreb.go} (76%)
 create mode 100644 server/database/xorm.go

diff --git a/.gitignore b/.gitignore
index dfe69ac..8745935 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
 .idea/
+.cache/
 *.iml
 key-database.pogreb/
 acme-account.json
 build/
 vendor/
 pages
+certs.sqlite
diff --git a/.golangci.yml b/.golangci.yml
index 6d9b95a..488ca09 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -12,9 +12,23 @@ linters-settings:
       - hugeParam
 
 linters:
+  disable-all: true
   enable:
     - unconvert
     - gocritic
+    - gofumpt
+    - bidichk
+    - errcheck
+    - gofmt
+    - goimports
+    - gosimple
+    - govet
+    - ineffassign
+    - misspell
+    - staticcheck
+    - typecheck
+    - unused
+    - whitespace
 
 run:
   timeout: 5m
diff --git a/.woodpecker.yml b/.woodpecker.yml
index 7e6d694..ba44f82 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,7 +1,7 @@
 pipeline:
   # use vendor to cache dependencies
   vendor:
-    image: golang:1.18
+    image: golang:1.20
     commands:
       - go mod vendor
 
@@ -65,6 +65,19 @@ pipeline:
       - RAW_DOMAIN=raw.localhost.mock.directory
       - PORT=4430
 
+  # TODO: remove in next version
+  integration-tests-legacy:
+    group: test
+    image: codeberg.org/6543/docker-images/golang_just
+    commands:
+      - just integration
+    environment:
+      - ACME_API=https://acme.mock.directory
+      - PAGES_DOMAIN=localhost.mock.directory
+      - RAW_DOMAIN=raw.localhost.mock.directory
+      - PORT=4430
+      - DB_TYPE=
+
   release:
     image: plugins/gitea-release
     settings:
diff --git a/Dockerfile b/Dockerfile
index 904d6f4..eec97de 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,9 @@
-FROM golang:alpine as build
+FROM techknowlogick/xgo as build
 
 WORKDIR /workspace
 
-RUN apk add ca-certificates
 COPY . .
-RUN CGO_ENABLED=0 go build .
+RUN CGO_ENABLED=1 go build -tags 'sqlite sqlite_unlock_notify netgo' -ldflags '-s -w -extldflags "-static" -linkmode external' .
 
 FROM scratch
 COPY --from=build /workspace/pages /pages
diff --git a/Justfile b/Justfile
index 7d72fe8..683222d 100644
--- a/Justfile
+++ b/Justfile
@@ -1,3 +1,6 @@
+CGO_FLAGS := '-extldflags "-static" -linkmode external'
+TAGS      := 'sqlite sqlite_unlock_notify netgo'
+
 dev:
     #!/usr/bin/env bash
     set -euxo pipefail
@@ -7,16 +10,15 @@ dev:
     export RAW_DOMAIN=raw.localhost.mock.directory
     export PORT=4430
     export LOG_LEVEL=trace
-    go run .
+    go run -tags '{{TAGS}}' .
 
 build:
-    CGO_ENABLED=0 go build -ldflags '-s -w' -v -o build/codeberg-pages-server ./
+    CGO_ENABLED=1 go build -tags '{{TAGS}}' -ldflags '-s -w {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./
 
 build-tag VERSION:
-    CGO_ENABLED=0 go build -ldflags '-s -w -X "codeberg.org/codeberg/pages/server/version.Version={{VERSION}}"' -v -o build/codeberg-pages-server ./
+    CGO_ENABLED=1 go build -tags '{{TAGS}}' -ldflags '-s -w -X "codeberg.org/codeberg/pages/server/version.Version={{VERSION}}" {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./
 
 lint: tool-golangci tool-gofumpt
-    [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
     golangci-lint run --timeout 5m --build-tags integration
     # TODO: run editorconfig-checker
 
@@ -25,7 +27,7 @@ fmt: tool-gofumpt
 
 clean:
     go clean ./...
-    rm -rf build/
+    rm -rf build/ integration/certs.sqlite integration/key-database.pogreb/ integration/acme-account.json
 
 tool-golangci:
     @hash golangci-lint> /dev/null 2>&1; if [ $? -ne 0 ]; then \
@@ -38,13 +40,13 @@ tool-gofumpt:
     fi
 
 test:
-    go test -race codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
+    go test -race -cover -tags '{{TAGS}}' codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
 
 test-run TEST:
-    go test -race -run "^{{TEST}}$" codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
+    go test -race -tags '{{TAGS}}' -run "^{{TEST}}$" codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
 
 integration:
-    go test -race -tags integration codeberg.org/codeberg/pages/integration/...
+    go test -race -tags 'integration {{TAGS}}' codeberg.org/codeberg/pages/integration/...
 
 integration-run TEST:
-    go test -race -tags integration -run "^{{TEST}}$" codeberg.org/codeberg/pages/integration/...
+    go test -race -tags 'integration {{TAGS}}' -run "^{{TEST}}$" codeberg.org/codeberg/pages/integration/...
diff --git a/README.md b/README.md
index 50cdc3c..6d5a294 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,7 @@ Thank you very much.
 
 run `just dev`
 now this pages should work:
- - https://magiclike.localhost.mock.directory:4430/
+ - https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
  - https://momar.localhost.mock.directory:4430/ci-testing/
  - https://momar.localhost.mock.directory:4430/pag/@master/
+ - https://mock-pages.codeberg-test.org:4430/README.md
diff --git a/cmd/certs.go b/cmd/certs.go
index d93fe13..4adf076 100644
--- a/cmd/certs.go
+++ b/cmd/certs.go
@@ -2,8 +2,10 @@ package cmd
 
 import (
 	"fmt"
+	"time"
 
-	"github.com/akrylysov/pogreb"
+	"github.com/rs/zerolog"
+	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
 
 	"codeberg.org/codeberg/pages/server/database"
@@ -23,25 +25,85 @@ var Certs = &cli.Command{
 			Usage:  "remove a certificate from the database",
 			Action: removeCert,
 		},
+		{
+			Name:   "migrate",
+			Usage:  "migrate from \"pogreb\" driver to dbms driver",
+			Action: migrateCerts,
+		},
 	},
+	Flags: append(CertStorageFlags, []cli.Flag{
+		&cli.BoolFlag{
+			Name:    "verbose",
+			Usage:   "print trace info",
+			EnvVars: []string{"VERBOSE"},
+			Value:   false,
+		},
+	}...),
+}
+
+func migrateCerts(ctx *cli.Context) error {
+	dbType := ctx.String("db-type")
+	if dbType == "" {
+		dbType = "sqlite3"
+	}
+	dbConn := ctx.String("db-conn")
+	dbPogrebConn := ctx.String("db-pogreb")
+	verbose := ctx.Bool("verbose")
+
+	log.Level(zerolog.InfoLevel)
+	if verbose {
+		log.Level(zerolog.TraceLevel)
+	}
+
+	xormDB, err := database.NewXormDB(dbType, dbConn)
+	if err != nil {
+		return fmt.Errorf("could not connect to database: %w", err)
+	}
+	defer xormDB.Close()
+
+	pogrebDB, err := database.NewPogreb(dbPogrebConn)
+	if err != nil {
+		return fmt.Errorf("could not open database: %w", err)
+	}
+	defer pogrebDB.Close()
+
+	fmt.Printf("Start migration from \"%s\" to \"%s:%s\" ...\n", dbPogrebConn, dbType, dbConn)
+
+	certs, err := pogrebDB.Items(0, 0)
+	if err != nil {
+		return err
+	}
+
+	for _, cert := range certs {
+		if err := xormDB.Put(cert.Domain, cert.Raw()); err != nil {
+			return err
+		}
+	}
+
+	fmt.Println("... done")
+	return nil
 }
 
 func listCerts(ctx *cli.Context) error {
-	// TODO: make "key-database.pogreb" set via flag
-	keyDatabase, err := database.New("key-database.pogreb")
+	certDB, closeFn, err := openCertDB(ctx)
 	if err != nil {
-		return fmt.Errorf("could not create database: %v", err)
+		return err
+	}
+	defer closeFn()
+
+	items, err := certDB.Items(0, 0)
+	if err != nil {
+		return err
 	}
 
-	items := keyDatabase.Items()
-	for domain, _, err := items.Next(); err != pogreb.ErrIterationDone; domain, _, err = items.Next() {
-		if err != nil {
-			return err
+	fmt.Printf("Domain\tValidTill\n\n")
+	for _, cert := range items {
+		if cert.Domain[0] == '.' {
+			cert.Domain = "*" + cert.Domain
 		}
-		if domain[0] == '.' {
-			fmt.Printf("*")
-		}
-		fmt.Printf("%s\n", domain)
+		fmt.Printf("%s\t%s\n",
+			cert.Domain,
+			time.Unix(cert.ValidTill, 0).Format(time.RFC3339))
 	}
 	return nil
 }
@@ -53,20 +115,17 @@ func removeCert(ctx *cli.Context) error {
 
 	domains := ctx.Args().Slice()
 
-	// TODO: make "key-database.pogreb" set via flag
-	keyDatabase, err := database.New("key-database.pogreb")
+	certDB, closeFn, err := openCertDB(ctx)
 	if err != nil {
-		return fmt.Errorf("could not create database: %v", err)
+		return err
 	}
+	defer closeFn()
 
 	for _, domain := range domains {
 		fmt.Printf("Removing domain %s from the database...\n", domain)
-		if err := keyDatabase.Delete(domain); err != nil {
+		if err := certDB.Delete(domain); err != nil {
 			return err
 		}
 	}
-	if err := keyDatabase.Close(); err != nil {
-		return err
-	}
 	return nil
 }
diff --git a/cmd/flags.go b/cmd/flags.go
index 8ac09ec..40bb053 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -4,120 +4,149 @@ import (
 	"github.com/urfave/cli/v2"
 )
 
-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".
-	&cli.StringFlag{
-		Name:    "pages-domain",
-		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.page",
-	},
-	// 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{"RAW_INFO_PAGE"},
-		Value:   "https://docs.codeberg.org/codeberg-pages/raw-content/",
-	},
+var (
+	CertStorageFlags = []cli.Flag{
+		&cli.StringFlag{
+			// TODO: remove in next version
+			// DEPRICATED
+			Name:    "db-pogreb",
+			Value:   "key-database.pogreb",
+			EnvVars: []string{"DB_POGREB"},
+		},
+		&cli.StringFlag{
+			Name:    "db-type",
+			Value:   "", // TODO: "sqlite3" in next version
+			EnvVars: []string{"DB_TYPE"},
+		},
+		&cli.StringFlag{
+			Name:    "db-conn",
+			Value:   "certs.sqlite",
+			EnvVars: []string{"DB_CONN"},
+		},
+	}
 
-	// Server
-	&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",
-	},
-	&cli.BoolFlag{
-		Name: "enable-http-server",
-		// TODO: desc
-		EnvVars: []string{"ENABLE_HTTP_SERVER"},
-	},
-	// Server Options
-	&cli.BoolFlag{
-		Name:    "enable-lfs-support",
-		Usage:   "enable lfs support, require gitea v1.17.0 as backend",
-		EnvVars: []string{"ENABLE_LFS_SUPPORT"},
-		Value:   true,
-	},
-	&cli.BoolFlag{
-		Name:    "enable-symlink-support",
-		Usage:   "follow symlinks if enabled, require gitea v1.18.0 as backend",
-		EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
-		Value:   true,
-	},
-	&cli.StringFlag{
-		Name:    "log-level",
-		Value:   "warn",
-		Usage:   "specify at which log level should be logged. Possible options: info, warn, error, fatal",
-		EnvVars: []string{"LOG_LEVEL"},
-	},
+	ServerFlags = append(CertStorageFlags, []cli.Flag{
+		// #############
+		// ### Gitea ###
+		// #############
+		// 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:   "",
+		},
 
-	// ACME
-	&cli.StringFlag{
-		Name:    "acme-api-endpoint",
-		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",
-	},
-	&cli.BoolFlag{
-		Name: "acme-use-rate-limits",
-		// TODO: Usage
-		EnvVars: []string{"ACME_USE_RATE_LIMITS"},
-		Value:   true,
-	},
-	&cli.BoolFlag{
-		Name: "acme-accept-terms",
-		// TODO: Usage
-		EnvVars: []string{"ACME_ACCEPT_TERMS"},
-	},
-	&cli.StringFlag{
-		Name: "acme-eab-kid",
-		// TODO: Usage
-		EnvVars: []string{"ACME_EAB_KID"},
-	},
-	&cli.StringFlag{
-		Name: "acme-eab-hmac",
-		// TODO: Usage
-		EnvVars: []string{"ACME_EAB_HMAC"},
-	},
-	&cli.StringFlag{
-		Name: "dns-provider",
-		// TODO: Usage
-		EnvVars: []string{"DNS_PROVIDER"},
-	},
-}
+		// ###########################
+		// ### Page Server Domains ###
+		// ###########################
+		// 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".
+		&cli.StringFlag{
+			Name:    "pages-domain",
+			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",
+		},
+		// 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.page",
+		},
+		// 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{"RAW_INFO_PAGE"},
+			Value:   "https://docs.codeberg.org/codeberg-pages/raw-content/",
+		},
+
+		// Server
+		&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",
+		},
+		&cli.BoolFlag{
+			Name: "enable-http-server",
+			// TODO: desc
+			EnvVars: []string{"ENABLE_HTTP_SERVER"},
+		},
+		// Server Options
+		&cli.BoolFlag{
+			Name:    "enable-lfs-support",
+			Usage:   "enable lfs support, require gitea v1.17.0 as backend",
+			EnvVars: []string{"ENABLE_LFS_SUPPORT"},
+			Value:   true,
+		},
+		&cli.BoolFlag{
+			Name:    "enable-symlink-support",
+			Usage:   "follow symlinks if enabled, require gitea v1.18.0 as backend",
+			EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
+			Value:   true,
+		},
+		&cli.StringFlag{
+			Name:    "log-level",
+			Value:   "warn",
+			Usage:   "specify at which log level should be logged. Possible options: info, warn, error, fatal",
+			EnvVars: []string{"LOG_LEVEL"},
+		},
+
+		// ACME
+		&cli.StringFlag{
+			Name:    "acme-api-endpoint",
+			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",
+		},
+		&cli.BoolFlag{
+			Name: "acme-use-rate-limits",
+			// TODO: Usage
+			EnvVars: []string{"ACME_USE_RATE_LIMITS"},
+			Value:   true,
+		},
+		&cli.BoolFlag{
+			Name: "acme-accept-terms",
+			// TODO: Usage
+			EnvVars: []string{"ACME_ACCEPT_TERMS"},
+		},
+		&cli.StringFlag{
+			Name: "acme-eab-kid",
+			// TODO: Usage
+			EnvVars: []string{"ACME_EAB_KID"},
+		},
+		&cli.StringFlag{
+			Name: "acme-eab-hmac",
+			// TODO: Usage
+			EnvVars: []string{"ACME_EAB_HMAC"},
+		},
+		&cli.StringFlag{
+			Name: "dns-provider",
+			// TODO: Usage
+			EnvVars: []string{"DNS_PROVIDER"},
+		},
+	}...)
+)
diff --git a/cmd/main.go b/cmd/main.go
index b72013a..af4d2ce 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -18,7 +18,6 @@ import (
 	"codeberg.org/codeberg/pages/server"
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/certificates"
-	"codeberg.org/codeberg/pages/server/database"
 	"codeberg.org/codeberg/pages/server/gitea"
 	"codeberg.org/codeberg/pages/server/handler"
 )
@@ -38,7 +37,7 @@ var BlacklistedPaths = []string{
 
 // Serve sets up and starts the web server.
 func Serve(ctx *cli.Context) error {
-	// Initalize the logger.
+	// Initialize the logger.
 	logLevel, err := zerolog.ParseLevel(ctx.String("log-level"))
 	if err != nil {
 		return err
@@ -74,6 +73,13 @@ func Serve(ctx *cli.Context) error {
 		mainDomainSuffix = "." + mainDomainSuffix
 	}
 
+	// Init ssl cert database
+	certDB, closeFn, err := openCertDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer closeFn()
+
 	keyCache := cache.NewKeyValueCache()
 	challengeCache := cache.NewKeyValueCache()
 	// canonicalDomainCache stores canonical domains
@@ -104,13 +110,6 @@ func Serve(ctx *cli.Context) error {
 		return fmt.Errorf("couldn't create listener: %v", err)
 	}
 
-	// TODO: make "key-database.pogreb" set via flag
-	certDB, err := database.New("key-database.pogreb")
-	if err != nil {
-		return fmt.Errorf("could not create database: %v", err)
-	}
-	defer certDB.Close() //nolint:errcheck    // database has no close ... sync behave like it
-
 	listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix,
 		giteaClient,
 		dnsProvider,
diff --git a/cmd/setup.go b/cmd/setup.go
new file mode 100644
index 0000000..3d1d6ee
--- /dev/null
+++ b/cmd/setup.go
@@ -0,0 +1,45 @@
+package cmd
+
+import (
+	"fmt"
+
+	"github.com/rs/zerolog/log"
+	"github.com/urfave/cli/v2"
+
+	"codeberg.org/codeberg/pages/server/database"
+)
+
+func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
+	if ctx.String("db-type") != "" {
+		log.Trace().Msg("use xorm mode")
+		certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
+		if err != nil {
+			return nil, nil, fmt.Errorf("could not connect to database: %w", err)
+		}
+	} else {
+		// TODO: remove in next version
+		fmt.Println(`
+######################
+## W A R N I N G !!! #
+######################
+
+You use "pogreb" witch is deprecated and will be removed in the next version.
+Please switch to sqlite, mysql or postgres !!!
+
+The simplest way is, to use './pages certs migrate' and set environment var DB_TYPE to 'sqlite' on next start.`)
+		log.Error().Msg("depricated \"pogreb\" used\n")
+
+		certDB, err = database.NewPogreb(ctx.String("db-pogreb"))
+		if err != nil {
+			return nil, nil, fmt.Errorf("could not create database: %w", err)
+		}
+	}
+
+	closeFn = func() {
+		if err := certDB.Close(); err != nil {
+			log.Error().Err(err)
+		}
+	}
+
+	return certDB, closeFn, nil
+}
diff --git a/go.mod b/go.mod
index 77ed762..2e75c8e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,17 +1,21 @@
 module codeberg.org/codeberg/pages
 
-go 1.19
+go 1.20
 
 require (
 	code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
 	github.com/akrylysov/pogreb v0.10.1
 	github.com/go-acme/lego/v4 v4.5.3
+	github.com/go-sql-driver/mysql v1.6.0
 	github.com/joho/godotenv v1.4.0
+	github.com/lib/pq v1.10.7
+	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli/v2 v2.3.0
+	xorm.io/xorm v1.3.2
 )
 
 require (
@@ -47,11 +51,13 @@ require (
 	github.com/go-errors/errors v1.0.1 // indirect
 	github.com/go-fed/httpsig v1.1.0 // indirect
 	github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
-	github.com/gofrs/uuid v3.2.0+incompatible // indirect
+	github.com/goccy/go-json v0.8.1 // indirect
+	github.com/gofrs/uuid v4.0.0+incompatible // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
-	github.com/google/uuid v1.1.1 // indirect
+	github.com/google/uuid v1.3.0 // indirect
 	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
 	github.com/gophercloud/gophercloud v0.16.0 // indirect
 	github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
@@ -62,7 +68,7 @@ require (
 	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
 	github.com/jarcoal/httpmock v1.0.6 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
-	github.com/json-iterator/go v1.1.7 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
 	github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
 	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
@@ -78,7 +84,7 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
-	github.com/modern-go/reflect2 v1.0.1 // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
 	github.com/nrdcg/auroradns v1.0.1 // indirect
 	github.com/nrdcg/desec v0.6.0 // indirect
@@ -103,6 +109,7 @@ require (
 	github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
 	github.com/spf13/cast v1.3.1 // indirect
 	github.com/stretchr/objx v0.3.0 // indirect
+	github.com/syndtr/goleveldb v1.0.0 // indirect
 	github.com/transip/gotransip/v6 v6.6.1 // indirect
 	github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 // indirect
 	github.com/vultr/govultr/v2 v2.7.1 // indirect
@@ -124,4 +131,5 @@ require (
 	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	xorm.io/builder v0.3.12 // indirect
 )
diff --git a/go.sum b/go.sum
index a44001c..c1042c9 100644
--- a/go.sum
+++ b/go.sum
@@ -25,6 +25,9 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa h1:OVwgYrY6vr6gWZvgnmevFhtL0GVA4HKaFOhD+joPoNk=
 code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
+gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
 github.com/Azure/azure-sdk-for-go v32.4.0+incompatible h1:1JP8SKfroEakYiQU2ZyPDosh8w2Tg9UopKt88VyQPt4=
 github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
@@ -55,24 +58,38 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
 github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a h1:Cf4CrDeyrIcuIiJZEZJAH5dapqQ6J3OmP/vHPbDjaFA=
 github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a/go.mod h1:ig6eVXkYn/9dz0Vm8UdLf+E0u1bE6kBSn3n2hqk6jas=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 h1:bLzehmpyCwQiqCE1Qe9Ny6fbFqs7hPlmo9vKv2orUxs=
 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8=
 github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
 github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 h1:dkj8/dxOQ4L1XpwCzRLqukvUBbxuNdz3FeyvHFnRjmo=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
+github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo=
 github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -81,27 +98,39 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
+github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
 github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/cloudflare-go v0.20.0 h1:y2a6KwYHTFxhw+8PLhz0q5hpTGj6Un3W1pbpQLhzFaE=
 github.com/cloudflare/cloudflare-go v0.20.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
 github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -110,6 +139,7 @@ github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454Wv
 github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpAMecnw=
 github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
+github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
@@ -118,6 +148,13 @@ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/
 github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
 github.com/dnsimple/dnsimple-go v0.70.1 h1:cSZndVjttLpgplDuesY4LFIvfKf/zRA1J7mCATBbzSM=
 github.com/dnsimple/dnsimple-go v0.70.1/go.mod h1:F9WHww9cC76hrnwGFfAfrqdW99j3MOYasQcIwTS/aUk=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/exoscale/egoscale v0.67.0 h1:qgWh7T5IZGrNWtg6ib4dr+76WThvB+odTtGG+DGbXF8=
@@ -127,6 +164,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -144,24 +183,37 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
 github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
 github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
+github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
+github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -186,6 +238,10 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -194,6 +250,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
@@ -208,8 +265,10 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -220,16 +279,23 @@ github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15
 github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -246,6 +312,7 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
@@ -258,12 +325,61 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
 github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
+github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
+github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
+github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
+github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
+github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
+github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
+github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
+github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
+github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
+github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
 github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g=
 github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
@@ -277,8 +393,10 @@ github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -286,6 +404,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc=
@@ -298,6 +418,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk=
@@ -306,6 +427,15 @@ github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk=
 github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
 github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 github.com/linode/linodego v0.31.1 h1:dBtjKo7J9UhNFhTOclEXb12RRyQDaRBxISdONVuU+DA=
 github.com/linode/linodego v0.31.1/go.mod h1:BR0gVkCJffEdIGJSl6bHR80Ty+Uvg/2jkjmrWaFectM=
 github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
@@ -315,19 +445,25 @@ github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAW
 github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
 github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4=
 github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
+github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
@@ -337,6 +473,9 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
 github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -360,11 +499,19 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
 github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y=
 github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
@@ -383,29 +530,49 @@ github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
 github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
 github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
 github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
 github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk=
 github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
+github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
+github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -414,27 +581,41 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
 github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
 github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
+github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad h1:WtSUHi5zthjudjIi3L6QmL/V9vpJPbc/j/F2u55d3fs=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad/go.mod h1:h0+DiDRe2Y+6iHTjIq/9HzUq7NII/Nffp0HkFrsAKq4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
 github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
@@ -442,12 +623,18 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI=
 github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
+github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f h1:WSnaD0/cvbKJgSTYbjAPf4RJXVvNNDAwVm+W8wEmnGE=
 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
@@ -466,21 +653,28 @@ github.com/softlayer/softlayer-go v1.0.3/go.mod h1:6HepcfAXROz0Rf63krk5hPZyHT6qy
 github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
 github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
 github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -490,11 +684,16 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/transip/gotransip/v6 v6.6.1 h1:nsCU1ErZS5G0FeOpgGXc4FsWvBff9GPswSMggsC4564=
 github.com/transip/gotransip/v6 v6.6.1/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
 github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
 github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
@@ -511,7 +710,13 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -519,23 +724,37 @@ go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 h1:d9qaMM+ODpCq+9We41//fu/sHsTnXcrqd1en3x+GKy4=
 go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -570,6 +789,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -579,6 +799,7 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -589,6 +810,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -629,9 +851,11 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -641,6 +865,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -648,6 +873,7 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -663,13 +889,16 @@ golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -684,6 +913,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -692,6 +922,7 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
 golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -700,14 +931,18 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -716,6 +951,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -725,12 +961,17 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -743,6 +984,7 @@ google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
 google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
 google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
@@ -753,6 +995,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
@@ -767,10 +1010,15 @@ google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfG
 google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U=
 google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
@@ -789,10 +1037,13 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
 gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -806,6 +1057,7 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI
 gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -818,12 +1070,129 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
+lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
+modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
+modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
+modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
+modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
+modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
+modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
+modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
+modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
+modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
+modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
+modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
+modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
+modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
+modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
+modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
+modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
+modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
+modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
+modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
+modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
+modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
+modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
+modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
+modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
+modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
+modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
+modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
+modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
+modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
+modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
+modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
+modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
+modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
+modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
+modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
+modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
+modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
+modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
+modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
+modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
+modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
+modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
+modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
+modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
+modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
+modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
+modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
+modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
+modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
+modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
+modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
+modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
+modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
+modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
+modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
+modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
+modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
+modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
+modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
+modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
+modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
+modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
+modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
+modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
+modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
+modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
+modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
+modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
+modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
+modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
+modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
+modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
+modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
+modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
+modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
+modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
+modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
+modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
+modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
+modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
+modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
+modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
+modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
+modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
+xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
+xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
diff --git a/integration/main_test.go b/integration/main_test.go
index 406b33a..3e0e187 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -40,11 +40,12 @@ func startServer(ctx context.Context) error {
 	setEnvIfNotSet("PAGES_DOMAIN", "localhost.mock.directory")
 	setEnvIfNotSet("RAW_DOMAIN", "raw.localhost.mock.directory")
 	setEnvIfNotSet("PORT", "4430")
+	setEnvIfNotSet("DB_TYPE", "sqlite3")
 
 	app := cli.NewApp()
 	app.Name = "pages-server"
 	app.Action = cmd.Serve
-	app.Flags = cmd.ServeFlags
+	app.Flags = cmd.ServerFlags
 
 	go func() {
 		if err := app.RunContext(ctx, args); err != nil {
diff --git a/main.go b/main.go
index 2836b86..8eac1fb 100644
--- a/main.go
+++ b/main.go
@@ -19,7 +19,7 @@ func main() {
 	app.Version = version
 	app.Usage = "pages server"
 	app.Action = cmd.Serve
-	app.Flags = cmd.ServeFlags
+	app.Flags = cmd.ServerFlags
 	app.Commands = []*cli.Command{
 		cmd.Certs,
 	}
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 0bf5672..555539e 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -1,14 +1,12 @@
 package certificates
 
 import (
-	"bytes"
 	"context"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
 	"crypto/tls"
 	"crypto/x509"
-	"encoding/gob"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -100,10 +98,9 @@ func TLSConfig(mainDomainSuffix string,
 				return tlsCertificate.(*tls.Certificate), nil
 			}
 
-			var tlsCertificate tls.Certificate
+			var tlsCertificate *tls.Certificate
 			var err error
-			var ok bool
-			if tlsCertificate, ok = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); !ok {
+			if tlsCertificate, err = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); err != nil {
 				// request a new certificate
 				if strings.EqualFold(sni, mainDomainSuffix) {
 					return nil, errors.New("won't request certificate for main domain, something really bad has happened")
@@ -119,12 +116,11 @@ func TLSConfig(mainDomainSuffix string,
 				}
 			}
 
-			if err := keyCache.Set(sni, &tlsCertificate, 15*time.Minute); err != nil {
+			if err := keyCache.Set(sni, tlsCertificate, 15*time.Minute); err != nil {
 				return nil, err
 			}
-			return &tlsCertificate, nil
+			return tlsCertificate, nil
 		},
-		PreferServerCipherSuites: true,
 		NextProtos: []string{
 			"h2",
 			"http/1.1",
@@ -205,54 +201,53 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
 	return nil
 }
 
-func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) {
+func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (*tls.Certificate, error) {
 	// parse certificate from database
 	res, err := certDB.Get(sni)
 	if err != nil {
-		panic(err) // TODO: no panic
-	}
-	if res == nil {
-		return tls.Certificate{}, false
+		return nil, err
+	} else if res == nil {
+		return nil, database.ErrNotFound
 	}
 
 	tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
 
 	// TODO: document & put into own function
 	if !strings.EqualFold(sni, mainDomainSuffix) {
 		tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
 		if err != nil {
-			panic(err)
+			return nil, fmt.Errorf("error parsin leaf tlsCert: %w", err)
 		}
 
 		// renew certificates 7 days before they expire
 		if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
-			// TODO: add ValidUntil to custom res struct
+			// TODO: use ValidTill of custom cert struct
 			if res.CSR != nil && len(res.CSR) > 0 {
 				// CSR stores the time when the renewal shall be tried again
 				nextTryUnix, err := strconv.ParseInt(string(res.CSR), 10, 64)
 				if err == nil && time.Now().Before(time.Unix(nextTryUnix, 0)) {
-					return tlsCertificate, true
+					return &tlsCertificate, nil
 				}
 			}
+			// TODO: make a queue ?
 			go (func() {
 				res.CSR = nil // acme client doesn't like CSR to be set
-				tlsCertificate, err = obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
-				if err != nil {
+				if _, err := obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB); err != nil {
 					log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
 				}
 			})()
 		}
 	}
 
-	return tlsCertificate, true
+	return &tlsCertificate, nil
 }
 
 var obtainLocks = sync.Map{}
 
-func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, error) {
+func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, acmeUseRateLimits bool, keyDatabase database.CertDB) (*tls.Certificate, error) {
 	name := strings.TrimPrefix(domains[0], "*")
 	if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' {
 		domains = domains[1:]
@@ -265,16 +260,16 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 			time.Sleep(100 * time.Millisecond)
 			_, working = obtainLocks.Load(name)
 		}
-		cert, ok := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
-		if !ok {
-			return tls.Certificate{}, errors.New("certificate failed in synchronous request")
+		cert, err := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
+		if err != nil {
+			return nil, fmt.Errorf("certificate failed in synchronous request: %w", err)
 		}
 		return cert, nil
 	}
 	defer obtainLocks.Delete(name)
 
 	if acmeClient == nil {
-		return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase), nil
+		return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase)
 	}
 
 	// request actual cert
@@ -297,7 +292,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 	if res == nil {
 		if user != "" {
 			if err := checkUserLimit(user); err != nil {
-				return tls.Certificate{}, err
+				return nil, err
 			}
 		}
 
@@ -320,33 +315,42 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 		if renew != nil && renew.CertURL != "" {
 			tlsCertificate, err := tls.X509KeyPair(renew.Certificate, renew.PrivateKey)
 			if err != nil {
-				return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
+				mockC, err2 := mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase)
+				if err2 != nil {
+					return nil, errors.Join(err, err2)
+				}
+				return mockC, err
 			}
 			leaf, err := leaf(&tlsCertificate)
 			if err == nil && leaf.NotAfter.After(time.Now()) {
 				// avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at
 				renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
 				if err := keyDatabase.Put(name, renew); err != nil {
-					return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
+					mockC, err2 := mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase)
+					if err2 != nil {
+						return nil, errors.Join(err, err2)
+					}
+					return mockC, err
 				}
-				return tlsCertificate, nil
+				return &tlsCertificate, nil
 			}
 		}
-		return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
+		return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase)
 	}
 	log.Debug().Msgf("Obtained certificate for %v", domains)
 
 	if err := keyDatabase.Put(name, res); err != nil {
-		return tls.Certificate{}, err
+		return nil, err
 	}
 	tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
 	if err != nil {
-		return tls.Certificate{}, err
+		return nil, err
 	}
-	return tlsCertificate, nil
+	return &tlsCertificate, nil
 }
 
 func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
+	// TODO: make it a config flag
 	const configFile = "acme-account.json"
 	var myAcmeAccount AcmeAccount
 	var myAcmeConfig *lego.Config
@@ -431,8 +435,8 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
 func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error {
 	// getting main cert before ACME account so that we can fail here without hitting rate limits
 	mainCertBytes, err := certDB.Get(mainDomainSuffix)
-	if err != nil {
-		return fmt.Errorf("cert database is not working")
+	if err != nil && !errors.Is(err, database.ErrNotFound) {
+		return fmt.Errorf("cert database is not working: %w", err)
 	}
 
 	acmeClient, err = lego.NewClient(acmeConfig)
@@ -485,41 +489,35 @@ func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Co
 
 func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) {
 	for {
-		// clean up expired certs
-		now := time.Now()
+		// delete expired certs that will be invalid until next clean up
+		threshold := time.Now().Add(interval)
 		expiredCertCount := 0
-		keyDatabaseIterator := certDB.Items()
-		key, resBytes, err := keyDatabaseIterator.Next()
-		for err == nil {
-			if !strings.EqualFold(string(key), mainDomainSuffix) {
-				resGob := bytes.NewBuffer(resBytes)
-				resDec := gob.NewDecoder(resGob)
-				res := &certificate.Resource{}
-				err = resDec.Decode(res)
-				if err != nil {
-					panic(err)
-				}
 
-				tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
-				if err != nil || tlsCertificates[0].NotAfter.Before(now) {
-					err := certDB.Delete(string(key))
-					if err != nil {
-						log.Error().Err(err).Msgf("Deleting expired certificate for %q failed", string(key))
-					} else {
-						expiredCertCount++
+		certs, err := certDB.Items(0, 0)
+		if err != nil {
+			log.Error().Err(err).Msg("could not get certs from list")
+		} else {
+			for _, cert := range certs {
+				if !strings.EqualFold(cert.Domain, strings.TrimPrefix(mainDomainSuffix, ".")) {
+					if time.Unix(cert.ValidTill, 0).Before(threshold) {
+						err := certDB.Delete(cert.Domain)
+						if err != nil {
+							log.Error().Err(err).Msgf("Deleting expired certificate for %q failed", cert.Domain)
+						} else {
+							expiredCertCount++
+						}
 					}
 				}
 			}
-			key, resBytes, err = keyDatabaseIterator.Next()
-		}
-		log.Debug().Msgf("Removed %d expired certificates from the database", expiredCertCount)
+			log.Debug().Msgf("Removed %d expired certificates from the database", expiredCertCount)
 
-		// compact the database
-		msg, err := certDB.Compact()
-		if err != nil {
-			log.Error().Err(err).Msg("Compacting key database failed")
-		} else {
-			log.Debug().Msgf("Compacted key database: %s", msg)
+			// compact the database
+			msg, err := certDB.Compact()
+			if err != nil {
+				log.Error().Err(err).Msg("Compacting key database failed")
+			} else {
+				log.Debug().Msgf("Compacted key database: %s", msg)
+			}
 		}
 
 		// update main cert
@@ -530,9 +528,10 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 			log.Error().Msgf("Couldn't renew certificate for main domain %q expected main domain cert to exist, but it's missing - seems like the database is corrupted", mainDomainSuffix)
 		} else {
 			tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
-
-			// renew main certificate 30 days before it expires
-			if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
+			if err != nil {
+				log.Error().Err(fmt.Errorf("could not parse cert for mainDomainSuffix: %w", err))
+			} else if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
+				// renew main certificate 30 days before it expires
 				go (func() {
 					_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
 					if err != nil {
diff --git a/server/certificates/mock.go b/server/certificates/mock.go
index 0e87e6e..a28d0f4 100644
--- a/server/certificates/mock.go
+++ b/server/certificates/mock.go
@@ -13,14 +13,15 @@ import (
 
 	"github.com/go-acme/lego/v4/certcrypto"
 	"github.com/go-acme/lego/v4/certificate"
+	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/database"
 )
 
-func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB) tls.Certificate {
+func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
 	key, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
 
 	template := x509.Certificate{
@@ -52,7 +53,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
 		key,
 	)
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
 
 	out := &bytes.Buffer{}
@@ -61,7 +62,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
 		Type:  "CERTIFICATE",
 	})
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
 	outBytes := out.Bytes()
 	res := &certificate.Resource{
@@ -75,12 +76,12 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
 		databaseName = mainDomainSuffix
 	}
 	if err := keyDatabase.Put(databaseName, res); err != nil {
-		panic(err)
+		log.Error().Err(err)
 	}
 
 	tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
-	return tlsCertificate
+	return &tlsCertificate, nil
 }
diff --git a/server/certificates/mock_test.go b/server/certificates/mock_test.go
index 1cbd1f6..00e1b21 100644
--- a/server/certificates/mock_test.go
+++ b/server/certificates/mock_test.go
@@ -10,7 +10,8 @@ import (
 func TestMockCert(t *testing.T) {
 	db, err := database.NewTmpDB()
 	assert.NoError(t, err)
-	cert := mockCert("example.com", "some error msg", "codeberg.page", db)
+	cert, err := mockCert("example.com", "some error msg", "codeberg.page", db)
+	assert.NoError(t, err)
 	if assert.NotEmpty(t, cert) {
 		assert.NotEmpty(t, cert.Certificate)
 	}
diff --git a/server/database/interface.go b/server/database/interface.go
index 3ba3efc..56537a4 100644
--- a/server/database/interface.go
+++ b/server/database/interface.go
@@ -1,8 +1,11 @@
 package database
 
 import (
-	"github.com/akrylysov/pogreb"
+	"fmt"
+
+	"github.com/go-acme/lego/v4/certcrypto"
 	"github.com/go-acme/lego/v4/certificate"
+	"github.com/rs/zerolog/log"
 )
 
 type CertDB interface {
@@ -10,6 +13,61 @@ type CertDB interface {
 	Put(name string, cert *certificate.Resource) error
 	Get(name string) (*certificate.Resource, error)
 	Delete(key string) error
+	Items(page, pageSize int) ([]*Cert, error)
+	// Compact deprecated // TODO: remove in next version
 	Compact() (string, error)
-	Items() *pogreb.ItemIterator
+}
+
+type Cert struct {
+	Domain    string `xorm:"pk      NOT NULL UNIQUE    'domain'"`
+	Created   int64  `xorm:"created NOT NULL DEFAULT 0 'created'"`
+	Updated   int64  `xorm:"updated NOT NULL DEFAULT 0 'updated'"`
+	ValidTill int64  `xorm:"        NOT NULL DEFAULT 0 'valid_till'"`
+	// certificate.Resource
+	CertURL           string `xorm:"'cert_url'"`
+	CertStableURL     string `xorm:"'cert_stable_url'"`
+	PrivateKey        []byte `xorm:"'private_key'"`
+	Certificate       []byte `xorm:"'certificate'"`
+	IssuerCertificate []byte `xorm:"'issuer_certificate'"`
+}
+
+func (c Cert) Raw() *certificate.Resource {
+	return &certificate.Resource{
+		Domain:            c.Domain,
+		CertURL:           c.CertURL,
+		CertStableURL:     c.CertStableURL,
+		PrivateKey:        c.PrivateKey,
+		Certificate:       c.Certificate,
+		IssuerCertificate: c.IssuerCertificate,
+	}
+}
+
+func toCert(name string, c *certificate.Resource) (*Cert, error) {
+	tlsCertificates, err := certcrypto.ParsePEMBundle(c.Certificate)
+	if err != nil {
+		return nil, err
+	}
+	if len(tlsCertificates) == 0 || tlsCertificates[0] == nil {
+		err := fmt.Errorf("parsed cert resource has no cert")
+		log.Error().Err(err).Str("domain", c.Domain).Msgf("cert: %v", c)
+		return nil, err
+	}
+	validTill := tlsCertificates[0].NotAfter.Unix()
+
+	// TODO: do we need this or can we just go with domain name for wildcard cert
+	// default *.mock cert is prefixed with '.'
+	if name != c.Domain && name[1:] != c.Domain && name[0] != '.' {
+		return nil, fmt.Errorf("domain key and cert domain not equal")
+	}
+
+	return &Cert{
+		Domain:    c.Domain,
+		ValidTill: validTill,
+
+		CertURL:           c.CertURL,
+		CertStableURL:     c.CertStableURL,
+		PrivateKey:        c.PrivateKey,
+		Certificate:       c.Certificate,
+		IssuerCertificate: c.IssuerCertificate,
+	}, nil
 }
diff --git a/server/database/mock.go b/server/database/mock.go
index dfe2316..7c3735e 100644
--- a/server/database/mock.go
+++ b/server/database/mock.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/OrlovEvgeny/go-mcache"
-	"github.com/akrylysov/pogreb"
 	"github.com/go-acme/lego/v4/certificate"
 )
 
@@ -43,8 +42,8 @@ func (p tmpDB) Compact() (string, error) {
 	return "Truncate done", nil
 }
 
-func (p tmpDB) Items() *pogreb.ItemIterator {
-	panic("ItemIterator not implemented for tmpDB")
+func (p tmpDB) Items(page, pageSize int) ([]*Cert, error) {
+	return nil, fmt.Errorf("items not implemented for tmpDB")
 }
 
 func NewTmpDB() (CertDB, error) {
diff --git a/server/database/setup.go b/server/database/pogreb.go
similarity index 76%
rename from server/database/setup.go
rename to server/database/pogreb.go
index 097c63e..9a53faf 100644
--- a/server/database/setup.go
+++ b/server/database/pogreb.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	"encoding/gob"
+	"errors"
 	"fmt"
 	"time"
 
@@ -62,8 +63,32 @@ func (p aDB) Compact() (string, error) {
 	return fmt.Sprintf("%+v", result), nil
 }
 
-func (p aDB) Items() *pogreb.ItemIterator {
-	return p.intern.Items()
+func (p aDB) Items(_, _ int) ([]*Cert, error) {
+	items := make([]*Cert, 0, p.intern.Count())
+	iterator := p.intern.Items()
+	for {
+		key, resBytes, err := iterator.Next()
+		if err != nil {
+			if errors.Is(err, pogreb.ErrIterationDone) {
+				break
+			}
+			return nil, err
+		}
+
+		res := &certificate.Resource{}
+		if err := gob.NewDecoder(bytes.NewBuffer(resBytes)).Decode(res); err != nil {
+			return nil, err
+		}
+
+		cert, err := toCert(string(key), res)
+		if err != nil {
+			return nil, err
+		}
+
+		items = append(items, cert)
+	}
+
+	return items, nil
 }
 
 var _ CertDB = &aDB{}
@@ -82,7 +107,7 @@ func (p aDB) sync() {
 	}
 }
 
-func New(path string) (CertDB, error) {
+func NewPogreb(path string) (CertDB, error) {
 	if path == "" {
 		return nil, fmt.Errorf("path not set")
 	}
diff --git a/server/database/xorm.go b/server/database/xorm.go
new file mode 100644
index 0000000..68ff18c
--- /dev/null
+++ b/server/database/xorm.go
@@ -0,0 +1,121 @@
+package database
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/rs/zerolog/log"
+
+	"github.com/go-acme/lego/v4/certificate"
+	"xorm.io/xorm"
+
+	// register sql driver
+	_ "github.com/go-sql-driver/mysql"
+	_ "github.com/lib/pq"
+	_ "github.com/mattn/go-sqlite3"
+)
+
+var _ CertDB = xDB{}
+
+var ErrNotFound = errors.New("entry not found")
+
+type xDB struct {
+	engine *xorm.Engine
+}
+
+func NewXormDB(dbType, dbConn string) (CertDB, error) {
+	if !supportedDriver(dbType) {
+		return nil, fmt.Errorf("not supported db type '%s'", dbType)
+	}
+	if dbConn == "" {
+		return nil, fmt.Errorf("no db connection provided")
+	}
+
+	e, err := xorm.NewEngine(dbType, dbConn)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := e.Sync2(new(Cert)); err != nil {
+		return nil, fmt.Errorf("cound not sync db model :%w", err)
+	}
+
+	return &xDB{
+		engine: e,
+	}, nil
+}
+
+func (x xDB) Close() error {
+	return x.engine.Close()
+}
+
+func (x xDB) Put(domain string, cert *certificate.Resource) error {
+	log.Trace().Str("domain", cert.Domain).Msg("inserting cert to db")
+	c, err := toCert(domain, cert)
+	if err != nil {
+		return err
+	}
+
+	_, err = x.engine.Insert(c)
+	return err
+}
+
+func (x xDB) Get(domain string) (*certificate.Resource, error) {
+	// TODO: do we need this or can we just go with domain name for wildcard cert
+	domain = strings.TrimPrefix(domain, ".")
+
+	cert := new(Cert)
+	log.Trace().Str("domain", domain).Msg("get cert from db")
+	if found, err := x.engine.ID(domain).Get(cert); err != nil {
+		return nil, err
+	} else if !found {
+		return nil, fmt.Errorf("%w: name='%s'", ErrNotFound, domain)
+	}
+	return cert.Raw(), nil
+}
+
+func (x xDB) Delete(domain string) error {
+	log.Trace().Str("domain", domain).Msg("delete cert from db")
+	_, err := x.engine.ID(domain).Delete(new(Cert))
+	return err
+}
+
+func (x xDB) Compact() (string, error) {
+	// not needed
+	return "", nil
+}
+
+// Items return al certs from db, if pageSize is 0 it does not use limit
+func (x xDB) Items(page, pageSize int) ([]*Cert, error) {
+	// paginated return
+	if pageSize > 0 {
+		certs := make([]*Cert, 0, pageSize)
+		if page >= 0 {
+			page = 1
+		}
+		err := x.engine.Limit(pageSize, (page-1)*pageSize).Find(&certs)
+		return certs, err
+	}
+
+	// return all
+	certs := make([]*Cert, 0, 64)
+	err := x.engine.Find(&certs)
+	return certs, err
+}
+
+// Supported database drivers
+const (
+	DriverSqlite   = "sqlite3"
+	DriverMysql    = "mysql"
+	DriverPostgres = "postgres"
+)
+
+func supportedDriver(driver string) bool {
+	switch driver {
+	case DriverMysql, DriverPostgres, DriverSqlite:
+		return true
+	default:
+		return false
+	}
+}
diff --git a/server/upstream/helper.go b/server/upstream/helper.go
index a84d4f0..ac0ab3f 100644
--- a/server/upstream/helper.go
+++ b/server/upstream/helper.go
@@ -17,17 +17,17 @@ func (o *Options) GetBranchTimestamp(giteaClient *gitea.Client) (bool, error) {
 		// Get default branch
 		defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(o.TargetOwner, o.TargetRepo)
 		if err != nil {
-			log.Err(err).Msg("Could't fetch default branch from repository")
+			log.Err(err).Msg("Couldn't fetch default branch from repository")
 			return false, err
 		}
-		log.Debug().Msgf("Succesfully fetched default branch %q from Gitea", defaultBranch)
+		log.Debug().Msgf("Successfully fetched default branch %q from Gitea", defaultBranch)
 		o.TargetBranch = defaultBranch
 	}
 
 	timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(o.TargetOwner, o.TargetRepo, o.TargetBranch)
 	if err != nil {
 		if !errors.Is(err, gitea.ErrorNotFound) {
-			log.Error().Err(err).Msg("Could not get latest commit's timestamp from branch")
+			log.Error().Err(err).Msg("Could not get latest commit timestamp from branch")
 		}
 		return false, err
 	}
@@ -36,7 +36,7 @@ func (o *Options) GetBranchTimestamp(giteaClient *gitea.Client) (bool, error) {
 		return false, fmt.Errorf("empty response")
 	}
 
-	log.Debug().Msgf("Succesfully fetched latest commit's timestamp from branch: %#v", timestamp)
+	log.Debug().Msgf("Successfully fetched latest commit timestamp from branch: %#v", timestamp)
 	o.BranchTimestamp = timestamp.Timestamp
 	o.TargetBranch = timestamp.Branch
 	return true, nil

From 1b6ea4b6e1b0b1fb79f3e2887f61f92c4c00330c Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 10 Feb 2023 04:33:28 +0100
Subject: [PATCH 063/196] use same version var on cli app as header

---
 main.go | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/main.go b/main.go
index 8eac1fb..6c1d0cc 100644
--- a/main.go
+++ b/main.go
@@ -8,15 +8,13 @@ import (
 	"github.com/urfave/cli/v2"
 
 	"codeberg.org/codeberg/pages/cmd"
+	"codeberg.org/codeberg/pages/server/version"
 )
 
-// can be changed with -X on compile
-var version = "dev"
-
 func main() {
 	app := cli.NewApp()
 	app.Name = "pages-server"
-	app.Version = version
+	app.Version = version.Version
 	app.Usage = "pages server"
 	app.Action = cmd.Serve
 	app.Flags = cmd.ServerFlags

From d8d119b0b3a4d557b3c1e1fd6cecb6efc5289901 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 11 Feb 2023 00:31:56 +0000
Subject: [PATCH 064/196] Fix Cache Bug (#178)

error io.EOF is gracefully end of file read.

so we don't need to cancel cache saving

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/178
---
 cmd/main.go                | 4 ++--
 server/gitea/cache.go      | 2 +-
 server/upstream/domains.go | 5 +++--
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go
index af4d2ce..4f0c019 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -44,7 +44,7 @@ func Serve(ctx *cli.Context) error {
 	}
 	log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel)
 
-	giteaRoot := strings.TrimSuffix(ctx.String("gitea-root"), "/")
+	giteaRoot := ctx.String("gitea-root")
 	giteaAPIToken := ctx.String("gitea-api-token")
 	rawDomain := ctx.String("raw-domain")
 	mainDomainSuffix := ctx.String("pages-domain")
@@ -68,7 +68,7 @@ func Serve(ctx *cli.Context) error {
 		allowedCorsDomains = append(allowedCorsDomains, rawDomain)
 	}
 
-	// Make sure MainDomain has a trailing dot, and GiteaRoot has no trailing slash
+	// Make sure MainDomain has a trailing dot
 	if !strings.HasPrefix(mainDomainSuffix, ".") {
 		mainDomainSuffix = "." + mainDomainSuffix
 	}
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index 85cbcde..af61edf 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -80,7 +80,7 @@ type writeCacheReader struct {
 
 func (t *writeCacheReader) Read(p []byte) (n int, err error) {
 	n, err = t.originalReader.Read(p)
-	if err != nil {
+	if err != nil && err != io.EOF {
 		log.Trace().Err(err).Msgf("[cache] original reader for %q has returned an error", t.cacheKey)
 		t.hasError = true
 	} else if n > 0 {
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 5b274b6..bb4b57a 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -1,6 +1,7 @@
 package upstream
 
 import (
+	"errors"
 	"strings"
 	"time"
 
@@ -30,8 +31,8 @@ func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain,
 	}
 
 	body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, canonicalDomainConfig)
-	if err == nil || err == gitea.ErrorNotFound {
-		log.Info().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
+	if err != nil && !errors.Is(err, gitea.ErrorNotFound) {
+		log.Error().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
 	}
 
 	var domains []string

From 272c7ca76fcc75e60ad9841b17f396dd5df63e6a Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 11 Feb 2023 01:26:21 +0000
Subject: [PATCH 065/196] Fix xorm regressions by handle wildcard certs
 correctly (#177)

close #176

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/177
---
 cmd/certs.go                 |  3 --
 cmd/flags.go                 | 33 +++++++------
 integration/get_test.go      |  4 +-
 server/database/interface.go | 12 +++--
 server/database/xorm.go      | 45 ++++++++++++++++--
 server/database/xorm_test.go | 92 ++++++++++++++++++++++++++++++++++++
 server/upstream/domains.go   |  2 +-
 7 files changed, 162 insertions(+), 29 deletions(-)
 create mode 100644 server/database/xorm_test.go

diff --git a/cmd/certs.go b/cmd/certs.go
index 4adf076..96244b7 100644
--- a/cmd/certs.go
+++ b/cmd/certs.go
@@ -98,9 +98,6 @@ func listCerts(ctx *cli.Context) error {
 
 	fmt.Printf("Domain\tValidTill\n\n")
 	for _, cert := range items {
-		if cert.Domain[0] == '.' {
-			cert.Domain = "*" + cert.Domain
-		}
 		fmt.Printf("%s\t%s\n",
 			cert.Domain,
 			time.Unix(cert.ValidTill, 0).Format(time.RFC3339))
diff --git a/cmd/flags.go b/cmd/flags.go
index 40bb053..da3febc 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -43,6 +43,18 @@ var (
 			EnvVars: []string{"GITEA_API_TOKEN"},
 			Value:   "",
 		},
+		&cli.BoolFlag{
+			Name:    "enable-lfs-support",
+			Usage:   "enable lfs support, require gitea >= v1.17.0 as backend",
+			EnvVars: []string{"ENABLE_LFS_SUPPORT"},
+			Value:   true,
+		},
+		&cli.BoolFlag{
+			Name:    "enable-symlink-support",
+			Usage:   "follow symlinks if enabled, require gitea >= v1.18.0 as backend",
+			EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
+			Value:   true,
+		},
 
 		// ###########################
 		// ### Page Server Domains ###
@@ -73,7 +85,9 @@ var (
 			Value:   "https://docs.codeberg.org/codeberg-pages/raw-content/",
 		},
 
-		// Server
+		// #########################
+		// ### Page Server Setup ###
+		// #########################
 		&cli.StringFlag{
 			Name:    "host",
 			Usage:   "specifies host of listening address",
@@ -91,19 +105,6 @@ var (
 			// TODO: desc
 			EnvVars: []string{"ENABLE_HTTP_SERVER"},
 		},
-		// Server Options
-		&cli.BoolFlag{
-			Name:    "enable-lfs-support",
-			Usage:   "enable lfs support, require gitea v1.17.0 as backend",
-			EnvVars: []string{"ENABLE_LFS_SUPPORT"},
-			Value:   true,
-		},
-		&cli.BoolFlag{
-			Name:    "enable-symlink-support",
-			Usage:   "follow symlinks if enabled, require gitea v1.18.0 as backend",
-			EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
-			Value:   true,
-		},
 		&cli.StringFlag{
 			Name:    "log-level",
 			Value:   "warn",
@@ -111,7 +112,9 @@ var (
 			EnvVars: []string{"LOG_LEVEL"},
 		},
 
-		// ACME
+		// ############################
+		// ### ACME Client Settings ###
+		// ############################
 		&cli.StringFlag{
 			Name:    "acme-api-endpoint",
 			EnvVars: []string{"ACME_API"},
diff --git a/integration/get_test.go b/integration/get_test.go
index 6dd57bc..55e6d12 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -20,7 +20,9 @@ func TestGetRedirect(t *testing.T) {
 	log.Println("=== TestGetRedirect ===")
 	// test custom domain redirect
 	resp, err := getTestHTTPSClient().Get("https://calciumdibromid.localhost.mock.directory:4430")
-	assert.NoError(t, err)
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
 	if !assert.EqualValues(t, http.StatusTemporaryRedirect, resp.StatusCode) {
 		t.FailNow()
 	}
diff --git a/server/database/interface.go b/server/database/interface.go
index 56537a4..a0edc91 100644
--- a/server/database/interface.go
+++ b/server/database/interface.go
@@ -54,10 +54,14 @@ func toCert(name string, c *certificate.Resource) (*Cert, error) {
 	}
 	validTill := tlsCertificates[0].NotAfter.Unix()
 
-	// TODO: do we need this or can we just go with domain name for wildcard cert
-	// default *.mock cert is prefixed with '.'
-	if name != c.Domain && name[1:] != c.Domain && name[0] != '.' {
-		return nil, fmt.Errorf("domain key and cert domain not equal")
+	// handle wildcard certs
+	if name[:1] == "." {
+		name = "*" + name
+	}
+	if name != c.Domain {
+		err := fmt.Errorf("domain key '%s' and cert domain '%s' not equal", name, c.Domain)
+		log.Error().Err(err).Msg("toCert conversion did discover mismatch")
+		// TODO: fail hard: return nil, err
 	}
 
 	return &Cert{
diff --git a/server/database/xorm.go b/server/database/xorm.go
index 68ff18c..a14f887 100644
--- a/server/database/xorm.go
+++ b/server/database/xorm.go
@@ -3,7 +3,6 @@ package database
 import (
 	"errors"
 	"fmt"
-	"strings"
 
 	"github.com/rs/zerolog/log"
 
@@ -52,18 +51,38 @@ func (x xDB) Close() error {
 
 func (x xDB) Put(domain string, cert *certificate.Resource) error {
 	log.Trace().Str("domain", cert.Domain).Msg("inserting cert to db")
+
+	domain = integrationTestReplacements(domain)
 	c, err := toCert(domain, cert)
 	if err != nil {
 		return err
 	}
 
-	_, err = x.engine.Insert(c)
-	return err
+	sess := x.engine.NewSession()
+	if err := sess.Begin(); err != nil {
+		return err
+	}
+	defer sess.Close()
+
+	if exist, _ := sess.ID(c.Domain).Exist(); exist {
+		if _, err := sess.ID(c.Domain).Update(c); err != nil {
+			return err
+		}
+	} else {
+		if _, err = sess.Insert(c); err != nil {
+			return err
+		}
+	}
+
+	return sess.Commit()
 }
 
 func (x xDB) Get(domain string) (*certificate.Resource, error) {
-	// TODO: do we need this or can we just go with domain name for wildcard cert
-	domain = strings.TrimPrefix(domain, ".")
+	// handle wildcard certs
+	if domain[:1] == "." {
+		domain = "*" + domain
+	}
+	domain = integrationTestReplacements(domain)
 
 	cert := new(Cert)
 	log.Trace().Str("domain", domain).Msg("get cert from db")
@@ -76,6 +95,12 @@ func (x xDB) Get(domain string) (*certificate.Resource, error) {
 }
 
 func (x xDB) Delete(domain string) error {
+	// handle wildcard certs
+	if domain[:1] == "." {
+		domain = "*" + domain
+	}
+	domain = integrationTestReplacements(domain)
+
 	log.Trace().Str("domain", domain).Msg("delete cert from db")
 	_, err := x.engine.ID(domain).Delete(new(Cert))
 	return err
@@ -119,3 +144,13 @@ func supportedDriver(driver string) bool {
 		return false
 	}
 }
+
+// integrationTestReplacements is needed because integration tests use a single domain cert,
+// while production use a wildcard cert
+// TODO: find a better way to handle this
+func integrationTestReplacements(domainKey string) string {
+	if domainKey == "*.localhost.mock.directory" {
+		return "localhost.mock.directory"
+	}
+	return domainKey
+}
diff --git a/server/database/xorm_test.go b/server/database/xorm_test.go
new file mode 100644
index 0000000..9c032ee
--- /dev/null
+++ b/server/database/xorm_test.go
@@ -0,0 +1,92 @@
+package database
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/go-acme/lego/v4/certificate"
+	"github.com/stretchr/testify/assert"
+	"xorm.io/xorm"
+)
+
+func newTestDB(t *testing.T) *xDB {
+	e, err := xorm.NewEngine("sqlite3", ":memory:")
+	assert.NoError(t, err)
+	assert.NoError(t, e.Sync2(new(Cert)))
+	return &xDB{engine: e}
+}
+
+func TestSanitizeWildcardCerts(t *testing.T) {
+	certDB := newTestDB(t)
+
+	_, err := certDB.Get(".not.found")
+	assert.True(t, errors.Is(err, ErrNotFound))
+
+	// TODO: cert key and domain mismatch are don not fail hard jet
+	// https://codeberg.org/Codeberg/pages-server/src/commit/d8595cee882e53d7f44f1ddc4ef8a1f7b8f31d8d/server/database/interface.go#L64
+	//
+	// assert.Error(t, certDB.Put(".wildcard.de", &certificate.Resource{
+	// 	Domain:      "*.localhost.mock.directory",
+	// 	Certificate: localhost_mock_directory_certificate,
+	// }))
+
+	// insert new wildcard cert
+	assert.NoError(t, certDB.Put(".wildcard.de", &certificate.Resource{
+		Domain:      "*.wildcard.de",
+		Certificate: localhost_mock_directory_certificate,
+	}))
+
+	// update existing cert
+	assert.Error(t, certDB.Put(".wildcard.de", &certificate.Resource{
+		Domain:      "*.wildcard.de",
+		Certificate: localhost_mock_directory_certificate,
+	}))
+
+	c1, err := certDB.Get(".wildcard.de")
+	assert.NoError(t, err)
+	c2, err := certDB.Get("*.wildcard.de")
+	assert.NoError(t, err)
+	assert.EqualValues(t, c1, c2)
+}
+
+var localhost_mock_directory_certificate = []byte(`-----BEGIN CERTIFICATE-----
+MIIDczCCAlugAwIBAgIIJyBaXHmLk6gwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
+AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA0OWE0ZmIwHhcNMjMwMjEwMDEwOTA2
+WhcNMjgwMjEwMDEwOTA2WjAjMSEwHwYDVQQDExhsb2NhbGhvc3QubW9jay5kaXJl
+Y3RvcnkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIU/CjzS7t62Gj
+neEMqvP7sn99ULT7AEUzEfWL05fWG2z714qcUg1hXkZLgdVDgmsCpplyddip7+2t
+ZH/9rLPLMqJphzvOL4CF6jDLbeifETtKyjnt9vUZFnnNWcP3tu8lo8iYSl08qsUI
+Pp/hiEriAQzCDjTbR5m9xUPNPYqxzcS4ALzmmCX9Qfc4CuuhMkdv2G4TT7rylWrA
+SCSRPnGjeA7pCByfNrO/uXbxmzl3sMO3k5sqgMkx1QIHEN412V8+vtx88mt2sM6k
+xjzGZWWKXlRq+oufIKX9KPplhsCjMH6E3VNAzgOPYDqXagtUcGmLWghURltO8Mt2
+zwM6OgjjAgMBAAGjgaUwgaIwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG
+AQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSMQvlJ1755
+sarf8i1KNqj7s5o/aDAfBgNVHSMEGDAWgBTcZcxJMhWdP7MecHCCpNkFURC/YzAj
+BgNVHREEHDAaghhsb2NhbGhvc3QubW9jay5kaXJlY3RvcnkwDQYJKoZIhvcNAQEL
+BQADggEBACcd7TT28OWwzQN2PcH0aG38JX5Wp2iOS/unDCfWjNAztXHW7nBDMxza
+VtyebkJfccexpuVuOsjOX+bww0vtEYIvKX3/GbkhogksBrNkE0sJZtMnZWMR33wa
+YxAy/kJBTmLi02r8fX9ZhwjldStHKBav4USuP7DXZjrgX7LFQhR4LIDrPaYqQRZ8
+ltC3mM9LDQ9rQyIFP5cSBMO3RUAm4I8JyLoOdb/9G2uxjHr7r6eG1g8DmLYSKBsQ
+mWGQDOYgR3cGltDe2yMxM++yHY+b1uhxGOWMrDA1+1k7yI19LL8Ifi2FMovDfu/X
+JxYk1NNNtdctwaYJFenmGQvDaIq1KgE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDUDCCAjigAwIBAgIIKBJ7IIA6W1swDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVUGViYmxlIFJvb3QgQ0EgNTdmZjE2MCAXDTIzMDIwOTA1MzMxMloYDzIwNTMw
+MjA5MDUzMzEyWjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDQ5
+YTRmYjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOvlqRx8SXQFWo2
+gFCiXxls53eENcyr8+meFyjgnS853eEvplaPxoa2MREKd+ZYxM8EMMfj2XGvR3UI
+aqR5QyLQ9ihuRqvQo4fG91usBHgH+vDbGPdMX8gDmm9HgnmtOVhSKJU+M2jfE1SW
+UuWB9xOa3LMreTXbTNfZEMoXf+GcWZMbx5WPgEga3DvfmV+RsfNvB55eD7YAyZgF
+ZnQ3Dskmnxxlkz0EGgd7rqhFHHNB9jARlL22gITADwoWZidlr3ciM9DISymRKQ0c
+mRN15fQjNWdtuREgJlpXecbYQMGhdTOmFrqdHkveD1o63rGSC4z+s/APV6xIbcRp
+aNpO7L8CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
+BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNxlzEky
+FZ0/sx5wcIKk2QVREL9jMB8GA1UdIwQYMBaAFOqfkm9rebIz4z0SDIKW5edLg5JM
+MA0GCSqGSIb3DQEBCwUAA4IBAQBRG9AHEnyj2fKzVDDbQaKHjAF5jh0gwyHoIeRK
+FkP9mQNSWxhvPWI0tK/E49LopzmVuzSbDd5kZsaii73rAs6f6Rf9W5veo3AFSEad
+stM+Zv0f2vWB38nuvkoCRLXMX+QUeuL65rKxdEpyArBju4L3/PqAZRgMLcrH+ak8
+nvw5RdAq+Km/ZWyJgGikK6cfMmh91YALCDFnoWUWrCjkBaBFKrG59ONV9f0IQX07
+aNfFXFCF5l466xw9dHjw5iaFib10cpY3iq4kyPYIMs6uaewkCtxWKKjiozM4g4w3
+HqwyUyZ52WUJOJ/6G9DJLDtN3fgGR+IAp8BhYd5CqOscnt3h
+-----END CERTIFICATE-----`)
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index bb4b57a..eb30394 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -49,7 +49,7 @@ func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain,
 		}
 	}
 
-	// Add [owner].[pages-domain] as valid domnain.
+	// Add [owner].[pages-domain] as valid domain.
 	domains = append(domains, o.TargetOwner+mainDomainSuffix)
 	if domains[len(domains)-1] == actualDomain {
 		valid = true

From fd643d15f0f44081a6469be7613b1162b8a29258 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 11 Feb 2023 02:04:57 +0000
Subject: [PATCH 066/196] Drop: pogreb support (#175)

followup of #173

close #95

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/175
---
 .woodpecker.yml                     |  13 ---
 Justfile                            |   2 +-
 cmd/certs.go                        |  61 +------------
 cmd/flags.go                        |   9 +-
 cmd/setup.go                        |  26 +-----
 go.mod                              |   1 -
 go.sum                              |   2 -
 server/certificates/certificates.go |   8 --
 server/database/interface.go        |   2 -
 server/database/mock.go             |   5 --
 server/database/pogreb.go           | 134 ----------------------------
 server/database/xorm.go             |   5 --
 12 files changed, 6 insertions(+), 262 deletions(-)
 delete mode 100644 server/database/pogreb.go

diff --git a/.woodpecker.yml b/.woodpecker.yml
index ba44f82..5b07053 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -65,19 +65,6 @@ pipeline:
       - RAW_DOMAIN=raw.localhost.mock.directory
       - PORT=4430
 
-  # TODO: remove in next version
-  integration-tests-legacy:
-    group: test
-    image: codeberg.org/6543/docker-images/golang_just
-    commands:
-      - just integration
-    environment:
-      - ACME_API=https://acme.mock.directory
-      - PAGES_DOMAIN=localhost.mock.directory
-      - RAW_DOMAIN=raw.localhost.mock.directory
-      - PORT=4430
-      - DB_TYPE=
-
   release:
     image: plugins/gitea-release
     settings:
diff --git a/Justfile b/Justfile
index 683222d..0db7845 100644
--- a/Justfile
+++ b/Justfile
@@ -27,7 +27,7 @@ fmt: tool-gofumpt
 
 clean:
     go clean ./...
-    rm -rf build/ integration/certs.sqlite integration/key-database.pogreb/ integration/acme-account.json
+    rm -rf build/ integration/certs.sqlite integration/acme-account.json
 
 tool-golangci:
     @hash golangci-lint> /dev/null 2>&1; if [ $? -ne 0 ]; then \
diff --git a/cmd/certs.go b/cmd/certs.go
index 96244b7..6012b6e 100644
--- a/cmd/certs.go
+++ b/cmd/certs.go
@@ -4,11 +4,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/rs/zerolog"
-	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
-
-	"codeberg.org/codeberg/pages/server/database"
 )
 
 var Certs = &cli.Command{
@@ -25,63 +21,8 @@ var Certs = &cli.Command{
 			Usage:  "remove a certificate from the database",
 			Action: removeCert,
 		},
-		{
-			Name:   "migrate",
-			Usage:  "migrate from \"pogreb\" driver to dbms driver",
-			Action: migrateCerts,
-		},
 	},
-	Flags: append(CertStorageFlags, []cli.Flag{
-		&cli.BoolFlag{
-			Name:    "verbose",
-			Usage:   "print trace info",
-			EnvVars: []string{"VERBOSE"},
-			Value:   false,
-		},
-	}...),
-}
-
-func migrateCerts(ctx *cli.Context) error {
-	dbType := ctx.String("db-type")
-	if dbType == "" {
-		dbType = "sqlite3"
-	}
-	dbConn := ctx.String("db-conn")
-	dbPogrebConn := ctx.String("db-pogreb")
-	verbose := ctx.Bool("verbose")
-
-	log.Level(zerolog.InfoLevel)
-	if verbose {
-		log.Level(zerolog.TraceLevel)
-	}
-
-	xormDB, err := database.NewXormDB(dbType, dbConn)
-	if err != nil {
-		return fmt.Errorf("could not connect to database: %w", err)
-	}
-	defer xormDB.Close()
-
-	pogrebDB, err := database.NewPogreb(dbPogrebConn)
-	if err != nil {
-		return fmt.Errorf("could not open database: %w", err)
-	}
-	defer pogrebDB.Close()
-
-	fmt.Printf("Start migration from \"%s\" to \"%s:%s\" ...\n", dbPogrebConn, dbType, dbConn)
-
-	certs, err := pogrebDB.Items(0, 0)
-	if err != nil {
-		return err
-	}
-
-	for _, cert := range certs {
-		if err := xormDB.Put(cert.Domain, cert.Raw()); err != nil {
-			return err
-		}
-	}
-
-	fmt.Println("... done")
-	return nil
+	Flags: CertStorageFlags,
 }
 
 func listCerts(ctx *cli.Context) error {
diff --git a/cmd/flags.go b/cmd/flags.go
index da3febc..a37b179 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -6,16 +6,9 @@ import (
 
 var (
 	CertStorageFlags = []cli.Flag{
-		&cli.StringFlag{
-			// TODO: remove in next version
-			// DEPRICATED
-			Name:    "db-pogreb",
-			Value:   "key-database.pogreb",
-			EnvVars: []string{"DB_POGREB"},
-		},
 		&cli.StringFlag{
 			Name:    "db-type",
-			Value:   "", // TODO: "sqlite3" in next version
+			Value:   "sqlite3",
 			EnvVars: []string{"DB_TYPE"},
 		},
 		&cli.StringFlag{
diff --git a/cmd/setup.go b/cmd/setup.go
index 3d1d6ee..bccba03 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -10,29 +10,9 @@ import (
 )
 
 func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
-	if ctx.String("db-type") != "" {
-		log.Trace().Msg("use xorm mode")
-		certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
-		if err != nil {
-			return nil, nil, fmt.Errorf("could not connect to database: %w", err)
-		}
-	} else {
-		// TODO: remove in next version
-		fmt.Println(`
-######################
-## W A R N I N G !!! #
-######################
-
-You use "pogreb" witch is deprecated and will be removed in the next version.
-Please switch to sqlite, mysql or postgres !!!
-
-The simplest way is, to use './pages certs migrate' and set environment var DB_TYPE to 'sqlite' on next start.`)
-		log.Error().Msg("depricated \"pogreb\" used\n")
-
-		certDB, err = database.NewPogreb(ctx.String("db-pogreb"))
-		if err != nil {
-			return nil, nil, fmt.Errorf("could not create database: %w", err)
-		}
+	certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
+	if err != nil {
+		return nil, nil, fmt.Errorf("could not connect to database: %w", err)
 	}
 
 	closeFn = func() {
diff --git a/go.mod b/go.mod
index 2e75c8e..944e2ad 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ go 1.20
 require (
 	code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
-	github.com/akrylysov/pogreb v0.10.1
 	github.com/go-acme/lego/v4 v4.5.3
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/joho/godotenv v1.4.0
diff --git a/go.sum b/go.sum
index c1042c9..b5a7568 100644
--- a/go.sum
+++ b/go.sum
@@ -71,8 +71,6 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 h1:bLzehmpyCwQiqCE1Qe9Ny6fbFqs7hPlmo9vKv2orUxs=
 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8=
-github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
-github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 555539e..23fad17 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -510,14 +510,6 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 				}
 			}
 			log.Debug().Msgf("Removed %d expired certificates from the database", expiredCertCount)
-
-			// compact the database
-			msg, err := certDB.Compact()
-			if err != nil {
-				log.Error().Err(err).Msg("Compacting key database failed")
-			} else {
-				log.Debug().Msgf("Compacted key database: %s", msg)
-			}
 		}
 
 		// update main cert
diff --git a/server/database/interface.go b/server/database/interface.go
index a0edc91..92068c5 100644
--- a/server/database/interface.go
+++ b/server/database/interface.go
@@ -14,8 +14,6 @@ type CertDB interface {
 	Get(name string) (*certificate.Resource, error)
 	Delete(key string) error
 	Items(page, pageSize int) ([]*Cert, error)
-	// Compact deprecated // TODO: remove in next version
-	Compact() (string, error)
 }
 
 type Cert struct {
diff --git a/server/database/mock.go b/server/database/mock.go
index 7c3735e..6148287 100644
--- a/server/database/mock.go
+++ b/server/database/mock.go
@@ -37,11 +37,6 @@ func (p tmpDB) Delete(key string) error {
 	return nil
 }
 
-func (p tmpDB) Compact() (string, error) {
-	p.intern.Truncate()
-	return "Truncate done", nil
-}
-
 func (p tmpDB) Items(page, pageSize int) ([]*Cert, error) {
 	return nil, fmt.Errorf("items not implemented for tmpDB")
 }
diff --git a/server/database/pogreb.go b/server/database/pogreb.go
deleted file mode 100644
index 9a53faf..0000000
--- a/server/database/pogreb.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package database
-
-import (
-	"bytes"
-	"context"
-	"encoding/gob"
-	"errors"
-	"fmt"
-	"time"
-
-	"github.com/akrylysov/pogreb"
-	"github.com/akrylysov/pogreb/fs"
-	"github.com/go-acme/lego/v4/certificate"
-	"github.com/rs/zerolog/log"
-)
-
-var _ CertDB = aDB{}
-
-type aDB struct {
-	ctx          context.Context
-	cancel       context.CancelFunc
-	intern       *pogreb.DB
-	syncInterval time.Duration
-}
-
-func (p aDB) Close() error {
-	p.cancel()
-	return p.intern.Sync()
-}
-
-func (p aDB) Put(name string, cert *certificate.Resource) error {
-	var resGob bytes.Buffer
-	if err := gob.NewEncoder(&resGob).Encode(cert); err != nil {
-		return err
-	}
-	return p.intern.Put([]byte(name), resGob.Bytes())
-}
-
-func (p aDB) Get(name string) (*certificate.Resource, error) {
-	cert := &certificate.Resource{}
-	resBytes, err := p.intern.Get([]byte(name))
-	if err != nil {
-		return nil, err
-	}
-	if resBytes == nil {
-		return nil, nil
-	}
-	if err := gob.NewDecoder(bytes.NewBuffer(resBytes)).Decode(cert); err != nil {
-		return nil, err
-	}
-	return cert, nil
-}
-
-func (p aDB) Delete(key string) error {
-	return p.intern.Delete([]byte(key))
-}
-
-func (p aDB) Compact() (string, error) {
-	result, err := p.intern.Compact()
-	if err != nil {
-		return "", err
-	}
-	return fmt.Sprintf("%+v", result), nil
-}
-
-func (p aDB) Items(_, _ int) ([]*Cert, error) {
-	items := make([]*Cert, 0, p.intern.Count())
-	iterator := p.intern.Items()
-	for {
-		key, resBytes, err := iterator.Next()
-		if err != nil {
-			if errors.Is(err, pogreb.ErrIterationDone) {
-				break
-			}
-			return nil, err
-		}
-
-		res := &certificate.Resource{}
-		if err := gob.NewDecoder(bytes.NewBuffer(resBytes)).Decode(res); err != nil {
-			return nil, err
-		}
-
-		cert, err := toCert(string(key), res)
-		if err != nil {
-			return nil, err
-		}
-
-		items = append(items, cert)
-	}
-
-	return items, nil
-}
-
-var _ CertDB = &aDB{}
-
-func (p aDB) sync() {
-	for {
-		err := p.intern.Sync()
-		if err != nil {
-			log.Error().Err(err).Msg("Syncing cert database failed")
-		}
-		select {
-		case <-p.ctx.Done():
-			return
-		case <-time.After(p.syncInterval):
-		}
-	}
-}
-
-func NewPogreb(path string) (CertDB, error) {
-	if path == "" {
-		return nil, fmt.Errorf("path not set")
-	}
-	db, err := pogreb.Open(path, &pogreb.Options{
-		BackgroundSyncInterval:       30 * time.Second,
-		BackgroundCompactionInterval: 6 * time.Hour,
-		FileSystem:                   fs.OSMMap,
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	ctx, cancel := context.WithCancel(context.Background())
-	result := &aDB{
-		ctx:          ctx,
-		cancel:       cancel,
-		intern:       db,
-		syncInterval: 5 * time.Minute,
-	}
-
-	go result.sync()
-
-	return result, nil
-}
diff --git a/server/database/xorm.go b/server/database/xorm.go
index a14f887..4b43cbb 100644
--- a/server/database/xorm.go
+++ b/server/database/xorm.go
@@ -106,11 +106,6 @@ func (x xDB) Delete(domain string) error {
 	return err
 }
 
-func (x xDB) Compact() (string, error) {
-	// not needed
-	return "", nil
-}
-
 // Items return al certs from db, if pageSize is 0 it does not use limit
 func (x xDB) Items(page, pageSize int) ([]*Cert, error) {
 	// paginated return

From 5753f7136deb4cc29bae91640f0ae1ef1472b8f6 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 11 Feb 2023 02:29:08 +0000
Subject: [PATCH 067/196] Move acmeClient creation into own file & struct
 (#179)

get rid of gobal vars and make make functions with less args :)

tldr: collect funcs and create a own ACME client to manage that stuff

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/179
---
 cmd/flags.go                              |  10 +-
 cmd/main.go                               |  35 +---
 cmd/setup.go                              |  34 +++
 server/certificates/acme_client.go        |  95 +++++++++
 server/certificates/acme_config.go        | 100 +++++++++
 server/certificates/cached_challengers.go |  41 ++++
 server/certificates/certificates.go       | 242 +++-------------------
 server/certificates/mock_test.go          |   3 +-
 8 files changed, 323 insertions(+), 237 deletions(-)
 create mode 100644 server/certificates/acme_client.go
 create mode 100644 server/certificates/acme_config.go
 create mode 100644 server/certificates/cached_challengers.go

diff --git a/cmd/flags.go b/cmd/flags.go
index a37b179..8052421 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -140,9 +140,15 @@ var (
 			EnvVars: []string{"ACME_EAB_HMAC"},
 		},
 		&cli.StringFlag{
-			Name: "dns-provider",
-			// TODO: Usage
+			Name:    "dns-provider",
+			Usage:   "Use DNS-Challenge for main domain\n\nRead more at: https://go-acme.github.io/lego/dns/",
 			EnvVars: []string{"DNS_PROVIDER"},
 		},
+		&cli.StringFlag{
+			Name:    "acme-account-config",
+			Usage:   "json file of acme account",
+			Value:   "acme-account.json",
+			EnvVars: []string{"ACME_ACCOUNT_CONFIG"},
+		},
 	}...)
 )
diff --git a/cmd/main.go b/cmd/main.go
index 4f0c019..a1c3b97 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -3,7 +3,6 @@ package cmd
 import (
 	"context"
 	"crypto/tls"
-	"errors"
 	"fmt"
 	"net"
 	"net/http"
@@ -52,17 +51,6 @@ func Serve(ctx *cli.Context) error {
 	listeningAddress := fmt.Sprintf("%s:%s", ctx.String("host"), ctx.String("port"))
 	enableHTTPServer := ctx.Bool("enable-http-server")
 
-	acmeAPI := ctx.String("acme-api-endpoint")
-	acmeMail := ctx.String("acme-email")
-	acmeUseRateLimits := ctx.Bool("acme-use-rate-limits")
-	acmeAcceptTerms := ctx.Bool("acme-accept-terms")
-	acmeEabKID := ctx.String("acme-eab-kid")
-	acmeEabHmac := ctx.String("acme-eab-hmac")
-	dnsProvider := ctx.String("dns-provider")
-	if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" {
-		return errors.New("you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory")
-	}
-
 	allowedCorsDomains := AllowedCorsDomains
 	if rawDomain != "" {
 		allowedCorsDomains = append(allowedCorsDomains, rawDomain)
@@ -94,6 +82,15 @@ func Serve(ctx *cli.Context) error {
 		return fmt.Errorf("could not create new gitea client: %v", err)
 	}
 
+	acmeClient, err := createAcmeClient(ctx, enableHTTPServer, challengeCache)
+	if err != nil {
+		return err
+	}
+
+	if err := certificates.SetupMainDomainCertificates(mainDomainSuffix, acmeClient, certDB); err != nil {
+		return err
+	}
+
 	// Create handler based on settings
 	httpsHandler := handler.Handler(mainDomainSuffix, rawDomain,
 		giteaClient,
@@ -112,24 +109,14 @@ func Serve(ctx *cli.Context) error {
 
 	listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix,
 		giteaClient,
-		dnsProvider,
-		acmeUseRateLimits,
+		acmeClient,
 		keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
 		certDB))
 
-	acmeConfig, err := certificates.SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, acmeAcceptTerms)
-	if err != nil {
-		return err
-	}
-
-	if err := certificates.SetupCertificates(mainDomainSuffix, dnsProvider, acmeConfig, acmeUseRateLimits, enableHTTPServer, challengeCache, certDB); err != nil {
-		return err
-	}
-
 	interval := 12 * time.Hour
 	certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background())
 	defer cancelCertMaintain()
-	go certificates.MaintainCertDB(certMaintainCtx, interval, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB)
+	go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, mainDomainSuffix, certDB)
 
 	if enableHTTPServer {
 		go func() {
diff --git a/cmd/setup.go b/cmd/setup.go
index bccba03..bb9f8cb 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -1,14 +1,19 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
 
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/certificates"
 	"codeberg.org/codeberg/pages/server/database"
 )
 
+var ErrAcmeMissConfig = errors.New("ACME client has wrong config")
+
 func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
 	certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
 	if err != nil {
@@ -23,3 +28,32 @@ func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err e
 
 	return certDB, closeFn, nil
 }
+
+func createAcmeClient(ctx *cli.Context, enableHTTPServer bool, challengeCache cache.SetGetKey) (*certificates.AcmeClient, error) {
+	acmeAPI := ctx.String("acme-api-endpoint")
+	acmeMail := ctx.String("acme-email")
+	acmeEabHmac := ctx.String("acme-eab-hmac")
+	acmeEabKID := ctx.String("acme-eab-kid")
+	acmeAcceptTerms := ctx.Bool("acme-accept-terms")
+	dnsProvider := ctx.String("dns-provider")
+	acmeUseRateLimits := ctx.Bool("acme-use-rate-limits")
+	acmeAccountConf := ctx.String("acme-account-config")
+
+	// check config
+	if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" {
+		return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
+	}
+
+	return certificates.NewAcmeClient(
+		acmeAccountConf,
+		acmeAPI,
+		acmeMail,
+		acmeEabHmac,
+		acmeEabKID,
+		dnsProvider,
+		acmeAcceptTerms,
+		enableHTTPServer,
+		acmeUseRateLimits,
+		challengeCache,
+	)
+}
diff --git a/server/certificates/acme_client.go b/server/certificates/acme_client.go
new file mode 100644
index 0000000..7737396
--- /dev/null
+++ b/server/certificates/acme_client.go
@@ -0,0 +1,95 @@
+package certificates
+
+import (
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/go-acme/lego/v4/lego"
+	"github.com/go-acme/lego/v4/providers/dns"
+	"github.com/reugn/equalizer"
+	"github.com/rs/zerolog/log"
+
+	"codeberg.org/codeberg/pages/server/cache"
+)
+
+type AcmeClient struct {
+	legoClient              *lego.Client
+	dnsChallengerLegoClient *lego.Client
+
+	obtainLocks sync.Map
+
+	acmeUseRateLimits bool
+
+	// limiter
+	acmeClientOrderLimit              *equalizer.TokenBucket
+	acmeClientRequestLimit            *equalizer.TokenBucket
+	acmeClientFailLimit               *equalizer.TokenBucket
+	acmeClientCertificateLimitPerUser map[string]*equalizer.TokenBucket
+}
+
+func NewAcmeClient(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, dnsProvider string, acmeAcceptTerms, enableHTTPServer, acmeUseRateLimits bool, challengeCache cache.SetGetKey) (*AcmeClient, error) {
+	acmeConfig, err := setupAcmeConfig(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, acmeAcceptTerms)
+	if err != nil {
+		return nil, err
+	}
+
+	acmeClient, err := lego.NewClient(acmeConfig)
+	if err != nil {
+		log.Fatal().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
+	} else {
+		err = acmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
+		if err != nil {
+			log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
+		}
+		if enableHTTPServer {
+			err = acmeClient.Challenge.SetHTTP01Provider(AcmeHTTPChallengeProvider{challengeCache})
+			if err != nil {
+				log.Error().Err(err).Msg("Can't create HTTP-01 provider")
+			}
+		}
+	}
+
+	mainDomainAcmeClient, err := lego.NewClient(acmeConfig)
+	if err != nil {
+		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
+	} else {
+		if dnsProvider == "" {
+			// using mock server, don't use wildcard certs
+			err := mainDomainAcmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
+			if err != nil {
+				log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
+			}
+		} else {
+			// use DNS-Challenge https://go-acme.github.io/lego/dns/
+			provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
+			if err != nil {
+				return nil, fmt.Errorf("can not create DNS Challenge provider: %w", err)
+			}
+			if err := mainDomainAcmeClient.Challenge.SetDNS01Provider(provider); err != nil {
+				return nil, fmt.Errorf("can not create DNS-01 provider: %w", err)
+			}
+		}
+	}
+
+	return &AcmeClient{
+		legoClient:              acmeClient,
+		dnsChallengerLegoClient: mainDomainAcmeClient,
+
+		acmeUseRateLimits: acmeUseRateLimits,
+
+		obtainLocks: sync.Map{},
+
+		// limiter
+
+		// rate limit is 300 / 3 hours, we want 200 / 2 hours but to refill more often, so that's 25 new domains every 15 minutes
+		// TODO: when this is used a lot, we probably have to think of a somewhat better solution?
+		acmeClientOrderLimit: equalizer.NewTokenBucket(25, 15*time.Minute),
+		// rate limit is 20 / second, we want 5 / second (especially as one cert takes at least two requests)
+		acmeClientRequestLimit: equalizer.NewTokenBucket(5, 1*time.Second),
+		// rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/
+		acmeClientFailLimit: equalizer.NewTokenBucket(5, 1*time.Hour),
+		// checkUserLimit() use this to rate als per user
+		acmeClientCertificateLimitPerUser: map[string]*equalizer.TokenBucket{},
+	}, nil
+}
diff --git a/server/certificates/acme_config.go b/server/certificates/acme_config.go
new file mode 100644
index 0000000..69568e6
--- /dev/null
+++ b/server/certificates/acme_config.go
@@ -0,0 +1,100 @@
+package certificates
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"encoding/json"
+	"fmt"
+	"os"
+
+	"github.com/go-acme/lego/v4/certcrypto"
+	"github.com/go-acme/lego/v4/lego"
+	"github.com/go-acme/lego/v4/registration"
+	"github.com/rs/zerolog/log"
+)
+
+func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
+	var myAcmeAccount AcmeAccount
+	var myAcmeConfig *lego.Config
+
+	if account, err := os.ReadFile(configFile); err == nil {
+		log.Info().Msgf("found existing acme account config file '%s'", configFile)
+		if err := json.Unmarshal(account, &myAcmeAccount); err != nil {
+			return nil, err
+		}
+		myAcmeAccount.Key, err = certcrypto.ParsePEMPrivateKey([]byte(myAcmeAccount.KeyPEM))
+		if err != nil {
+			return nil, err
+		}
+		myAcmeConfig = lego.NewConfig(&myAcmeAccount)
+		myAcmeConfig.CADirURL = acmeAPI
+		myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
+
+		// Validate Config
+		_, err := lego.NewClient(myAcmeConfig)
+		if err != nil {
+			log.Info().Err(err).Msg("config validation failed, you might just delete the config file and let it recreate")
+			return nil, fmt.Errorf("acme config validation failed: %w", err)
+		}
+		return myAcmeConfig, nil
+	} else if !os.IsNotExist(err) {
+		return nil, err
+	}
+
+	log.Info().Msgf("no existing acme account config found, try to create a new one")
+
+	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+	myAcmeAccount = AcmeAccount{
+		Email:  acmeMail,
+		Key:    privateKey,
+		KeyPEM: string(certcrypto.PEMEncode(privateKey)),
+	}
+	myAcmeConfig = lego.NewConfig(&myAcmeAccount)
+	myAcmeConfig.CADirURL = acmeAPI
+	myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
+	tempClient, err := lego.NewClient(myAcmeConfig)
+	if err != nil {
+		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
+	} else {
+		// accept terms & log in to EAB
+		if acmeEabKID == "" || acmeEabHmac == "" {
+			reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: acmeAcceptTerms})
+			if err != nil {
+				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
+			} else {
+				myAcmeAccount.Registration = reg
+			}
+		} else {
+			reg, err := tempClient.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
+				TermsOfServiceAgreed: acmeAcceptTerms,
+				Kid:                  acmeEabKID,
+				HmacEncoded:          acmeEabHmac,
+			})
+			if err != nil {
+				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
+			} else {
+				myAcmeAccount.Registration = reg
+			}
+		}
+
+		if myAcmeAccount.Registration != nil {
+			acmeAccountJSON, err := json.Marshal(myAcmeAccount)
+			if err != nil {
+				log.Error().Err(err).Msg("json.Marshalfailed, waiting for manual restart to avoid rate limits")
+				select {}
+			}
+			log.Info().Msgf("new acme account created. write to config file '%s'", configFile)
+			err = os.WriteFile(configFile, acmeAccountJSON, 0o600)
+			if err != nil {
+				log.Error().Err(err).Msg("os.WriteFile failed, waiting for manual restart to avoid rate limits")
+				select {}
+			}
+		}
+	}
+
+	return myAcmeConfig, nil
+}
diff --git a/server/certificates/cached_challengers.go b/server/certificates/cached_challengers.go
new file mode 100644
index 0000000..6ce6e67
--- /dev/null
+++ b/server/certificates/cached_challengers.go
@@ -0,0 +1,41 @@
+package certificates
+
+import (
+	"time"
+
+	"github.com/go-acme/lego/v4/challenge"
+
+	"codeberg.org/codeberg/pages/server/cache"
+)
+
+type AcmeTLSChallengeProvider struct {
+	challengeCache cache.SetGetKey
+}
+
+// make sure AcmeTLSChallengeProvider match Provider interface
+var _ challenge.Provider = AcmeTLSChallengeProvider{}
+
+func (a AcmeTLSChallengeProvider) Present(domain, _, keyAuth string) error {
+	return a.challengeCache.Set(domain, keyAuth, 1*time.Hour)
+}
+
+func (a AcmeTLSChallengeProvider) CleanUp(domain, _, _ string) error {
+	a.challengeCache.Remove(domain)
+	return nil
+}
+
+type AcmeHTTPChallengeProvider struct {
+	challengeCache cache.SetGetKey
+}
+
+// make sure AcmeHTTPChallengeProvider match Provider interface
+var _ challenge.Provider = AcmeHTTPChallengeProvider{}
+
+func (a AcmeHTTPChallengeProvider) Present(domain, token, keyAuth string) error {
+	return a.challengeCache.Set(domain+"/"+token, keyAuth, 1*time.Hour)
+}
+
+func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
+	a.challengeCache.Remove(domain + "/" + token)
+	return nil
+}
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 23fad17..3ea440f 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -2,27 +2,18 @@ package certificates
 
 import (
 	"context"
-	"crypto/ecdsa"
-	"crypto/elliptic"
-	"crypto/rand"
 	"crypto/tls"
 	"crypto/x509"
-	"encoding/json"
 	"errors"
 	"fmt"
-	"os"
 	"strconv"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/go-acme/lego/v4/certcrypto"
 	"github.com/go-acme/lego/v4/certificate"
-	"github.com/go-acme/lego/v4/challenge"
 	"github.com/go-acme/lego/v4/challenge/tlsalpn01"
 	"github.com/go-acme/lego/v4/lego"
-	"github.com/go-acme/lego/v4/providers/dns"
-	"github.com/go-acme/lego/v4/registration"
 	"github.com/reugn/equalizer"
 	"github.com/rs/zerolog/log"
 
@@ -33,11 +24,12 @@ import (
 	"codeberg.org/codeberg/pages/server/upstream"
 )
 
+var ErrUserRateLimitExceeded = errors.New("rate limit exceeded: 10 certificates per user per 24 hours")
+
 // TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates.
 func TLSConfig(mainDomainSuffix string,
 	giteaClient *gitea.Client,
-	dnsProvider string,
-	acmeUseRateLimits bool,
+	acmeClient *AcmeClient,
 	keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.SetGetKey,
 	certDB database.CertDB,
 ) *tls.Config {
@@ -100,7 +92,7 @@ func TLSConfig(mainDomainSuffix string,
 
 			var tlsCertificate *tls.Certificate
 			var err error
-			if tlsCertificate, err = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); err != nil {
+			if tlsCertificate, err = acmeClient.retrieveCertFromDB(sni, mainDomainSuffix, false, certDB); err != nil {
 				// request a new certificate
 				if strings.EqualFold(sni, mainDomainSuffix) {
 					return nil, errors.New("won't request certificate for main domain, something really bad has happened")
@@ -110,7 +102,7 @@ func TLSConfig(mainDomainSuffix string,
 					return nil, fmt.Errorf("won't request certificate for %q", sni)
 				}
 
-				tlsCertificate, err = obtainCert(acmeClient, []string{sni}, nil, targetOwner, dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
+				tlsCertificate, err = acmeClient.obtainCert(acmeClient.legoClient, []string{sni}, nil, targetOwner, false, mainDomainSuffix, certDB)
 				if err != nil {
 					return nil, err
 				}
@@ -141,67 +133,20 @@ func TLSConfig(mainDomainSuffix string,
 	}
 }
 
-func checkUserLimit(user string) error {
-	userLimit, ok := acmeClientCertificateLimitPerUser[user]
+func (c *AcmeClient) checkUserLimit(user string) error {
+	userLimit, ok := c.acmeClientCertificateLimitPerUser[user]
 	if !ok {
-		// Each Codeberg user can only add 10 new domains per day.
+		// Each user can only add 10 new domains per day.
 		userLimit = equalizer.NewTokenBucket(10, time.Hour*24)
-		acmeClientCertificateLimitPerUser[user] = userLimit
+		c.acmeClientCertificateLimitPerUser[user] = userLimit
 	}
 	if !userLimit.Ask() {
-		return errors.New("rate limit exceeded: 10 certificates per user per 24 hours")
+		return fmt.Errorf("user '%s' error: %w", user, ErrUserRateLimitExceeded)
 	}
 	return nil
 }
 
-var (
-	acmeClient, mainDomainAcmeClient  *lego.Client
-	acmeClientCertificateLimitPerUser = map[string]*equalizer.TokenBucket{}
-)
-
-// rate limit is 300 / 3 hours, we want 200 / 2 hours but to refill more often, so that's 25 new domains every 15 minutes
-// TODO: when this is used a lot, we probably have to think of a somewhat better solution?
-var acmeClientOrderLimit = equalizer.NewTokenBucket(25, 15*time.Minute)
-
-// rate limit is 20 / second, we want 5 / second (especially as one cert takes at least two requests)
-var acmeClientRequestLimit = equalizer.NewTokenBucket(5, 1*time.Second)
-
-// rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/
-var acmeClientFailLimit = equalizer.NewTokenBucket(5, 1*time.Hour)
-
-type AcmeTLSChallengeProvider struct {
-	challengeCache cache.SetGetKey
-}
-
-// make sure AcmeTLSChallengeProvider match Provider interface
-var _ challenge.Provider = AcmeTLSChallengeProvider{}
-
-func (a AcmeTLSChallengeProvider) Present(domain, _, keyAuth string) error {
-	return a.challengeCache.Set(domain, keyAuth, 1*time.Hour)
-}
-
-func (a AcmeTLSChallengeProvider) CleanUp(domain, _, _ string) error {
-	a.challengeCache.Remove(domain)
-	return nil
-}
-
-type AcmeHTTPChallengeProvider struct {
-	challengeCache cache.SetGetKey
-}
-
-// make sure AcmeHTTPChallengeProvider match Provider interface
-var _ challenge.Provider = AcmeHTTPChallengeProvider{}
-
-func (a AcmeHTTPChallengeProvider) Present(domain, token, keyAuth string) error {
-	return a.challengeCache.Set(domain+"/"+token, keyAuth, 1*time.Hour)
-}
-
-func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
-	a.challengeCache.Remove(domain + "/" + token)
-	return nil
-}
-
-func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (*tls.Certificate, error) {
+func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProvider bool, certDB database.CertDB) (*tls.Certificate, error) {
 	// parse certificate from database
 	res, err := certDB.Get(sni)
 	if err != nil {
@@ -235,7 +180,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLi
 			// TODO: make a queue ?
 			go (func() {
 				res.CSR = nil // acme client doesn't like CSR to be set
-				if _, err := obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB); err != nil {
+				if _, err := c.obtainCert(c.legoClient, []string{sni}, res, "", useDnsProvider, mainDomainSuffix, certDB); err != nil {
 					log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
 				}
 			})()
@@ -245,28 +190,26 @@ func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLi
 	return &tlsCertificate, nil
 }
 
-var obtainLocks = sync.Map{}
-
-func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, acmeUseRateLimits bool, keyDatabase database.CertDB) (*tls.Certificate, error) {
+func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user string, useDnsProvider bool, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
 	name := strings.TrimPrefix(domains[0], "*")
-	if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' {
+	if useDnsProvider && len(domains[0]) > 0 && domains[0][0] == '*' {
 		domains = domains[1:]
 	}
 
 	// lock to avoid simultaneous requests
-	_, working := obtainLocks.LoadOrStore(name, struct{}{})
+	_, working := c.obtainLocks.LoadOrStore(name, struct{}{})
 	if working {
 		for working {
 			time.Sleep(100 * time.Millisecond)
-			_, working = obtainLocks.Load(name)
+			_, working = c.obtainLocks.Load(name)
 		}
-		cert, err := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
+		cert, err := c.retrieveCertFromDB(name, mainDomainSuffix, useDnsProvider, keyDatabase)
 		if err != nil {
 			return nil, fmt.Errorf("certificate failed in synchronous request: %w", err)
 		}
 		return cert, nil
 	}
-	defer obtainLocks.Delete(name)
+	defer c.obtainLocks.Delete(name)
 
 	if acmeClient == nil {
 		return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase)
@@ -276,29 +219,29 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 	var res *certificate.Resource
 	var err error
 	if renew != nil && renew.CertURL != "" {
-		if acmeUseRateLimits {
-			acmeClientRequestLimit.Take()
+		if c.acmeUseRateLimits {
+			c.acmeClientRequestLimit.Take()
 		}
 		log.Debug().Msgf("Renewing certificate for: %v", domains)
 		res, err = acmeClient.Certificate.Renew(*renew, true, false, "")
 		if err != nil {
 			log.Error().Err(err).Msgf("Couldn't renew certificate for %v, trying to request a new one", domains)
-			if acmeUseRateLimits {
-				acmeClientFailLimit.Take()
+			if c.acmeUseRateLimits {
+				c.acmeClientFailLimit.Take()
 			}
 			res = nil
 		}
 	}
 	if res == nil {
 		if user != "" {
-			if err := checkUserLimit(user); err != nil {
+			if err := c.checkUserLimit(user); err != nil {
 				return nil, err
 			}
 		}
 
-		if acmeUseRateLimits {
-			acmeClientOrderLimit.Take()
-			acmeClientRequestLimit.Take()
+		if c.acmeUseRateLimits {
+			c.acmeClientOrderLimit.Take()
+			c.acmeClientRequestLimit.Take()
 		}
 		log.Debug().Msgf("Re-requesting new certificate for %v", domains)
 		res, err = acmeClient.Certificate.Obtain(certificate.ObtainRequest{
@@ -306,8 +249,8 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 			Bundle:     true,
 			MustStaple: false,
 		})
-		if acmeUseRateLimits && err != nil {
-			acmeClientFailLimit.Take()
+		if c.acmeUseRateLimits && err != nil {
+			c.acmeClientFailLimit.Take()
 		}
 	}
 	if err != nil {
@@ -349,136 +292,15 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
 	return &tlsCertificate, nil
 }
 
-func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
-	// TODO: make it a config flag
-	const configFile = "acme-account.json"
-	var myAcmeAccount AcmeAccount
-	var myAcmeConfig *lego.Config
-
-	if account, err := os.ReadFile(configFile); err == nil {
-		if err := json.Unmarshal(account, &myAcmeAccount); err != nil {
-			return nil, err
-		}
-		myAcmeAccount.Key, err = certcrypto.ParsePEMPrivateKey([]byte(myAcmeAccount.KeyPEM))
-		if err != nil {
-			return nil, err
-		}
-		myAcmeConfig = lego.NewConfig(&myAcmeAccount)
-		myAcmeConfig.CADirURL = acmeAPI
-		myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
-
-		// Validate Config
-		_, err := lego.NewClient(myAcmeConfig)
-		if err != nil {
-			// TODO: should we fail hard instead?
-			log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
-		}
-		return myAcmeConfig, nil
-	} else if !os.IsNotExist(err) {
-		return nil, err
-	}
-
-	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		return nil, err
-	}
-	myAcmeAccount = AcmeAccount{
-		Email:  acmeMail,
-		Key:    privateKey,
-		KeyPEM: string(certcrypto.PEMEncode(privateKey)),
-	}
-	myAcmeConfig = lego.NewConfig(&myAcmeAccount)
-	myAcmeConfig.CADirURL = acmeAPI
-	myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
-	tempClient, err := lego.NewClient(myAcmeConfig)
-	if err != nil {
-		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
-	} else {
-		// accept terms & log in to EAB
-		if acmeEabKID == "" || acmeEabHmac == "" {
-			reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: acmeAcceptTerms})
-			if err != nil {
-				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
-			} else {
-				myAcmeAccount.Registration = reg
-			}
-		} else {
-			reg, err := tempClient.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
-				TermsOfServiceAgreed: acmeAcceptTerms,
-				Kid:                  acmeEabKID,
-				HmacEncoded:          acmeEabHmac,
-			})
-			if err != nil {
-				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
-			} else {
-				myAcmeAccount.Registration = reg
-			}
-		}
-
-		if myAcmeAccount.Registration != nil {
-			acmeAccountJSON, err := json.Marshal(myAcmeAccount)
-			if err != nil {
-				log.Error().Err(err).Msg("json.Marshalfailed, waiting for manual restart to avoid rate limits")
-				select {}
-			}
-			err = os.WriteFile(configFile, acmeAccountJSON, 0o600)
-			if err != nil {
-				log.Error().Err(err).Msg("os.WriteFile failed, waiting for manual restart to avoid rate limits")
-				select {}
-			}
-		}
-	}
-
-	return myAcmeConfig, nil
-}
-
-func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error {
+func SetupMainDomainCertificates(mainDomainSuffix string, acmeClient *AcmeClient, certDB database.CertDB) error {
 	// getting main cert before ACME account so that we can fail here without hitting rate limits
 	mainCertBytes, err := certDB.Get(mainDomainSuffix)
 	if err != nil && !errors.Is(err, database.ErrNotFound) {
 		return fmt.Errorf("cert database is not working: %w", err)
 	}
 
-	acmeClient, err = lego.NewClient(acmeConfig)
-	if err != nil {
-		log.Fatal().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
-	} else {
-		err = acmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
-		if err != nil {
-			log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
-		}
-		if enableHTTPServer {
-			err = acmeClient.Challenge.SetHTTP01Provider(AcmeHTTPChallengeProvider{challengeCache})
-			if err != nil {
-				log.Error().Err(err).Msg("Can't create HTTP-01 provider")
-			}
-		}
-	}
-
-	mainDomainAcmeClient, err = lego.NewClient(acmeConfig)
-	if err != nil {
-		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
-	} else {
-		if dnsProvider == "" {
-			// using mock server, don't use wildcard certs
-			err := mainDomainAcmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
-			if err != nil {
-				log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
-			}
-		} else {
-			provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
-			if err != nil {
-				log.Error().Err(err).Msg("Can't create DNS Challenge provider")
-			}
-			err = mainDomainAcmeClient.Challenge.SetDNS01Provider(provider)
-			if err != nil {
-				log.Error().Err(err).Msg("Can't create DNS-01 provider")
-			}
-		}
-	}
-
 	if mainCertBytes == nil {
-		_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, nil, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
+		_, err = acmeClient.obtainCert(acmeClient.dnsChallengerLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, nil, "", true, mainDomainSuffix, certDB)
 		if err != nil {
 			log.Error().Err(err).Msg("Couldn't renew main domain certificate, continuing with mock certs only")
 		}
@@ -487,7 +309,7 @@ func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Co
 	return nil
 }
 
-func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) {
+func MaintainCertDB(ctx context.Context, interval time.Duration, acmeClient *AcmeClient, mainDomainSuffix string, certDB database.CertDB) {
 	for {
 		// delete expired certs that will be invalid until next clean up
 		threshold := time.Now().Add(interval)
@@ -525,7 +347,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
 			} else if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
 				// renew main certificate 30 days before it expires
 				go (func() {
-					_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
+					_, err = acmeClient.obtainCert(acmeClient.dnsChallengerLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", true, mainDomainSuffix, certDB)
 					if err != nil {
 						log.Error().Err(err).Msg("Couldn't renew certificate for main domain")
 					}
diff --git a/server/certificates/mock_test.go b/server/certificates/mock_test.go
index 00e1b21..5d0dde0 100644
--- a/server/certificates/mock_test.go
+++ b/server/certificates/mock_test.go
@@ -3,8 +3,9 @@ package certificates
 import (
 	"testing"
 
-	"codeberg.org/codeberg/pages/server/database"
 	"github.com/stretchr/testify/assert"
+
+	"codeberg.org/codeberg/pages/server/database"
 )
 
 func TestMockCert(t *testing.T) {

From 08d4e70cfd4df37748b29a859b1e5d408d93dd59 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 11 Feb 2023 03:39:38 +0100
Subject: [PATCH 068/196] Update Readme to point out new Architecture

---
 README.md | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index 6d5a294..5707d65 100644
--- a/README.md
+++ b/README.md
@@ -87,20 +87,20 @@ You can also contact the maintainers of this project:
 
 ### First steps
 
-The code of this repository is split in several modules.
-While heavy refactoring work is currently undergo, you can easily understand the basic structure:
+The code of this repository is split in several modules.  
+The [Architecture is explained](https://codeberg.org/Codeberg/pages-server/wiki/Architecture) in the wiki.
+
 The `cmd` folder holds the data necessary for interacting with the service via the cli.
-If you are considering to deploy the service yourself, make sure to check it out.
 The heart of the software lives in the `server` folder and is split in several modules.
-After scanning the code, you should quickly be able to understand their function and start hacking on them.
 
 Again: Feel free to get in touch with us for any questions that might arise.
 Thank you very much.
 
-
 ### Test Server
 
-run `just dev`
+Make sure you have [golang](https://go.dev) v1.20 or newer and [just](https://just.systems/man/en/) installed.
+
+run `just dev`  
 now this pages should work:
  - https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
  - https://momar.localhost.mock.directory:4430/ci-testing/

From 46316f9e2f472b070d187ee1e06742dea0739413 Mon Sep 17 00:00:00 2001
From: crystal <crystal@noreply.codeberg.org>
Date: Sat, 11 Feb 2023 03:12:42 +0000
Subject: [PATCH 069/196] Fix raw domain for branches with custom domains and
 index.html (#159)

fix #156
fix #157

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/159
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crystal <crystal@noreply.codeberg.org>
Co-committed-by: crystal <crystal@noreply.codeberg.org>
---
 integration/get_test.go     | 28 ++++++++++++++++++++++++++++
 server/handler/try.go       |  2 +-
 server/upstream/upstream.go |  2 +-
 3 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index 55e6d12..b70eeed 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -109,6 +109,34 @@ func TestCustomDomainRedirects(t *testing.T) {
 	assert.EqualValues(t, "https://mock-pages.codeberg-test.org/README.md", resp.Header.Get("Location"))
 }
 
+func TestRawCustomDomain(t *testing.T) {
+	log.Println("=== TestRawCustomDomain ===")
+	// test raw domain response for custom domain branch
+	resp, err := getTestHTTPSClient().Get("https://raw.localhost.mock.directory:4430/cb_pages_tests/raw-test/example") // need cb_pages_tests fork
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
+	assert.EqualValues(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "76", resp.Header.Get("Content-Length"))
+	assert.EqualValues(t, 76, getSize(resp.Body))
+}
+
+func TestRawIndex(t *testing.T) {
+	log.Println("=== TestRawCustomDomain ===")
+	// test raw domain response for index.html
+	resp, err := getTestHTTPSClient().Get("https://raw.localhost.mock.directory:4430/cb_pages_tests/raw-test/@branch-test/index.html") // need cb_pages_tests fork
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
+	assert.EqualValues(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "597", resp.Header.Get("Content-Length"))
+	assert.EqualValues(t, 597, getSize(resp.Body))
+}
+
 func TestGetNotFound(t *testing.T) {
 	log.Println("=== TestGetNotFound ===")
 	// test custom not found pages
diff --git a/server/handler/try.go b/server/handler/try.go
index 5a09b91..5c65138 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -20,7 +20,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 	canonicalDomainCache cache.SetGetKey,
 ) {
 	// check if a canonical domain exists on a request on MainDomain
-	if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
+	if strings.HasSuffix(trimmedHost, mainDomainSuffix) && !options.ServeRaw {
 		canonicalDomain, _ := options.CheckCanonicalDomain(giteaClient, "", mainDomainSuffix, canonicalDomainCache)
 		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix) {
 			canonicalPath := ctx.Req.RequestURI
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 7c3c848..3845969 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -168,7 +168,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 		ctx.Redirect(ctx.Path()+"/", http.StatusTemporaryRedirect)
 		return true
 	}
-	if strings.HasSuffix(ctx.Path(), "/index.html") {
+	if strings.HasSuffix(ctx.Path(), "/index.html") && !o.ServeRaw {
 		ctx.Redirect(strings.TrimSuffix(ctx.Path(), "index.html"), http.StatusTemporaryRedirect)
 		return true
 	}

From 9a3d1c36dce7c7d9c460dfdb684f6e4bae5848f9 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 13 Feb 2023 20:14:45 +0000
Subject: [PATCH 070/196] Document more flags & make http port customizable
 (#183)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/183
---
 .gitignore                                |  1 +
 Justfile                                  |  3 ++
 cmd/flags.go                              | 34 +++++++++++-------
 cmd/main.go                               | 40 +++++++++++----------
 cmd/setup.go                              |  5 +++
 server/certificates/acme_config.go        |  2 ++
 server/certificates/cached_challengers.go | 19 ++++++++++
 server/certificates/certificates.go       | 43 +++++++++++++----------
 server/setup.go                           | 27 --------------
 9 files changed, 97 insertions(+), 77 deletions(-)
 delete mode 100644 server/setup.go

diff --git a/.gitignore b/.gitignore
index 8745935..3035107 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ build/
 vendor/
 pages
 certs.sqlite
+.bash_history
diff --git a/Justfile b/Justfile
index 0db7845..9ee7eb3 100644
--- a/Justfile
+++ b/Justfile
@@ -50,3 +50,6 @@ integration:
 
 integration-run TEST:
     go test -race -tags 'integration {{TAGS}}' -run "^{{TEST}}$" codeberg.org/codeberg/pages/integration/...
+
+docker:
+    docker run --rm -it --user $(id -u) -v $(pwd):/work --workdir /work -e HOME=/work codeberg.org/6543/docker-images/golang_just
diff --git a/cmd/flags.go b/cmd/flags.go
index 8052421..5bc638b 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -8,11 +8,13 @@ var (
 	CertStorageFlags = []cli.Flag{
 		&cli.StringFlag{
 			Name:    "db-type",
+			Usage:   "Specify the database driver. Valid options are \"sqlite3\", \"mysql\" and \"postgres\". Read more at https://xorm.io",
 			Value:   "sqlite3",
 			EnvVars: []string{"DB_TYPE"},
 		},
 		&cli.StringFlag{
 			Name:    "db-conn",
+			Usage:   "Specify the database connection. For \"sqlite3\" it's the filepath. Read more at https://go.dev/doc/tutorial/database-access",
 			Value:   "certs.sqlite",
 			EnvVars: []string{"DB_CONN"},
 		},
@@ -87,15 +89,21 @@ var (
 			EnvVars: []string{"HOST"},
 			Value:   "[::]",
 		},
-		&cli.StringFlag{
+		&cli.UintFlag{
 			Name:    "port",
-			Usage:   "specifies port of listening address",
-			EnvVars: []string{"PORT"},
-			Value:   "443",
+			Usage:   "specifies the https port to listen to ssl requests",
+			EnvVars: []string{"PORT", "HTTPS_PORT"},
+			Value:   443,
+		},
+		&cli.UintFlag{
+			Name:    "http-port",
+			Usage:   "specifies the http port, you also have to enable http server via ENABLE_HTTP_SERVER=true",
+			EnvVars: []string{"HTTP_PORT"},
+			Value:   80,
 		},
 		&cli.BoolFlag{
-			Name: "enable-http-server",
-			// TODO: desc
+			Name:    "enable-http-server",
+			Usage:   "start a http server to redirect to https and respond to http acme challenges",
 			EnvVars: []string{"ENABLE_HTTP_SERVER"},
 		},
 		&cli.StringFlag{
@@ -125,23 +133,23 @@ var (
 			Value:   true,
 		},
 		&cli.BoolFlag{
-			Name: "acme-accept-terms",
-			// TODO: Usage
+			Name:    "acme-accept-terms",
+			Usage:   "To accept the ACME ToS",
 			EnvVars: []string{"ACME_ACCEPT_TERMS"},
 		},
 		&cli.StringFlag{
-			Name: "acme-eab-kid",
-			// TODO: Usage
+			Name:    "acme-eab-kid",
+			Usage:   "Register the current account to the ACME server with external binding.",
 			EnvVars: []string{"ACME_EAB_KID"},
 		},
 		&cli.StringFlag{
-			Name: "acme-eab-hmac",
-			// TODO: Usage
+			Name:    "acme-eab-hmac",
+			Usage:   "Register the current account to the ACME server with external binding.",
 			EnvVars: []string{"ACME_EAB_HMAC"},
 		},
 		&cli.StringFlag{
 			Name:    "dns-provider",
-			Usage:   "Use DNS-Challenge for main domain\n\nRead more at: https://go-acme.github.io/lego/dns/",
+			Usage:   "Use DNS-Challenge for main domain. Read more at: https://go-acme.github.io/lego/dns/",
 			EnvVars: []string{"DNS_PROVIDER"},
 		},
 		&cli.StringFlag{
diff --git a/cmd/main.go b/cmd/main.go
index a1c3b97..8a65d43 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -14,7 +14,6 @@ import (
 	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
 
-	"codeberg.org/codeberg/pages/server"
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/certificates"
 	"codeberg.org/codeberg/pages/server/gitea"
@@ -48,7 +47,9 @@ func Serve(ctx *cli.Context) error {
 	rawDomain := ctx.String("raw-domain")
 	mainDomainSuffix := ctx.String("pages-domain")
 	rawInfoPage := ctx.String("raw-info-page")
-	listeningAddress := fmt.Sprintf("%s:%s", ctx.String("host"), ctx.String("port"))
+	listeningHost := ctx.String("host")
+	listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("port"))
+	listeningHTTPAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("http-port"))
 	enableHTTPServer := ctx.Bool("enable-http-server")
 
 	allowedCorsDomains := AllowedCorsDomains
@@ -91,22 +92,14 @@ func Serve(ctx *cli.Context) error {
 		return err
 	}
 
-	// Create handler based on settings
-	httpsHandler := handler.Handler(mainDomainSuffix, rawDomain,
-		giteaClient,
-		rawInfoPage,
-		BlacklistedPaths, allowedCorsDomains,
-		dnsLookupCache, canonicalDomainCache)
-
-	httpHandler := server.SetupHTTPACMEChallengeServer(challengeCache)
-
-	// Setup listener and TLS
-	log.Info().Msgf("Listening on https://%s", listeningAddress)
-	listener, err := net.Listen("tcp", listeningAddress)
+	// Create listener for SSL connections
+	log.Info().Msgf("Listening on https://%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(mainDomainSuffix,
 		giteaClient,
 		acmeClient,
@@ -119,18 +112,29 @@ func Serve(ctx *cli.Context) error {
 	go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, mainDomainSuffix, certDB)
 
 	if enableHTTPServer {
+		// Create handler for http->https redirect and http acme challenges
+		httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache)
+
+		// Create listener for http and start listening
 		go func() {
-			log.Info().Msg("Start HTTP server listening on :80")
-			err := http.ListenAndServe("[::]:80", httpHandler)
+			log.Info().Msgf("Start HTTP server listening on %s", listeningHTTPAddress)
+			err := http.ListenAndServe(listeningHTTPAddress, httpHandler)
 			if err != nil {
 				log.Panic().Err(err).Msg("Couldn't start HTTP fastServer")
 			}
 		}()
 	}
 
-	// Start the web fastServer
+	// Create ssl handler based on settings
+	sslHandler := handler.Handler(mainDomainSuffix, rawDomain,
+		giteaClient,
+		rawInfoPage,
+		BlacklistedPaths, allowedCorsDomains,
+		dnsLookupCache, canonicalDomainCache)
+
+	// Start the ssl listener
 	log.Info().Msgf("Start listening on %s", listener.Addr())
-	if err := http.Serve(listener, httpsHandler); err != nil {
+	if err := http.Serve(listener, sslHandler); err != nil {
 		log.Panic().Err(err).Msg("Couldn't start fastServer")
 	}
 
diff --git a/cmd/setup.go b/cmd/setup.go
index bb9f8cb..cde4bc9 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -43,6 +43,11 @@ func createAcmeClient(ctx *cli.Context, enableHTTPServer bool, challengeCache ca
 	if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" {
 		return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
 	}
+	if acmeEabHmac != "" && acmeEabKID == "" {
+		return nil, fmt.Errorf("%w: ACME_EAB_HMAC also needs ACME_EAB_KID to be set", ErrAcmeMissConfig)
+	} else if acmeEabHmac == "" && acmeEabKID != "" {
+		return nil, fmt.Errorf("%w: ACME_EAB_KID also needs ACME_EAB_HMAC to be set", ErrAcmeMissConfig)
+	}
 
 	return certificates.NewAcmeClient(
 		acmeAccountConf,
diff --git a/server/certificates/acme_config.go b/server/certificates/acme_config.go
index 69568e6..12ad7c6 100644
--- a/server/certificates/acme_config.go
+++ b/server/certificates/acme_config.go
@@ -14,6 +14,8 @@ import (
 	"github.com/rs/zerolog/log"
 )
 
+const challengePath = "/.well-known/acme-challenge/"
+
 func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
 	var myAcmeAccount AcmeAccount
 	var myAcmeConfig *lego.Config
diff --git a/server/certificates/cached_challengers.go b/server/certificates/cached_challengers.go
index 6ce6e67..02474b3 100644
--- a/server/certificates/cached_challengers.go
+++ b/server/certificates/cached_challengers.go
@@ -1,11 +1,15 @@
 package certificates
 
 import (
+	"net/http"
+	"strings"
 	"time"
 
 	"github.com/go-acme/lego/v4/challenge"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/utils"
 )
 
 type AcmeTLSChallengeProvider struct {
@@ -39,3 +43,18 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
 	a.challengeCache.Remove(domain + "/" + token)
 	return nil
 }
+
+func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) http.HandlerFunc {
+	return func(w http.ResponseWriter, req *http.Request) {
+		ctx := context.New(w, req)
+		if strings.HasPrefix(ctx.Path(), challengePath) {
+			challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + strings.TrimPrefix(ctx.Path(), challengePath))
+			if !ok || challenge == nil {
+				ctx.String("no challenge for this token", http.StatusNotFound)
+			}
+			ctx.String(challenge.(string))
+		} else {
+			ctx.Redirect("https://"+ctx.Host()+ctx.Path(), http.StatusMovedPermanently)
+		}
+	}
+}
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 3ea440f..707672c 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -36,22 +36,23 @@ func TLSConfig(mainDomainSuffix string,
 	return &tls.Config{
 		// check DNS name & get certificate from Let's Encrypt
 		GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
-			sni := strings.ToLower(strings.TrimSpace(info.ServerName))
-			if len(sni) < 1 {
-				return nil, errors.New("missing sni")
+			domain := strings.ToLower(strings.TrimSpace(info.ServerName))
+			if len(domain) < 1 {
+				return nil, errors.New("missing domain info via SNI (RFC 4366, Section 3.1)")
 			}
 
+			// https request init is actually a acme challenge
 			if info.SupportedProtos != nil {
 				for _, proto := range info.SupportedProtos {
 					if proto != tlsalpn01.ACMETLS1Protocol {
 						continue
 					}
 
-					challenge, ok := challengeCache.Get(sni)
+					challenge, ok := challengeCache.Get(domain)
 					if !ok {
 						return nil, errors.New("no challenge for this domain")
 					}
-					cert, err := tlsalpn01.ChallengeCert(sni, challenge.(string))
+					cert, err := tlsalpn01.ChallengeCert(domain, challenge.(string))
 					if err != nil {
 						return nil, err
 					}
@@ -61,22 +62,22 @@ func TLSConfig(mainDomainSuffix string,
 
 			targetOwner := ""
 			mayObtainCert := true
-			if strings.HasSuffix(sni, mainDomainSuffix) || strings.EqualFold(sni, mainDomainSuffix[1:]) {
+			if strings.HasSuffix(domain, mainDomainSuffix) || strings.EqualFold(domain, mainDomainSuffix[1:]) {
 				// deliver default certificate for the main domain (*.codeberg.page)
-				sni = mainDomainSuffix
+				domain = mainDomainSuffix
 			} else {
 				var targetRepo, targetBranch string
-				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(sni, mainDomainSuffix, dnsLookupCache)
+				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, dnsLookupCache)
 				if targetOwner == "" {
 					// DNS not set up, return main certificate to redirect to the docs
-					sni = mainDomainSuffix
+					domain = mainDomainSuffix
 				} else {
 					targetOpt := &upstream.Options{
 						TargetOwner:  targetOwner,
 						TargetRepo:   targetRepo,
 						TargetBranch: targetBranch,
 					}
-					_, valid := targetOpt.CheckCanonicalDomain(giteaClient, sni, mainDomainSuffix, canonicalDomainCache)
+					_, valid := targetOpt.CheckCanonicalDomain(giteaClient, domain, mainDomainSuffix, canonicalDomainCache)
 					if !valid {
 						// We shouldn't obtain a certificate when we cannot check if the
 						// repository has specified this domain in the `.domains` file.
@@ -85,30 +86,34 @@ func TLSConfig(mainDomainSuffix string,
 				}
 			}
 
-			if tlsCertificate, ok := keyCache.Get(sni); ok {
+			if tlsCertificate, ok := keyCache.Get(domain); ok {
 				// we can use an existing certificate object
 				return tlsCertificate.(*tls.Certificate), nil
 			}
 
 			var tlsCertificate *tls.Certificate
 			var err error
-			if tlsCertificate, err = acmeClient.retrieveCertFromDB(sni, mainDomainSuffix, false, certDB); err != nil {
-				// request a new certificate
-				if strings.EqualFold(sni, mainDomainSuffix) {
+			if tlsCertificate, err = acmeClient.retrieveCertFromDB(domain, mainDomainSuffix, false, certDB); err != nil {
+				if !errors.Is(err, database.ErrNotFound) {
+					return nil, err
+				}
+				// we could not find a cert in db, request a new certificate
+
+				// first check if we are allowed to obtain a cert for this domain
+				if strings.EqualFold(domain, mainDomainSuffix) {
 					return nil, errors.New("won't request certificate for main domain, something really bad has happened")
 				}
-
 				if !mayObtainCert {
-					return nil, fmt.Errorf("won't request certificate for %q", sni)
+					return nil, fmt.Errorf("won't request certificate for %q", domain)
 				}
 
-				tlsCertificate, err = acmeClient.obtainCert(acmeClient.legoClient, []string{sni}, nil, targetOwner, false, mainDomainSuffix, certDB)
+				tlsCertificate, err = acmeClient.obtainCert(acmeClient.legoClient, []string{domain}, nil, targetOwner, false, mainDomainSuffix, certDB)
 				if err != nil {
 					return nil, err
 				}
 			}
 
-			if err := keyCache.Set(sni, tlsCertificate, 15*time.Minute); err != nil {
+			if err := keyCache.Set(domain, tlsCertificate, 15*time.Minute); err != nil {
 				return nil, err
 			}
 			return tlsCertificate, nil
@@ -164,7 +169,7 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
 	if !strings.EqualFold(sni, mainDomainSuffix) {
 		tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
 		if err != nil {
-			return nil, fmt.Errorf("error parsin leaf tlsCert: %w", err)
+			return nil, fmt.Errorf("error parsing leaf tlsCert: %w", err)
 		}
 
 		// renew certificates 7 days before they expire
diff --git a/server/setup.go b/server/setup.go
deleted file mode 100644
index 282e692..0000000
--- a/server/setup.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package server
-
-import (
-	"net/http"
-	"strings"
-
-	"codeberg.org/codeberg/pages/server/cache"
-	"codeberg.org/codeberg/pages/server/context"
-	"codeberg.org/codeberg/pages/server/utils"
-)
-
-func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) http.HandlerFunc {
-	challengePath := "/.well-known/acme-challenge/"
-
-	return func(w http.ResponseWriter, req *http.Request) {
-		ctx := context.New(w, req)
-		if strings.HasPrefix(ctx.Path(), challengePath) {
-			challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + strings.TrimPrefix(ctx.Path(), challengePath))
-			if !ok || challenge == nil {
-				ctx.String("no challenge for this token", http.StatusNotFound)
-			}
-			ctx.String(challenge.(string))
-		} else {
-			ctx.Redirect("https://"+ctx.Host()+ctx.Path(), http.StatusMovedPermanently)
-		}
-	}
-}

From 42b3f8d1b7933e67a2a24867d1189d522f2fb884 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 13 Feb 2023 23:13:30 +0000
Subject: [PATCH 071/196] use mockery for mock code generation (#185)

close 	#181

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/185
---
 server/certificates/mock_test.go |   6 +-
 server/database/interface.go     |   3 +
 server/database/mock.go          | 139 +++++++++++++++++++++++--------
 3 files changed, 113 insertions(+), 35 deletions(-)

diff --git a/server/certificates/mock_test.go b/server/certificates/mock_test.go
index 5d0dde0..644e8a9 100644
--- a/server/certificates/mock_test.go
+++ b/server/certificates/mock_test.go
@@ -4,13 +4,15 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 
 	"codeberg.org/codeberg/pages/server/database"
 )
 
 func TestMockCert(t *testing.T) {
-	db, err := database.NewTmpDB()
-	assert.NoError(t, err)
+	db := database.NewMockCertDB(t)
+	db.Mock.On("Put", mock.Anything, mock.Anything).Return(nil)
+
 	cert, err := mockCert("example.com", "some error msg", "codeberg.page", db)
 	assert.NoError(t, err)
 	if assert.NotEmpty(t, cert) {
diff --git a/server/database/interface.go b/server/database/interface.go
index 92068c5..7fdbae7 100644
--- a/server/database/interface.go
+++ b/server/database/interface.go
@@ -8,6 +8,9 @@ import (
 	"github.com/rs/zerolog/log"
 )
 
+//go:generate go install github.com/vektra/mockery/v2@latest
+//go:generate mockery --name CertDB --output . --filename mock.go --inpackage --case underscore
+
 type CertDB interface {
 	Close() error
 	Put(name string, cert *certificate.Resource) error
diff --git a/server/database/mock.go b/server/database/mock.go
index 6148287..e7e2c38 100644
--- a/server/database/mock.go
+++ b/server/database/mock.go
@@ -1,49 +1,122 @@
+// Code generated by mockery v2.20.0. DO NOT EDIT.
+
 package database
 
 import (
-	"fmt"
-	"time"
-
-	"github.com/OrlovEvgeny/go-mcache"
-	"github.com/go-acme/lego/v4/certificate"
+	certificate "github.com/go-acme/lego/v4/certificate"
+	mock "github.com/stretchr/testify/mock"
 )
 
-var _ CertDB = tmpDB{}
-
-type tmpDB struct {
-	intern *mcache.CacheDriver
-	ttl    time.Duration
+// MockCertDB is an autogenerated mock type for the CertDB type
+type MockCertDB struct {
+	mock.Mock
 }
 
-func (p tmpDB) Close() error {
-	_ = p.intern.Close()
-	return nil
-}
+// Close provides a mock function with given fields:
+func (_m *MockCertDB) Close() error {
+	ret := _m.Called()
 
-func (p tmpDB) Put(name string, cert *certificate.Resource) error {
-	return p.intern.Set(name, cert, p.ttl)
-}
-
-func (p tmpDB) Get(name string) (*certificate.Resource, error) {
-	cert, has := p.intern.Get(name)
-	if !has {
-		return nil, fmt.Errorf("cert for %q not found", name)
+	var r0 error
+	if rf, ok := ret.Get(0).(func() error); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Error(0)
 	}
-	return cert.(*certificate.Resource), nil
+
+	return r0
 }
 
-func (p tmpDB) Delete(key string) error {
-	p.intern.Remove(key)
-	return nil
+// Delete provides a mock function with given fields: key
+func (_m *MockCertDB) Delete(key string) error {
+	ret := _m.Called(key)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string) error); ok {
+		r0 = rf(key)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
 }
 
-func (p tmpDB) Items(page, pageSize int) ([]*Cert, error) {
-	return nil, fmt.Errorf("items not implemented for tmpDB")
+// Get provides a mock function with given fields: name
+func (_m *MockCertDB) Get(name string) (*certificate.Resource, error) {
+	ret := _m.Called(name)
+
+	var r0 *certificate.Resource
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) (*certificate.Resource, error)); ok {
+		return rf(name)
+	}
+	if rf, ok := ret.Get(0).(func(string) *certificate.Resource); ok {
+		r0 = rf(name)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*certificate.Resource)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(name)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
 }
 
-func NewTmpDB() (CertDB, error) {
-	return &tmpDB{
-		intern: mcache.New(),
-		ttl:    time.Minute,
-	}, nil
+// Items provides a mock function with given fields: page, pageSize
+func (_m *MockCertDB) Items(page int, pageSize int) ([]*Cert, error) {
+	ret := _m.Called(page, pageSize)
+
+	var r0 []*Cert
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int, int) ([]*Cert, error)); ok {
+		return rf(page, pageSize)
+	}
+	if rf, ok := ret.Get(0).(func(int, int) []*Cert); ok {
+		r0 = rf(page, pageSize)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*Cert)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int, int) error); ok {
+		r1 = rf(page, pageSize)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Put provides a mock function with given fields: name, cert
+func (_m *MockCertDB) Put(name string, cert *certificate.Resource) error {
+	ret := _m.Called(name, cert)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string, *certificate.Resource) error); ok {
+		r0 = rf(name, cert)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewMockCertDB interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewMockCertDB creates a new instance of MockCertDB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewMockCertDB(t mockConstructorTestingTNewMockCertDB) *MockCertDB {
+	mock := &MockCertDB{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
 }

From 0adac9a5b15746a139e76e1de3996d4a20e791fa Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Tue, 14 Feb 2023 02:23:28 +0000
Subject: [PATCH 072/196] fix http -> https redirect and add integration tests
 for it (#184)

and more logging

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/184
---
 Justfile                                  |  2 ++
 cmd/main.go                               |  9 ++++---
 integration/get_test.go                   | 12 +++++++++
 integration/main_test.go                  |  2 ++
 server/certificates/cached_challengers.go | 33 +++++++++++++++++++----
 server/certificates/certificates.go       |  1 +
 6 files changed, 50 insertions(+), 9 deletions(-)

diff --git a/Justfile b/Justfile
index 9ee7eb3..0b8f814 100644
--- a/Justfile
+++ b/Justfile
@@ -9,6 +9,8 @@ dev:
     export PAGES_DOMAIN=localhost.mock.directory
     export RAW_DOMAIN=raw.localhost.mock.directory
     export PORT=4430
+    export HTTP_PORT=8880
+    export ENABLE_HTTP_SERVER=true
     export LOG_LEVEL=trace
     go run -tags '{{TAGS}}' .
 
diff --git a/cmd/main.go b/cmd/main.go
index 8a65d43..aa00f54 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -48,7 +48,8 @@ func Serve(ctx *cli.Context) error {
 	mainDomainSuffix := ctx.String("pages-domain")
 	rawInfoPage := ctx.String("raw-info-page")
 	listeningHost := ctx.String("host")
-	listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("port"))
+	listeningSSLPort := ctx.Uint("port")
+	listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, listeningSSLPort)
 	listeningHTTPAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("http-port"))
 	enableHTTPServer := ctx.Bool("enable-http-server")
 
@@ -93,7 +94,7 @@ func Serve(ctx *cli.Context) error {
 	}
 
 	// Create listener for SSL connections
-	log.Info().Msgf("Listening on https://%s", listeningSSLAddress)
+	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)
@@ -113,7 +114,7 @@ func Serve(ctx *cli.Context) error {
 
 	if enableHTTPServer {
 		// Create handler for http->https redirect and http acme challenges
-		httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache)
+		httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache, listeningSSLPort)
 
 		// Create listener for http and start listening
 		go func() {
@@ -133,7 +134,7 @@ func Serve(ctx *cli.Context) error {
 		dnsLookupCache, canonicalDomainCache)
 
 	// Start the ssl listener
-	log.Info().Msgf("Start listening on %s", listener.Addr())
+	log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())
 	if err := http.Serve(listener, sslHandler); err != nil {
 		log.Panic().Err(err).Msg("Couldn't start fastServer")
 	}
diff --git a/integration/get_test.go b/integration/get_test.go
index b70eeed..3a7190a 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -193,6 +193,18 @@ func TestGetOptions(t *testing.T) {
 	assert.EqualValues(t, "GET, HEAD, OPTIONS", resp.Header.Get("Allow"))
 }
 
+func TestHttpRedirect(t *testing.T) {
+	log.Println("=== TestHttpRedirect ===")
+	resp, err := getTestHTTPSClient().Get("http://mock-pages.codeberg-test.org:8880/README.md")
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusMovedPermanently, resp.StatusCode)
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "https://mock-pages.codeberg-test.org:4430/README.md", resp.Header.Get("Location"))
+}
+
 func getTestHTTPSClient() *http.Client {
 	cookieJar, _ := cookiejar.New(nil)
 	return &http.Client{
diff --git a/integration/main_test.go b/integration/main_test.go
index 3e0e187..a6579fd 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -40,6 +40,8 @@ func startServer(ctx context.Context) error {
 	setEnvIfNotSet("PAGES_DOMAIN", "localhost.mock.directory")
 	setEnvIfNotSet("RAW_DOMAIN", "raw.localhost.mock.directory")
 	setEnvIfNotSet("PORT", "4430")
+	setEnvIfNotSet("HTTP_PORT", "8880")
+	setEnvIfNotSet("ENABLE_HTTP_SERVER", "true")
 	setEnvIfNotSet("DB_TYPE", "sqlite3")
 
 	app := cli.NewApp()
diff --git a/server/certificates/cached_challengers.go b/server/certificates/cached_challengers.go
index 02474b3..bc9ea67 100644
--- a/server/certificates/cached_challengers.go
+++ b/server/certificates/cached_challengers.go
@@ -1,15 +1,17 @@
 package certificates
 
 import (
+	"fmt"
 	"net/http"
+	"net/url"
 	"strings"
 	"time"
 
 	"github.com/go-acme/lego/v4/challenge"
+	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/context"
-	"codeberg.org/codeberg/pages/server/utils"
 )
 
 type AcmeTLSChallengeProvider struct {
@@ -44,17 +46,38 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
 	return nil
 }
 
-func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) http.HandlerFunc {
+func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey, sslPort uint) http.HandlerFunc {
+	// handle custom-ssl-ports to be added on https redirects
+	portPart := ""
+	if sslPort != 443 {
+		portPart = fmt.Sprintf(":%d", sslPort)
+	}
+
 	return func(w http.ResponseWriter, req *http.Request) {
 		ctx := context.New(w, req)
+		domain := ctx.TrimHostPort()
+
+		// it's an acme request
 		if strings.HasPrefix(ctx.Path(), challengePath) {
-			challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + strings.TrimPrefix(ctx.Path(), challengePath))
+			challenge, ok := challengeCache.Get(domain + "/" + strings.TrimPrefix(ctx.Path(), challengePath))
 			if !ok || challenge == nil {
+				log.Info().Msgf("HTTP-ACME challenge for '%s' failed: token not found", domain)
 				ctx.String("no challenge for this token", http.StatusNotFound)
 			}
+			log.Info().Msgf("HTTP-ACME challenge for '%s' succeeded", domain)
 			ctx.String(challenge.(string))
-		} else {
-			ctx.Redirect("https://"+ctx.Host()+ctx.Path(), http.StatusMovedPermanently)
+			return
 		}
+
+		// it's a normal http request that needs to be redirected
+		u, err := url.Parse(fmt.Sprintf("https://%s%s%s", domain, portPart, ctx.Path()))
+		if err != nil {
+			log.Error().Err(err).Msg("could not craft http to https redirect")
+			ctx.String("", http.StatusInternalServerError)
+		}
+
+		newURL := u.String()
+		log.Debug().Msgf("redirect http to https: %s", newURL)
+		ctx.Redirect(newURL, http.StatusMovedPermanently)
 	}
 }
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 707672c..ce1420d 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -47,6 +47,7 @@ func TLSConfig(mainDomainSuffix string,
 					if proto != tlsalpn01.ACMETLS1Protocol {
 						continue
 					}
+					log.Info().Msgf("Detect ACME-TLS1 challenge for '%s'", domain)
 
 					challenge, ok := challengeCache.Get(domain)
 					if !ok {

From 42d5802b9ba77556dfa529e20caf0442ac6b1fa4 Mon Sep 17 00:00:00 2001
From: deblan <deblan@noreply.codeberg.org>
Date: Tue, 14 Feb 2023 03:03:00 +0000
Subject: [PATCH 073/196] Allow to define default branches (#125)

This try to address #115

Co-authored-by: Simon Vieille <simon@deblan.fr>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/125
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: deblan <deblan@noreply.codeberg.org>
Co-committed-by: deblan <deblan@noreply.codeberg.org>
---
 cmd/flags.go                            |  7 ++++
 cmd/main.go                             |  7 ++++
 go.mod                                  |  3 +-
 go.sum                                  |  4 +++
 integration/main_test.go                |  1 +
 server/certificates/certificates.go     |  3 +-
 server/dns/dns.go                       | 10 +++---
 server/handler/handler.go               |  4 ++-
 server/handler/handler_custom_domain.go |  5 +--
 server/handler/handler_sub_domain.go    | 46 ++++++++++++++++++++-----
 server/handler/handler_test.go          |  1 +
 11 files changed, 73 insertions(+), 18 deletions(-)

diff --git a/cmd/flags.go b/cmd/flags.go
index 5bc638b..a71dd35 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -112,6 +112,13 @@ var (
 			Usage:   "specify at which log level should be logged. Possible options: info, warn, error, fatal",
 			EnvVars: []string{"LOG_LEVEL"},
 		},
+		// Default branches to fetch assets from
+		&cli.StringSliceFlag{
+			Name:    "pages-branch",
+			Usage:   "define a branch to fetch assets from",
+			EnvVars: []string{"PAGES_BRANCHES"},
+			Value:   cli.NewStringSlice("pages"),
+		},
 
 		// ############################
 		// ### ACME Client Settings ###
diff --git a/cmd/main.go b/cmd/main.go
index aa00f54..45e151d 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -45,6 +45,7 @@ func Serve(ctx *cli.Context) error {
 	giteaRoot := ctx.String("gitea-root")
 	giteaAPIToken := ctx.String("gitea-api-token")
 	rawDomain := ctx.String("raw-domain")
+	defaultBranches := ctx.StringSlice("pages-branch")
 	mainDomainSuffix := ctx.String("pages-domain")
 	rawInfoPage := ctx.String("raw-info-page")
 	listeningHost := ctx.String("host")
@@ -63,6 +64,10 @@ func Serve(ctx *cli.Context) error {
 		mainDomainSuffix = "." + mainDomainSuffix
 	}
 
+	if len(defaultBranches) == 0 {
+		return fmt.Errorf("no default branches set (PAGES_BRANCHES)")
+	}
+
 	// Init ssl cert database
 	certDB, closeFn, err := openCertDB(ctx)
 	if err != nil {
@@ -104,6 +109,7 @@ func Serve(ctx *cli.Context) error {
 	listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix,
 		giteaClient,
 		acmeClient,
+		defaultBranches[0],
 		keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
 		certDB))
 
@@ -131,6 +137,7 @@ func Serve(ctx *cli.Context) error {
 		giteaClient,
 		rawInfoPage,
 		BlacklistedPaths, allowedCorsDomains,
+		defaultBranches,
 		dnsLookupCache, canonicalDomainCache)
 
 	// Start the ssl listener
diff --git a/go.mod b/go.mod
index 944e2ad..bfde7f7 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli/v2 v2.3.0
+	golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
 	xorm.io/xorm v1.3.2
 )
 
@@ -117,7 +118,7 @@ require (
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	golang.org/x/sys v0.1.0 // indirect
 	golang.org/x/text v0.3.6 // indirect
 	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 	google.golang.org/api v0.20.0 // indirect
diff --git a/go.sum b/go.sum
index b5a7568..b10305c 100644
--- a/go.sum
+++ b/go.sum
@@ -768,6 +768,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
+golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -899,6 +901,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
diff --git a/integration/main_test.go b/integration/main_test.go
index a6579fd..a397110 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -39,6 +39,7 @@ func startServer(ctx context.Context) error {
 	setEnvIfNotSet("ACME_API", "https://acme.mock.directory")
 	setEnvIfNotSet("PAGES_DOMAIN", "localhost.mock.directory")
 	setEnvIfNotSet("RAW_DOMAIN", "raw.localhost.mock.directory")
+	setEnvIfNotSet("PAGES_BRANCHES", "pages,main,master")
 	setEnvIfNotSet("PORT", "4430")
 	setEnvIfNotSet("HTTP_PORT", "8880")
 	setEnvIfNotSet("ENABLE_HTTP_SERVER", "true")
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index ce1420d..3ae891a 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -30,6 +30,7 @@ var ErrUserRateLimitExceeded = errors.New("rate limit exceeded: 10 certificates
 func TLSConfig(mainDomainSuffix string,
 	giteaClient *gitea.Client,
 	acmeClient *AcmeClient,
+	firstDefaultBranch string,
 	keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.SetGetKey,
 	certDB database.CertDB,
 ) *tls.Config {
@@ -68,7 +69,7 @@ func TLSConfig(mainDomainSuffix string,
 				domain = mainDomainSuffix
 			} else {
 				var targetRepo, targetBranch string
-				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, dnsLookupCache)
+				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
 				if targetOwner == "" {
 					// DNS not set up, return main certificate to redirect to the docs
 					domain = mainDomainSuffix
diff --git a/server/dns/dns.go b/server/dns/dns.go
index 2719d4d..c11b278 100644
--- a/server/dns/dns.go
+++ b/server/dns/dns.go
@@ -11,9 +11,11 @@ import (
 // lookupCacheTimeout specifies the timeout for the DNS lookup cache.
 var lookupCacheTimeout = 15 * time.Minute
 
+var defaultPagesRepo = "pages"
+
 // GetTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix.
 // If everything is fine, it returns the target data.
-func GetTargetFromDNS(domain, mainDomainSuffix string, dnsLookupCache cache.SetGetKey) (targetOwner, targetRepo, targetBranch string) {
+func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string, dnsLookupCache cache.SetGetKey) (targetOwner, targetRepo, targetBranch string) {
 	// Get CNAME or TXT
 	var cname string
 	var err error
@@ -50,10 +52,10 @@ func GetTargetFromDNS(domain, mainDomainSuffix string, dnsLookupCache cache.SetG
 		targetBranch = cnameParts[len(cnameParts)-3]
 	}
 	if targetRepo == "" {
-		targetRepo = "pages"
+		targetRepo = defaultPagesRepo
 	}
-	if targetBranch == "" && targetRepo != "pages" {
-		targetBranch = "pages"
+	if targetBranch == "" && targetRepo != defaultPagesRepo {
+		targetBranch = firstDefaultBranch
 	}
 	// if targetBranch is still empty, the caller must find the default branch
 	return
diff --git a/server/handler/handler.go b/server/handler/handler.go
index 78301e9..a944c7e 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -17,7 +17,6 @@ const (
 	headerAccessControlAllowOrigin  = "Access-Control-Allow-Origin"
 	headerAccessControlAllowMethods = "Access-Control-Allow-Methods"
 	defaultPagesRepo                = "pages"
-	defaultPagesBranch              = "pages"
 )
 
 // Handler handles a single HTTP request to the web server.
@@ -25,6 +24,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 	giteaClient *gitea.Client,
 	rawInfoPage string,
 	blacklistedPaths, allowedCorsDomains []string,
+	defaultPagesBranches []string,
 	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
 ) http.HandlerFunc {
 	return func(w http.ResponseWriter, req *http.Request) {
@@ -98,6 +98,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 			log.Debug().Msg("subdomain request detecded")
 			handleSubDomain(log, ctx, giteaClient,
 				mainDomainSuffix,
+				defaultPagesBranches,
 				trimmedHost,
 				pathElements,
 				canonicalDomainCache)
@@ -107,6 +108,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 				mainDomainSuffix,
 				trimmedHost,
 				pathElements,
+				defaultPagesBranches[0],
 				dnsLookupCache, canonicalDomainCache)
 		}
 	}
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index a541b74..1b85f62 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -18,10 +18,11 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 	mainDomainSuffix string,
 	trimmedHost string,
 	pathElements []string,
+	firstDefaultBranch string,
 	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
 ) {
 	// Serve pages from custom domains
-	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, dnsLookupCache)
+	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
 	if targetOwner == "" {
 		html.ReturnErrorPage(ctx,
 			"could not obtain repo owner from custom domain",
@@ -52,7 +53,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 			return
 		} else if canonicalDomain != trimmedHost {
 			// only redirect if the target is also a codeberg page!
-			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, dnsLookupCache)
+			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
 			if targetOwner != "" {
 				ctx.Redirect("https://"+canonicalDomain+"/"+targetOpt.TargetPath, http.StatusTemporaryRedirect)
 				return
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 2a75e9f..68f4822 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/rs/zerolog"
+	"golang.org/x/exp/slices"
 
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
@@ -17,6 +18,7 @@ import (
 
 func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
 	mainDomainSuffix string,
+	defaultPagesBranches []string,
 	trimmedHost string,
 	pathElements []string,
 	canonicalDomainCache cache.SetGetKey,
@@ -63,12 +65,21 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 	// Check if the first directory is a branch for the defaultPagesRepo
 	// example.codeberg.page/@main/index.html
 	if strings.HasPrefix(pathElements[0], "@") {
+		targetBranch := pathElements[0][1:]
+
+		// if the default pages branch can be determined exactly, it does not need to be set
+		if len(defaultPagesBranches) == 1 && slices.Contains(defaultPagesBranches, targetBranch) {
+			// example.codeberg.org/@pages/... redirects to example.codeberg.org/...
+			ctx.Redirect("/"+strings.Join(pathElements[1:], "/"), http.StatusTemporaryRedirect)
+			return
+		}
+
 		log.Debug().Msg("main domain preparations, now trying with specified branch")
 		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
 			TryIndexPages: true,
 			TargetOwner:   targetOwner,
 			TargetRepo:    defaultPagesRepo,
-			TargetBranch:  pathElements[0][1:],
+			TargetBranch:  targetBranch,
 			TargetPath:    path.Join(pathElements[1:]...),
 		}, true); works {
 			log.Trace().Msg("tryUpstream: serve default pages repo with specified branch")
@@ -81,19 +92,36 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		return
 	}
 
-	// Check if the first directory is a repo with a defaultPagesRepo branch
-	// example.codeberg.page/myrepo/index.html
-	// example.codeberg.page/pages/... is not allowed here.
-	log.Debug().Msg("main domain preparations, now trying with specified repo")
-	if pathElements[0] != defaultPagesRepo {
+	for _, defaultPagesBranch := range defaultPagesBranches {
+		// Check if the first directory is a repo with a default pages branch
+		// example.codeberg.page/myrepo/index.html
+		// example.codeberg.page/{PAGES_BRANCHE}/... is not allowed here.
+		log.Debug().Msg("main domain preparations, now trying with specified repo")
+		if pathElements[0] != defaultPagesBranch {
+			if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
+				TryIndexPages: true,
+				TargetOwner:   targetOwner,
+				TargetRepo:    pathElements[0],
+				TargetBranch:  defaultPagesBranch,
+				TargetPath:    path.Join(pathElements[1:]...),
+			}, false); works {
+				log.Debug().Msg("tryBranch, now trying upstream 5")
+				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+				return
+			}
+		}
+
+		// Try to use the defaultPagesRepo on an default pages branch
+		// example.codeberg.page/index.html
+		log.Debug().Msg("main domain preparations, now trying with default repo")
 		if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
 			TryIndexPages: true,
 			TargetOwner:   targetOwner,
-			TargetRepo:    pathElements[0],
+			TargetRepo:    defaultPagesRepo,
 			TargetBranch:  defaultPagesBranch,
-			TargetPath:    path.Join(pathElements[1:]...),
+			TargetPath:    path.Join(pathElements...),
 		}, false); works {
-			log.Debug().Msg("tryBranch, now trying upstream 5")
+			log.Debug().Msg("tryBranch, now trying upstream 6")
 			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
 			return
 		}
diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index 626564a..ed063b2 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -18,6 +18,7 @@ func TestHandlerPerformance(t *testing.T) {
 		"https://docs.codeberg.org/pages/raw-content/",
 		[]string{"/.well-known/acme-challenge/"},
 		[]string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
+		[]string{"pages"},
 		cache.NewKeyValueCache(),
 		cache.NewKeyValueCache(),
 	)

From c9050e5722120f03fc5c8e632aa785d790f2b513 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 11 Mar 2023 05:07:17 +0000
Subject: [PATCH 074/196] Handle Relative Symlinks (#205)

enhance #114

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/205
---
 integration/get_test.go            | 13 ++++++++++++-
 integration/main_test.go           |  2 +-
 server/certificates/acme_client.go |  2 +-
 server/database/xorm.go            |  2 +-
 server/gitea/client.go             |  6 +++++-
 5 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index 3a7190a..c0a3a47 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -64,7 +64,7 @@ func TestGetContent(t *testing.T) {
 	assert.True(t, getSize(resp.Body) > 100)
 	assert.Len(t, resp.Header.Get("ETag"), 44)
 
-	// TODO: test get of non cachable content (content size > fileCacheSizeLimit)
+	// TODO: test get of non cacheable content (content size > fileCacheSizeLimit)
 }
 
 func TestCustomDomain(t *testing.T) {
@@ -154,6 +154,7 @@ func TestGetNotFound(t *testing.T) {
 func TestFollowSymlink(t *testing.T) {
 	log.Printf("=== TestFollowSymlink ===\n")
 
+	// file symlink
 	resp, err := getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/tests_for_pages-server/@main/link")
 	assert.NoError(t, err)
 	if !assert.NotNil(t, resp) {
@@ -165,6 +166,16 @@ func TestFollowSymlink(t *testing.T) {
 	body := getBytes(resp.Body)
 	assert.EqualValues(t, 4, len(body))
 	assert.EqualValues(t, "abc\n", string(body))
+
+	// relative file links (../index.html file in this case)
+	resp, err = getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/tests_for_pages-server/@main/dir_aim/some/")
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "an index\n", string(getBytes(resp.Body)))
 }
 
 func TestLFSSupport(t *testing.T) {
diff --git a/integration/main_test.go b/integration/main_test.go
index a397110..6566f78 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
 	}
 	defer func() {
 		serverCancel()
-		log.Println("=== TestMain: Server STOPED ===")
+		log.Println("=== TestMain: Server STOPPED ===")
 	}()
 
 	time.Sleep(10 * time.Second)
diff --git a/server/certificates/acme_client.go b/server/certificates/acme_client.go
index 7737396..ba83e50 100644
--- a/server/certificates/acme_client.go
+++ b/server/certificates/acme_client.go
@@ -89,7 +89,7 @@ func NewAcmeClient(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID,
 		acmeClientRequestLimit: equalizer.NewTokenBucket(5, 1*time.Second),
 		// rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/
 		acmeClientFailLimit: equalizer.NewTokenBucket(5, 1*time.Hour),
-		// checkUserLimit() use this to rate als per user
+		// checkUserLimit() use this to rate also per user
 		acmeClientCertificateLimitPerUser: map[string]*equalizer.TokenBucket{},
 	}, nil
 }
diff --git a/server/database/xorm.go b/server/database/xorm.go
index 4b43cbb..fb1dc17 100644
--- a/server/database/xorm.go
+++ b/server/database/xorm.go
@@ -37,7 +37,7 @@ func NewXormDB(dbType, dbConn string) (CertDB, error) {
 	}
 
 	if err := e.Sync2(new(Cert)); err != nil {
-		return nil, fmt.Errorf("cound not sync db model :%w", err)
+		return nil, fmt.Errorf("could not sync db model :%w", err)
 	}
 
 	return &xDB{
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 51647ba..7a2bf63 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -112,7 +112,7 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 	if cache, ok := client.responseCache.Get(cacheKey); ok {
 		cache := cache.(FileResponse)
 		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey)
-		// TODO: check against some timestamp missmatch?!?
+		// TODO: check against some timestamp mismatch?!?
 		if cache.Exists {
 			if cache.IsSymlink {
 				linkDest := string(cache.Body)
@@ -145,6 +145,10 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 					}
 					linkDest := strings.TrimSpace(string(linkDestBytes))
 
+					// handle relative links
+					// we first remove the link from the path, and make a relative join (resolve parent paths like "/../" too)
+					linkDest = path.Join(path.Dir(resource), linkDest)
+
 					// we store symlink not content to reduce duplicates in cache
 					if err := client.responseCache.Set(cacheKey, FileResponse{
 						Exists:    true,

From 26d59b71f049d459b663ec885156bfb8b89aa0c9 Mon Sep 17 00:00:00 2001
From: Crystal <crystal@noreply.codeberg.org>
Date: Mon, 20 Mar 2023 22:52:42 +0000
Subject: [PATCH 075/196] Fix typo in integration test log (#210)

I forgot to update the name of this function in the CI log so it looks like it's running the same test twice even though it's not.

Co-authored-by: crystal <crystal@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/210
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Crystal <crystal@noreply.codeberg.org>
Co-committed-by: Crystal <crystal@noreply.codeberg.org>
---
 integration/get_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/integration/get_test.go b/integration/get_test.go
index c0a3a47..9d97390 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -124,7 +124,7 @@ func TestRawCustomDomain(t *testing.T) {
 }
 
 func TestRawIndex(t *testing.T) {
-	log.Println("=== TestRawCustomDomain ===")
+	log.Println("=== TestRawIndex ===")
 	// test raw domain response for index.html
 	resp, err := getTestHTTPSClient().Get("https://raw.localhost.mock.directory:4430/cb_pages_tests/raw-test/@branch-test/index.html") // need cb_pages_tests fork
 	assert.NoError(t, err)

From c40dddf471277260ee3ca95c5701cf723c1e24cb Mon Sep 17 00:00:00 2001
From: Crystal <crystal@noreply.codeberg.org>
Date: Mon, 20 Mar 2023 22:57:26 +0000
Subject: [PATCH 076/196] Fix certificate renewal (#209)

A database bug in xorm.go prevents the pages-server from saving a
renewed certificate for a domain that already has one in the database.

Co-authored-by: crystal <crystal@noreply.codeberg.org>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/209
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Crystal <crystal@noreply.codeberg.org>
Co-committed-by: Crystal <crystal@noreply.codeberg.org>
---
 server/database/xorm.go      | 2 +-
 server/database/xorm_test.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/database/xorm.go b/server/database/xorm.go
index fb1dc17..217b6d1 100644
--- a/server/database/xorm.go
+++ b/server/database/xorm.go
@@ -64,7 +64,7 @@ func (x xDB) Put(domain string, cert *certificate.Resource) error {
 	}
 	defer sess.Close()
 
-	if exist, _ := sess.ID(c.Domain).Exist(); exist {
+	if exist, _ := sess.ID(c.Domain).Exist(new(Cert)); exist {
 		if _, err := sess.ID(c.Domain).Update(c); err != nil {
 			return err
 		}
diff --git a/server/database/xorm_test.go b/server/database/xorm_test.go
index 9c032ee..50d8a7f 100644
--- a/server/database/xorm_test.go
+++ b/server/database/xorm_test.go
@@ -37,7 +37,7 @@ func TestSanitizeWildcardCerts(t *testing.T) {
 	}))
 
 	// update existing cert
-	assert.Error(t, certDB.Put(".wildcard.de", &certificate.Resource{
+	assert.NoError(t, certDB.Put(".wildcard.de", &certificate.Resource{
 		Domain:      "*.wildcard.de",
 		Certificate: localhost_mock_directory_certificate,
 	}))

From 98d7a771be9783a333a3f9abd8d6e08d1ad4a46f Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Tue, 21 Mar 2023 01:53:07 +0100
Subject: [PATCH 077/196] Readme.md: add link to chat & main repo

---
 README.md | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/README.md b/README.md
index 5707d65..7e1b2c5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
 # Codeberg Pages
 
+[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue)](https://opensource.org/license/eupl-1-2/)
+[![status-badge](https://ci.codeberg.org/api/badges/Codeberg/pages-server/status.svg)](https://ci.codeberg.org/Codeberg/pages-server)
+<a href="https://matrix.to/#/#gitea-pages-server:obermui.de" title="Join the Matrix room at https://matrix.to/#/#gitea-pages-server:obermui.de">
+  <img src="https://img.shields.io/matrix/gitea-pages-server:obermui.de?label=matrix">
+</a>
+
 Gitea lacks the ability to host static pages from Git.
 The Codeberg Pages Server addresses this lack by implementing a standalone service
 that connects to Gitea via API.
@@ -8,6 +14,9 @@ It is suitable to be deployed by other Gitea instances, too, to offer static pag
 **End user documentation** can mainly be found at the [Wiki](https://codeberg.org/Codeberg/pages-server/wiki/Overview)
 and the [Codeberg Documentation](https://docs.codeberg.org/codeberg-pages/).
 
+
+<a href="https://codeberg.org/Codeberg/pages-server"> <img src="https://codeberg.org/Codeberg/GetItOnCodeberg/raw/branch/main/get-it-on-blue-on-white.svg" alt="Get It On Codeberg" width="250"/> <a/>
+
 ## Quickstart
 
 This is the new Codeberg Pages server, a solution for serving static pages from Gitea repositories.
@@ -29,6 +38,10 @@ record that points to your repo (just like the CNAME record):
 
 Certificates are generated, updated and cleaned up automatically via Let's Encrypt through a TLS challenge.
 
+## Chat for admins & devs
+
+[matrix: #gitea-pages-server:obermui.de](https://matrix.to/#/#gitea-pages-server:obermui.de)
+
 ## Deployment
 
 **Warning: Some Caveats Apply**  

From 970c13cf5c006dfa1011ba9e6721baafa3f1ad55 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Tue, 21 Mar 2023 02:32:25 +0100
Subject: [PATCH 078/196] Readme.md: use matrix.org for room alias

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 7e1b2c5..b694e29 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
 
 [![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue)](https://opensource.org/license/eupl-1-2/)
 [![status-badge](https://ci.codeberg.org/api/badges/Codeberg/pages-server/status.svg)](https://ci.codeberg.org/Codeberg/pages-server)
-<a href="https://matrix.to/#/#gitea-pages-server:obermui.de" title="Join the Matrix room at https://matrix.to/#/#gitea-pages-server:obermui.de">
-  <img src="https://img.shields.io/matrix/gitea-pages-server:obermui.de?label=matrix">
+<a href="https://matrix.to/#/#gitea-pages-server:matrix.org" title="Join the Matrix room at https://matrix.to/#/#gitea-pages-server:matrix.org">
+  <img src="https://img.shields.io/matrix/gitea-pages-server:matrix.org?label=matrix">
 </a>
 
 Gitea lacks the ability to host static pages from Git.
@@ -40,7 +40,7 @@ Certificates are generated, updated and cleaned up automatically via Let's Encry
 
 ## Chat for admins & devs
 
-[matrix: #gitea-pages-server:obermui.de](https://matrix.to/#/#gitea-pages-server:obermui.de)
+[matrix: #gitea-pages-server:matrix.org](https://matrix.to/#/#gitea-pages-server:matrix.org)
 
 ## Deployment
 

From 974229681f4cc7f1ed31df9b05eabef2df013809 Mon Sep 17 00:00:00 2001
From: video-prize-ranch <video-prize-ranch@noreply.codeberg.org>
Date: Thu, 30 Mar 2023 21:36:31 +0000
Subject: [PATCH 079/196] Initial redirects implementation (#148)

Adds basic support for `_redirects` files. It supports a subset of what IPFS supports: https://docs.ipfs.tech/how-to/websites-on-ipfs/redirects-and-custom-404s/

Example:
```
/redirect https://example.com/ 301
/another-redirect /page 301
/302  https://example.com/  302
/app/* /index.html 200
/articles/* /posts/:splat 301
```
301 redirect: https://video-prize-ranch.localhost.mock.directory:4430/redirect
SPA rewrite: https://video-prize-ranch.localhost.mock.directory:4430/app/path/path
Catch-all with splat: https://video-prize-ranch.localhost.mock.directory:4430/articles/path/path

Closes #46

Co-authored-by: video-prize-ranch <cb.8a3w5@simplelogin.co>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/148
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: video-prize-ranch <video-prize-ranch@noreply.codeberg.org>
Co-committed-by: video-prize-ranch <video-prize-ranch@noreply.codeberg.org>
---
 FEATURES.md                             |  45 +++++++++
 cmd/main.go                             |   4 +-
 integration/get_test.go                 |  40 ++++++++
 server/handler/handler.go               |   8 +-
 server/handler/handler_custom_domain.go |   4 +-
 server/handler/handler_raw_domain.go    |   6 +-
 server/handler/handler_sub_domain.go    |  12 +--
 server/handler/handler_test.go          |   1 +
 server/handler/try.go                   |   3 +-
 server/upstream/redirects.go            | 117 ++++++++++++++++++++++++
 server/upstream/upstream.go             |  16 +++-
 11 files changed, 235 insertions(+), 21 deletions(-)
 create mode 100644 FEATURES.md
 create mode 100644 server/upstream/redirects.go

diff --git a/FEATURES.md b/FEATURES.md
new file mode 100644
index 0000000..7560a1d
--- /dev/null
+++ b/FEATURES.md
@@ -0,0 +1,45 @@
+# Features
+
+## Custom domains
+
+...
+
+## Redirects
+
+Redirects can be created with a `_redirects` file with the following format:
+
+```
+# Comment
+from  to  [status]
+```
+
+* Lines starting with `#` are ignored
+* `from` - the path to redirect from (Note: repository and branch names are removed from request URLs)
+* `to` - the path or URL to redirect to
+* `status` - status code to use when redirecting (default 301)
+
+### Status codes
+
+* `200` - returns content from specified path (no external URLs) without changing the URL (rewrite)
+* `301` - Moved Permanently (Permanent redirect)
+* `302` - Found (Temporary redirect)
+
+### Examples
+
+#### SPA (single-page application) rewrite
+
+Redirects all paths to `/index.html` for single-page apps.
+
+```
+/*  /index.html 200
+```
+
+#### Splats
+
+Redirects every path under `/articles` to `/posts` while keeping the path.
+
+```
+/articles/*  /posts/:splat  302
+```
+
+Example: `/articles/2022/10/12/post-1/` -> `/posts/2022/10/12/post-1/`
diff --git a/cmd/main.go b/cmd/main.go
index 45e151d..84915c9 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -81,6 +81,8 @@ func Serve(ctx *cli.Context) error {
 	canonicalDomainCache := cache.NewKeyValueCache()
 	// dnsLookupCache stores DNS lookups for custom domains
 	dnsLookupCache := cache.NewKeyValueCache()
+	// redirectsCache stores redirects in _redirects files
+	redirectsCache := cache.NewKeyValueCache()
 	// clientResponseCache stores responses from the Gitea server
 	clientResponseCache := cache.NewKeyValueCache()
 
@@ -138,7 +140,7 @@ func Serve(ctx *cli.Context) error {
 		rawInfoPage,
 		BlacklistedPaths, allowedCorsDomains,
 		defaultBranches,
-		dnsLookupCache, canonicalDomainCache)
+		dnsLookupCache, canonicalDomainCache, redirectsCache)
 
 	// Start the ssl listener
 	log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())
diff --git a/integration/get_test.go b/integration/get_test.go
index 9d97390..cfb7188 100644
--- a/integration/get_test.go
+++ b/integration/get_test.go
@@ -151,6 +151,46 @@ func TestGetNotFound(t *testing.T) {
 	assert.EqualValues(t, 37, getSize(resp.Body))
 }
 
+func TestRedirect(t *testing.T) {
+	log.Println("=== TestRedirect ===")
+	// test redirects
+	resp, err := getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/some_redirects/redirect")
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusMovedPermanently, resp.StatusCode)
+	assert.EqualValues(t, "https://example.com/", resp.Header.Get("Location"))
+}
+
+func TestSPARedirect(t *testing.T) {
+	log.Println("=== TestSPARedirect ===")
+	// test SPA redirects
+	url := "https://cb_pages_tests.localhost.mock.directory:4430/some_redirects/app/aqdjw"
+	resp, err := getTestHTTPSClient().Get(url)
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusOK, resp.StatusCode)
+	assert.EqualValues(t, url, resp.Request.URL.String())
+	assert.EqualValues(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
+	assert.EqualValues(t, "258", resp.Header.Get("Content-Length"))
+	assert.EqualValues(t, 258, getSize(resp.Body))
+}
+
+func TestSplatRedirect(t *testing.T) {
+	log.Println("=== TestSplatRedirect ===")
+	// test splat redirects
+	resp, err := getTestHTTPSClient().Get("https://cb_pages_tests.localhost.mock.directory:4430/some_redirects/articles/qfopefe")
+	assert.NoError(t, err)
+	if !assert.NotNil(t, resp) {
+		t.FailNow()
+	}
+	assert.EqualValues(t, http.StatusMovedPermanently, resp.StatusCode)
+	assert.EqualValues(t, "/posts/qfopefe", resp.Header.Get("Location"))
+}
+
 func TestFollowSymlink(t *testing.T) {
 	log.Printf("=== TestFollowSymlink ===\n")
 
diff --git a/server/handler/handler.go b/server/handler/handler.go
index a944c7e..7edcf95 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -25,7 +25,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 	rawInfoPage string,
 	blacklistedPaths, allowedCorsDomains []string,
 	defaultPagesBranches []string,
-	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
+	dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey,
 ) http.HandlerFunc {
 	return func(w http.ResponseWriter, req *http.Request) {
 		log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
@@ -93,7 +93,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 				mainDomainSuffix, rawInfoPage,
 				trimmedHost,
 				pathElements,
-				canonicalDomainCache)
+				canonicalDomainCache, redirectsCache)
 		} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
 			log.Debug().Msg("subdomain request detecded")
 			handleSubDomain(log, ctx, giteaClient,
@@ -101,7 +101,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 				defaultPagesBranches,
 				trimmedHost,
 				pathElements,
-				canonicalDomainCache)
+				canonicalDomainCache, redirectsCache)
 		} else {
 			log.Debug().Msg("custom domain request detecded")
 			handleCustomDomain(log, ctx, giteaClient,
@@ -109,7 +109,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 				trimmedHost,
 				pathElements,
 				defaultPagesBranches[0],
-				dnsLookupCache, canonicalDomainCache)
+				dnsLookupCache, canonicalDomainCache, redirectsCache)
 		}
 	}
 }
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 1b85f62..8742be4 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -19,7 +19,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 	trimmedHost string,
 	pathElements []string,
 	firstDefaultBranch string,
-	dnsLookupCache, canonicalDomainCache cache.SetGetKey,
+	dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey,
 ) {
 	// Serve pages from custom domains
 	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
@@ -64,7 +64,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 		}
 
 		log.Debug().Msg("tryBranch, now trying upstream 7")
-		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		return
 	}
 
diff --git a/server/handler/handler_raw_domain.go b/server/handler/handler_raw_domain.go
index 5e974da..aa41c52 100644
--- a/server/handler/handler_raw_domain.go
+++ b/server/handler/handler_raw_domain.go
@@ -19,7 +19,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 	mainDomainSuffix, rawInfoPage string,
 	trimmedHost string,
 	pathElements []string,
-	canonicalDomainCache cache.SetGetKey,
+	canonicalDomainCache, redirectsCache cache.SetGetKey,
 ) {
 	// Serve raw content from RawDomain
 	log.Debug().Msg("raw domain")
@@ -41,7 +41,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 			TargetPath:   path.Join(pathElements[3:]...),
 		}, true); works {
 			log.Trace().Msg("tryUpstream: serve raw domain with specified branch")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 			return
 		}
 		log.Debug().Msg("missing branch info")
@@ -58,7 +58,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 		TargetPath:    path.Join(pathElements[2:]...),
 	}, true); works {
 		log.Trace().Msg("tryUpstream: serve raw domain with default branch")
-		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 	} else {
 		html.ReturnErrorPage(ctx,
 			fmt.Sprintf("raw domain could not find repo '%s/%s' or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo),
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 68f4822..8731bec 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -21,7 +21,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 	defaultPagesBranches []string,
 	trimmedHost string,
 	pathElements []string,
-	canonicalDomainCache cache.SetGetKey,
+	canonicalDomainCache, redirectsCache cache.SetGetKey,
 ) {
 	// Serve pages from subdomains of MainDomainSuffix
 	log.Debug().Msg("main domain suffix")
@@ -53,7 +53,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			TargetPath:    path.Join(pathElements[2:]...),
 		}, true); works {
 			log.Trace().Msg("tryUpstream: serve with specified repo and branch")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		} else {
 			html.ReturnErrorPage(ctx,
 				fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
@@ -83,7 +83,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			TargetPath:    path.Join(pathElements[1:]...),
 		}, true); works {
 			log.Trace().Msg("tryUpstream: serve default pages repo with specified branch")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		} else {
 			html.ReturnErrorPage(ctx,
 				fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
@@ -106,7 +106,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 				TargetPath:    path.Join(pathElements[1:]...),
 			}, false); works {
 				log.Debug().Msg("tryBranch, now trying upstream 5")
-				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 				return
 			}
 		}
@@ -122,7 +122,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			TargetPath:    path.Join(pathElements...),
 		}, false); works {
 			log.Debug().Msg("tryBranch, now trying upstream 6")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 			return
 		}
 	}
@@ -137,7 +137,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		TargetPath:    path.Join(pathElements...),
 	}, false); works {
 		log.Debug().Msg("tryBranch, now trying upstream 6")
-		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache)
+		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		return
 	}
 
diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index ed063b2..de705ec 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -21,6 +21,7 @@ func TestHandlerPerformance(t *testing.T) {
 		[]string{"pages"},
 		cache.NewKeyValueCache(),
 		cache.NewKeyValueCache(),
+		cache.NewKeyValueCache(),
 	)
 
 	testCase := func(uri string, status int) {
diff --git a/server/handler/try.go b/server/handler/try.go
index 5c65138..6cfe08e 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -18,6 +18,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 	mainDomainSuffix, trimmedHost string,
 	options *upstream.Options,
 	canonicalDomainCache cache.SetGetKey,
+	redirectsCache cache.SetGetKey,
 ) {
 	// check if a canonical domain exists on a request on MainDomain
 	if strings.HasSuffix(trimmedHost, mainDomainSuffix) && !options.ServeRaw {
@@ -39,7 +40,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 	options.Host = trimmedHost
 
 	// Try to request the file from the Gitea API
-	if !options.Upstream(ctx, giteaClient) {
+	if !options.Upstream(ctx, giteaClient, redirectsCache) {
 		html.ReturnErrorPage(ctx, "", ctx.StatusCode)
 	}
 }
diff --git a/server/upstream/redirects.go b/server/upstream/redirects.go
new file mode 100644
index 0000000..ab6c971
--- /dev/null
+++ b/server/upstream/redirects.go
@@ -0,0 +1,117 @@
+package upstream
+
+import (
+	"strconv"
+	"strings"
+	"time"
+
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
+	"codeberg.org/codeberg/pages/server/gitea"
+	"github.com/rs/zerolog/log"
+)
+
+type Redirect struct {
+	From       string
+	To         string
+	StatusCode int
+}
+
+// redirectsCacheTimeout specifies the timeout for the redirects cache.
+var redirectsCacheTimeout = 10 * time.Minute
+
+const redirectsConfig = "_redirects"
+
+// getRedirects returns redirects specified in the _redirects file.
+func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.SetGetKey) []Redirect {
+	var redirects []Redirect
+	cacheKey := o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch
+
+	// Check for cached redirects
+	if cachedValue, ok := redirectsCache.Get(cacheKey); ok {
+		redirects = cachedValue.([]Redirect)
+	} else {
+		// Get _redirects file and parse
+		body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, redirectsConfig)
+		if err == nil {
+			for _, line := range strings.Split(string(body), "\n") {
+				redirectArr := strings.Fields(line)
+
+				// Ignore comments and invalid lines
+				if strings.HasPrefix(line, "#") || len(redirectArr) < 2 {
+					continue
+				}
+
+				// Get redirect status code
+				statusCode := 301
+				if len(redirectArr) == 3 {
+					statusCode, err = strconv.Atoi(redirectArr[2])
+					if err != nil {
+						log.Info().Err(err).Msgf("could not read %s of %s/%s", redirectsConfig, o.TargetOwner, o.TargetRepo)
+					}
+				}
+
+				redirects = append(redirects, Redirect{
+					From:       redirectArr[0],
+					To:         redirectArr[1],
+					StatusCode: statusCode,
+				})
+			}
+		}
+		_ = redirectsCache.Set(cacheKey, redirects, redirectsCacheTimeout)
+	}
+	return redirects
+}
+
+func (o *Options) matchRedirects(ctx *context.Context, giteaClient *gitea.Client, redirects []Redirect, redirectsCache cache.SetGetKey) (final bool) {
+	if len(redirects) > 0 {
+		for _, redirect := range redirects {
+			reqUrl := ctx.Req.RequestURI
+			// remove repo and branch from request url
+			reqUrl = strings.TrimPrefix(reqUrl, "/"+o.TargetRepo)
+			reqUrl = strings.TrimPrefix(reqUrl, "/@"+o.TargetBranch)
+
+			// check if from url matches request url
+			if strings.TrimSuffix(redirect.From, "/") == strings.TrimSuffix(reqUrl, "/") {
+				// do rewrite if status code is 200
+				if redirect.StatusCode == 200 {
+					o.TargetPath = redirect.To
+					o.Upstream(ctx, giteaClient, redirectsCache)
+					return true
+				} else {
+					ctx.Redirect(redirect.To, redirect.StatusCode)
+					return true
+				}
+			}
+
+			// handle wildcard redirects
+			trimmedFromUrl := strings.TrimSuffix(redirect.From, "/*")
+			if strings.HasSuffix(redirect.From, "/*") && strings.HasPrefix(reqUrl, trimmedFromUrl) {
+				if strings.Contains(redirect.To, ":splat") {
+					splatUrl := strings.ReplaceAll(redirect.To, ":splat", strings.TrimPrefix(reqUrl, trimmedFromUrl))
+					// do rewrite if status code is 200
+					if redirect.StatusCode == 200 {
+						o.TargetPath = splatUrl
+						o.Upstream(ctx, giteaClient, redirectsCache)
+						return true
+					} else {
+						ctx.Redirect(splatUrl, redirect.StatusCode)
+						return true
+					}
+				} else {
+					// do rewrite if status code is 200
+					if redirect.StatusCode == 200 {
+						o.TargetPath = redirect.To
+						o.Upstream(ctx, giteaClient, redirectsCache)
+						return true
+					} else {
+						ctx.Redirect(redirect.To, redirect.StatusCode)
+						return true
+					}
+				}
+			}
+		}
+	}
+
+	return false
+}
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 3845969..f97c6ae 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -11,6 +11,7 @@ import (
 	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/html"
+	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/gitea"
 )
@@ -52,7 +53,7 @@ type Options struct {
 }
 
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
-func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (final bool) {
+func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.SetGetKey) (final bool) {
 	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
 
 	if o.TargetOwner == "" || o.TargetRepo == "" {
@@ -103,6 +104,12 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 
 	// Handle not found error
 	if err != nil && errors.Is(err, gitea.ErrorNotFound) {
+		// Get and match redirects
+		redirects := o.getRedirects(giteaClient, redirectsCache)
+		if o.matchRedirects(ctx, giteaClient, redirects, redirectsCache) {
+			return true
+		}
+
 		if o.TryIndexPages {
 			// copy the o struct & try if an index page exists
 			optionsForIndexPages := *o
@@ -110,7 +117,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 			optionsForIndexPages.appendTrailingSlash = true
 			for _, indexPage := range upstreamIndexPages {
 				optionsForIndexPages.TargetPath = strings.TrimSuffix(o.TargetPath, "/") + "/" + indexPage
-				if optionsForIndexPages.Upstream(ctx, giteaClient) {
+				if optionsForIndexPages.Upstream(ctx, giteaClient, redirectsCache) {
 					return true
 				}
 			}
@@ -118,7 +125,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 			optionsForIndexPages.appendTrailingSlash = false
 			optionsForIndexPages.redirectIfExists = strings.TrimSuffix(ctx.Path(), "/") + ".html"
 			optionsForIndexPages.TargetPath = o.TargetPath + ".html"
-			if optionsForIndexPages.Upstream(ctx, giteaClient) {
+			if optionsForIndexPages.Upstream(ctx, giteaClient, redirectsCache) {
 				return true
 			}
 		}
@@ -131,11 +138,12 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
 			optionsForNotFoundPages.appendTrailingSlash = false
 			for _, notFoundPage := range upstreamNotFoundPages {
 				optionsForNotFoundPages.TargetPath = "/" + notFoundPage
-				if optionsForNotFoundPages.Upstream(ctx, giteaClient) {
+				if optionsForNotFoundPages.Upstream(ctx, giteaClient, redirectsCache) {
 					return true
 				}
 			}
 		}
+
 		return false
 	}
 

From 7f318f89a6696e9a39d510bb8568ba74c4c0043c Mon Sep 17 00:00:00 2001
From: Gusted <gusted@noreply.codeberg.org>
Date: Sun, 16 Jul 2023 22:34:46 +0000
Subject: [PATCH 080/196] Fix escaped error message (#230)

- This specific message will [already be generated](https://codeberg.org/Codeberg/pages-server/src/commit/974229681f4cc7f1ed31df9b05eabef2df01380/html/error.go#L44) when `http.StatusMisdirectedRequest` is set as status with [an empty message](https://codeberg.org/Codeberg/pages-server/src/commit/974229681f4cc7f1ed31df9b05eabef2df013809/html/error.go#L25-L28).
- Resolves https://codeberg.org/Codeberg/pages-server/issues/228

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/230
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-committed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/handler/handler_custom_domain.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 8742be4..299d7cf 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -49,7 +49,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 	}, canonicalLink); works {
 		canonicalDomain, valid := targetOpt.CheckCanonicalDomain(giteaClient, trimmedHost, mainDomainSuffix, canonicalDomainCache)
 		if !valid {
-			html.ReturnErrorPage(ctx, "domain not specified in <code>.domains</code> file", http.StatusMisdirectedRequest)
+			html.ReturnErrorPage(ctx, "", http.StatusMisdirectedRequest)
 			return
 		} else if canonicalDomain != trimmedHost {
 			// only redirect if the target is also a codeberg page!

From d720d25e42eb5f1e63462a6665afdd0bebd364ae Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 17 Jul 2023 19:44:58 +0000
Subject: [PATCH 081/196] Use http.NoBody as per linter (#231)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/231
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
---
 server/handler/handler_test.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index de705ec..6521633 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -1,6 +1,7 @@
 package handler
 
 import (
+	"net/http"
 	"net/http/httptest"
 	"testing"
 	"time"
@@ -26,7 +27,7 @@ func TestHandlerPerformance(t *testing.T) {
 
 	testCase := func(uri string, status int) {
 		t.Run(uri, func(t *testing.T) {
-			req := httptest.NewRequest("GET", uri, nil)
+			req := httptest.NewRequest("GET", uri, http.NoBody)
 			w := httptest.NewRecorder()
 
 			log.Printf("Start: %v\n", time.Now())

From 56d3e291c4579c484b173fbcee07b3334c910c23 Mon Sep 17 00:00:00 2001
From: Moritz Marquardt <git@momar.de>
Date: Sun, 27 Aug 2023 10:13:15 +0200
Subject: [PATCH 082/196] Security Fix: clean paths correctly to avoid
 circumvention of BlacklistedPaths

---
 server/context/context.go  |  6 ++--
 server/utils/utils.go      | 14 ++++++++++
 server/utils/utils_test.go | 56 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 72 insertions(+), 4 deletions(-)

diff --git a/server/context/context.go b/server/context/context.go
index 481fee2..6650164 100644
--- a/server/context/context.go
+++ b/server/context/context.go
@@ -48,11 +48,9 @@ func (c *Context) Redirect(uri string, statusCode int) {
 	http.Redirect(c.RespWriter, c.Req, uri, statusCode)
 }
 
-// Path returns requested path.
-//
-// The returned bytes are valid until your request handler returns.
+// Path returns the cleaned requested path.
 func (c *Context) Path() string {
-	return c.Req.URL.Path
+	return utils.CleanPath(c.Req.URL.Path)
 }
 
 func (c *Context) Host() string {
diff --git a/server/utils/utils.go b/server/utils/utils.go
index 30f948d..91ed359 100644
--- a/server/utils/utils.go
+++ b/server/utils/utils.go
@@ -1,6 +1,8 @@
 package utils
 
 import (
+	"net/url"
+	"path"
 	"strings"
 )
 
@@ -11,3 +13,15 @@ func TrimHostPort(host string) string {
 	}
 	return host
 }
+
+func CleanPath(uriPath string) string {
+	unescapedPath, _ := url.PathUnescape(uriPath)
+	cleanedPath := path.Join("/", unescapedPath)
+
+	// If the path refers to a directory, add a trailing slash.
+	if !strings.HasSuffix(cleanedPath, "/") && (strings.HasSuffix(unescapedPath, "/") || strings.HasSuffix(unescapedPath, "/.") || strings.HasSuffix(unescapedPath, "/..")) {
+		cleanedPath += "/"
+	}
+
+	return cleanedPath
+}
diff --git a/server/utils/utils_test.go b/server/utils/utils_test.go
index 2532392..b8fcea9 100644
--- a/server/utils/utils_test.go
+++ b/server/utils/utils_test.go
@@ -11,3 +11,59 @@ func TestTrimHostPort(t *testing.T) {
 	assert.EqualValues(t, "", TrimHostPort(":"))
 	assert.EqualValues(t, "example.com", TrimHostPort("example.com:80"))
 }
+
+// TestCleanPath is mostly copied from fasthttp, to keep the behaviour we had before migrating away from it.
+// Source (MIT licensed): https://github.com/valyala/fasthttp/blob/v1.48.0/uri_test.go#L154
+// Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors
+func TestCleanPath(t *testing.T) {
+	// double slash
+	testURIPathNormalize(t, "/aa//bb", "/aa/bb")
+
+	// triple slash
+	testURIPathNormalize(t, "/x///y/", "/x/y/")
+
+	// multi slashes
+	testURIPathNormalize(t, "/abc//de///fg////", "/abc/de/fg/")
+
+	// encoded slashes
+	testURIPathNormalize(t, "/xxxx%2fyyy%2f%2F%2F", "/xxxx/yyy/")
+
+	// dotdot
+	testURIPathNormalize(t, "/aaa/..", "/")
+
+	// dotdot with trailing slash
+	testURIPathNormalize(t, "/xxx/yyy/../", "/xxx/")
+
+	// multi dotdots
+	testURIPathNormalize(t, "/aaa/bbb/ccc/../../ddd", "/aaa/ddd")
+
+	// dotdots separated by other data
+	testURIPathNormalize(t, "/a/b/../c/d/../e/..", "/a/c/")
+
+	// too many dotdots
+	testURIPathNormalize(t, "/aaa/../../../../xxx", "/xxx")
+	testURIPathNormalize(t, "/../../../../../..", "/")
+	testURIPathNormalize(t, "/../../../../../../", "/")
+
+	// encoded dotdots
+	testURIPathNormalize(t, "/aaa%2Fbbb%2F%2E.%2Fxxx", "/aaa/xxx")
+
+	// double slash with dotdots
+	testURIPathNormalize(t, "/aaa////..//b", "/b")
+
+	// fake dotdot
+	testURIPathNormalize(t, "/aaa/..bbb/ccc/..", "/aaa/..bbb/")
+
+	// single dot
+	testURIPathNormalize(t, "/a/./b/././c/./d.html", "/a/b/c/d.html")
+	testURIPathNormalize(t, "./foo/", "/foo/")
+	testURIPathNormalize(t, "./../.././../../aaa/bbb/../../../././../", "/")
+	testURIPathNormalize(t, "./a/./.././../b/./foo.html", "/b/foo.html")
+}
+
+func testURIPathNormalize(t *testing.T, requestURI, expectedPath string) {
+	cleanedPath := CleanPath(requestURI)
+	if cleanedPath != expectedPath {
+		t.Fatalf("Unexpected path %q. Expected %q. requestURI=%q", cleanedPath, expectedPath, requestURI)
+	}
+}

From ff3cd1ba353f42fefe7ef1c1ba907e9ea2c8bc5e Mon Sep 17 00:00:00 2001
From: Moritz Marquardt <git@momar.de>
Date: Sun, 27 Aug 2023 11:10:55 +0200
Subject: [PATCH 083/196] Fix CI pipeline (replace "pipeline" with "steps")

---
 .woodpecker.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 5b07053..7de8ac8 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,4 +1,4 @@
-pipeline:
+steps:
   # use vendor to cache dependencies
   vendor:
     image: golang:1.20

From b6103c6a1b5b2c0802a75ccead7dc3e3f080da12 Mon Sep 17 00:00:00 2001
From: thepaperpilot <thepaperpilot@noreply.codeberg.org>
Date: Sun, 17 Sep 2023 16:45:20 +0000
Subject: [PATCH 084/196] Chore: Fix env var description (#251)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/251
Reviewed-by: crapStone <crapstone01@gmail.com>
Co-authored-by: thepaperpilot <thepaperpilot@noreply.codeberg.org>
Co-committed-by: thepaperpilot <thepaperpilot@noreply.codeberg.org>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index b694e29..1cb6967 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ and especially have a look at [this section of the haproxy.cfg](https://codeberg
 - `RAW_INFO_PAGE` (default: https://docs.codeberg.org/pages/raw-content/): info page for raw resources, shown if no resource is provided.
 - `ACME_API` (default: https://acme-v02.api.letsencrypt.org/directory): set this to https://acme.mock.director to use invalid certificates without any verification (great for debugging).  
   ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
-- `ACME_EMAIL` (default: `noreply@example.email`): Set this to "true" to accept the Terms of Service of your ACME provider.
+- `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
 - `ACME_EAB_KID` &  `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
 - `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider.
 - `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.

From a8272f0ce9fab991229aad53f512e6aab9c771d2 Mon Sep 17 00:00:00 2001
From: crapStone <crapstone01@gmail.com>
Date: Wed, 15 Nov 2023 01:49:29 +0000
Subject: [PATCH 085/196] Don't send server version to client (#254)

closes #247

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/254
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
---
 server/handler/handler.go | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/server/handler/handler.go b/server/handler/handler.go
index 7edcf95..fbdafd7 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -10,7 +10,6 @@ import (
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/gitea"
-	"codeberg.org/codeberg/pages/server/version"
 )
 
 const (
@@ -31,7 +30,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 		log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
 		ctx := context.New(w, req)
 
-		ctx.RespWriter.Header().Set("Server", "CodebergPages/"+version.Version)
+		ctx.RespWriter.Header().Set("Server", "pages-server")
 
 		// Force new default from specification (since November 2020) - see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
 		ctx.RespWriter.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
@@ -88,14 +87,14 @@ func Handler(mainDomainSuffix, rawDomain string,
 		pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
 
 		if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) {
-			log.Debug().Msg("raw domain request detecded")
+			log.Debug().Msg("raw domain request detected")
 			handleRaw(log, ctx, giteaClient,
 				mainDomainSuffix, rawInfoPage,
 				trimmedHost,
 				pathElements,
 				canonicalDomainCache, redirectsCache)
 		} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
-			log.Debug().Msg("subdomain request detecded")
+			log.Debug().Msg("subdomain request detected")
 			handleSubDomain(log, ctx, giteaClient,
 				mainDomainSuffix,
 				defaultPagesBranches,
@@ -103,7 +102,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 				pathElements,
 				canonicalDomainCache, redirectsCache)
 		} else {
-			log.Debug().Msg("custom domain request detecded")
+			log.Debug().Msg("custom domain request detected")
 			handleCustomDomain(log, ctx, giteaClient,
 				mainDomainSuffix,
 				trimmedHost,

From be92f30e6434131fda8a78f31ef6e10ea7e9dfdd Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Wed, 15 Nov 2023 10:27:27 +0000
Subject: [PATCH 086/196] Update gitea sdk to e23e8aa3004f (#257)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/257
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
---
 go.mod |  2 +-
 go.sum | 13 +++++--------
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/go.mod b/go.mod
index bfde7f7..72eb289 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module codeberg.org/codeberg/pages
 go 1.20
 
 require (
-	code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa
+	code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
 	github.com/go-acme/lego/v4 v4.5.3
 	github.com/go-sql-driver/mysql v1.6.0
diff --git a/go.sum b/go.sum
index b10305c..f78ae18 100644
--- a/go.sum
+++ b/go.sum
@@ -22,8 +22,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa h1:OVwgYrY6vr6gWZvgnmevFhtL0GVA4HKaFOhD+joPoNk=
-code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
+code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f h1:nMmwDgUIAWj9XQjzHz5unC3ZMfhhwHRk6rnuwLzdu1o=
+code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
@@ -249,8 +249,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
 github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -789,8 +789,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -899,8 +899,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -964,14 +962,13 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=

From 1e1c67be93f59af3fad68cdc8faad72aac8a742b Mon Sep 17 00:00:00 2001
From: crapStone <crapstone01@gmail.com>
Date: Wed, 15 Nov 2023 15:25:14 +0000
Subject: [PATCH 087/196] let gitea client send user-agent with version (#258)

closes #255

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/258
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
---
 server/gitea/client.go | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/server/gitea/client.go b/server/gitea/client.go
index 7a2bf63..f3bda54 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -17,12 +17,13 @@ import (
 	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/version"
 )
 
 var ErrorNotFound = errors.New("not found")
 
 const (
-	// cache key prefixe
+	// cache key prefixes
 	branchTimestampCacheKeyPrefix = "branchTime"
 	defaultBranchCacheKeyPrefix   = "defaultBranch"
 	rawContentCacheKeyPrefix      = "rawContent"
@@ -76,7 +77,13 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, follo
 		defaultMimeType = "application/octet-stream"
 	}
 
-	sdk, err := gitea.NewClient(giteaRoot, gitea.SetHTTPClient(&stdClient), gitea.SetToken(giteaAPIToken))
+	sdk, err := gitea.NewClient(
+		giteaRoot,
+		gitea.SetHTTPClient(&stdClient),
+		gitea.SetToken(giteaAPIToken),
+		gitea.SetUserAgent("pages-server/"+version.Version),
+	)
+
 	return &Client{
 		sdkClient:     sdk,
 		responseCache: respCache,
@@ -172,7 +179,7 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 				return reader, resp.Response.Header, resp.StatusCode, err
 			}
 
-			// now we write to cache and respond at the sime time
+			// now we write to cache and respond at the same time
 			fileResp := FileResponse{
 				Exists:   true,
 				ETag:     resp.Header.Get(ETagHeader),
@@ -278,11 +285,11 @@ func shouldRespBeSavedToCache(resp *http.Response) bool {
 		return false
 	}
 
-	contentLeng, err := strconv.ParseInt(contentLengthRaw, 10, 64)
+	contentLength, err := strconv.ParseInt(contentLengthRaw, 10, 64)
 	if err != nil {
 		log.Error().Err(err).Msg("could not parse content length")
 	}
 
 	// if content to big or could not be determined we not cache it
-	return contentLeng > 0 && contentLeng < fileCacheSizeLimit
+	return contentLength > 0 && contentLength < fileCacheSizeLimit
 }

From ea68a82cd22a8a8c1f265260af22b9406f13e3a9 Mon Sep 17 00:00:00 2001
From: crapStone <crapstone01@gmail.com>
Date: Wed, 15 Nov 2023 15:25:56 +0000
Subject: [PATCH 088/196] new maintainer (#256)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/256
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
---
 README.md | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 1cb6967..9ec3aab 100644
--- a/README.md
+++ b/README.md
@@ -91,9 +91,13 @@ Since we are working nicely in a team, it might be hard at times to get started
 (still check out the issues, we always aim to have some things to get you started).
 
 If you have any questions, want to work on a feature or could imagine collaborating with us for some time,
-feel free to ping us in an issue or in a general Matrix chatgroup.
+feel free to ping us in an issue or in a general [Matrix chat room](#chat-for-admins--devs).
 
-You can also contact the maintainers of this project:
+You can also contact the maintainer(s) of this project:
+
+- [crapStone](https://codeberg.org/crapStone) [(Matrix)](https://matrix.to/#/@crapstone:obermui.de)
+
+Previous maintainers:
 
 - [momar](https://codeberg.org/momar) [(Matrix)](https://matrix.to/#/@moritz:wuks.space)
 - [6543](https://codeberg.org/6543) [(Matrix)](https://matrix.to/#/@marddl:obermui.de)

From 7f0a4e5ca999189527bf0739467787fe74a51f8a Mon Sep 17 00:00:00 2001
From: crapStone <crapstone01@gmail.com>
Date: Wed, 15 Nov 2023 17:59:04 +0000
Subject: [PATCH 089/196] small cleanup (#259)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/259
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
---
 FEATURES.md                                   | 14 +++++++---
 README.md                                     | 27 +++++++++----------
 .../haproxy-sni}/.gitignore                   |  0
 .../haproxy-sni}/README.md                    |  0
 .../haproxy-sni}/dhparam.pem                  |  0
 .../haproxy-sni}/docker-compose.yml           |  0
 .../haproxy-sni}/gitea-www/index.html         |  0
 .../haproxy-sni}/gitea.Caddyfile              |  0
 .../haproxy-certificates/codeberg.org.pem     |  0
 .../haproxy-certificates/codeberg.org.pem.key |  0
 .../haproxy-sni}/haproxy.cfg                  |  0
 .../haproxy-sni}/pages-www/index.html         |  0
 .../haproxy-sni}/pages.Caddyfile              |  0
 {haproxy-sni => examples/haproxy-sni}/test.sh |  0
 14 files changed, 23 insertions(+), 18 deletions(-)
 rename {haproxy-sni => examples/haproxy-sni}/.gitignore (100%)
 rename {haproxy-sni => examples/haproxy-sni}/README.md (100%)
 rename {haproxy-sni => examples/haproxy-sni}/dhparam.pem (100%)
 rename {haproxy-sni => examples/haproxy-sni}/docker-compose.yml (100%)
 rename {haproxy-sni => examples/haproxy-sni}/gitea-www/index.html (100%)
 rename {haproxy-sni => examples/haproxy-sni}/gitea.Caddyfile (100%)
 rename {haproxy-sni => examples/haproxy-sni}/haproxy-certificates/codeberg.org.pem (100%)
 rename {haproxy-sni => examples/haproxy-sni}/haproxy-certificates/codeberg.org.pem.key (100%)
 rename {haproxy-sni => examples/haproxy-sni}/haproxy.cfg (100%)
 rename {haproxy-sni => examples/haproxy-sni}/pages-www/index.html (100%)
 rename {haproxy-sni => examples/haproxy-sni}/pages.Caddyfile (100%)
 rename {haproxy-sni => examples/haproxy-sni}/test.sh (100%)

diff --git a/FEATURES.md b/FEATURES.md
index 7560a1d..3d2f394 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -2,13 +2,19 @@
 
 ## Custom domains
 
-...
+Custom domains can be used by creating a `.domains` file with the domain name, e.g.:
+
+```text
+codeberg.page
+```
+
+You also have to set some DNS records, see the [Codeberg Documentation](https://docs.codeberg.org/codeberg-pages/using-custom-domain/).
 
 ## Redirects
 
 Redirects can be created with a `_redirects` file with the following format:
 
-```
+```text
 # Comment
 from  to  [status]
 ```
@@ -30,7 +36,7 @@ from  to  [status]
 
 Redirects all paths to `/index.html` for single-page apps.
 
-```
+```text
 /*  /index.html 200
 ```
 
@@ -38,7 +44,7 @@ Redirects all paths to `/index.html` for single-page apps.
 
 Redirects every path under `/articles` to `/posts` while keeping the path.
 
-```
+```text
 /articles/*  /posts/:splat  302
 ```
 
diff --git a/README.md b/README.md
index 9ec3aab..39caa24 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,7 @@ It is suitable to be deployed by other Gitea instances, too, to offer static pag
 **End user documentation** can mainly be found at the [Wiki](https://codeberg.org/Codeberg/pages-server/wiki/Overview)
 and the [Codeberg Documentation](https://docs.codeberg.org/codeberg-pages/).
 
-
-<a href="https://codeberg.org/Codeberg/pages-server"> <img src="https://codeberg.org/Codeberg/GetItOnCodeberg/raw/branch/main/get-it-on-blue-on-white.svg" alt="Get It On Codeberg" width="250"/> <a/>
+<a href="https://codeberg.org/Codeberg/pages-server"> <img src="https://codeberg.org/Codeberg/GetItOnCodeberg/raw/branch/main/get-it-on-blue-on-white.svg" alt="Get It On Codeberg" width="250"/> </a>
 
 ## Quickstart
 
@@ -61,18 +60,18 @@ but if you want to run it on a shared IP address (and not a standalone),
 you'll need to configure your reverse proxy not to terminate the TLS connections,
 but forward the requests on the IP level to the Pages Server.
 
-You can check out a proof of concept in the `haproxy-sni` folder,
-and especially have a look at [this section of the haproxy.cfg](https://codeberg.org/Codeberg/pages-server/src/branch/main/haproxy-sni/haproxy.cfg#L38).
+You can check out a proof of concept in the `examples/haproxy-sni` folder,
+and especially have a look at [this section of the haproxy.cfg](https://codeberg.org/Codeberg/pages-server/src/branch/main/examples/haproxy-sni/haproxy.cfg#L38).
 
-### Environment
+### Environment Variables
 
 - `HOST` & `PORT` (default: `[::]` & `443`): listen address.
 - `PAGES_DOMAIN` (default: `codeberg.page`): main domain for pages.
-- `RAW_DOMAIN` (default: `raw.codeberg.page`): domain for raw resources.
+- `RAW_DOMAIN` (default: `raw.codeberg.page`): domain for raw resources (must be subdomain of `PAGES_DOMAIN`).
 - `GITEA_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance.
 - `GITEA_API_TOKEN` (default: empty): API token for the Gitea instance to access non-public (e.g. limited) repos.
-- `RAW_INFO_PAGE` (default: https://docs.codeberg.org/pages/raw-content/): info page for raw resources, shown if no resource is provided.
-- `ACME_API` (default: https://acme-v02.api.letsencrypt.org/directory): set this to https://acme.mock.director to use invalid certificates without any verification (great for debugging).  
+- `RAW_INFO_PAGE` (default: <https://docs.codeberg.org/pages/raw-content/>): info page for raw resources, shown if no resource is provided.
+- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging).  
   ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
 - `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
 - `ACME_EAB_KID` &  `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
@@ -80,10 +79,9 @@ and especially have a look at [this section of the haproxy.cfg](https://codeberg
 - `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.
 - `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
 - `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard.  
-  See https://go-acme.github.io/lego/dns/ for available values & additional environment variables.
+  See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
 - `LOG_LEVEL` (default: warn): Set this to specify the level of logging.
 
-
 ## Contributing to the development
 
 The Codeberg team is very open to your contribution.
@@ -119,7 +117,8 @@ Make sure you have [golang](https://go.dev) v1.20 or newer and [just](https://ju
 
 run `just dev`  
 now this pages should work:
- - https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
- - https://momar.localhost.mock.directory:4430/ci-testing/
- - https://momar.localhost.mock.directory:4430/pag/@master/
- - https://mock-pages.codeberg-test.org:4430/README.md
+
+- <https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg>
+- <https://momar.localhost.mock.directory:4430/ci-testing/>
+- <https://momar.localhost.mock.directory:4430/pag/@master/>
+- <https://mock-pages.codeberg-test.org:4430/README.md>
diff --git a/haproxy-sni/.gitignore b/examples/haproxy-sni/.gitignore
similarity index 100%
rename from haproxy-sni/.gitignore
rename to examples/haproxy-sni/.gitignore
diff --git a/haproxy-sni/README.md b/examples/haproxy-sni/README.md
similarity index 100%
rename from haproxy-sni/README.md
rename to examples/haproxy-sni/README.md
diff --git a/haproxy-sni/dhparam.pem b/examples/haproxy-sni/dhparam.pem
similarity index 100%
rename from haproxy-sni/dhparam.pem
rename to examples/haproxy-sni/dhparam.pem
diff --git a/haproxy-sni/docker-compose.yml b/examples/haproxy-sni/docker-compose.yml
similarity index 100%
rename from haproxy-sni/docker-compose.yml
rename to examples/haproxy-sni/docker-compose.yml
diff --git a/haproxy-sni/gitea-www/index.html b/examples/haproxy-sni/gitea-www/index.html
similarity index 100%
rename from haproxy-sni/gitea-www/index.html
rename to examples/haproxy-sni/gitea-www/index.html
diff --git a/haproxy-sni/gitea.Caddyfile b/examples/haproxy-sni/gitea.Caddyfile
similarity index 100%
rename from haproxy-sni/gitea.Caddyfile
rename to examples/haproxy-sni/gitea.Caddyfile
diff --git a/haproxy-sni/haproxy-certificates/codeberg.org.pem b/examples/haproxy-sni/haproxy-certificates/codeberg.org.pem
similarity index 100%
rename from haproxy-sni/haproxy-certificates/codeberg.org.pem
rename to examples/haproxy-sni/haproxy-certificates/codeberg.org.pem
diff --git a/haproxy-sni/haproxy-certificates/codeberg.org.pem.key b/examples/haproxy-sni/haproxy-certificates/codeberg.org.pem.key
similarity index 100%
rename from haproxy-sni/haproxy-certificates/codeberg.org.pem.key
rename to examples/haproxy-sni/haproxy-certificates/codeberg.org.pem.key
diff --git a/haproxy-sni/haproxy.cfg b/examples/haproxy-sni/haproxy.cfg
similarity index 100%
rename from haproxy-sni/haproxy.cfg
rename to examples/haproxy-sni/haproxy.cfg
diff --git a/haproxy-sni/pages-www/index.html b/examples/haproxy-sni/pages-www/index.html
similarity index 100%
rename from haproxy-sni/pages-www/index.html
rename to examples/haproxy-sni/pages-www/index.html
diff --git a/haproxy-sni/pages.Caddyfile b/examples/haproxy-sni/pages.Caddyfile
similarity index 100%
rename from haproxy-sni/pages.Caddyfile
rename to examples/haproxy-sni/pages.Caddyfile
diff --git a/haproxy-sni/test.sh b/examples/haproxy-sni/test.sh
similarity index 100%
rename from haproxy-sni/test.sh
rename to examples/haproxy-sni/test.sh

From cbb2ce6d0732bcf2372cbc201fc1c1f2733aadba Mon Sep 17 00:00:00 2001
From: crapStone <crapstone01@gmail.com>
Date: Thu, 16 Nov 2023 17:11:35 +0000
Subject: [PATCH 090/196] add go templating engine for error page and make
 errors more clear (#260)

ping #199
closes #213

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/260
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
---
 .woodpecker.yml                         |  2 +-
 README.md                               |  2 +-
 go.mod                                  | 15 ++++--
 go.sum                                  | 30 +++++++----
 html/404.html                           | 37 -------------
 html/error.go                           | 50 ------------------
 html/error.html                         | 38 --------------
 html/error_test.go                      | 38 --------------
 html/html.go                            | 54 +++++++++++++++++--
 html/html_test.go                       | 54 +++++++++++++++++++
 html/templates/error.html               | 69 +++++++++++++++++++++++++
 server/handler/handler.go               |  2 +-
 server/handler/handler_custom_domain.go |  2 +-
 server/handler/handler_raw_domain.go    |  2 +-
 server/handler/handler_sub_domain.go    | 22 +++++---
 server/handler/try.go                   |  2 +-
 server/upstream/upstream.go             | 16 +++---
 17 files changed, 232 insertions(+), 203 deletions(-)
 delete mode 100644 html/404.html
 delete mode 100644 html/error.go
 delete mode 100644 html/error.html
 delete mode 100644 html/error_test.go
 create mode 100644 html/html_test.go
 create mode 100644 html/templates/error.html

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 7de8ac8..759e2f1 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,7 +1,7 @@
 steps:
   # use vendor to cache dependencies
   vendor:
-    image: golang:1.20
+    image: golang:1.21
     commands:
       - go mod vendor
 
diff --git a/README.md b/README.md
index 39caa24..fb2a4b9 100644
--- a/README.md
+++ b/README.md
@@ -113,7 +113,7 @@ Thank you very much.
 
 ### Test Server
 
-Make sure you have [golang](https://go.dev) v1.20 or newer and [just](https://just.systems/man/en/) installed.
+Make sure you have [golang](https://go.dev) v1.21 or newer and [just](https://just.systems/man/en/) installed.
 
 run `just dev`  
 now this pages should work:
diff --git a/go.mod b/go.mod
index 72eb289..eba292e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,8 @@
 module codeberg.org/codeberg/pages
 
-go 1.20
+go 1.21
+
+toolchain go1.21.4
 
 require (
 	code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f
@@ -10,6 +12,7 @@ require (
 	github.com/joho/godotenv v1.4.0
 	github.com/lib/pq v1.10.7
 	github.com/mattn/go-sqlite3 v1.14.16
+	github.com/microcosm-cc/bluemonday v1.0.26
 	github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.7.0
@@ -35,6 +38,7 @@ require (
 	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
 	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 // indirect
 	github.com/aws/aws-sdk-go v1.39.0 // indirect
+	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
 	github.com/cenkalti/backoff/v4 v4.1.1 // indirect
 	github.com/cloudflare/cloudflare-go v0.20.0 // indirect
@@ -61,6 +65,7 @@ require (
 	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
 	github.com/gophercloud/gophercloud v0.16.0 // indirect
 	github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
+	github.com/gorilla/css v1.0.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
 	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
 	github.com/hashicorp/go-version v1.6.0 // indirect
@@ -115,11 +120,11 @@ require (
 	github.com/vultr/govultr/v2 v2.7.1 // indirect
 	go.opencensus.io v0.22.3 // indirect
 	go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect
-	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
-	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
+	golang.org/x/crypto v0.14.0 // indirect
+	golang.org/x/net v0.17.0 // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
-	golang.org/x/sys v0.1.0 // indirect
-	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 	google.golang.org/api v0.20.0 // indirect
 	google.golang.org/appengine v1.6.5 // indirect
diff --git a/go.sum b/go.sum
index f78ae18..7ea8b78 100644
--- a/go.sum
+++ b/go.sum
@@ -88,6 +88,8 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
 github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo=
 github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -251,6 +253,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -278,6 +281,8 @@ github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
@@ -477,6 +482,8 @@ github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
 github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
 github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
+github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
 github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
@@ -756,8 +763,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -790,7 +797,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -826,8 +834,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -899,11 +908,12 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -911,8 +921,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -963,7 +974,8 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs
 golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/html/404.html b/html/404.html
deleted file mode 100644
index 7c721b5..0000000
--- a/html/404.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!doctype html>
-<html class="codeberg-design">
-<head>
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width">
-    <title>%status%</title>
-
-    <link rel="stylesheet" href="https://design.codeberg.org/design-kit/codeberg.css" />
-    <link href="https://fonts.codeberg.org/dist/inter/Inter%20Web/inter.css" rel="stylesheet" />
-    <link href="https://fonts.codeberg.org/dist/fontawesome5/css/all.min.css" rel="stylesheet" />
-
-    <style>
-        body {
-            margin: 0; padding: 1rem; box-sizing: border-box;
-            width: 100%; min-height: 100vh;
-            display: flex;
-            flex-direction: column;
-            align-items: center;
-            justify-content: center;
-        }
-    </style>
-</head>
-<body>
-    <i class="fa fa-search text-primary" style="font-size: 96px;"></i>
-    <h1 class="mb-0 text-primary">
-        Page not found!
-    </h1>
-    <h5 class="text-center" style="max-width: 25em;">
-        Sorry, but this page couldn't be found or is inaccessible (%status%).<br/>
-        We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
-    </h5>
-    <small class="text-muted">
-        <img src="https://design.codeberg.org/logo-kit/icon.svg" class="align-top">
-        Static pages made easy - <a href="https://codeberg.page">Codeberg Pages</a>
-    </small>
-</body>
-</html>
diff --git a/html/error.go b/html/error.go
deleted file mode 100644
index 206b123..0000000
--- a/html/error.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package html
-
-import (
-	"html/template"
-	"net/http"
-	"strconv"
-	"strings"
-
-	"codeberg.org/codeberg/pages/server/context"
-)
-
-// ReturnErrorPage sets the response status code and writes NotFoundPage to the response body,
-// with "%status%" and %message% replaced with the provided statusCode and msg
-func ReturnErrorPage(ctx *context.Context, msg string, statusCode int) {
-	ctx.RespWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
-	ctx.RespWriter.WriteHeader(statusCode)
-
-	msg = generateResponse(msg, statusCode)
-
-	_, _ = ctx.RespWriter.Write([]byte(msg))
-}
-
-// TODO: use template engine
-func generateResponse(msg string, statusCode int) string {
-	if msg == "" {
-		msg = strings.ReplaceAll(NotFoundPage,
-			"%status%",
-			strconv.Itoa(statusCode)+" "+errorMessage(statusCode))
-	} else {
-		msg = strings.ReplaceAll(
-			strings.ReplaceAll(ErrorPage, "%message%", template.HTMLEscapeString(msg)),
-			"%status%",
-			http.StatusText(statusCode))
-	}
-
-	return msg
-}
-
-func errorMessage(statusCode int) string {
-	message := http.StatusText(statusCode)
-
-	switch statusCode {
-	case http.StatusMisdirectedRequest:
-		message += " - domain not specified in <code>.domains</code> file"
-	case http.StatusFailedDependency:
-		message += " - target repo/branch doesn't exist or is private"
-	}
-
-	return message
-}
diff --git a/html/error.html b/html/error.html
deleted file mode 100644
index f1975f7..0000000
--- a/html/error.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!doctype html>
-<html class="codeberg-design">
-<head>
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width">
-    <title>%status%</title>
-
-    <link rel="stylesheet" href="https://design.codeberg.org/design-kit/codeberg.css" />
-    <link href="https://fonts.codeberg.org/dist/inter/Inter%20Web/inter.css" rel="stylesheet" />
-    <link href="https://fonts.codeberg.org/dist/fontawesome5/css/all.min.css" rel="stylesheet" />
-
-    <style>
-        body {
-            margin: 0; padding: 1rem; box-sizing: border-box;
-            width: 100%; min-height: 100vh;
-            display: flex;
-            flex-direction: column;
-            align-items: center;
-            justify-content: center;
-        }
-    </style>
-</head>
-<body>
-    <i class="fa fa-search text-primary" style="font-size: 96px;"></i>
-    <h1 class="mb-0 text-primary">
-        %status%!
-    </h1>
-    <h5 class="text-center" style="max-width: 25em;">
-        Sorry, but this page couldn't be served.<br/>
-        We got an <b>"%message%"</b><br/>
-        We hope this isn't a problem on our end ;) - Make sure to check the <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank">troubleshooting section in the Docs</a>!
-    </h5>
-    <small class="text-muted">
-        <img src="https://design.codeberg.org/logo-kit/icon.svg" class="align-top">
-        Static pages made easy - <a href="https://codeberg.page">Codeberg Pages</a>
-    </small>
-</body>
-</html>
diff --git a/html/error_test.go b/html/error_test.go
deleted file mode 100644
index f5da08c..0000000
--- a/html/error_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package html
-
-import (
-	"net/http"
-	"strings"
-	"testing"
-)
-
-func TestValidMessage(t *testing.T) {
-	testString := "requested blacklisted path"
-	statusCode := http.StatusForbidden
-
-	expected := strings.ReplaceAll(
-		strings.ReplaceAll(ErrorPage, "%message%", testString),
-		"%status%",
-		http.StatusText(statusCode))
-	actual := generateResponse(testString, statusCode)
-
-	if expected != actual {
-		t.Errorf("generated response did not match: expected: '%s', got: '%s'", expected, actual)
-	}
-}
-
-func TestMessageWithHtml(t *testing.T) {
-	testString := `abc<img src=1 onerror=alert("xss");`
-	escapedString := "abc&lt;img src=1 onerror=alert(&#34;xss&#34;);"
-	statusCode := http.StatusNotFound
-
-	expected := strings.ReplaceAll(
-		strings.ReplaceAll(ErrorPage, "%message%", escapedString),
-		"%status%",
-		http.StatusText(statusCode))
-	actual := generateResponse(testString, statusCode)
-
-	if expected != actual {
-		t.Errorf("generated response did not match: expected: '%s', got: '%s'", expected, actual)
-	}
-}
diff --git a/html/html.go b/html/html.go
index a76ce59..ff8b6b4 100644
--- a/html/html.go
+++ b/html/html.go
@@ -1,9 +1,53 @@
 package html
 
-import _ "embed"
+import (
+	_ "embed"
+	"net/http"
+	"text/template" // do not use html/template here, we sanitize the message before passing it to the template
 
-//go:embed 404.html
-var NotFoundPage string
+	"codeberg.org/codeberg/pages/server/context"
+	"github.com/microcosm-cc/bluemonday"
+	"github.com/rs/zerolog/log"
+)
 
-//go:embed error.html
-var ErrorPage string
+//go:embed templates/error.html
+var errorPage string
+
+var (
+	errorTemplate = template.Must(template.New("error").Parse(errorPage))
+	sanitizer     = createBlueMondayPolicy()
+)
+
+type TemplateContext struct {
+	StatusCode int
+	StatusText string
+	Message    string
+}
+
+// ReturnErrorPage sets the response status code and writes the error page to the response body.
+// The error page contains a sanitized version of the message and the statusCode both in text and numeric form.
+//
+// Currently, only the following html tags are supported: <code>
+func ReturnErrorPage(ctx *context.Context, msg string, statusCode int) {
+	ctx.RespWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
+	ctx.RespWriter.WriteHeader(statusCode)
+
+	templateContext := TemplateContext{
+		StatusCode: statusCode,
+		StatusText: http.StatusText(statusCode),
+		Message:    sanitizer.Sanitize(msg),
+	}
+
+	err := errorTemplate.Execute(ctx.RespWriter, templateContext)
+	if err != nil {
+		log.Err(err).Str("message", msg).Int("status", statusCode).Msg("could not write response")
+	}
+}
+
+func createBlueMondayPolicy() *bluemonday.Policy {
+	p := bluemonday.NewPolicy()
+
+	p.AllowElements("code")
+
+	return p
+}
diff --git a/html/html_test.go b/html/html_test.go
new file mode 100644
index 0000000..b395bb2
--- /dev/null
+++ b/html/html_test.go
@@ -0,0 +1,54 @@
+package html
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSanitizerSimpleString(t *testing.T) {
+	str := "simple text message without any html elements"
+
+	assert.Equal(t, str, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithCodeTag(t *testing.T) {
+	str := "simple text message with <code>html</code> tag"
+
+	assert.Equal(t, str, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithCodeTagWithAttribute(t *testing.T) {
+	str := "simple text message with <code id=\"code\">html</code> tag"
+	expected := "simple text message with <code>html</code> tag"
+
+	assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithATag(t *testing.T) {
+	str := "simple text message with <a>a link to another page</a>"
+	expected := "simple text message with a link to another page"
+
+	assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithATagAndHref(t *testing.T) {
+	str := "simple text message with <a href=\"http://evil.site\">a link to another page</a>"
+	expected := "simple text message with a link to another page"
+
+	assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithImgTag(t *testing.T) {
+	str := "simple text message with a <img alt=\"not found\" src=\"http://evil.site\">"
+	expected := "simple text message with a "
+
+	assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithImgTagAndOnerrorAttribute(t *testing.T) {
+	str := "simple text message with a <img alt=\"not found\" src=\"http://evil.site\" onerror=\"alert(secret)\">"
+	expected := "simple text message with a "
+
+	assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
diff --git a/html/templates/error.html b/html/templates/error.html
new file mode 100644
index 0000000..6094a26
--- /dev/null
+++ b/html/templates/error.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html class="codeberg-design">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width" />
+    <title>{{.StatusText}}</title>
+
+    <link
+      rel="stylesheet"
+      href="https://design.codeberg.org/design-kit/codeberg.css"
+    />
+    <link
+      rel="stylesheet"
+      href="https://fonts.codeberg.org/dist/inter/Inter%20Web/inter.css"
+    />
+
+    <style>
+      body {
+        margin: 0;
+        padding: 1rem;
+        box-sizing: border-box;
+        width: 100%;
+        min-height: 100vh;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+      }
+      code {
+        border-radius: 0.25rem;
+        padding: 0.25rem;
+        background-color: silver;
+      }
+    </style>
+  </head>
+  <body>
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
+      height="10em"
+      viewBox="0 0 24 24"
+      fill="var(--blue-color)"
+    >
+      <path
+        d="M 9 2 C 5.1458514 2 2 5.1458514 2 9 C 2 12.854149 5.1458514 16 9 16 C 10.747998 16 12.345009 15.348024 13.574219 14.28125 L 14 14.707031 L 14 16 L 19.585938 21.585938 C 20.137937 22.137937 21.033938 22.137938 21.585938 21.585938 C 22.137938 21.033938 22.137938 20.137938 21.585938 19.585938 L 16 14 L 14.707031 14 L 14.28125 13.574219 C 15.348024 12.345009 16 10.747998 16 9 C 16 5.1458514 12.854149 2 9 2 z M 9 4 C 11.773268 4 14 6.2267316 14 9 C 14 11.773268 11.773268 14 9 14 C 6.2267316 14 4 11.773268 4 9 C 4 6.2267316 6.2267316 4 9 4 z"
+      />
+    </svg>
+    <h1 class="mb-0 text-primary">{{.StatusText}} ({{.StatusCode}})!</h1>
+    <h5 class="text-center" style="max-width: 25em">
+      <p>Sorry, but this page couldn't be served.</p>
+      <p><b>"{{.Message}}"</b></p>
+      <p>
+        We hope this isn't a problem on our end ;) - Make sure to check the
+        <a
+          href="https://docs.codeberg.org/codeberg-pages/troubleshooting/"
+          target="_blank"
+          >troubleshooting section in the Docs</a
+        >!
+      </p>
+    </h5>
+    <small class="text-muted">
+      <img
+        src="https://design.codeberg.org/logo-kit/icon.svg"
+        class="align-top"
+      />
+      Static pages made easy -
+      <a href="https://codeberg.page">Codeberg Pages</a>
+    </small>
+  </body>
+</html>
diff --git a/server/handler/handler.go b/server/handler/handler.go
index fbdafd7..a3011f3 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -65,7 +65,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 		// Block blacklisted paths (like ACME challenges)
 		for _, blacklistedPath := range blacklistedPaths {
 			if strings.HasPrefix(ctx.Path(), blacklistedPath) {
-				html.ReturnErrorPage(ctx, "requested blacklisted path", http.StatusForbidden)
+				html.ReturnErrorPage(ctx, "requested path is blacklisted", http.StatusForbidden)
 				return
 			}
 		}
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 299d7cf..8742be4 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -49,7 +49,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 	}, canonicalLink); works {
 		canonicalDomain, valid := targetOpt.CheckCanonicalDomain(giteaClient, trimmedHost, mainDomainSuffix, canonicalDomainCache)
 		if !valid {
-			html.ReturnErrorPage(ctx, "", http.StatusMisdirectedRequest)
+			html.ReturnErrorPage(ctx, "domain not specified in <code>.domains</code> file", http.StatusMisdirectedRequest)
 			return
 		} else if canonicalDomain != trimmedHost {
 			// only redirect if the target is also a codeberg page!
diff --git a/server/handler/handler_raw_domain.go b/server/handler/handler_raw_domain.go
index aa41c52..b87991e 100644
--- a/server/handler/handler_raw_domain.go
+++ b/server/handler/handler_raw_domain.go
@@ -61,7 +61,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 	} else {
 		html.ReturnErrorPage(ctx,
-			fmt.Sprintf("raw domain could not find repo '%s/%s' or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo),
+			fmt.Sprintf("raw domain could not find repo <code>%s/%s</code> or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo),
 			http.StatusNotFound)
 	}
 }
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 8731bec..e7cb3a6 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -55,9 +55,11 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			log.Trace().Msg("tryUpstream: serve with specified repo and branch")
 			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		} else {
-			html.ReturnErrorPage(ctx,
-				fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
-				http.StatusFailedDependency)
+			html.ReturnErrorPage(
+				ctx,
+				formatSetBranchNotFoundMessage(targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
+				http.StatusFailedDependency,
+			)
 		}
 		return
 	}
@@ -85,9 +87,11 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			log.Trace().Msg("tryUpstream: serve default pages repo with specified branch")
 			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		} else {
-			html.ReturnErrorPage(ctx,
-				fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
-				http.StatusFailedDependency)
+			html.ReturnErrorPage(
+				ctx,
+				formatSetBranchNotFoundMessage(targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
+				http.StatusFailedDependency,
+			)
 		}
 		return
 	}
@@ -143,6 +147,10 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 
 	// Couldn't find a valid repo/branch
 	html.ReturnErrorPage(ctx,
-		fmt.Sprintf("could not find a valid repository[%s]", targetRepo),
+		fmt.Sprintf("could not find a valid repository or branch for repository: <code>%s</code>", targetRepo),
 		http.StatusNotFound)
 }
+
+func formatSetBranchNotFoundMessage(branch, owner, repo string) string {
+	return fmt.Sprintf("explicitly set branch <code>%q</code> does not exist at <code>%s/%s</code>", branch, owner, repo)
+}
diff --git a/server/handler/try.go b/server/handler/try.go
index 6cfe08e..838ae27 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -41,7 +41,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 
 	// Try to request the file from the Gitea API
 	if !options.Upstream(ctx, giteaClient, redirectsCache) {
-		html.ReturnErrorPage(ctx, "", ctx.StatusCode)
+		html.ReturnErrorPage(ctx, "gitea client failed", ctx.StatusCode)
 	}
 }
 
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index f97c6ae..1a444e4 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -53,11 +53,11 @@ type Options struct {
 }
 
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
-func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.SetGetKey) (final bool) {
+func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.SetGetKey) bool {
 	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
 
 	if o.TargetOwner == "" || o.TargetRepo == "" {
-		html.ReturnErrorPage(ctx, "either repo owner or name info is missing", http.StatusBadRequest)
+		html.ReturnErrorPage(ctx, "gitea client: either repo owner or name info is missing", http.StatusBadRequest)
 		return true
 	}
 
@@ -67,7 +67,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 		// handle 404
 		if err != nil && errors.Is(err, gitea.ErrorNotFound) || !branchExist {
 			html.ReturnErrorPage(ctx,
-				fmt.Sprintf("branch %q for '%s/%s' not found", o.TargetBranch, o.TargetOwner, o.TargetRepo),
+				fmt.Sprintf("branch <code>%q</code> for <code>%s/%s</code> not found", o.TargetBranch, o.TargetOwner, o.TargetRepo),
 				http.StatusNotFound)
 			return true
 		}
@@ -75,7 +75,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 		// handle unexpected errors
 		if err != nil {
 			html.ReturnErrorPage(ctx,
-				fmt.Sprintf("could not get timestamp of branch %q: %v", o.TargetBranch, err),
+				fmt.Sprintf("could not get timestamp of branch <code>%q</code>: '%v'", o.TargetBranch, err),
 				http.StatusFailedDependency)
 			return true
 		}
@@ -153,16 +153,16 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 		var msg string
 
 		if err != nil {
-			msg = "gitea client returned unexpected error"
+			msg = "gitea client: returned unexpected error"
 			log.Error().Err(err).Msg(msg)
-			msg = fmt.Sprintf("%s: %v", msg, err)
+			msg = fmt.Sprintf("%s: '%v'", msg, err)
 		}
 		if reader == nil {
-			msg = "gitea client returned no reader"
+			msg = "gitea client: returned no reader"
 			log.Error().Msg(msg)
 		}
 		if statusCode != http.StatusOK {
-			msg = fmt.Sprintf("Couldn't fetch contents (status code %d)", statusCode)
+			msg = fmt.Sprintf("gitea client: couldn't fetch contents: <code>%d - %s</code>", statusCode, http.StatusText(statusCode))
 			log.Error().Msg(msg)
 		}
 

From fffb8ffcb68d44572830bf03f90b4fa5b9db262e Mon Sep 17 00:00:00 2001
From: crapStone <crapstone01@gmail.com>
Date: Thu, 16 Nov 2023 17:33:39 +0000
Subject: [PATCH 091/196] remove use of rawInfoPage redirect (#261)

closes #244

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/261
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
---
 cmd/flags.go                         |  7 -------
 cmd/main.go                          |  2 --
 server/handler/handler.go            |  3 +--
 server/handler/handler_raw_domain.go | 10 +++++++---
 server/handler/handler_test.go       |  1 -
 5 files changed, 8 insertions(+), 15 deletions(-)

diff --git a/cmd/flags.go b/cmd/flags.go
index a71dd35..7ac94e6 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -72,13 +72,6 @@ var (
 			EnvVars: []string{"RAW_DOMAIN"},
 			Value:   "raw.codeberg.page",
 		},
-		// 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{"RAW_INFO_PAGE"},
-			Value:   "https://docs.codeberg.org/codeberg-pages/raw-content/",
-		},
 
 		// #########################
 		// ### Page Server Setup ###
diff --git a/cmd/main.go b/cmd/main.go
index 84915c9..683e859 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -47,7 +47,6 @@ func Serve(ctx *cli.Context) error {
 	rawDomain := ctx.String("raw-domain")
 	defaultBranches := ctx.StringSlice("pages-branch")
 	mainDomainSuffix := ctx.String("pages-domain")
-	rawInfoPage := ctx.String("raw-info-page")
 	listeningHost := ctx.String("host")
 	listeningSSLPort := ctx.Uint("port")
 	listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, listeningSSLPort)
@@ -137,7 +136,6 @@ func Serve(ctx *cli.Context) error {
 	// Create ssl handler based on settings
 	sslHandler := handler.Handler(mainDomainSuffix, rawDomain,
 		giteaClient,
-		rawInfoPage,
 		BlacklistedPaths, allowedCorsDomains,
 		defaultBranches,
 		dnsLookupCache, canonicalDomainCache, redirectsCache)
diff --git a/server/handler/handler.go b/server/handler/handler.go
index a3011f3..7da5d39 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -21,7 +21,6 @@ const (
 // Handler handles a single HTTP request to the web server.
 func Handler(mainDomainSuffix, rawDomain string,
 	giteaClient *gitea.Client,
-	rawInfoPage string,
 	blacklistedPaths, allowedCorsDomains []string,
 	defaultPagesBranches []string,
 	dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey,
@@ -89,7 +88,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 		if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) {
 			log.Debug().Msg("raw domain request detected")
 			handleRaw(log, ctx, giteaClient,
-				mainDomainSuffix, rawInfoPage,
+				mainDomainSuffix,
 				trimmedHost,
 				pathElements,
 				canonicalDomainCache, redirectsCache)
diff --git a/server/handler/handler_raw_domain.go b/server/handler/handler_raw_domain.go
index b87991e..caa8209 100644
--- a/server/handler/handler_raw_domain.go
+++ b/server/handler/handler_raw_domain.go
@@ -16,7 +16,7 @@ import (
 )
 
 func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
-	mainDomainSuffix, rawInfoPage string,
+	mainDomainSuffix string,
 	trimmedHost string,
 	pathElements []string,
 	canonicalDomainCache, redirectsCache cache.SetGetKey,
@@ -25,8 +25,12 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 	log.Debug().Msg("raw domain")
 
 	if len(pathElements) < 2 {
-		// https://{RawDomain}/{owner}/{repo}[/@{branch}]/{path} is required
-		ctx.Redirect(rawInfoPage, http.StatusTemporaryRedirect)
+		html.ReturnErrorPage(
+			ctx,
+			"a url in the form of <code>https://{domain}/{owner}/{repo}[/@{branch}]/{path}</code> is required",
+			http.StatusBadRequest,
+		)
+
 		return
 	}
 
diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index 6521633..d04ebda 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -16,7 +16,6 @@ func TestHandlerPerformance(t *testing.T) {
 	testHandler := Handler(
 		"codeberg.page", "raw.codeberg.org",
 		giteaClient,
-		"https://docs.codeberg.org/pages/raw-content/",
 		[]string{"/.well-known/acme-challenge/"},
 		[]string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
 		[]string{"pages"},

From dd5124912ecde8246bdcaad223ecde7737928429 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 17 Nov 2023 21:46:52 +0000
Subject: [PATCH 092/196] CI: run on pull only once (#264)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/264
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
---
 .woodpecker.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 759e2f1..de8341c 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,3 +1,6 @@
+when:
+  branch: main
+
 steps:
   # use vendor to cache dependencies
   vendor:

From 15916444e18c71c066ffe8a7cd37cf272ec115eb Mon Sep 17 00:00:00 2001
From: Gusted <postmaster@gusted.xyz>
Date: Thu, 18 Jan 2024 14:31:46 +0000
Subject: [PATCH 093/196] Fix panic in formatting not found message (#276)

Fix panic that was found in the logs, `targetOpt` is `nil`:

http2: panic serving 10.0.3.1:[...]: runtime error: invalid memory address or nil pointer dereference
net/http.(*http2serverConn).runHandler.func1()
        /usr/local/go/src/net/http/h2_bundle.go:6104 +0x145
panic({0x19c6820, 0x2d66db0})
        /usr/local/go/src/runtime/panic.go:884 +0x213
codeberg.org/codeberg/pages/server/handler.handleSubDomain({{0x2008c68, 0xc00047df90}, 0x2, {0x0, 0x0}, {0xc0fe3ef800, 0x55, 0x1f4}, {0xc00047dfa0, 0x1, ...}, ...}, ...)
        /woodpecker/src/codeberg.org/Codeberg/pages-server/server/handler/handler_sub_domain.go:59 +0x5e0

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/276
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
---
 server/handler/handler_sub_domain.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index e7cb3a6..6c14393 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -57,7 +57,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		} else {
 			html.ReturnErrorPage(
 				ctx,
-				formatSetBranchNotFoundMessage(targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
+				formatSetBranchNotFoundMessage(pathElements[1][1:], targetOwner, pathElements[0]),
 				http.StatusFailedDependency,
 			)
 		}
@@ -89,7 +89,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		} else {
 			html.ReturnErrorPage(
 				ctx,
-				formatSetBranchNotFoundMessage(targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
+				formatSetBranchNotFoundMessage(targetBranch, targetOwner, defaultPagesRepo),
 				http.StatusFailedDependency,
 			)
 		}

From a09bee68adf43c2ade9eb3264708f29b68d599ea Mon Sep 17 00:00:00 2001
From: "Panagiotis \"Ivory\" Vasilopoulos" <git@n0toose.net>
Date: Thu, 18 Jan 2024 20:35:32 +0000
Subject: [PATCH 094/196] Meta: Redirect user support to Codeberg/Community
 (#277)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/277
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
Co-committed-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
---
 .gitea/ISSUE_TEMPLATE/config.yml | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .gitea/ISSUE_TEMPLATE/config.yml

diff --git a/.gitea/ISSUE_TEMPLATE/config.yml b/.gitea/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..5a9cce6
--- /dev/null
+++ b/.gitea/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+  - name: Codeberg Pages Usage Support
+    url: https://codeberg.org/Codeberg/Community/issues/
+    about: If you need help with configuring Codeberg Pages on codeberg.org, please go here.

From c1fbe861fe793eb335cab63a524e5ae2bb3fb7f1 Mon Sep 17 00:00:00 2001
From: crapStone <crapstone01@gmail.com>
Date: Sun, 11 Feb 2024 12:43:25 +0000
Subject: [PATCH 095/196] rename gitea to forge in html error messages (#287)

closes #286

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/287
Reviewed-by: Andreas Shimokawa <ashimokawa@noreply.codeberg.org>
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
---
 server/certificates/certificates.go | 2 +-
 server/handler/try.go               | 2 +-
 server/upstream/domains.go          | 2 +-
 server/upstream/upstream.go         | 8 ++++----
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 3ae891a..4fb99fa 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -199,7 +199,7 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
 
 func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user string, useDnsProvider bool, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
 	name := strings.TrimPrefix(domains[0], "*")
-	if useDnsProvider && len(domains[0]) > 0 && domains[0][0] == '*' {
+	if useDnsProvider && domains[0] != "" && domains[0][0] == '*' {
 		domains = domains[1:]
 	}
 
diff --git a/server/handler/try.go b/server/handler/try.go
index 838ae27..bb79dce 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -41,7 +41,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 
 	// Try to request the file from the Gitea API
 	if !options.Upstream(ctx, giteaClient, redirectsCache) {
-		html.ReturnErrorPage(ctx, "gitea client failed", ctx.StatusCode)
+		html.ReturnErrorPage(ctx, "forge client failed", ctx.StatusCode)
 	}
 }
 
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index eb30394..230f268 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -41,7 +41,7 @@ func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain,
 		domain = strings.TrimSpace(domain)
 		domain = strings.TrimPrefix(domain, "http://")
 		domain = strings.TrimPrefix(domain, "https://")
-		if len(domain) > 0 && !strings.HasPrefix(domain, "#") && !strings.ContainsAny(domain, "\t /") && strings.ContainsRune(domain, '.') {
+		if domain != "" && !strings.HasPrefix(domain, "#") && !strings.ContainsAny(domain, "\t /") && strings.ContainsRune(domain, '.') {
 			domains = append(domains, domain)
 		}
 		if domain == actualDomain {
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 1a444e4..4a85503 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -57,7 +57,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
 
 	if o.TargetOwner == "" || o.TargetRepo == "" {
-		html.ReturnErrorPage(ctx, "gitea client: either repo owner or name info is missing", http.StatusBadRequest)
+		html.ReturnErrorPage(ctx, "forge client: either repo owner or name info is missing", http.StatusBadRequest)
 		return true
 	}
 
@@ -153,16 +153,16 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 		var msg string
 
 		if err != nil {
-			msg = "gitea client: returned unexpected error"
+			msg = "forge client: returned unexpected error"
 			log.Error().Err(err).Msg(msg)
 			msg = fmt.Sprintf("%s: '%v'", msg, err)
 		}
 		if reader == nil {
-			msg = "gitea client: returned no reader"
+			msg = "forge client: returned no reader"
 			log.Error().Msg(msg)
 		}
 		if statusCode != http.StatusOK {
-			msg = fmt.Sprintf("gitea client: couldn't fetch contents: <code>%d - %s</code>", statusCode, http.StatusText(statusCode))
+			msg = fmt.Sprintf("forge client: couldn't fetch contents: <code>%d - %s</code>", statusCode, http.StatusText(statusCode))
 			log.Error().Msg(msg)
 		}
 

From 7e80ade24b8aac072804122b343a2a1a70667983 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Thu, 15 Feb 2024 16:08:29 +0000
Subject: [PATCH 096/196] Add config file and rework cli parsing and passing of
 config values (#263)

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/263
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .env-dev                                  |  11 +
 .vscode/launch.json                       |  26 +
 Justfile                                  |  15 +-
 {cmd => cli}/certs.go                     |   6 +-
 {cmd => cli}/flags.go                     |  52 +-
 cli/setup.go                              |  39 ++
 cmd/main.go                               | 150 ------
 cmd/setup.go                              |  64 ---
 config/assets/test_config.toml            |  33 ++
 config/config.go                          |  46 ++
 config/setup.go                           | 147 ++++++
 config/setup_test.go                      | 596 ++++++++++++++++++++++
 example_config.toml                       |  32 ++
 go.mod                                    |   8 +-
 go.sum                                    |  16 +-
 integration/main_test.go                  |  17 +-
 main.go                                   |  20 +-
 server/acme/client.go                     |  26 +
 server/cache/interface.go                 |   3 +-
 server/cache/{setup.go => memory.go}      |   2 +-
 server/certificates/acme_client.go        |  11 +-
 server/certificates/acme_config.go        |  34 +-
 server/certificates/cached_challengers.go |   6 +-
 server/certificates/certificates.go       |   2 +-
 server/dns/dns.go                         |   2 +-
 server/gitea/cache.go                     |   4 +-
 server/gitea/client.go                    |  28 +-
 server/handler/handler.go                 |  30 +-
 server/handler/handler_custom_domain.go   |   2 +-
 server/handler/handler_raw_domain.go      |   2 +-
 server/handler/handler_sub_domain.go      |   2 +-
 server/handler/handler_test.go            |  29 +-
 server/handler/try.go                     |   4 +-
 server/startup.go                         | 141 +++++
 server/upstream/domains.go                |   2 +-
 server/upstream/redirects.go              |   4 +-
 server/upstream/upstream.go               |   2 +-
 37 files changed, 1270 insertions(+), 344 deletions(-)
 create mode 100644 .env-dev
 create mode 100644 .vscode/launch.json
 rename {cmd => cli}/certs.go (92%)
 rename {cmd => cli}/flags.go (80%)
 create mode 100644 cli/setup.go
 delete mode 100644 cmd/main.go
 delete mode 100644 cmd/setup.go
 create mode 100644 config/assets/test_config.toml
 create mode 100644 config/config.go
 create mode 100644 config/setup.go
 create mode 100644 config/setup_test.go
 create mode 100644 example_config.toml
 create mode 100644 server/acme/client.go
 rename server/cache/{setup.go => memory.go} (69%)
 create mode 100644 server/startup.go

diff --git a/.env-dev b/.env-dev
new file mode 100644
index 0000000..2286005
--- /dev/null
+++ b/.env-dev
@@ -0,0 +1,11 @@
+ACME_API=https://acme.mock.directory
+ACME_ACCEPT_TERMS=true
+PAGES_DOMAIN=localhost.mock.directory
+RAW_DOMAIN=raw.localhost.mock.directory
+PAGES_BRANCHES=pages,master,main
+GITEA_ROOT=https://codeberg.org
+PORT=4430
+HTTP_PORT=8880
+ENABLE_HTTP_SERVER=true
+LOG_LEVEL=trace
+ACME_ACCOUNT_CONFIG=integration/acme-account.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..d2cc8d1
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,26 @@
+{
+  // Use IntelliSense to learn about possible attributes.
+  // Hover to view descriptions of existing attributes.
+  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Launch PagesServer",
+      "type": "go",
+      "request": "launch",
+      "mode": "auto",
+      "program": "${workspaceFolder}/main.go",
+      "args": ["sqlite", "sqlite_unlock_notify", "netgo"],
+      "envFile": "${workspaceFolder}/.env-dev"
+    },
+    {
+      "name": "Launch PagesServer integration test",
+      "type": "go",
+      "request": "launch",
+      "mode": "auto",
+      "program": "${workspaceFolder}/integration/main_test.go",
+      "args": ["codeberg.org/codeberg/pages/integration/..."],
+      "buildFlags": ["-tags", "'integration sqlite sqlite_unlock_notify netgo'"]
+    }
+  ]
+}
diff --git a/Justfile b/Justfile
index 0b8f814..0bf38a3 100644
--- a/Justfile
+++ b/Justfile
@@ -4,14 +4,9 @@ TAGS      := 'sqlite sqlite_unlock_notify netgo'
 dev:
     #!/usr/bin/env bash
     set -euxo pipefail
-    export ACME_API=https://acme.mock.directory
-    export ACME_ACCEPT_TERMS=true
-    export PAGES_DOMAIN=localhost.mock.directory
-    export RAW_DOMAIN=raw.localhost.mock.directory
-    export PORT=4430
-    export HTTP_PORT=8880
-    export ENABLE_HTTP_SERVER=true
-    export LOG_LEVEL=trace
+    set -a # automatically export all variables
+    source .env-dev
+    set +a
     go run -tags '{{TAGS}}' .
 
 build:
@@ -42,10 +37,10 @@ tool-gofumpt:
     fi
 
 test:
-    go test -race -cover -tags '{{TAGS}}' codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
+    go test -race -cover -tags '{{TAGS}}' codeberg.org/codeberg/pages/config/ codeberg.org/codeberg/pages/html/ codeberg.org/codeberg/pages/server/...
 
 test-run TEST:
-    go test -race -tags '{{TAGS}}' -run "^{{TEST}}$" codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
+    go test -race -tags '{{TAGS}}' -run "^{{TEST}}$" codeberg.org/codeberg/pages/config/ codeberg.org/codeberg/pages/html/ codeberg.org/codeberg/pages/server/...
 
 integration:
     go test -race -tags 'integration {{TAGS}}' codeberg.org/codeberg/pages/integration/...
diff --git a/cmd/certs.go b/cli/certs.go
similarity index 92%
rename from cmd/certs.go
rename to cli/certs.go
index 6012b6e..76df7f0 100644
--- a/cmd/certs.go
+++ b/cli/certs.go
@@ -1,4 +1,4 @@
-package cmd
+package cli
 
 import (
 	"fmt"
@@ -26,7 +26,7 @@ var Certs = &cli.Command{
 }
 
 func listCerts(ctx *cli.Context) error {
-	certDB, closeFn, err := openCertDB(ctx)
+	certDB, closeFn, err := OpenCertDB(ctx)
 	if err != nil {
 		return err
 	}
@@ -53,7 +53,7 @@ func removeCert(ctx *cli.Context) error {
 
 	domains := ctx.Args().Slice()
 
-	certDB, closeFn, err := openCertDB(ctx)
+	certDB, closeFn, err := OpenCertDB(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/flags.go b/cli/flags.go
similarity index 80%
rename from cmd/flags.go
rename to cli/flags.go
index 7ac94e6..097cf4f 100644
--- a/cmd/flags.go
+++ b/cli/flags.go
@@ -1,4 +1,4 @@
-package cmd
+package cli
 
 import (
 	"github.com/urfave/cli/v2"
@@ -29,26 +29,35 @@ var (
 			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:   "",
 		},
 		&cli.BoolFlag{
 			Name:    "enable-lfs-support",
 			Usage:   "enable lfs support, require gitea >= v1.17.0 as backend",
 			EnvVars: []string{"ENABLE_LFS_SUPPORT"},
-			Value:   true,
+			Value:   false,
 		},
 		&cli.BoolFlag{
 			Name:    "enable-symlink-support",
 			Usage:   "follow symlinks if enabled, require gitea >= v1.18.0 as backend",
 			EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
-			Value:   true,
+			Value:   false,
+		},
+		&cli.StringFlag{
+			Name:    "default-mime-type",
+			Usage:   "specifies the default mime type for files that don't have a specific mime type.",
+			EnvVars: []string{"DEFAULT_MIME_TYPE"},
+			Value:   "application/octet-stream",
+		},
+		&cli.StringSliceFlag{
+			Name:    "forbidden-mime-types",
+			Usage:   "specifies the forbidden mime types. Use this flag multiple times for multiple mime types.",
+			EnvVars: []string{"FORBIDDEN_MIME_TYPES"},
 		},
 
 		// ###########################
@@ -61,7 +70,6 @@ var (
 			Name:    "pages-domain",
 			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",
 		},
 		// 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...}
@@ -70,7 +78,6 @@ var (
 			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.page",
 		},
 
 		// #########################
@@ -98,19 +105,38 @@ var (
 			Name:    "enable-http-server",
 			Usage:   "start a http server to redirect to https and respond to http acme challenges",
 			EnvVars: []string{"ENABLE_HTTP_SERVER"},
+			Value:   false,
 		},
+		// Default branches to fetch assets from
+		&cli.StringSliceFlag{
+			Name:    "pages-branch",
+			Usage:   "define a branch to fetch assets from. Use this flag multiple times for multiple branches.",
+			EnvVars: []string{"PAGES_BRANCHES"},
+			Value:   cli.NewStringSlice("pages"),
+		},
+
+		&cli.StringSliceFlag{
+			Name:    "allowed-cors-domains",
+			Usage:   "specify allowed CORS domains. Use this flag multiple times for multiple domains.",
+			EnvVars: []string{"ALLOWED_CORS_DOMAINS"},
+		},
+		&cli.StringSliceFlag{
+			Name:    "blacklisted-paths",
+			Usage:   "return an error on these url paths.Use this flag multiple times for multiple paths.",
+			EnvVars: []string{"BLACKLISTED_PATHS"},
+		},
+
 		&cli.StringFlag{
 			Name:    "log-level",
 			Value:   "warn",
 			Usage:   "specify at which log level should be logged. Possible options: info, warn, error, fatal",
 			EnvVars: []string{"LOG_LEVEL"},
 		},
-		// Default branches to fetch assets from
-		&cli.StringSliceFlag{
-			Name:    "pages-branch",
-			Usage:   "define a branch to fetch assets from",
-			EnvVars: []string{"PAGES_BRANCHES"},
-			Value:   cli.NewStringSlice("pages"),
+		&cli.StringFlag{
+			Name:    "config-file",
+			Usage:   "specify the location of the config file",
+			Aliases: []string{"config"},
+			EnvVars: []string{"CONFIG_FILE"},
 		},
 
 		// ############################
diff --git a/cli/setup.go b/cli/setup.go
new file mode 100644
index 0000000..6bbff40
--- /dev/null
+++ b/cli/setup.go
@@ -0,0 +1,39 @@
+package cli
+
+import (
+	"fmt"
+
+	"github.com/rs/zerolog/log"
+	"github.com/urfave/cli/v2"
+
+	"codeberg.org/codeberg/pages/server/database"
+	"codeberg.org/codeberg/pages/server/version"
+)
+
+func CreatePagesApp() *cli.App {
+	app := cli.NewApp()
+	app.Name = "pages-server"
+	app.Version = version.Version
+	app.Usage = "pages server"
+	app.Flags = ServerFlags
+	app.Commands = []*cli.Command{
+		Certs,
+	}
+
+	return app
+}
+
+func OpenCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
+	certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
+	if err != nil {
+		return nil, nil, fmt.Errorf("could not connect to database: %w", err)
+	}
+
+	closeFn = func() {
+		if err := certDB.Close(); err != nil {
+			log.Error().Err(err)
+		}
+	}
+
+	return certDB, closeFn, nil
+}
diff --git a/cmd/main.go b/cmd/main.go
deleted file mode 100644
index 683e859..0000000
--- a/cmd/main.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package cmd
-
-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"
-
-	"codeberg.org/codeberg/pages/server/cache"
-	"codeberg.org/codeberg/pages/server/certificates"
-	"codeberg.org/codeberg/pages/server/gitea"
-	"codeberg.org/codeberg/pages/server/handler"
-)
-
-// AllowedCorsDomains lists the domains for which Cross-Origin Resource Sharing is allowed.
-// TODO: make it a flag
-var AllowedCorsDomains = []string{
-	"fonts.codeberg.org",
-	"design.codeberg.org",
-}
-
-// BlacklistedPaths specifies forbidden path prefixes for all Codeberg Pages.
-// TODO: Make it a flag too
-var BlacklistedPaths = []string{
-	"/.well-known/acme-challenge/",
-}
-
-// Serve sets up and starts the web server.
-func Serve(ctx *cli.Context) error {
-	// Initialize the logger.
-	logLevel, err := zerolog.ParseLevel(ctx.String("log-level"))
-	if err != nil {
-		return err
-	}
-	log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel)
-
-	giteaRoot := ctx.String("gitea-root")
-	giteaAPIToken := ctx.String("gitea-api-token")
-	rawDomain := ctx.String("raw-domain")
-	defaultBranches := ctx.StringSlice("pages-branch")
-	mainDomainSuffix := ctx.String("pages-domain")
-	listeningHost := ctx.String("host")
-	listeningSSLPort := ctx.Uint("port")
-	listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, listeningSSLPort)
-	listeningHTTPAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("http-port"))
-	enableHTTPServer := ctx.Bool("enable-http-server")
-
-	allowedCorsDomains := AllowedCorsDomains
-	if rawDomain != "" {
-		allowedCorsDomains = append(allowedCorsDomains, rawDomain)
-	}
-
-	// Make sure MainDomain has a trailing dot
-	if !strings.HasPrefix(mainDomainSuffix, ".") {
-		mainDomainSuffix = "." + mainDomainSuffix
-	}
-
-	if len(defaultBranches) == 0 {
-		return fmt.Errorf("no default branches set (PAGES_BRANCHES)")
-	}
-
-	// Init ssl cert database
-	certDB, closeFn, err := openCertDB(ctx)
-	if err != nil {
-		return err
-	}
-	defer closeFn()
-
-	keyCache := cache.NewKeyValueCache()
-	challengeCache := cache.NewKeyValueCache()
-	// canonicalDomainCache stores canonical domains
-	canonicalDomainCache := cache.NewKeyValueCache()
-	// dnsLookupCache stores DNS lookups for custom domains
-	dnsLookupCache := cache.NewKeyValueCache()
-	// redirectsCache stores redirects in _redirects files
-	redirectsCache := cache.NewKeyValueCache()
-	// clientResponseCache stores responses from the Gitea server
-	clientResponseCache := cache.NewKeyValueCache()
-
-	giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken, clientResponseCache, ctx.Bool("enable-symlink-support"), ctx.Bool("enable-lfs-support"))
-	if err != nil {
-		return fmt.Errorf("could not create new gitea client: %v", err)
-	}
-
-	acmeClient, err := createAcmeClient(ctx, enableHTTPServer, challengeCache)
-	if err != nil {
-		return err
-	}
-
-	if err := certificates.SetupMainDomainCertificates(mainDomainSuffix, 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(mainDomainSuffix,
-		giteaClient,
-		acmeClient,
-		defaultBranches[0],
-		keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
-		certDB))
-
-	interval := 12 * time.Hour
-	certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background())
-	defer cancelCertMaintain()
-	go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, mainDomainSuffix, certDB)
-
-	if enableHTTPServer {
-		// Create handler for http->https redirect and http acme challenges
-		httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache, listeningSSLPort)
-
-		// 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.Panic().Err(err).Msg("Couldn't start HTTP fastServer")
-			}
-		}()
-	}
-
-	// Create ssl handler based on settings
-	sslHandler := handler.Handler(mainDomainSuffix, rawDomain,
-		giteaClient,
-		BlacklistedPaths, allowedCorsDomains,
-		defaultBranches,
-		dnsLookupCache, canonicalDomainCache, redirectsCache)
-
-	// Start the ssl listener
-	log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())
-	if err := http.Serve(listener, sslHandler); err != nil {
-		log.Panic().Err(err).Msg("Couldn't start fastServer")
-	}
-
-	return nil
-}
diff --git a/cmd/setup.go b/cmd/setup.go
deleted file mode 100644
index cde4bc9..0000000
--- a/cmd/setup.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package cmd
-
-import (
-	"errors"
-	"fmt"
-
-	"github.com/rs/zerolog/log"
-	"github.com/urfave/cli/v2"
-
-	"codeberg.org/codeberg/pages/server/cache"
-	"codeberg.org/codeberg/pages/server/certificates"
-	"codeberg.org/codeberg/pages/server/database"
-)
-
-var ErrAcmeMissConfig = errors.New("ACME client has wrong config")
-
-func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
-	certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
-	if err != nil {
-		return nil, nil, fmt.Errorf("could not connect to database: %w", err)
-	}
-
-	closeFn = func() {
-		if err := certDB.Close(); err != nil {
-			log.Error().Err(err)
-		}
-	}
-
-	return certDB, closeFn, nil
-}
-
-func createAcmeClient(ctx *cli.Context, enableHTTPServer bool, challengeCache cache.SetGetKey) (*certificates.AcmeClient, error) {
-	acmeAPI := ctx.String("acme-api-endpoint")
-	acmeMail := ctx.String("acme-email")
-	acmeEabHmac := ctx.String("acme-eab-hmac")
-	acmeEabKID := ctx.String("acme-eab-kid")
-	acmeAcceptTerms := ctx.Bool("acme-accept-terms")
-	dnsProvider := ctx.String("dns-provider")
-	acmeUseRateLimits := ctx.Bool("acme-use-rate-limits")
-	acmeAccountConf := ctx.String("acme-account-config")
-
-	// check config
-	if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" {
-		return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
-	}
-	if acmeEabHmac != "" && acmeEabKID == "" {
-		return nil, fmt.Errorf("%w: ACME_EAB_HMAC also needs ACME_EAB_KID to be set", ErrAcmeMissConfig)
-	} else if acmeEabHmac == "" && acmeEabKID != "" {
-		return nil, fmt.Errorf("%w: ACME_EAB_KID also needs ACME_EAB_HMAC to be set", ErrAcmeMissConfig)
-	}
-
-	return certificates.NewAcmeClient(
-		acmeAccountConf,
-		acmeAPI,
-		acmeMail,
-		acmeEabHmac,
-		acmeEabKID,
-		dnsProvider,
-		acmeAcceptTerms,
-		enableHTTPServer,
-		acmeUseRateLimits,
-		challengeCache,
-	)
-}
diff --git a/config/assets/test_config.toml b/config/assets/test_config.toml
new file mode 100644
index 0000000..6a2f0d0
--- /dev/null
+++ b/config/assets/test_config.toml
@@ -0,0 +1,33 @@
+logLevel = 'trace'
+
+[server]
+host = '127.0.0.1'
+port = 443
+httpPort = 80
+httpServerEnabled = true
+mainDomain = 'codeberg.page'
+rawDomain = 'raw.codeberg.page'
+allowedCorsDomains = ['fonts.codeberg.org', 'design.codeberg.org']
+blacklistedPaths = ['do/not/use']
+
+[gitea]
+root = 'codeberg.org'
+token = 'XXXXXXXX'
+lfsEnabled = true
+followSymlinks = true
+defaultMimeType = "application/wasm"
+forbiddenMimeTypes = ["text/html"]
+
+[database]
+type = 'sqlite'
+conn = 'certs.sqlite'
+
+[ACME]
+email = 'a@b.c'
+apiEndpoint = 'https://example.com'
+acceptTerms = false
+useRateLimits = true
+eab_hmac = 'asdf'
+eab_kid = 'qwer'
+dnsProvider = 'cloudflare.com'
+accountConfigFile = 'nope'
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..6cb972b
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,46 @@
+package config
+
+type Config struct {
+	LogLevel string `default:"warn"`
+	Server   ServerConfig
+	Gitea    GiteaConfig
+	Database DatabaseConfig
+	ACME     ACMEConfig
+}
+
+type ServerConfig struct {
+	Host               string `default:"[::]"`
+	Port               uint16 `default:"443"`
+	HttpPort           uint16 `default:"80"`
+	HttpServerEnabled  bool   `default:"true"`
+	MainDomain         string
+	RawDomain          string
+	PagesBranches      []string
+	AllowedCorsDomains []string
+	BlacklistedPaths   []string
+}
+
+type GiteaConfig struct {
+	Root               string
+	Token              string
+	LFSEnabled         bool   `default:"false"`
+	FollowSymlinks     bool   `default:"false"`
+	DefaultMimeType    string `default:"application/octet-stream"`
+	ForbiddenMimeTypes []string
+}
+
+type DatabaseConfig struct {
+	Type string `default:"sqlite3"`
+	Conn string `default:"certs.sqlite"`
+}
+
+type ACMEConfig struct {
+	Email             string
+	APIEndpoint       string `default:"https://acme-v02.api.letsencrypt.org/directory"`
+	AcceptTerms       bool   `default:"false"`
+	UseRateLimits     bool   `default:"true"`
+	EAB_HMAC          string
+	EAB_KID           string
+	DNSProvider       string
+	AccountConfigFile string `default:"acme-account.json"`
+}
diff --git a/config/setup.go b/config/setup.go
new file mode 100644
index 0000000..e774084
--- /dev/null
+++ b/config/setup.go
@@ -0,0 +1,147 @@
+package config
+
+import (
+	"os"
+	"path"
+
+	"github.com/creasty/defaults"
+	"github.com/pelletier/go-toml/v2"
+	"github.com/rs/zerolog/log"
+	"github.com/urfave/cli/v2"
+)
+
+var ALWAYS_BLACKLISTED_PATHS = []string{
+	"/.well-known/acme-challenge/",
+}
+
+func NewDefaultConfig() Config {
+	config := Config{}
+	if err := defaults.Set(&config); err != nil {
+		panic(err)
+	}
+
+	// defaults does not support setting arrays from strings
+	config.Server.PagesBranches = []string{"main", "master", "pages"}
+
+	return config
+}
+
+func ReadConfig(ctx *cli.Context) (*Config, error) {
+	config := NewDefaultConfig()
+	// if config is not given as argument return empty config
+	if !ctx.IsSet("config-file") {
+		return &config, nil
+	}
+
+	configFile := path.Clean(ctx.String("config-file"))
+
+	log.Debug().Str("config-file", configFile).Msg("reading config file")
+	content, err := os.ReadFile(configFile)
+	if err != nil {
+		return nil, err
+	}
+
+	err = toml.Unmarshal(content, &config)
+	return &config, err
+}
+
+func MergeConfig(ctx *cli.Context, config *Config) {
+	if ctx.IsSet("log-level") {
+		config.LogLevel = ctx.String("log-level")
+	}
+
+	mergeServerConfig(ctx, &config.Server)
+	mergeGiteaConfig(ctx, &config.Gitea)
+	mergeDatabaseConfig(ctx, &config.Database)
+	mergeACMEConfig(ctx, &config.ACME)
+}
+
+func mergeServerConfig(ctx *cli.Context, config *ServerConfig) {
+	if ctx.IsSet("host") {
+		config.Host = ctx.String("host")
+	}
+	if ctx.IsSet("port") {
+		config.Port = uint16(ctx.Uint("port"))
+	}
+	if ctx.IsSet("http-port") {
+		config.HttpPort = uint16(ctx.Uint("http-port"))
+	}
+	if ctx.IsSet("enable-http-server") {
+		config.HttpServerEnabled = ctx.Bool("enable-http-server")
+	}
+	if ctx.IsSet("pages-domain") {
+		config.MainDomain = ctx.String("pages-domain")
+	}
+	if ctx.IsSet("raw-domain") {
+		config.RawDomain = ctx.String("raw-domain")
+	}
+	if ctx.IsSet("pages-branch") {
+		config.PagesBranches = ctx.StringSlice("pages-branch")
+	}
+	if ctx.IsSet("allowed-cors-domains") {
+		config.AllowedCorsDomains = ctx.StringSlice("allowed-cors-domains")
+	}
+	if ctx.IsSet("blacklisted-paths") {
+		config.BlacklistedPaths = ctx.StringSlice("blacklisted-paths")
+	}
+
+	// add the paths that should always be blacklisted
+	config.BlacklistedPaths = append(config.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
+}
+
+func mergeGiteaConfig(ctx *cli.Context, config *GiteaConfig) {
+	if ctx.IsSet("gitea-root") {
+		config.Root = ctx.String("gitea-root")
+	}
+	if ctx.IsSet("gitea-api-token") {
+		config.Token = ctx.String("gitea-api-token")
+	}
+	if ctx.IsSet("enable-lfs-support") {
+		config.LFSEnabled = ctx.Bool("enable-lfs-support")
+	}
+	if ctx.IsSet("enable-symlink-support") {
+		config.FollowSymlinks = ctx.Bool("enable-symlink-support")
+	}
+	if ctx.IsSet("default-mime-type") {
+		config.DefaultMimeType = ctx.String("default-mime-type")
+	}
+	if ctx.IsSet("forbidden-mime-types") {
+		config.ForbiddenMimeTypes = ctx.StringSlice("forbidden-mime-types")
+	}
+}
+
+func mergeDatabaseConfig(ctx *cli.Context, config *DatabaseConfig) {
+	if ctx.IsSet("db-type") {
+		config.Type = ctx.String("db-type")
+	}
+	if ctx.IsSet("db-conn") {
+		config.Conn = ctx.String("db-conn")
+	}
+}
+
+func mergeACMEConfig(ctx *cli.Context, config *ACMEConfig) {
+	if ctx.IsSet("acme-email") {
+		config.Email = ctx.String("acme-email")
+	}
+	if ctx.IsSet("acme-api-endpoint") {
+		config.APIEndpoint = ctx.String("acme-api-endpoint")
+	}
+	if ctx.IsSet("acme-accept-terms") {
+		config.AcceptTerms = ctx.Bool("acme-accept-terms")
+	}
+	if ctx.IsSet("acme-use-rate-limits") {
+		config.UseRateLimits = ctx.Bool("acme-use-rate-limits")
+	}
+	if ctx.IsSet("acme-eab-hmac") {
+		config.EAB_HMAC = ctx.String("acme-eab-hmac")
+	}
+	if ctx.IsSet("acme-eab-kid") {
+		config.EAB_KID = ctx.String("acme-eab-kid")
+	}
+	if ctx.IsSet("dns-provider") {
+		config.DNSProvider = ctx.String("dns-provider")
+	}
+	if ctx.IsSet("acme-account-config") {
+		config.AccountConfigFile = ctx.String("acme-account-config")
+	}
+}
diff --git a/config/setup_test.go b/config/setup_test.go
new file mode 100644
index 0000000..a863e2f
--- /dev/null
+++ b/config/setup_test.go
@@ -0,0 +1,596 @@
+package config
+
+import (
+	"context"
+	"os"
+	"testing"
+
+	"github.com/pelletier/go-toml/v2"
+	"github.com/stretchr/testify/assert"
+	"github.com/urfave/cli/v2"
+
+	cmd "codeberg.org/codeberg/pages/cli"
+)
+
+func runApp(t *testing.T, fn func(*cli.Context) error, args []string) {
+	app := cmd.CreatePagesApp()
+	app.Action = fn
+
+	appCtx, appCancel := context.WithCancel(context.Background())
+	defer appCancel()
+
+	// os.Args always contains the binary name
+	args = append([]string{"testing"}, args...)
+
+	err := app.RunContext(appCtx, args)
+	assert.NoError(t, err)
+}
+
+// fixArrayFromCtx fixes the number of "changed" strings in a string slice according to the number of values in the context.
+// This is a workaround because the cli library has a bug where the number of values in the context gets bigger the more tests are run.
+func fixArrayFromCtx(ctx *cli.Context, key string, expected []string) []string {
+	if ctx.IsSet(key) {
+		ctxSlice := ctx.StringSlice(key)
+
+		if len(ctxSlice) > 1 {
+			for i := 1; i < len(ctxSlice); i++ {
+				expected = append([]string{"changed"}, expected...)
+			}
+		}
+	}
+
+	return expected
+}
+
+func readTestConfig() (*Config, error) {
+	content, err := os.ReadFile("assets/test_config.toml")
+	if err != nil {
+		return nil, err
+	}
+
+	expectedConfig := NewDefaultConfig()
+	err = toml.Unmarshal(content, &expectedConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	return &expectedConfig, nil
+}
+
+func TestReadConfigShouldReturnEmptyConfigWhenConfigArgEmpty(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg, err := ReadConfig(ctx)
+			expected := NewDefaultConfig()
+			assert.Equal(t, &expected, cfg)
+
+			return err
+		},
+		[]string{},
+	)
+}
+
+func TestReadConfigShouldReturnConfigFromFileWhenConfigArgPresent(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg, err := ReadConfig(ctx)
+			if err != nil {
+				return err
+			}
+
+			expectedConfig, err := readTestConfig()
+			if err != nil {
+				return err
+			}
+
+			assert.Equal(t, expectedConfig, cfg)
+
+			return nil
+		},
+		[]string{"--config-file", "assets/test_config.toml"},
+	)
+}
+
+func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg, err := ReadConfig(ctx)
+			if err != nil {
+				return err
+			}
+
+			MergeConfig(ctx, cfg)
+
+			expectedConfig, err := readTestConfig()
+			if err != nil {
+				return err
+			}
+
+			expectedConfig.LogLevel = "debug"
+			expectedConfig.Gitea.Root = "not-codeberg.org"
+			expectedConfig.ACME.AcceptTerms = true
+			expectedConfig.Server.Host = "172.17.0.2"
+			expectedConfig.Server.BlacklistedPaths = append(expectedConfig.Server.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
+
+			assert.Equal(t, expectedConfig, cfg)
+
+			return nil
+		},
+		[]string{
+			"--config-file", "assets/test_config.toml",
+			"--log-level", "debug",
+			"--gitea-root", "not-codeberg.org",
+			"--acme-accept-terms",
+			"--host", "172.17.0.2",
+		},
+	)
+}
+
+func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg := &Config{
+				LogLevel: "original",
+				Server: ServerConfig{
+					Host:               "original",
+					Port:               8080,
+					HttpPort:           80,
+					HttpServerEnabled:  false,
+					MainDomain:         "original",
+					RawDomain:          "original",
+					PagesBranches:      []string{"original"},
+					AllowedCorsDomains: []string{"original"},
+					BlacklistedPaths:   []string{"original"},
+				},
+				Gitea: GiteaConfig{
+					Root:               "original",
+					Token:              "original",
+					LFSEnabled:         false,
+					FollowSymlinks:     false,
+					DefaultMimeType:    "original",
+					ForbiddenMimeTypes: []string{"original"},
+				},
+				Database: DatabaseConfig{
+					Type: "original",
+					Conn: "original",
+				},
+				ACME: ACMEConfig{
+					Email:             "original",
+					APIEndpoint:       "original",
+					AcceptTerms:       false,
+					UseRateLimits:     false,
+					EAB_HMAC:          "original",
+					EAB_KID:           "original",
+					DNSProvider:       "original",
+					AccountConfigFile: "original",
+				},
+			}
+
+			MergeConfig(ctx, cfg)
+
+			expectedConfig := &Config{
+				LogLevel: "changed",
+				Server: ServerConfig{
+					Host:               "changed",
+					Port:               8443,
+					HttpPort:           443,
+					HttpServerEnabled:  true,
+					MainDomain:         "changed",
+					RawDomain:          "changed",
+					PagesBranches:      []string{"changed"},
+					AllowedCorsDomains: []string{"changed"},
+					BlacklistedPaths:   append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...),
+				},
+				Gitea: GiteaConfig{
+					Root:               "changed",
+					Token:              "changed",
+					LFSEnabled:         true,
+					FollowSymlinks:     true,
+					DefaultMimeType:    "changed",
+					ForbiddenMimeTypes: []string{"changed"},
+				},
+				Database: DatabaseConfig{
+					Type: "changed",
+					Conn: "changed",
+				},
+				ACME: ACMEConfig{
+					Email:             "changed",
+					APIEndpoint:       "changed",
+					AcceptTerms:       true,
+					UseRateLimits:     true,
+					EAB_HMAC:          "changed",
+					EAB_KID:           "changed",
+					DNSProvider:       "changed",
+					AccountConfigFile: "changed",
+				},
+			}
+
+			assert.Equal(t, expectedConfig, cfg)
+
+			return nil
+		},
+		[]string{
+			"--log-level", "changed",
+			// Server
+			"--pages-domain", "changed",
+			"--raw-domain", "changed",
+			"--allowed-cors-domains", "changed",
+			"--blacklisted-paths", "changed",
+			"--pages-branch", "changed",
+			"--host", "changed",
+			"--port", "8443",
+			"--http-port", "443",
+			"--enable-http-server",
+			// Gitea
+			"--gitea-root", "changed",
+			"--gitea-api-token", "changed",
+			"--enable-lfs-support",
+			"--enable-symlink-support",
+			"--default-mime-type", "changed",
+			"--forbidden-mime-types", "changed",
+			// Database
+			"--db-type", "changed",
+			"--db-conn", "changed",
+			// ACME
+			"--acme-email", "changed",
+			"--acme-api-endpoint", "changed",
+			"--acme-accept-terms",
+			"--acme-use-rate-limits",
+			"--acme-eab-hmac", "changed",
+			"--acme-eab-kid", "changed",
+			"--dns-provider", "changed",
+			"--acme-account-config", "changed",
+		},
+	)
+}
+
+func TestMergeServerConfigShouldAddDefaultBlacklistedPathsToBlacklistedPaths(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg := &ServerConfig{}
+			mergeServerConfig(ctx, cfg)
+
+			expected := ALWAYS_BLACKLISTED_PATHS
+			assert.Equal(t, expected, cfg.BlacklistedPaths)
+
+			return nil
+		},
+		[]string{},
+	)
+}
+
+func TestMergeServerConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+	for range []uint8{0, 1} {
+		runApp(
+			t,
+			func(ctx *cli.Context) error {
+				cfg := &ServerConfig{
+					Host:               "original",
+					Port:               8080,
+					HttpPort:           80,
+					HttpServerEnabled:  false,
+					MainDomain:         "original",
+					RawDomain:          "original",
+					AllowedCorsDomains: []string{"original"},
+					BlacklistedPaths:   []string{"original"},
+				}
+
+				mergeServerConfig(ctx, cfg)
+
+				expectedConfig := &ServerConfig{
+					Host:               "changed",
+					Port:               8443,
+					HttpPort:           443,
+					HttpServerEnabled:  true,
+					MainDomain:         "changed",
+					RawDomain:          "changed",
+					AllowedCorsDomains: fixArrayFromCtx(ctx, "allowed-cors-domains", []string{"changed"}),
+					BlacklistedPaths:   fixArrayFromCtx(ctx, "blacklisted-paths", append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...)),
+				}
+
+				assert.Equal(t, expectedConfig, cfg)
+
+				return nil
+			},
+			[]string{
+				"--pages-domain", "changed",
+				"--raw-domain", "changed",
+				"--allowed-cors-domains", "changed",
+				"--blacklisted-paths", "changed",
+				"--host", "changed",
+				"--port", "8443",
+				"--http-port", "443",
+				"--enable-http-server",
+			},
+		)
+	}
+}
+
+func TestMergeServerConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+	type testValuePair struct {
+		args     []string
+		callback func(*ServerConfig)
+	}
+	testValuePairs := []testValuePair{
+		{args: []string{"--host", "changed"}, callback: func(sc *ServerConfig) { sc.Host = "changed" }},
+		{args: []string{"--port", "8443"}, callback: func(sc *ServerConfig) { sc.Port = 8443 }},
+		{args: []string{"--http-port", "443"}, callback: func(sc *ServerConfig) { sc.HttpPort = 443 }},
+		{args: []string{"--enable-http-server"}, callback: func(sc *ServerConfig) { sc.HttpServerEnabled = true }},
+		{args: []string{"--pages-domain", "changed"}, callback: func(sc *ServerConfig) { sc.MainDomain = "changed" }},
+		{args: []string{"--raw-domain", "changed"}, callback: func(sc *ServerConfig) { sc.RawDomain = "changed" }},
+		{args: []string{"--pages-branch", "changed"}, callback: func(sc *ServerConfig) { sc.PagesBranches = []string{"changed"} }},
+		{args: []string{"--allowed-cors-domains", "changed"}, callback: func(sc *ServerConfig) { sc.AllowedCorsDomains = []string{"changed"} }},
+		{args: []string{"--blacklisted-paths", "changed"}, callback: func(sc *ServerConfig) { sc.BlacklistedPaths = []string{"changed"} }},
+	}
+
+	for _, pair := range testValuePairs {
+		runApp(
+			t,
+			func(ctx *cli.Context) error {
+				cfg := ServerConfig{
+					Host:               "original",
+					Port:               8080,
+					HttpPort:           80,
+					HttpServerEnabled:  false,
+					MainDomain:         "original",
+					RawDomain:          "original",
+					PagesBranches:      []string{"original"},
+					AllowedCorsDomains: []string{"original"},
+					BlacklistedPaths:   []string{"original"},
+				}
+
+				expectedConfig := cfg
+				pair.callback(&expectedConfig)
+				expectedConfig.BlacklistedPaths = append(expectedConfig.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
+
+				expectedConfig.PagesBranches = fixArrayFromCtx(ctx, "pages-branch", expectedConfig.PagesBranches)
+				expectedConfig.AllowedCorsDomains = fixArrayFromCtx(ctx, "allowed-cors-domains", expectedConfig.AllowedCorsDomains)
+				expectedConfig.BlacklistedPaths = fixArrayFromCtx(ctx, "blacklisted-paths", expectedConfig.BlacklistedPaths)
+
+				mergeServerConfig(ctx, &cfg)
+
+				assert.Equal(t, expectedConfig, cfg)
+
+				return nil
+			},
+			pair.args,
+		)
+	}
+}
+
+func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg := &GiteaConfig{
+				Root:               "original",
+				Token:              "original",
+				LFSEnabled:         false,
+				FollowSymlinks:     false,
+				DefaultMimeType:    "original",
+				ForbiddenMimeTypes: []string{"original"},
+			}
+
+			mergeGiteaConfig(ctx, cfg)
+
+			expectedConfig := &GiteaConfig{
+				Root:               "changed",
+				Token:              "changed",
+				LFSEnabled:         true,
+				FollowSymlinks:     true,
+				DefaultMimeType:    "changed",
+				ForbiddenMimeTypes: fixArrayFromCtx(ctx, "forbidden-mime-types", []string{"changed"}),
+			}
+
+			assert.Equal(t, expectedConfig, cfg)
+
+			return nil
+		},
+		[]string{
+			"--gitea-root", "changed",
+			"--gitea-api-token", "changed",
+			"--enable-lfs-support",
+			"--enable-symlink-support",
+			"--default-mime-type", "changed",
+			"--forbidden-mime-types", "changed",
+		},
+	)
+}
+
+func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+	type testValuePair struct {
+		args     []string
+		callback func(*GiteaConfig)
+	}
+	testValuePairs := []testValuePair{
+		{args: []string{"--gitea-root", "changed"}, callback: func(gc *GiteaConfig) { gc.Root = "changed" }},
+		{args: []string{"--gitea-api-token", "changed"}, callback: func(gc *GiteaConfig) { gc.Token = "changed" }},
+		{args: []string{"--enable-lfs-support"}, callback: func(gc *GiteaConfig) { gc.LFSEnabled = true }},
+		{args: []string{"--enable-symlink-support"}, callback: func(gc *GiteaConfig) { gc.FollowSymlinks = true }},
+		{args: []string{"--default-mime-type", "changed"}, callback: func(gc *GiteaConfig) { gc.DefaultMimeType = "changed" }},
+		{args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *GiteaConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }},
+	}
+
+	for _, pair := range testValuePairs {
+		runApp(
+			t,
+			func(ctx *cli.Context) error {
+				cfg := GiteaConfig{
+					Root:               "original",
+					Token:              "original",
+					LFSEnabled:         false,
+					FollowSymlinks:     false,
+					DefaultMimeType:    "original",
+					ForbiddenMimeTypes: []string{"original"},
+				}
+
+				expectedConfig := cfg
+				pair.callback(&expectedConfig)
+
+				mergeGiteaConfig(ctx, &cfg)
+
+				expectedConfig.ForbiddenMimeTypes = fixArrayFromCtx(ctx, "forbidden-mime-types", expectedConfig.ForbiddenMimeTypes)
+
+				assert.Equal(t, expectedConfig, cfg)
+
+				return nil
+			},
+			pair.args,
+		)
+	}
+}
+
+func TestMergeDatabaseConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg := &DatabaseConfig{
+				Type: "original",
+				Conn: "original",
+			}
+
+			mergeDatabaseConfig(ctx, cfg)
+
+			expectedConfig := &DatabaseConfig{
+				Type: "changed",
+				Conn: "changed",
+			}
+
+			assert.Equal(t, expectedConfig, cfg)
+
+			return nil
+		},
+		[]string{
+			"--db-type", "changed",
+			"--db-conn", "changed",
+		},
+	)
+}
+
+func TestMergeDatabaseConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+	type testValuePair struct {
+		args     []string
+		callback func(*DatabaseConfig)
+	}
+	testValuePairs := []testValuePair{
+		{args: []string{"--db-type", "changed"}, callback: func(gc *DatabaseConfig) { gc.Type = "changed" }},
+		{args: []string{"--db-conn", "changed"}, callback: func(gc *DatabaseConfig) { gc.Conn = "changed" }},
+	}
+
+	for _, pair := range testValuePairs {
+		runApp(
+			t,
+			func(ctx *cli.Context) error {
+				cfg := DatabaseConfig{
+					Type: "original",
+					Conn: "original",
+				}
+
+				expectedConfig := cfg
+				pair.callback(&expectedConfig)
+
+				mergeDatabaseConfig(ctx, &cfg)
+
+				assert.Equal(t, expectedConfig, cfg)
+
+				return nil
+			},
+			pair.args,
+		)
+	}
+}
+
+func TestMergeACMEConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg := &ACMEConfig{
+				Email:             "original",
+				APIEndpoint:       "original",
+				AcceptTerms:       false,
+				UseRateLimits:     false,
+				EAB_HMAC:          "original",
+				EAB_KID:           "original",
+				DNSProvider:       "original",
+				AccountConfigFile: "original",
+			}
+
+			mergeACMEConfig(ctx, cfg)
+
+			expectedConfig := &ACMEConfig{
+				Email:             "changed",
+				APIEndpoint:       "changed",
+				AcceptTerms:       true,
+				UseRateLimits:     true,
+				EAB_HMAC:          "changed",
+				EAB_KID:           "changed",
+				DNSProvider:       "changed",
+				AccountConfigFile: "changed",
+			}
+
+			assert.Equal(t, expectedConfig, cfg)
+
+			return nil
+		},
+		[]string{
+			"--acme-email", "changed",
+			"--acme-api-endpoint", "changed",
+			"--acme-accept-terms",
+			"--acme-use-rate-limits",
+			"--acme-eab-hmac", "changed",
+			"--acme-eab-kid", "changed",
+			"--dns-provider", "changed",
+			"--acme-account-config", "changed",
+		},
+	)
+}
+
+func TestMergeACMEConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+	type testValuePair struct {
+		args     []string
+		callback func(*ACMEConfig)
+	}
+	testValuePairs := []testValuePair{
+		{args: []string{"--acme-email", "changed"}, callback: func(gc *ACMEConfig) { gc.Email = "changed" }},
+		{args: []string{"--acme-api-endpoint", "changed"}, callback: func(gc *ACMEConfig) { gc.APIEndpoint = "changed" }},
+		{args: []string{"--acme-accept-terms"}, callback: func(gc *ACMEConfig) { gc.AcceptTerms = true }},
+		{args: []string{"--acme-use-rate-limits"}, callback: func(gc *ACMEConfig) { gc.UseRateLimits = true }},
+		{args: []string{"--acme-eab-hmac", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_HMAC = "changed" }},
+		{args: []string{"--acme-eab-kid", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_KID = "changed" }},
+		{args: []string{"--dns-provider", "changed"}, callback: func(gc *ACMEConfig) { gc.DNSProvider = "changed" }},
+		{args: []string{"--acme-account-config", "changed"}, callback: func(gc *ACMEConfig) { gc.AccountConfigFile = "changed" }},
+	}
+
+	for _, pair := range testValuePairs {
+		runApp(
+			t,
+			func(ctx *cli.Context) error {
+				cfg := ACMEConfig{
+					Email:             "original",
+					APIEndpoint:       "original",
+					AcceptTerms:       false,
+					UseRateLimits:     false,
+					EAB_HMAC:          "original",
+					EAB_KID:           "original",
+					DNSProvider:       "original",
+					AccountConfigFile: "original",
+				}
+
+				expectedConfig := cfg
+				pair.callback(&expectedConfig)
+
+				mergeACMEConfig(ctx, &cfg)
+
+				assert.Equal(t, expectedConfig, cfg)
+
+				return nil
+			},
+			pair.args,
+		)
+	}
+}
diff --git a/example_config.toml b/example_config.toml
new file mode 100644
index 0000000..30e77c4
--- /dev/null
+++ b/example_config.toml
@@ -0,0 +1,32 @@
+logLevel = 'debug'
+
+[server]
+host = '[::]'
+port = 443
+httpPort = 80
+httpServerEnabled = true
+mainDomain = 'codeberg.page'
+rawDomain = 'raw.codeberg.page'
+pagesBranches = ["pages"]
+allowedCorsDomains = []
+blacklistedPaths = []
+
+[gitea]
+root = 'https://codeberg.org'
+token = 'ASDF1234'
+lfsEnabled = true
+followSymlinks = true
+
+[database]
+type = 'sqlite'
+conn = 'certs.sqlite'
+
+[ACME]
+email = 'noreply@example.email'
+apiEndpoint = 'https://acme-v02.api.letsencrypt.org/directory'
+acceptTerms = false
+useRateLimits = false
+eab_hmac = ''
+eab_kid = ''
+dnsProvider = ''
+accountConfigFile = 'acme-account.json'
diff --git a/go.mod b/go.mod
index eba292e..47e1e71 100644
--- a/go.mod
+++ b/go.mod
@@ -13,9 +13,10 @@ require (
 	github.com/lib/pq v1.10.7
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/microcosm-cc/bluemonday v1.0.26
+	github.com/pelletier/go-toml/v2 v2.1.0
 	github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
 	github.com/rs/zerolog v1.27.0
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
 	golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
 	xorm.io/xorm v1.3.2
@@ -44,6 +45,7 @@ require (
 	github.com/cloudflare/cloudflare-go v0.20.0 // indirect
 	github.com/cpu/goacmedns v0.1.1 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
+	github.com/creasty/defaults v1.7.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/davidmz/go-pageant v1.0.2 // indirect
 	github.com/deepmap/oapi-codegen v1.6.1 // indirect
@@ -113,7 +115,7 @@ require (
 	github.com/softlayer/softlayer-go v1.0.3 // indirect
 	github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
 	github.com/spf13/cast v1.3.1 // indirect
-	github.com/stretchr/objx v0.3.0 // indirect
+	github.com/stretchr/objx v0.5.0 // indirect
 	github.com/syndtr/goleveldb v1.0.0 // indirect
 	github.com/transip/gotransip/v6 v6.6.1 // indirect
 	github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 // indirect
@@ -135,6 +137,6 @@ require (
 	gopkg.in/ns1/ns1-go.v2 v2.6.2 // indirect
 	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 	xorm.io/builder v0.3.12 // indirect
 )
diff --git a/go.sum b/go.sum
index 7ea8b78..1a10599 100644
--- a/go.sum
+++ b/go.sum
@@ -131,6 +131,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
 github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
+github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -570,6 +572,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -680,14 +684,19 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
 github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@@ -1079,8 +1088,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/integration/main_test.go b/integration/main_test.go
index 6566f78..86fd9d3 100644
--- a/integration/main_test.go
+++ b/integration/main_test.go
@@ -10,9 +10,10 @@ import (
 	"testing"
 	"time"
 
-	"codeberg.org/codeberg/pages/cmd"
-
 	"github.com/urfave/cli/v2"
+
+	cmd "codeberg.org/codeberg/pages/cli"
+	"codeberg.org/codeberg/pages/server"
 )
 
 func TestMain(m *testing.M) {
@@ -32,10 +33,7 @@ func TestMain(m *testing.M) {
 }
 
 func startServer(ctx context.Context) error {
-	args := []string{
-		"--verbose",
-		"--acme-accept-terms", "true",
-	}
+	args := []string{"integration"}
 	setEnvIfNotSet("ACME_API", "https://acme.mock.directory")
 	setEnvIfNotSet("PAGES_DOMAIN", "localhost.mock.directory")
 	setEnvIfNotSet("RAW_DOMAIN", "raw.localhost.mock.directory")
@@ -44,10 +42,15 @@ func startServer(ctx context.Context) error {
 	setEnvIfNotSet("HTTP_PORT", "8880")
 	setEnvIfNotSet("ENABLE_HTTP_SERVER", "true")
 	setEnvIfNotSet("DB_TYPE", "sqlite3")
+	setEnvIfNotSet("GITEA_ROOT", "https://codeberg.org")
+	setEnvIfNotSet("LOG_LEVEL", "trace")
+	setEnvIfNotSet("ENABLE_LFS_SUPPORT", "true")
+	setEnvIfNotSet("ENABLE_SYMLINK_SUPPORT", "true")
+	setEnvIfNotSet("ACME_ACCOUNT_CONFIG", "integration/acme-account.json")
 
 	app := cli.NewApp()
 	app.Name = "pages-server"
-	app.Action = cmd.Serve
+	app.Action = server.Serve
 	app.Flags = cmd.ServerFlags
 
 	go func() {
diff --git a/main.go b/main.go
index 6c1d0cc..87e21f3 100644
--- a/main.go
+++ b/main.go
@@ -1,29 +1,21 @@
 package main
 
 import (
-	"fmt"
 	"os"
 
 	_ "github.com/joho/godotenv/autoload"
-	"github.com/urfave/cli/v2"
+	"github.com/rs/zerolog/log"
 
-	"codeberg.org/codeberg/pages/cmd"
-	"codeberg.org/codeberg/pages/server/version"
+	"codeberg.org/codeberg/pages/cli"
+	"codeberg.org/codeberg/pages/server"
 )
 
 func main() {
-	app := cli.NewApp()
-	app.Name = "pages-server"
-	app.Version = version.Version
-	app.Usage = "pages server"
-	app.Action = cmd.Serve
-	app.Flags = cmd.ServerFlags
-	app.Commands = []*cli.Command{
-		cmd.Certs,
-	}
+	app := cli.CreatePagesApp()
+	app.Action = server.Serve
 
 	if err := app.Run(os.Args); err != nil {
-		_, _ = fmt.Fprintln(os.Stderr, err)
+		log.Error().Err(err).Msg("A fatal error occurred")
 		os.Exit(1)
 	}
 }
diff --git a/server/acme/client.go b/server/acme/client.go
new file mode 100644
index 0000000..38e2785
--- /dev/null
+++ b/server/acme/client.go
@@ -0,0 +1,26 @@
+package acme
+
+import (
+	"errors"
+	"fmt"
+
+	"codeberg.org/codeberg/pages/config"
+	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/certificates"
+)
+
+var ErrAcmeMissConfig = errors.New("ACME client has wrong config")
+
+func CreateAcmeClient(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache cache.ICache) (*certificates.AcmeClient, error) {
+	// check config
+	if (!cfg.AcceptTerms || cfg.DNSProvider == "") && cfg.APIEndpoint != "https://acme.mock.directory" {
+		return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
+	}
+	if cfg.EAB_HMAC != "" && cfg.EAB_KID == "" {
+		return nil, fmt.Errorf("%w: ACME_EAB_HMAC also needs ACME_EAB_KID to be set", ErrAcmeMissConfig)
+	} else if cfg.EAB_HMAC == "" && cfg.EAB_KID != "" {
+		return nil, fmt.Errorf("%w: ACME_EAB_KID also needs ACME_EAB_HMAC to be set", ErrAcmeMissConfig)
+	}
+
+	return certificates.NewAcmeClient(cfg, enableHTTPServer, challengeCache)
+}
diff --git a/server/cache/interface.go b/server/cache/interface.go
index 2952b29..b3412cc 100644
--- a/server/cache/interface.go
+++ b/server/cache/interface.go
@@ -2,7 +2,8 @@ package cache
 
 import "time"
 
-type SetGetKey interface {
+// ICache is an interface that defines how the pages server interacts with the cache.
+type ICache interface {
 	Set(key string, value interface{}, ttl time.Duration) error
 	Get(key string) (interface{}, bool)
 	Remove(key string)
diff --git a/server/cache/setup.go b/server/cache/memory.go
similarity index 69%
rename from server/cache/setup.go
rename to server/cache/memory.go
index a5928b0..093696f 100644
--- a/server/cache/setup.go
+++ b/server/cache/memory.go
@@ -2,6 +2,6 @@ package cache
 
 import "github.com/OrlovEvgeny/go-mcache"
 
-func NewKeyValueCache() SetGetKey {
+func NewInMemoryCache() ICache {
 	return mcache.New()
 }
diff --git a/server/certificates/acme_client.go b/server/certificates/acme_client.go
index ba83e50..d53e854 100644
--- a/server/certificates/acme_client.go
+++ b/server/certificates/acme_client.go
@@ -10,6 +10,7 @@ import (
 	"github.com/reugn/equalizer"
 	"github.com/rs/zerolog/log"
 
+	"codeberg.org/codeberg/pages/config"
 	"codeberg.org/codeberg/pages/server/cache"
 )
 
@@ -28,8 +29,8 @@ type AcmeClient struct {
 	acmeClientCertificateLimitPerUser map[string]*equalizer.TokenBucket
 }
 
-func NewAcmeClient(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, dnsProvider string, acmeAcceptTerms, enableHTTPServer, acmeUseRateLimits bool, challengeCache cache.SetGetKey) (*AcmeClient, error) {
-	acmeConfig, err := setupAcmeConfig(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, acmeAcceptTerms)
+func NewAcmeClient(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache cache.ICache) (*AcmeClient, error) {
+	acmeConfig, err := setupAcmeConfig(cfg)
 	if err != nil {
 		return nil, err
 	}
@@ -54,7 +55,7 @@ func NewAcmeClient(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID,
 	if err != nil {
 		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 	} else {
-		if dnsProvider == "" {
+		if cfg.DNSProvider == "" {
 			// using mock server, don't use wildcard certs
 			err := mainDomainAcmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
 			if err != nil {
@@ -62,7 +63,7 @@ func NewAcmeClient(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID,
 			}
 		} else {
 			// use DNS-Challenge https://go-acme.github.io/lego/dns/
-			provider, err := dns.NewDNSChallengeProviderByName(dnsProvider)
+			provider, err := dns.NewDNSChallengeProviderByName(cfg.DNSProvider)
 			if err != nil {
 				return nil, fmt.Errorf("can not create DNS Challenge provider: %w", err)
 			}
@@ -76,7 +77,7 @@ func NewAcmeClient(acmeAccountConf, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID,
 		legoClient:              acmeClient,
 		dnsChallengerLegoClient: mainDomainAcmeClient,
 
-		acmeUseRateLimits: acmeUseRateLimits,
+		acmeUseRateLimits: cfg.UseRateLimits,
 
 		obtainLocks: sync.Map{},
 
diff --git a/server/certificates/acme_config.go b/server/certificates/acme_config.go
index 12ad7c6..2b5151d 100644
--- a/server/certificates/acme_config.go
+++ b/server/certificates/acme_config.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"os"
 
+	"codeberg.org/codeberg/pages/config"
 	"github.com/go-acme/lego/v4/certcrypto"
 	"github.com/go-acme/lego/v4/lego"
 	"github.com/go-acme/lego/v4/registration"
@@ -16,21 +17,27 @@ import (
 
 const challengePath = "/.well-known/acme-challenge/"
 
-func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
+func setupAcmeConfig(cfg config.ACMEConfig) (*lego.Config, error) {
 	var myAcmeAccount AcmeAccount
 	var myAcmeConfig *lego.Config
 
-	if account, err := os.ReadFile(configFile); err == nil {
-		log.Info().Msgf("found existing acme account config file '%s'", configFile)
+	if cfg.AccountConfigFile == "" {
+		return nil, fmt.Errorf("invalid acme config file: '%s'", cfg.AccountConfigFile)
+	}
+
+	if account, err := os.ReadFile(cfg.AccountConfigFile); err == nil {
+		log.Info().Msgf("found existing acme account config file '%s'", cfg.AccountConfigFile)
 		if err := json.Unmarshal(account, &myAcmeAccount); err != nil {
 			return nil, err
 		}
+
 		myAcmeAccount.Key, err = certcrypto.ParsePEMPrivateKey([]byte(myAcmeAccount.KeyPEM))
 		if err != nil {
 			return nil, err
 		}
+
 		myAcmeConfig = lego.NewConfig(&myAcmeAccount)
-		myAcmeConfig.CADirURL = acmeAPI
+		myAcmeConfig.CADirURL = cfg.APIEndpoint
 		myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
 
 		// Validate Config
@@ -39,6 +46,7 @@ func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID stri
 			log.Info().Err(err).Msg("config validation failed, you might just delete the config file and let it recreate")
 			return nil, fmt.Errorf("acme config validation failed: %w", err)
 		}
+
 		return myAcmeConfig, nil
 	} else if !os.IsNotExist(err) {
 		return nil, err
@@ -51,20 +59,20 @@ func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID stri
 		return nil, err
 	}
 	myAcmeAccount = AcmeAccount{
-		Email:  acmeMail,
+		Email:  cfg.Email,
 		Key:    privateKey,
 		KeyPEM: string(certcrypto.PEMEncode(privateKey)),
 	}
 	myAcmeConfig = lego.NewConfig(&myAcmeAccount)
-	myAcmeConfig.CADirURL = acmeAPI
+	myAcmeConfig.CADirURL = cfg.APIEndpoint
 	myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048
 	tempClient, err := lego.NewClient(myAcmeConfig)
 	if err != nil {
 		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 	} else {
 		// accept terms & log in to EAB
-		if acmeEabKID == "" || acmeEabHmac == "" {
-			reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: acmeAcceptTerms})
+		if cfg.EAB_KID == "" || cfg.EAB_HMAC == "" {
+			reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: cfg.AcceptTerms})
 			if err != nil {
 				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
 			} else {
@@ -72,9 +80,9 @@ func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID stri
 			}
 		} else {
 			reg, err := tempClient.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
-				TermsOfServiceAgreed: acmeAcceptTerms,
-				Kid:                  acmeEabKID,
-				HmacEncoded:          acmeEabHmac,
+				TermsOfServiceAgreed: cfg.AcceptTerms,
+				Kid:                  cfg.EAB_KID,
+				HmacEncoded:          cfg.EAB_HMAC,
 			})
 			if err != nil {
 				log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only")
@@ -89,8 +97,8 @@ func setupAcmeConfig(configFile, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID stri
 				log.Error().Err(err).Msg("json.Marshalfailed, waiting for manual restart to avoid rate limits")
 				select {}
 			}
-			log.Info().Msgf("new acme account created. write to config file '%s'", configFile)
-			err = os.WriteFile(configFile, acmeAccountJSON, 0o600)
+			log.Info().Msgf("new acme account created. write to config file '%s'", cfg.AccountConfigFile)
+			err = os.WriteFile(cfg.AccountConfigFile, acmeAccountJSON, 0o600)
 			if err != nil {
 				log.Error().Err(err).Msg("os.WriteFile failed, waiting for manual restart to avoid rate limits")
 				select {}
diff --git a/server/certificates/cached_challengers.go b/server/certificates/cached_challengers.go
index bc9ea67..39439fb 100644
--- a/server/certificates/cached_challengers.go
+++ b/server/certificates/cached_challengers.go
@@ -15,7 +15,7 @@ import (
 )
 
 type AcmeTLSChallengeProvider struct {
-	challengeCache cache.SetGetKey
+	challengeCache cache.ICache
 }
 
 // make sure AcmeTLSChallengeProvider match Provider interface
@@ -31,7 +31,7 @@ func (a AcmeTLSChallengeProvider) CleanUp(domain, _, _ string) error {
 }
 
 type AcmeHTTPChallengeProvider struct {
-	challengeCache cache.SetGetKey
+	challengeCache cache.ICache
 }
 
 // make sure AcmeHTTPChallengeProvider match Provider interface
@@ -46,7 +46,7 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
 	return nil
 }
 
-func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey, sslPort uint) http.HandlerFunc {
+func SetupHTTPACMEChallengeServer(challengeCache cache.ICache, sslPort uint) http.HandlerFunc {
 	// handle custom-ssl-ports to be added on https redirects
 	portPart := ""
 	if sslPort != 443 {
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 4fb99fa..b638755 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -31,7 +31,7 @@ func TLSConfig(mainDomainSuffix string,
 	giteaClient *gitea.Client,
 	acmeClient *AcmeClient,
 	firstDefaultBranch string,
-	keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.SetGetKey,
+	keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.ICache,
 	certDB database.CertDB,
 ) *tls.Config {
 	return &tls.Config{
diff --git a/server/dns/dns.go b/server/dns/dns.go
index c11b278..970f0c0 100644
--- a/server/dns/dns.go
+++ b/server/dns/dns.go
@@ -15,7 +15,7 @@ var defaultPagesRepo = "pages"
 
 // GetTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix.
 // If everything is fine, it returns the target data.
-func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string, dnsLookupCache cache.SetGetKey) (targetOwner, targetRepo, targetBranch string) {
+func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string, dnsLookupCache cache.ICache) (targetOwner, targetRepo, targetBranch string) {
 	// Get CNAME or TXT
 	var cname string
 	var err error
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index af61edf..267c3d8 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -74,7 +74,7 @@ type writeCacheReader struct {
 	buffer         *bytes.Buffer
 	rileResponse   *FileResponse
 	cacheKey       string
-	cache          cache.SetGetKey
+	cache          cache.ICache
 	hasError       bool
 }
 
@@ -99,7 +99,7 @@ func (t *writeCacheReader) Close() error {
 	return t.originalReader.Close()
 }
 
-func (f FileResponse) CreateCacheReader(r io.ReadCloser, cache cache.SetGetKey, cacheKey string) io.ReadCloser {
+func (f FileResponse) CreateCacheReader(r io.ReadCloser, cache cache.ICache, cacheKey string) io.ReadCloser {
 	if r == nil || cache == nil || cacheKey == "" {
 		log.Error().Msg("could not create CacheReader")
 		return nil
diff --git a/server/gitea/client.go b/server/gitea/client.go
index f3bda54..42cf065 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/sdk/gitea"
 	"github.com/rs/zerolog/log"
 
+	"codeberg.org/codeberg/pages/config"
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/version"
 )
@@ -44,7 +45,7 @@ const (
 
 type Client struct {
 	sdkClient     *gitea.Client
-	responseCache cache.SetGetKey
+	responseCache cache.ICache
 
 	giteaRoot string
 
@@ -55,24 +56,21 @@ type Client struct {
 	defaultMimeType    string
 }
 
-func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, followSymlinks, supportLFS bool) (*Client, error) {
-	rootURL, err := url.Parse(giteaRoot)
+func NewClient(cfg config.GiteaConfig, respCache cache.ICache) (*Client, error) {
+	rootURL, err := url.Parse(cfg.Root)
 	if err != nil {
 		return nil, err
 	}
-	giteaRoot = strings.Trim(rootURL.String(), "/")
+	giteaRoot := strings.Trim(rootURL.String(), "/")
 
 	stdClient := http.Client{Timeout: 10 * time.Second}
 
-	// TODO: pass down
-	var (
-		forbiddenMimeTypes map[string]bool
-		defaultMimeType    string
-	)
-
-	if forbiddenMimeTypes == nil {
-		forbiddenMimeTypes = make(map[string]bool)
+	forbiddenMimeTypes := make(map[string]bool, len(cfg.ForbiddenMimeTypes))
+	for _, mimeType := range cfg.ForbiddenMimeTypes {
+		forbiddenMimeTypes[mimeType] = true
 	}
+
+	defaultMimeType := cfg.DefaultMimeType
 	if defaultMimeType == "" {
 		defaultMimeType = "application/octet-stream"
 	}
@@ -80,7 +78,7 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, follo
 	sdk, err := gitea.NewClient(
 		giteaRoot,
 		gitea.SetHTTPClient(&stdClient),
-		gitea.SetToken(giteaAPIToken),
+		gitea.SetToken(cfg.Token),
 		gitea.SetUserAgent("pages-server/"+version.Version),
 	)
 
@@ -90,8 +88,8 @@ func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, follo
 
 		giteaRoot: giteaRoot,
 
-		followSymlinks: followSymlinks,
-		supportLFS:     supportLFS,
+		followSymlinks: cfg.FollowSymlinks,
+		supportLFS:     cfg.LFSEnabled,
 
 		forbiddenMimeTypes: forbiddenMimeTypes,
 		defaultMimeType:    defaultMimeType,
diff --git a/server/handler/handler.go b/server/handler/handler.go
index 7da5d39..96788e4 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/rs/zerolog/log"
 
+	"codeberg.org/codeberg/pages/config"
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/context"
@@ -19,11 +20,10 @@ const (
 )
 
 // Handler handles a single HTTP request to the web server.
-func Handler(mainDomainSuffix, rawDomain string,
+func Handler(
+	cfg config.ServerConfig,
 	giteaClient *gitea.Client,
-	blacklistedPaths, allowedCorsDomains []string,
-	defaultPagesBranches []string,
-	dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey,
+	dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
 ) http.HandlerFunc {
 	return func(w http.ResponseWriter, req *http.Request) {
 		log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
@@ -39,8 +39,8 @@ func Handler(mainDomainSuffix, rawDomain string,
 
 		trimmedHost := ctx.TrimHostPort()
 
-		// Add HSTS for RawDomain and MainDomainSuffix
-		if hsts := getHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" {
+		// Add HSTS for RawDomain and MainDomain
+		if hsts := getHSTSHeader(trimmedHost, cfg.MainDomain, cfg.RawDomain); hsts != "" {
 			ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts)
 		}
 
@@ -62,7 +62,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 		}
 
 		// Block blacklisted paths (like ACME challenges)
-		for _, blacklistedPath := range blacklistedPaths {
+		for _, blacklistedPath := range cfg.BlacklistedPaths {
 			if strings.HasPrefix(ctx.Path(), blacklistedPath) {
 				html.ReturnErrorPage(ctx, "requested path is blacklisted", http.StatusForbidden)
 				return
@@ -71,7 +71,7 @@ func Handler(mainDomainSuffix, rawDomain string,
 
 		// Allow CORS for specified domains
 		allowCors := false
-		for _, allowedCorsDomain := range allowedCorsDomains {
+		for _, allowedCorsDomain := range cfg.AllowedCorsDomains {
 			if strings.EqualFold(trimmedHost, allowedCorsDomain) {
 				allowCors = true
 				break
@@ -85,28 +85,28 @@ func Handler(mainDomainSuffix, rawDomain string,
 		// Prepare request information to Gitea
 		pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/")
 
-		if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) {
+		if cfg.RawDomain != "" && strings.EqualFold(trimmedHost, cfg.RawDomain) {
 			log.Debug().Msg("raw domain request detected")
 			handleRaw(log, ctx, giteaClient,
-				mainDomainSuffix,
+				cfg.MainDomain,
 				trimmedHost,
 				pathElements,
 				canonicalDomainCache, redirectsCache)
-		} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
+		} else if strings.HasSuffix(trimmedHost, cfg.MainDomain) {
 			log.Debug().Msg("subdomain request detected")
 			handleSubDomain(log, ctx, giteaClient,
-				mainDomainSuffix,
-				defaultPagesBranches,
+				cfg.MainDomain,
+				cfg.PagesBranches,
 				trimmedHost,
 				pathElements,
 				canonicalDomainCache, redirectsCache)
 		} else {
 			log.Debug().Msg("custom domain request detected")
 			handleCustomDomain(log, ctx, giteaClient,
-				mainDomainSuffix,
+				cfg.MainDomain,
 				trimmedHost,
 				pathElements,
-				defaultPagesBranches[0],
+				cfg.PagesBranches[0],
 				dnsLookupCache, canonicalDomainCache, redirectsCache)
 		}
 	}
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 8742be4..82953f9 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -19,7 +19,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 	trimmedHost string,
 	pathElements []string,
 	firstDefaultBranch string,
-	dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey,
+	dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
 ) {
 	// Serve pages from custom domains
 	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
diff --git a/server/handler/handler_raw_domain.go b/server/handler/handler_raw_domain.go
index caa8209..f48e8e4 100644
--- a/server/handler/handler_raw_domain.go
+++ b/server/handler/handler_raw_domain.go
@@ -19,7 +19,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 	mainDomainSuffix string,
 	trimmedHost string,
 	pathElements []string,
-	canonicalDomainCache, redirectsCache cache.SetGetKey,
+	canonicalDomainCache, redirectsCache cache.ICache,
 ) {
 	// Serve raw content from RawDomain
 	log.Debug().Msg("raw domain")
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 6c14393..806fe7f 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -21,7 +21,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 	defaultPagesBranches []string,
 	trimmedHost string,
 	pathElements []string,
-	canonicalDomainCache, redirectsCache cache.SetGetKey,
+	canonicalDomainCache, redirectsCache cache.ICache,
 ) {
 	// Serve pages from subdomains of MainDomainSuffix
 	log.Debug().Msg("main domain suffix")
diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index d04ebda..4cb859a 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -6,23 +6,30 @@ import (
 	"testing"
 	"time"
 
+	"codeberg.org/codeberg/pages/config"
 	"codeberg.org/codeberg/pages/server/cache"
 	"codeberg.org/codeberg/pages/server/gitea"
 	"github.com/rs/zerolog/log"
 )
 
 func TestHandlerPerformance(t *testing.T) {
-	giteaClient, _ := gitea.NewClient("https://codeberg.org", "", cache.NewKeyValueCache(), false, false)
-	testHandler := Handler(
-		"codeberg.page", "raw.codeberg.org",
-		giteaClient,
-		[]string{"/.well-known/acme-challenge/"},
-		[]string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
-		[]string{"pages"},
-		cache.NewKeyValueCache(),
-		cache.NewKeyValueCache(),
-		cache.NewKeyValueCache(),
-	)
+	cfg := config.GiteaConfig{
+		Root:           "https://codeberg.org",
+		Token:          "",
+		LFSEnabled:     false,
+		FollowSymlinks: false,
+	}
+	giteaClient, _ := gitea.NewClient(cfg, cache.NewInMemoryCache())
+	serverCfg := config.ServerConfig{
+		MainDomain: "codeberg.page",
+		RawDomain:  "raw.codeberg.page",
+		BlacklistedPaths: []string{
+			"/.well-known/acme-challenge/",
+		},
+		AllowedCorsDomains: []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
+		PagesBranches:      []string{"pages"},
+	}
+	testHandler := Handler(serverCfg, giteaClient, cache.NewInMemoryCache(), cache.NewInMemoryCache(), cache.NewInMemoryCache())
 
 	testCase := func(uri string, status int) {
 		t.Run(uri, func(t *testing.T) {
diff --git a/server/handler/try.go b/server/handler/try.go
index bb79dce..145b1a9 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -17,8 +17,8 @@ import (
 func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 	mainDomainSuffix, trimmedHost string,
 	options *upstream.Options,
-	canonicalDomainCache cache.SetGetKey,
-	redirectsCache cache.SetGetKey,
+	canonicalDomainCache cache.ICache,
+	redirectsCache cache.ICache,
 ) {
 	// check if a canonical domain exists on a request on MainDomain
 	if strings.HasSuffix(trimmedHost, mainDomainSuffix) && !options.ServeRaw {
diff --git a/server/startup.go b/server/startup.go
new file mode 100644
index 0000000..ffdabb7
--- /dev/null
+++ b/server/startup.go
@@ -0,0 +1,141 @@
+package server
+
+import (
+	"context"
+	"crypto/tls"
+	"encoding/json"
+	"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)
+
+	foo, _ := json.Marshal(cfg)
+	log.Trace().RawJSON("config", foo).Msg("starting server with config")
+
+	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()
+
+	keyCache := cache.NewInMemoryCache()
+	challengeCache := cache.NewInMemoryCache()
+	// canonicalDomainCache stores canonical domains
+	canonicalDomainCache := cache.NewInMemoryCache()
+	// dnsLookupCache stores DNS lookups for custom domains
+	dnsLookupCache := cache.NewInMemoryCache()
+	// redirectsCache stores redirects in _redirects files
+	redirectsCache := cache.NewInMemoryCache()
+	// clientResponseCache stores responses from the Gitea server
+	clientResponseCache := cache.NewInMemoryCache()
+
+	giteaClient, err := gitea.NewClient(cfg.Gitea, clientResponseCache)
+	if err != nil {
+		return fmt.Errorf("could not create new gitea client: %v", err)
+	}
+
+	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],
+		keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
+		certDB,
+	))
+
+	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")
+			}
+		}()
+	}
+
+	// Create ssl handler based on settings
+	sslHandler := handler.Handler(cfg.Server, giteaClient, dnsLookupCache, canonicalDomainCache, redirectsCache)
+
+	// Start the ssl listener
+	log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())
+
+	return http.Serve(listener, sslHandler)
+}
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index 230f268..d53d586 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -17,7 +17,7 @@ var canonicalDomainCacheTimeout = 15 * time.Minute
 const canonicalDomainConfig = ".domains"
 
 // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file).
-func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.SetGetKey) (domain string, valid bool) {
+func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.ICache) (domain string, valid bool) {
 	// Check if this request is cached.
 	if cachedValue, ok := canonicalDomainCache.Get(o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch); ok {
 		domains := cachedValue.([]string)
diff --git a/server/upstream/redirects.go b/server/upstream/redirects.go
index ab6c971..dd36a84 100644
--- a/server/upstream/redirects.go
+++ b/server/upstream/redirects.go
@@ -23,7 +23,7 @@ var redirectsCacheTimeout = 10 * time.Minute
 const redirectsConfig = "_redirects"
 
 // getRedirects returns redirects specified in the _redirects file.
-func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.SetGetKey) []Redirect {
+func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.ICache) []Redirect {
 	var redirects []Redirect
 	cacheKey := o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch
 
@@ -63,7 +63,7 @@ func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.S
 	return redirects
 }
 
-func (o *Options) matchRedirects(ctx *context.Context, giteaClient *gitea.Client, redirects []Redirect, redirectsCache cache.SetGetKey) (final bool) {
+func (o *Options) matchRedirects(ctx *context.Context, giteaClient *gitea.Client, redirects []Redirect, redirectsCache cache.ICache) (final bool) {
 	if len(redirects) > 0 {
 		for _, redirect := range redirects {
 			reqUrl := ctx.Req.RequestURI
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 4a85503..07b6ad2 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -53,7 +53,7 @@ type Options struct {
 }
 
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
-func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.SetGetKey) bool {
+func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.ICache) bool {
 	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
 
 	if o.TargetOwner == "" || o.TargetRepo == "" {

From a6e9510c078758794da5f6a1983b595bfc415de9 Mon Sep 17 00:00:00 2001
From: Hoernschen <julian.hoernschemeyer@mailbox.org>
Date: Mon, 26 Feb 2024 22:21:42 +0000
Subject: [PATCH 097/196] FIX blank internal pages (#164) (#292)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Hello 👋

since it affected my deployment of the pages server I started to look into the problem of the blank pages and think I found a solution for it:

1. There is no check if the file response is empty, neither in cache retrieval nor in writing of a cache. Also the provided method for checking for empty responses had a bug.
2. I identified the redirect response to be the issue here. There is a cache write with the full cache key (e. g. rawContent/user/repo|branch|route/index.html) happening in the handling of the redirect response. But the written body here is empty. In the triggered request from the redirect response the server then finds a cache item to the key and serves the empty body. A quick fix is the check for empty file responses mentioned in 1.
3. The decision to redirect the user comes quite far down in the upstream function. Before that happens a lot of stuff that may not be important since after the redirect response comes a new request anyway. Also, I suspect that this causes the caching problem because there is a request to the forge server and its error handling with some recursions happening before. I propose to move two of the redirects before "Preparing"
4. The recursion in the upstream function makes it difficult to understand what is actually happening. I added some more logging to have an easier time with that.
5. I changed the default behaviour to append a trailing slash to the path to true. In my tested scenarios it happened anyway. This way there is no recursion happening before the redirect.

I am not developing in go frequently and rarely contribute to open source -> so feedback of all kind is appreciated

closes #164

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/292
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Hoernschen <julian.hoernschemeyer@mailbox.org>
Co-committed-by: Hoernschen <julian.hoernschemeyer@mailbox.org>
---
 server/gitea/cache.go       | 25 +++++++++++++++++--------
 server/gitea/client.go      | 19 +++++++++++--------
 server/handler/handler.go   |  1 +
 server/upstream/upstream.go | 12 ++++++++++++
 4 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index 267c3d8..4ecc5e0 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -39,7 +39,7 @@ type FileResponse struct {
 }
 
 func (f FileResponse) IsEmpty() bool {
-	return len(f.Body) != 0
+	return len(f.Body) == 0
 }
 
 func (f FileResponse) createHttpResponse(cacheKey string) (header http.Header, statusCode int) {
@@ -72,13 +72,14 @@ type BranchTimestamp struct {
 type writeCacheReader struct {
 	originalReader io.ReadCloser
 	buffer         *bytes.Buffer
-	rileResponse   *FileResponse
+	fileResponse   *FileResponse
 	cacheKey       string
 	cache          cache.ICache
 	hasError       bool
 }
 
 func (t *writeCacheReader) Read(p []byte) (n int, err error) {
+	log.Trace().Msgf("[cache] read %q", t.cacheKey)
 	n, err = t.originalReader.Read(p)
 	if err != nil && err != io.EOF {
 		log.Trace().Err(err).Msgf("[cache] original reader for %q has returned an error", t.cacheKey)
@@ -90,12 +91,20 @@ func (t *writeCacheReader) Read(p []byte) (n int, err error) {
 }
 
 func (t *writeCacheReader) Close() error {
-	if !t.hasError {
-		fc := *t.rileResponse
-		fc.Body = t.buffer.Bytes()
-		_ = t.cache.Set(t.cacheKey, fc, fileCacheTimeout)
+	doWrite := !t.hasError
+	fc := *t.fileResponse
+	fc.Body = t.buffer.Bytes()
+	if fc.IsEmpty() {
+		log.Trace().Msg("[cache] file response is empty")
+		doWrite = false
 	}
-	log.Trace().Msgf("cacheReader for %q saved=%t closed", t.cacheKey, !t.hasError)
+	if doWrite {
+		err := t.cache.Set(t.cacheKey, fc, fileCacheTimeout)
+		if err != nil {
+			log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey)
+		}
+	}
+	log.Trace().Msgf("cacheReader for %q saved=%t closed", t.cacheKey, doWrite)
 	return t.originalReader.Close()
 }
 
@@ -108,7 +117,7 @@ func (f FileResponse) CreateCacheReader(r io.ReadCloser, cache cache.ICache, cac
 	return &writeCacheReader{
 		originalReader: r,
 		buffer:         bytes.NewBuffer(make([]byte, 0)),
-		rileResponse:   &f,
+		fileResponse:   &f,
 		cache:          cache,
 		cacheKey:       cacheKey,
 	}
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 42cf065..4f7eaa6 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -112,26 +112,27 @@ func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource str
 func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (io.ReadCloser, http.Header, int, error) {
 	cacheKey := fmt.Sprintf("%s/%s/%s|%s|%s", rawContentCacheKeyPrefix, targetOwner, targetRepo, ref, resource)
 	log := log.With().Str("cache_key", cacheKey).Logger()
-
+	log.Trace().Msg("try file in cache")
 	// handle if cache entry exist
 	if cache, ok := client.responseCache.Get(cacheKey); ok {
 		cache := cache.(FileResponse)
 		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey)
 		// TODO: check against some timestamp mismatch?!?
 		if cache.Exists {
+			log.Debug().Msg("[cache] exists")
 			if cache.IsSymlink {
 				linkDest := string(cache.Body)
 				log.Debug().Msgf("[cache] follow symlink from %q to %q", resource, linkDest)
 				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
-			} else {
-				log.Debug().Msg("[cache] return bytes")
+			} else if !cache.IsEmpty() {
+				log.Debug().Msgf("[cache] return %d bytes", len(cache.Body))
 				return io.NopCloser(bytes.NewReader(cache.Body)), cachedHeader, cachedStatusCode, nil
+			} else if cache.IsEmpty() {
+				log.Debug().Msg("[cache] is empty")
 			}
-		} else {
-			return nil, cachedHeader, cachedStatusCode, ErrorNotFound
 		}
 	}
-
+	log.Trace().Msg("file not in cache")
 	// not in cache, open reader via gitea api
 	reader, resp, err := client.sdkClient.GetFileReader(targetOwner, targetRepo, ref, resource, client.supportLFS)
 	if resp != nil {
@@ -155,12 +156,14 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 					linkDest = path.Join(path.Dir(resource), linkDest)
 
 					// we store symlink not content to reduce duplicates in cache
-					if err := client.responseCache.Set(cacheKey, FileResponse{
+					fileResponse := FileResponse{
 						Exists:    true,
 						IsSymlink: true,
 						Body:      []byte(linkDest),
 						ETag:      resp.Header.Get(ETagHeader),
-					}, fileCacheTimeout); err != nil {
+					}
+					log.Trace().Msgf("file response has %d bytes", len(fileResponse.Body))
+					if err := client.responseCache.Set(cacheKey, fileResponse, fileCacheTimeout); err != nil {
 						log.Error().Err(err).Msg("[cache] error on cache write")
 					}
 
diff --git a/server/handler/handler.go b/server/handler/handler.go
index 96788e4..ffc3400 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -26,6 +26,7 @@ func Handler(
 	dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
 ) http.HandlerFunc {
 	return func(w http.ResponseWriter, req *http.Request) {
+		log.Debug().Msg("\n----------------------------------------------------------")
 		log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
 		ctx := context.New(w, req)
 
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 07b6ad2..d9c131e 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -56,6 +56,8 @@ type Options struct {
 func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.ICache) bool {
 	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
 
+	log.Debug().Msg("Start")
+
 	if o.TargetOwner == "" || o.TargetRepo == "" {
 		html.ReturnErrorPage(ctx, "forge client: either repo owner or name info is missing", http.StatusBadRequest)
 		return true
@@ -104,13 +106,16 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 
 	// Handle not found error
 	if err != nil && errors.Is(err, gitea.ErrorNotFound) {
+		log.Debug().Msg("Handling not found error")
 		// Get and match redirects
 		redirects := o.getRedirects(giteaClient, redirectsCache)
 		if o.matchRedirects(ctx, giteaClient, redirects, redirectsCache) {
+			log.Trace().Msg("redirect")
 			return true
 		}
 
 		if o.TryIndexPages {
+			log.Trace().Msg("try index page")
 			// copy the o struct & try if an index page exists
 			optionsForIndexPages := *o
 			optionsForIndexPages.TryIndexPages = false
@@ -121,6 +126,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 					return true
 				}
 			}
+			log.Trace().Msg("try html file with path name")
 			// compatibility fix for GitHub Pages (/example → /example.html)
 			optionsForIndexPages.appendTrailingSlash = false
 			optionsForIndexPages.redirectIfExists = strings.TrimSuffix(ctx.Path(), "/") + ".html"
@@ -130,8 +136,11 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 			}
 		}
 
+		log.Trace().Msg("not found")
+
 		ctx.StatusCode = http.StatusNotFound
 		if o.TryIndexPages {
+			log.Trace().Msg("try not found page")
 			// copy the o struct & try if a not found page exists
 			optionsForNotFoundPages := *o
 			optionsForNotFoundPages.TryIndexPages = false
@@ -142,6 +151,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 					return true
 				}
 			}
+			log.Trace().Msg("not found page missing")
 		}
 
 		return false
@@ -173,10 +183,12 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 	// Append trailing slash if missing (for index files), and redirect to fix filenames in general
 	// o.appendTrailingSlash is only true when looking for index pages
 	if o.appendTrailingSlash && !strings.HasSuffix(ctx.Path(), "/") {
+		log.Trace().Msg("append trailing slash and redirect")
 		ctx.Redirect(ctx.Path()+"/", http.StatusTemporaryRedirect)
 		return true
 	}
 	if strings.HasSuffix(ctx.Path(), "/index.html") && !o.ServeRaw {
+		log.Trace().Msg("remove index.html from path and redirect")
 		ctx.Redirect(strings.TrimSuffix(ctx.Path(), "index.html"), http.StatusTemporaryRedirect)
 		return true
 	}

From dd6d8bd60fe0e318602042d1ae8129654f4f5186 Mon Sep 17 00:00:00 2001
From: caelandb <bothacaelan@gmail.com>
Date: Tue, 12 Mar 2024 21:50:17 +0000
Subject: [PATCH 098/196] fixed one grammar error. (#297)

Just a small grammar change in the README.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/297
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: caelandb <bothacaelan@gmail.com>
Co-committed-by: caelandb <bothacaelan@gmail.com>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index fb2a4b9..7193691 100644
--- a/README.md
+++ b/README.md
@@ -116,7 +116,7 @@ Thank you very much.
 Make sure you have [golang](https://go.dev) v1.21 or newer and [just](https://just.systems/man/en/) installed.
 
 run `just dev`  
-now this pages should work:
+now these pages should work:
 
 - <https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg>
 - <https://momar.localhost.mock.directory:4430/ci-testing/>

From 03881382a4c286e3a7dc351f095f9147e89c2d6d Mon Sep 17 00:00:00 2001
From: Jean-Marie 'Histausse' Mineau <histausse@protonmail.com>
Date: Thu, 18 Apr 2024 17:05:20 +0000
Subject: [PATCH 099/196] Add option to disable DNS ACME provider (#290)

This PR add the `$NO_DNS_01` option (disabled by default) that removes the DNS ACME provider, and replaces the wildcard certificate by individual certificates obtained using the TLS ACME provider.

This option allows an instance to work without having to manage access tokens for the DNS provider. On the flip side, this means that a certificate can be requested for each subdomains. To limit the risk of DOS, the existence of the user/org corresponding to a subdomain is checked before requesting a cert, however, this limitation is not enough for an forge with a high number of users/orgs.

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/290
Reviewed-by: Moritz Marquardt <momar@noreply.codeberg.org>
Co-authored-by: Jean-Marie 'Histausse' Mineau <histausse@protonmail.com>
Co-committed-by: Jean-Marie 'Histausse' Mineau <histausse@protonmail.com>
---
 README.md                           |  1 +
 cli/flags.go                        |  5 +++++
 config/config.go                    |  1 +
 config/setup.go                     |  3 +++
 config/setup_test.go                |  7 ++++++
 server/acme/client.go               |  4 ++--
 server/certificates/acme_client.go  |  7 ++----
 server/certificates/certificates.go | 30 ++++++++++++++++++++------
 server/database/xorm.go             | 13 ------------
 server/gitea/cache.go               |  3 +++
 server/gitea/client.go              | 33 +++++++++++++++++++++++++++++
 server/startup.go                   |  2 ++
 12 files changed, 83 insertions(+), 26 deletions(-)

diff --git a/README.md b/README.md
index 7193691..f47a196 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,7 @@ and especially have a look at [this section of the haproxy.cfg](https://codeberg
 - `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
 - `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard.  
   See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
+- `NO_DNS_01` (default: `false`): Disable the use of ACME DNS. This means that the wildcard certificate is self-signed and all domains and subdomains will have a distinct certificate. Because this may lead to a rate limit from the ACME provider, this option is not recommended for Gitea/Forgejo instances with open registrations or a great number of users/orgs.
 - `LOG_LEVEL` (default: warn): Set this to specify the level of logging.
 
 ## Contributing to the development
diff --git a/cli/flags.go b/cli/flags.go
index 097cf4f..52e1c1c 100644
--- a/cli/flags.go
+++ b/cli/flags.go
@@ -178,6 +178,11 @@ var (
 			Usage:   "Use DNS-Challenge for main domain. Read more at: https://go-acme.github.io/lego/dns/",
 			EnvVars: []string{"DNS_PROVIDER"},
 		},
+		&cli.BoolFlag{
+			Name:    "no-dns-01",
+			Usage:   "Always use individual certificates instead of a DNS-01 wild card certificate",
+			EnvVars: []string{"NO_DNS_01"},
+		},
 		&cli.StringFlag{
 			Name:    "acme-account-config",
 			Usage:   "json file of acme account",
diff --git a/config/config.go b/config/config.go
index 6cb972b..0146e0f 100644
--- a/config/config.go
+++ b/config/config.go
@@ -42,5 +42,6 @@ type ACMEConfig struct {
 	EAB_HMAC          string
 	EAB_KID           string
 	DNSProvider       string
+	NoDNS01           bool   `default:"false"`
 	AccountConfigFile string `default:"acme-account.json"`
 }
diff --git a/config/setup.go b/config/setup.go
index e774084..6a2aa62 100644
--- a/config/setup.go
+++ b/config/setup.go
@@ -141,6 +141,9 @@ func mergeACMEConfig(ctx *cli.Context, config *ACMEConfig) {
 	if ctx.IsSet("dns-provider") {
 		config.DNSProvider = ctx.String("dns-provider")
 	}
+	if ctx.IsSet("no-dns-01") {
+		config.NoDNS01 = ctx.Bool("no-dns-01")
+	}
 	if ctx.IsSet("acme-account-config") {
 		config.AccountConfigFile = ctx.String("acme-account-config")
 	}
diff --git a/config/setup_test.go b/config/setup_test.go
index a863e2f..1a32740 100644
--- a/config/setup_test.go
+++ b/config/setup_test.go
@@ -166,6 +166,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
 					EAB_HMAC:          "original",
 					EAB_KID:           "original",
 					DNSProvider:       "original",
+					NoDNS01:           false,
 					AccountConfigFile: "original",
 				},
 			}
@@ -205,6 +206,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
 					EAB_HMAC:          "changed",
 					EAB_KID:           "changed",
 					DNSProvider:       "changed",
+					NoDNS01:           true,
 					AccountConfigFile: "changed",
 				},
 			}
@@ -243,6 +245,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
 			"--acme-eab-hmac", "changed",
 			"--acme-eab-kid", "changed",
 			"--dns-provider", "changed",
+			"--no-dns-01",
 			"--acme-account-config", "changed",
 		},
 	)
@@ -517,6 +520,7 @@ func TestMergeACMEConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testi
 				EAB_HMAC:          "original",
 				EAB_KID:           "original",
 				DNSProvider:       "original",
+				NoDNS01:           false,
 				AccountConfigFile: "original",
 			}
 
@@ -530,6 +534,7 @@ func TestMergeACMEConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testi
 				EAB_HMAC:          "changed",
 				EAB_KID:           "changed",
 				DNSProvider:       "changed",
+				NoDNS01:           true,
 				AccountConfigFile: "changed",
 			}
 
@@ -545,6 +550,7 @@ func TestMergeACMEConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testi
 			"--acme-eab-hmac", "changed",
 			"--acme-eab-kid", "changed",
 			"--dns-provider", "changed",
+			"--no-dns-01",
 			"--acme-account-config", "changed",
 		},
 	)
@@ -563,6 +569,7 @@ func TestMergeACMEConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExi
 		{args: []string{"--acme-eab-hmac", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_HMAC = "changed" }},
 		{args: []string{"--acme-eab-kid", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_KID = "changed" }},
 		{args: []string{"--dns-provider", "changed"}, callback: func(gc *ACMEConfig) { gc.DNSProvider = "changed" }},
+		{args: []string{"--no-dns-01"}, callback: func(gc *ACMEConfig) { gc.NoDNS01 = true }},
 		{args: []string{"--acme-account-config", "changed"}, callback: func(gc *ACMEConfig) { gc.AccountConfigFile = "changed" }},
 	}
 
diff --git a/server/acme/client.go b/server/acme/client.go
index 38e2785..d5c83d0 100644
--- a/server/acme/client.go
+++ b/server/acme/client.go
@@ -13,8 +13,8 @@ var ErrAcmeMissConfig = errors.New("ACME client has wrong config")
 
 func CreateAcmeClient(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache cache.ICache) (*certificates.AcmeClient, error) {
 	// check config
-	if (!cfg.AcceptTerms || cfg.DNSProvider == "") && cfg.APIEndpoint != "https://acme.mock.directory" {
-		return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
+	if (!cfg.AcceptTerms || (cfg.DNSProvider == "" && !cfg.NoDNS01)) && cfg.APIEndpoint != "https://acme.mock.directory" {
+		return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER or $NO_DNS_01, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
 	}
 	if cfg.EAB_HMAC != "" && cfg.EAB_KID == "" {
 		return nil, fmt.Errorf("%w: ACME_EAB_HMAC also needs ACME_EAB_KID to be set", ErrAcmeMissConfig)
diff --git a/server/certificates/acme_client.go b/server/certificates/acme_client.go
index d53e854..f42fd8f 100644
--- a/server/certificates/acme_client.go
+++ b/server/certificates/acme_client.go
@@ -56,11 +56,8 @@ func NewAcmeClient(cfg config.ACMEConfig, enableHTTPServer bool, challengeCache
 		log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only")
 	} else {
 		if cfg.DNSProvider == "" {
-			// using mock server, don't use wildcard certs
-			err := mainDomainAcmeClient.Challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{challengeCache})
-			if err != nil {
-				log.Error().Err(err).Msg("Can't create TLS-ALPN-01 provider")
-			}
+			// using mock wildcard certs
+			mainDomainAcmeClient = nil
 		} else {
 			// use DNS-Challenge https://go-acme.github.io/lego/dns/
 			provider, err := dns.NewDNSChallengeProviderByName(cfg.DNSProvider)
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index b638755..67219dd 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -33,6 +33,8 @@ func TLSConfig(mainDomainSuffix string,
 	firstDefaultBranch string,
 	keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.ICache,
 	certDB database.CertDB,
+	noDNS01 bool,
+	rawDomain string,
 ) *tls.Config {
 	return &tls.Config{
 		// check DNS name & get certificate from Let's Encrypt
@@ -64,9 +66,24 @@ func TLSConfig(mainDomainSuffix string,
 
 			targetOwner := ""
 			mayObtainCert := true
+
 			if strings.HasSuffix(domain, mainDomainSuffix) || strings.EqualFold(domain, mainDomainSuffix[1:]) {
-				// deliver default certificate for the main domain (*.codeberg.page)
-				domain = mainDomainSuffix
+				if noDNS01 {
+					// Limit the domains allowed to request a certificate to pages-server domains
+					// and domains for an existing user of org
+					if !strings.EqualFold(domain, mainDomainSuffix[1:]) && !strings.EqualFold(domain, rawDomain) {
+						targetOwner := strings.TrimSuffix(domain, mainDomainSuffix)
+						owner_exist, err := giteaClient.GiteaCheckIfOwnerExists(targetOwner)
+						mayObtainCert = owner_exist
+						if err != nil {
+							log.Error().Err(err).Msgf("Failed to check '%s' existence on the forge: %s", targetOwner, err)
+							mayObtainCert = false
+						}
+					}
+				} else {
+					// deliver default certificate for the main domain (*.codeberg.page)
+					domain = mainDomainSuffix
+				}
 			} else {
 				var targetRepo, targetBranch string
 				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
@@ -199,9 +216,6 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
 
 func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user string, useDnsProvider bool, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
 	name := strings.TrimPrefix(domains[0], "*")
-	if useDnsProvider && domains[0] != "" && domains[0][0] == '*' {
-		domains = domains[1:]
-	}
 
 	// lock to avoid simultaneous requests
 	_, working := c.obtainLocks.LoadOrStore(name, struct{}{})
@@ -219,7 +233,11 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew
 	defer c.obtainLocks.Delete(name)
 
 	if acmeClient == nil {
-		return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase)
+		if useDnsProvider {
+			return mockCert(domains[0], "DNS ACME client is not defined", mainDomainSuffix, keyDatabase)
+		} else {
+			return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase)
+		}
 	}
 
 	// request actual cert
diff --git a/server/database/xorm.go b/server/database/xorm.go
index 217b6d1..63fa39e 100644
--- a/server/database/xorm.go
+++ b/server/database/xorm.go
@@ -52,7 +52,6 @@ func (x xDB) Close() error {
 func (x xDB) Put(domain string, cert *certificate.Resource) error {
 	log.Trace().Str("domain", cert.Domain).Msg("inserting cert to db")
 
-	domain = integrationTestReplacements(domain)
 	c, err := toCert(domain, cert)
 	if err != nil {
 		return err
@@ -82,7 +81,6 @@ func (x xDB) Get(domain string) (*certificate.Resource, error) {
 	if domain[:1] == "." {
 		domain = "*" + domain
 	}
-	domain = integrationTestReplacements(domain)
 
 	cert := new(Cert)
 	log.Trace().Str("domain", domain).Msg("get cert from db")
@@ -99,7 +97,6 @@ func (x xDB) Delete(domain string) error {
 	if domain[:1] == "." {
 		domain = "*" + domain
 	}
-	domain = integrationTestReplacements(domain)
 
 	log.Trace().Str("domain", domain).Msg("delete cert from db")
 	_, err := x.engine.ID(domain).Delete(new(Cert))
@@ -139,13 +136,3 @@ func supportedDriver(driver string) bool {
 		return false
 	}
 }
-
-// integrationTestReplacements is needed because integration tests use a single domain cert,
-// while production use a wildcard cert
-// TODO: find a better way to handle this
-func integrationTestReplacements(domainKey string) string {
-	if domainKey == "*.localhost.mock.directory" {
-		return "localhost.mock.directory"
-	}
-	return domainKey
-}
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index 4ecc5e0..97024a1 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -26,6 +26,9 @@ const (
 	// TODO: move as option into cache interface
 	fileCacheTimeout = 5 * time.Minute
 
+	// ownerExistenceCacheTimeout specifies the timeout for the existence of a repo/org
+	ownerExistenceCacheTimeout = 5 * time.Minute
+
 	// fileCacheSizeLimit limits the maximum file size that will be cached, and is set to 1 MB by default.
 	fileCacheSizeLimit = int64(1000 * 1000)
 )
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 4f7eaa6..5955bfb 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -28,6 +28,7 @@ const (
 	branchTimestampCacheKeyPrefix = "branchTime"
 	defaultBranchCacheKeyPrefix   = "defaultBranch"
 	rawContentCacheKeyPrefix      = "rawContent"
+	ownerExistenceKeyPrefix       = "ownerExist"
 
 	// pages server
 	PagesCacheIndicatorHeader = "X-Pages-Cache"
@@ -266,6 +267,38 @@ func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (str
 	return branch, nil
 }
 
+func (client *Client) GiteaCheckIfOwnerExists(owner string) (bool, error) {
+	cacheKey := fmt.Sprintf("%s/%s", ownerExistenceKeyPrefix, owner)
+
+	if exist, ok := client.responseCache.Get(cacheKey); ok && exist != nil {
+		return exist.(bool), nil
+	}
+
+	_, resp, err := client.sdkClient.GetUserInfo(owner)
+	if resp.StatusCode == http.StatusOK && err == nil {
+		if err := client.responseCache.Set(cacheKey, true, ownerExistenceCacheTimeout); err != nil {
+			log.Error().Err(err).Msg("[cache] error on cache write")
+		}
+		return true, nil
+	} else if resp.StatusCode != http.StatusNotFound {
+		return false, err
+	}
+
+	_, resp, err = client.sdkClient.GetOrg(owner)
+	if resp.StatusCode == http.StatusOK && err == nil {
+		if err := client.responseCache.Set(cacheKey, true, ownerExistenceCacheTimeout); err != nil {
+			log.Error().Err(err).Msg("[cache] error on cache write")
+		}
+		return true, nil
+	} else if resp.StatusCode != http.StatusNotFound {
+		return false, err
+	}
+	if err := client.responseCache.Set(cacheKey, false, ownerExistenceCacheTimeout); err != nil {
+		log.Error().Err(err).Msg("[cache] error on cache write")
+	}
+	return false, nil
+}
+
 func (client *Client) getMimeTypeByExtension(resource string) string {
 	mimeType := mime.TypeByExtension(path.Ext(resource))
 	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
diff --git a/server/startup.go b/server/startup.go
index ffdabb7..149a07d 100644
--- a/server/startup.go
+++ b/server/startup.go
@@ -110,6 +110,8 @@ func Serve(ctx *cli.Context) error {
 		cfg.Server.PagesBranches[0],
 		keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
 		certDB,
+		cfg.ACME.NoDNS01,
+		cfg.Server.RawDomain,
 	))
 
 	interval := 12 * time.Hour

From 9ffdc9d4f9406b0cb2e1d07346f65c557a58568a Mon Sep 17 00:00:00 2001
From: Daniel Erat <dan@erat.org>
Date: Thu, 18 Apr 2024 21:03:16 +0000
Subject: [PATCH 100/196] Refactor redirect code and add tests (#304)

Move repetitive code from Options.matchRedirects into a new
Redirect.rewriteURL method and add a new test file.

No functional changes are intended; this is in preparation
for a later change to address #269.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/304
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Daniel Erat <dan@erat.org>
Co-committed-by: Daniel Erat <dan@erat.org>
---
 server/upstream/redirects.go      | 75 +++++++++++++------------------
 server/upstream/redirects_test.go | 37 +++++++++++++++
 2 files changed, 68 insertions(+), 44 deletions(-)
 create mode 100644 server/upstream/redirects_test.go

diff --git a/server/upstream/redirects.go b/server/upstream/redirects.go
index dd36a84..e0601d8 100644
--- a/server/upstream/redirects.go
+++ b/server/upstream/redirects.go
@@ -17,6 +17,24 @@ type Redirect struct {
 	StatusCode int
 }
 
+// rewriteURL returns the destination URL and true if r matches reqURL.
+func (r *Redirect) rewriteURL(reqURL string) (dstURL string, ok bool) {
+	// check if from url matches request url
+	if strings.TrimSuffix(r.From, "/") == strings.TrimSuffix(reqURL, "/") {
+		return r.To, true
+	}
+	// handle wildcard redirects
+	trimmedFromURL := strings.TrimSuffix(r.From, "/*")
+	if strings.HasSuffix(r.From, "/*") && strings.HasPrefix(reqURL, trimmedFromURL) {
+		if strings.Contains(r.To, ":splat") {
+			splatURL := strings.ReplaceAll(r.To, ":splat", strings.TrimPrefix(reqURL, trimmedFromURL))
+			return splatURL, true
+		}
+		return r.To, true
+	}
+	return "", false
+}
+
 // redirectsCacheTimeout specifies the timeout for the redirects cache.
 var redirectsCacheTimeout = 10 * time.Minute
 
@@ -64,52 +82,21 @@ func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.I
 }
 
 func (o *Options) matchRedirects(ctx *context.Context, giteaClient *gitea.Client, redirects []Redirect, redirectsCache cache.ICache) (final bool) {
-	if len(redirects) > 0 {
-		for _, redirect := range redirects {
-			reqUrl := ctx.Req.RequestURI
-			// remove repo and branch from request url
-			reqUrl = strings.TrimPrefix(reqUrl, "/"+o.TargetRepo)
-			reqUrl = strings.TrimPrefix(reqUrl, "/@"+o.TargetBranch)
+	reqURL := ctx.Req.RequestURI
+	// remove repo and branch from request url
+	reqURL = strings.TrimPrefix(reqURL, "/"+o.TargetRepo)
+	reqURL = strings.TrimPrefix(reqURL, "/@"+o.TargetBranch)
 
-			// check if from url matches request url
-			if strings.TrimSuffix(redirect.From, "/") == strings.TrimSuffix(reqUrl, "/") {
-				// do rewrite if status code is 200
-				if redirect.StatusCode == 200 {
-					o.TargetPath = redirect.To
-					o.Upstream(ctx, giteaClient, redirectsCache)
-					return true
-				} else {
-					ctx.Redirect(redirect.To, redirect.StatusCode)
-					return true
-				}
-			}
-
-			// handle wildcard redirects
-			trimmedFromUrl := strings.TrimSuffix(redirect.From, "/*")
-			if strings.HasSuffix(redirect.From, "/*") && strings.HasPrefix(reqUrl, trimmedFromUrl) {
-				if strings.Contains(redirect.To, ":splat") {
-					splatUrl := strings.ReplaceAll(redirect.To, ":splat", strings.TrimPrefix(reqUrl, trimmedFromUrl))
-					// do rewrite if status code is 200
-					if redirect.StatusCode == 200 {
-						o.TargetPath = splatUrl
-						o.Upstream(ctx, giteaClient, redirectsCache)
-						return true
-					} else {
-						ctx.Redirect(splatUrl, redirect.StatusCode)
-						return true
-					}
-				} else {
-					// do rewrite if status code is 200
-					if redirect.StatusCode == 200 {
-						o.TargetPath = redirect.To
-						o.Upstream(ctx, giteaClient, redirectsCache)
-						return true
-					} else {
-						ctx.Redirect(redirect.To, redirect.StatusCode)
-						return true
-					}
-				}
+	for _, redirect := range redirects {
+		if dstURL, ok := redirect.rewriteURL(reqURL); ok {
+			// do rewrite if status code is 200
+			if redirect.StatusCode == 200 {
+				o.TargetPath = dstURL
+				o.Upstream(ctx, giteaClient, redirectsCache)
+			} else {
+				ctx.Redirect(dstURL, redirect.StatusCode)
 			}
+			return true
 		}
 	}
 
diff --git a/server/upstream/redirects_test.go b/server/upstream/redirects_test.go
new file mode 100644
index 0000000..c1088a9
--- /dev/null
+++ b/server/upstream/redirects_test.go
@@ -0,0 +1,37 @@
+package upstream
+
+import (
+	"testing"
+)
+
+func TestRedirect_rewriteURL(t *testing.T) {
+	for _, tc := range []struct {
+		redirect   Redirect
+		reqURL     string
+		wantDstURL string
+		wantOk     bool
+	}{
+		{Redirect{"/", "/dst", 200}, "/", "/dst", true},
+		{Redirect{"/", "/dst", 200}, "/foo", "", false},
+		{Redirect{"/src", "/dst", 200}, "/src", "/dst", true},
+		{Redirect{"/src", "/dst", 200}, "/foo", "", false},
+		{Redirect{"/src", "/dst", 200}, "/src/foo", "", false},
+		{Redirect{"/*", "/dst", 200}, "/", "/dst", true},
+		{Redirect{"/*", "/dst", 200}, "/src", "/dst", true},
+		{Redirect{"/src/*", "/dst/:splat", 200}, "/src", "/dst/", true},
+		// TODO: There shouldn't be double-slashes in these URLs:
+		// https://codeberg.org/Codeberg/pages-server/issues/269
+		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/", "/dst//", true},
+		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/foo", "/dst//foo", true},
+		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/foo/bar", "/dst//foo/bar", true},
+		// TODO: This is a workaround for the above bug. Should it be preserved?
+		{Redirect{"/src/*", "/dst:splat", 200}, "/src/foo", "/dst/foo", true},
+		// TODO: This behavior is incorrect; no redirect should be performed.
+		{Redirect{"/src/*", "/dst", 200}, "/srcfoo", "/dst", true},
+	} {
+		if dstURL, ok := tc.redirect.rewriteURL(tc.reqURL); dstURL != tc.wantDstURL || ok != tc.wantOk {
+			t.Errorf("%#v.rewriteURL(%q) = %q, %v; want %q, %v",
+				tc.redirect, tc.reqURL, dstURL, ok, tc.wantDstURL, tc.wantOk)
+		}
+	}
+}

From a986a52755aba425b9a69c94ed48ceb01c56776a Mon Sep 17 00:00:00 2001
From: Moritz Marquardt <momar@noreply.codeberg.org>
Date: Thu, 18 Apr 2024 21:19:45 +0000
Subject: [PATCH 101/196] Fix masked error message from Gitea (#306)

This would yield to the error "forge client failed" instead of e.g. "404 Not Found". The issue was introduced in cbb2ce6d0732bcf2372cbc201fc1c1f2733aadba.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/306
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Moritz Marquardt <momar@noreply.codeberg.org>
Co-committed-by: Moritz Marquardt <momar@noreply.codeberg.org>
---
 server/handler/try.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/server/handler/try.go b/server/handler/try.go
index 145b1a9..e76891d 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -1,6 +1,7 @@
 package handler
 
 import (
+	"fmt"
 	"net/http"
 	"strings"
 
@@ -41,7 +42,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 
 	// Try to request the file from the Gitea API
 	if !options.Upstream(ctx, giteaClient, redirectsCache) {
-		html.ReturnErrorPage(ctx, "forge client failed", ctx.StatusCode)
+		html.ReturnErrorPage(ctx, fmt.Sprintf("Forge returned %d %s", ctx.StatusCode, http.StatusText(ctx.StatusCode)), ctx.StatusCode)
 	}
 }
 

From 69fb22a9e793e92e23e74900950ac9f60a049f9d Mon Sep 17 00:00:00 2001
From: Daniel Erat <dan@erat.org>
Date: Sat, 20 Apr 2024 11:00:15 +0000
Subject: [PATCH 102/196] Avoid extra slashes in redirects with :splat (#308)

Remove leading slashes from captured portions of paths when
redirecting using splats. This makes a directive like
"/articles/*  /posts/:splat  302" behave as described in
FEATURES.md, i.e. "/articles/foo" now redirects to
"/posts/foo" rather than to "/posts//foo". Fixes #269.

This also changes the behavior of a redirect like
"/articles/*  /posts:splat  302". "/articles/foo" will now
redirect to "/postsfoo" rather than to "/posts/foo".

This change also fixes an issue where paths like
"/articles123" would be incorrectly matched by the above
patterns.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/308
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Daniel Erat <dan@erat.org>
Co-committed-by: Daniel Erat <dan@erat.org>
---
 server/upstream/redirects.go      | 15 +++++++++------
 server/upstream/redirects_test.go | 17 ++++++++---------
 2 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/server/upstream/redirects.go b/server/upstream/redirects.go
index e0601d8..ddde375 100644
--- a/server/upstream/redirects.go
+++ b/server/upstream/redirects.go
@@ -24,13 +24,16 @@ func (r *Redirect) rewriteURL(reqURL string) (dstURL string, ok bool) {
 		return r.To, true
 	}
 	// handle wildcard redirects
-	trimmedFromURL := strings.TrimSuffix(r.From, "/*")
-	if strings.HasSuffix(r.From, "/*") && strings.HasPrefix(reqURL, trimmedFromURL) {
-		if strings.Contains(r.To, ":splat") {
-			splatURL := strings.ReplaceAll(r.To, ":splat", strings.TrimPrefix(reqURL, trimmedFromURL))
-			return splatURL, true
+	if strings.HasSuffix(r.From, "/*") {
+		trimmedFromURL := strings.TrimSuffix(r.From, "/*")
+		if reqURL == trimmedFromURL || strings.HasPrefix(reqURL, trimmedFromURL+"/") {
+			if strings.Contains(r.To, ":splat") {
+				matched := strings.TrimPrefix(reqURL, trimmedFromURL)
+				matched = strings.TrimPrefix(matched, "/")
+				return strings.ReplaceAll(r.To, ":splat", matched), true
+			}
+			return r.To, true
 		}
-		return r.To, true
 	}
 	return "", false
 }
diff --git a/server/upstream/redirects_test.go b/server/upstream/redirects_test.go
index c1088a9..6118a70 100644
--- a/server/upstream/redirects_test.go
+++ b/server/upstream/redirects_test.go
@@ -19,15 +19,14 @@ func TestRedirect_rewriteURL(t *testing.T) {
 		{Redirect{"/*", "/dst", 200}, "/", "/dst", true},
 		{Redirect{"/*", "/dst", 200}, "/src", "/dst", true},
 		{Redirect{"/src/*", "/dst/:splat", 200}, "/src", "/dst/", true},
-		// TODO: There shouldn't be double-slashes in these URLs:
-		// https://codeberg.org/Codeberg/pages-server/issues/269
-		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/", "/dst//", true},
-		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/foo", "/dst//foo", true},
-		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/foo/bar", "/dst//foo/bar", true},
-		// TODO: This is a workaround for the above bug. Should it be preserved?
-		{Redirect{"/src/*", "/dst:splat", 200}, "/src/foo", "/dst/foo", true},
-		// TODO: This behavior is incorrect; no redirect should be performed.
-		{Redirect{"/src/*", "/dst", 200}, "/srcfoo", "/dst", true},
+		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/", "/dst/", true},
+		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/foo", "/dst/foo", true},
+		{Redirect{"/src/*", "/dst/:splat", 200}, "/src/foo/bar", "/dst/foo/bar", true},
+		{Redirect{"/src/*", "/dst/:splatsuffix", 200}, "/src/foo", "/dst/foosuffix", true},
+		{Redirect{"/src/*", "/dst:splat", 200}, "/src/foo", "/dstfoo", true},
+		{Redirect{"/src/*", "/dst", 200}, "/srcfoo", "", false},
+		// This is the example from FEATURES.md:
+		{Redirect{"/articles/*", "/posts/:splat", 302}, "/articles/2022/10/12/post-1/", "/posts/2022/10/12/post-1/", true},
 	} {
 		if dstURL, ok := tc.redirect.rewriteURL(tc.reqURL); dstURL != tc.wantDstURL || ok != tc.wantOk {
 			t.Errorf("%#v.rewriteURL(%q) = %q, %v; want %q, %v",

From 019e85a0d0c8315248732e7e04eeab26bb3308cc Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sat, 27 Apr 2024 18:47:42 +0000
Subject: [PATCH 103/196] chore: Configure Renovate (#284)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Welcome to [Renovate](https://github.com/renovatebot/renovate)! This is an onboarding PR to help you understand and configure settings before regular Pull Requests begin.

🚦 To activate Renovate, merge this Pull Request. To disable Renovate, simply close this Pull Request unmerged.

---
### Detected Package Files

 * `Dockerfile` (dockerfile)
 * `go.mod` (gomod)
 * `.woodpecker.yml` (woodpecker)

### Configuration Summary

Based on the default config's presets, Renovate will:

  - Start dependency updates only once this onboarding PR is merged
  - Enable Renovate Dependency Dashboard creation.
  - Use semantic commit type `fix` for dependencies and `chore` for all others if semantic commits are in use.
  - Ignore `node_modules`, `bower_components`, `vendor` and various test/tests directories.
  - Group known monorepo packages together.
  - Use curated list of recommended non-monorepo package groupings.
  - Apply crowd-sourced package replacement rules.
  - Apply crowd-sourced workarounds for known problems with packages.
  - Run lock file maintenance (updates) early Monday mornings.
  - Schedule automerge daily.
  - Schedule for weekends.
  - Enable Renovate Dependency Dashboard creation.
  - Use semantic commit type `fix` for dependencies and `chore` for all others if semantic commits are in use.
  - Ignore `node_modules`, `bower_components`, `vendor` and various test/tests directories.
  - Group known monorepo packages together.
  - Use curated list of recommended non-monorepo package groupings.
  - Apply crowd-sourced package replacement rules.
  - Apply crowd-sourced workarounds for known problems with packages.
  - Run lock file maintenance (updates) early Monday mornings.
  - Enable the pre-commit manager.
  - Schedule automerge daily.
  - Schedule for weekends.
  - Run Renovate on following schedule: every weekend

🔡 Do you want to change how Renovate upgrades your dependencies? Add your custom config to `renovate.json` in this branch. Renovate will update the Pull Request description the next time it runs.

---

### What to Expect

With your current configuration, Renovate will create 3 Pull Requests:

<details>
<summary>fix(deps): update golang.org/x/exp digest to fe59bbe</summary>

  - Schedule: ["every weekend"]
  - Branch name: `renovate/golang.org-x-exp-digest`
  - Merge into: `main`
  - Upgrade golang.org/x/exp to `fe59bbe5cc7f158318a9631d96683d2df264a3c1`

</details>

<details>
<summary>chore(deps): update golang docker tag to v1.22</summary>

  - Schedule: ["every weekend"]
  - Branch name: `renovate/golang-1.x`
  - Merge into: `main`
  - Upgrade golang to `1.22`

</details>

<details>
<summary>fix(deps): update golang deps non-major</summary>

  - Schedule: ["before 4am"]
  - Branch name: `renovate/golang-deps-non-major`
  - Merge into: `main`
  - Upgrade [github.com/go-acme/lego/v4](https://github.com/go-acme/lego) to `v4.16.1`
  - Upgrade [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) to `v1.8.1`
  - Upgrade [github.com/joho/godotenv](https://github.com/joho/godotenv) to `v1.5.1`
  - Upgrade [github.com/lib/pq](https://github.com/lib/pq) to `v1.10.9`
  - Upgrade [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) to `v1.14.22`
  - Upgrade [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) to `v2.2.1`
  - Upgrade [github.com/reugn/equalizer](https://github.com/reugn/equalizer) to `11d4adaf94377c6ab5d3fc2d434cd539de78cb73`
  - Upgrade [github.com/rs/zerolog](https://github.com/rs/zerolog) to `v1.32.0`
  - Upgrade [github.com/stretchr/testify](https://github.com/stretchr/testify) to `v1.9.0`
  - Upgrade [github.com/urfave/cli/v2](https://github.com/urfave/cli) to `v2.27.1`
  - Upgrade [go](https://github.com/golang/go) to `1.22.2`
  - Upgrade xorm.io/xorm to `v1.3.9`

</details>

<br />

🚸 Branch creation will be limited to maximum 2 per hour, so it doesn't swamp any CI resources or overwhelm the project. See docs for `prhourlylimit` for details.

---
>
> ⚠ **Warning**
>
> Please correct - or verify that you can safely ignore - these dependency lookup failures before you merge this PR.
>
> -   `Could not determine new digest for update (go package code.gitea.io/sdk/gitea)`
>
> Files affected: `go.mod`

---

❓ Got questions? Check out Renovate's [Docs](https://docs.renovatebot.com/), particularly the Getting Started section.
If you need any further assistance then you can also [request help here](https://github.com/renovatebot/renovate/discussions).

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

<!--renovate-config-hash:acabd6a7cf9709f3e5bde5fbf7813a36992e721d3e0c5f81180c645a72861fec-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-authored-by: Patrick Schratz <pat-s@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/284
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 renovate.json | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 renovate.json

diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..9dd1cd7
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,27 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+  "extends": [
+    "config:recommended",
+    ":maintainLockFilesWeekly",
+    ":enablePreCommit",
+    "schedule:automergeDaily",
+    "schedule:weekends"
+  ],
+  "automergeType": "branch",
+  "automergeMajor": false,
+  "automerge": true,
+  "prConcurrentLimit": 5,
+  "labels": ["dependencies"],
+  "packageRules": [
+    {
+      "matchManagers": ["gomod", "dockerfile"]
+    },
+    {
+      "groupName": "golang deps non-major",
+      "matchManagers": ["gomod"],
+      "matchUpdateTypes": ["minor", "patch"],
+      "extends": ["schedule:daily"]
+    }
+  ],
+  "postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths"]
+}

From 7092883ebe08e4dcace543518b0a5a783bed5d6c Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sat, 27 Apr 2024 19:14:01 +0000
Subject: [PATCH 104/196] Address CI warnings (#312)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/312
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 .woodpecker.yml | 40 ++++++++++++++++++++++------------------
 1 file changed, 22 insertions(+), 18 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index de8341c..370ffed 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,5 +1,6 @@
 when:
   branch: main
+  event: ["push", "pull_request"]
 
 steps:
   # use vendor to cache dependencies
@@ -10,7 +11,7 @@ steps:
 
   lint:
     image: golangci/golangci-lint:latest
-    group: compliant
+    depends_on: vendor
     pull: true
     commands:
       - go version
@@ -19,46 +20,46 @@ steps:
       - golangci-lint run --timeout 5m --build-tags integration
 
   editor-config:
-    group: compliant
+    depends_on: []
     image: mstruebing/editorconfig-checker
 
   build:
-    group: compliant
+    depends_on: vendor
     image: codeberg.org/6543/docker-images/golang_just
     commands:
       - go version
       - just build
     when:
-      event: [ "pull_request", "push" ]
+      event: ["pull_request", "push"]
 
   docker-dryrun:
-    group: compliant
+    depends_on: vendor
     image: plugins/kaniko
     settings:
       dockerfile: Dockerfile
       no_push: true
       tags: latest
     when:
-      event: [ "pull_request", "push" ]
+      event: ["pull_request", "push"]
       path: Dockerfile
 
   build-tag:
-    group: compliant
+    depends_on: vendor
     image: codeberg.org/6543/docker-images/golang_just
     commands:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
     when:
-      event: [ "tag" ]
+      event: ["tag"]
 
   test:
-    group: test
+    depends_on: build
     image: codeberg.org/6543/docker-images/golang_just
     commands:
       - just test
 
   integration-tests:
-    group: test
+    depends_on: build
     image: codeberg.org/6543/docker-images/golang_just
     commands:
       - just integration
@@ -69,6 +70,7 @@ steps:
       - PORT=4430
 
   release:
+    depends_on: build
     image: plugins/gitea-release
     settings:
       base_url: https://codeberg.org
@@ -77,14 +79,15 @@ steps:
       api_key:
         from_secret: bot_token
     environment:
-      - DRONE_REPO_OWNER=${CI_REPO_OWNER}
-      - DRONE_REPO_NAME=${CI_REPO_NAME}
-      - DRONE_BUILD_EVENT=${CI_BUILD_EVENT}
-      - DRONE_COMMIT_REF=${CI_COMMIT_REF}
+      - CI_REPO_OWNER=${CI_REPO_OWNER}
+      - CI_REPO_NAME=${CI_REPO_NAME}
+      - CI_BUILD_EVENT=${CI_BUILD_EVENT}
+      - CI_COMMIT_REF=${CI_COMMIT_REF}
     when:
-      event: [ "tag" ]
+      event: ["tag"]
 
   docker-next:
+    depends_on: vendor
     image: plugins/kaniko
     settings:
       registry: codeberg.org
@@ -96,19 +99,20 @@ steps:
       password:
         from_secret: bot_token
     when:
-      event: [ "push" ]
+      event: ["push"]
       branch: ${CI_REPO_DEFAULT_BRANCH}
 
   docker-tag:
+    depends_on: vendor
     image: plugins/kaniko
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
       repo: codeberg.org/codeberg/pages-server
-      tags: [ latest, "${CI_COMMIT_TAG}" ]
+      tags: [latest, "${CI_COMMIT_TAG}"]
       username:
         from_secret: bot_user
       password:
         from_secret: bot_token
     when:
-      event: [ "tag" ]
+      event: ["tag"]

From c14c5474b6d50fdb7b6937a2c9ca3cc112145d11 Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sat, 27 Apr 2024 19:40:14 +0000
Subject: [PATCH 105/196] Update gitea sdk and tidy (#314)

Manually as renovate has issues looking up the currently pinned digest. It should work afterwards as the gitea sdk (without a digest) is also used in other projects where renovate is doing work.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/314
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 go.mod | 10 +++++-----
 go.sum | 44 ++++++++++++++++++++++++++++++--------------
 2 files changed, 35 insertions(+), 19 deletions(-)

diff --git a/go.mod b/go.mod
index 47e1e71..e4d880d 100644
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,9 @@ go 1.21
 toolchain go1.21.4
 
 require (
-	code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f
+	code.gitea.io/sdk/gitea v0.17.1
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
+	github.com/creasty/defaults v1.7.0
 	github.com/go-acme/lego/v4 v4.5.3
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/joho/godotenv v1.4.0
@@ -45,7 +46,6 @@ require (
 	github.com/cloudflare/cloudflare-go v0.20.0 // indirect
 	github.com/cpu/goacmedns v0.1.1 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
-	github.com/creasty/defaults v1.7.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/davidmz/go-pageant v1.0.2 // indirect
 	github.com/deepmap/oapi-codegen v1.6.1 // indirect
@@ -122,11 +122,11 @@ require (
 	github.com/vultr/govultr/v2 v2.7.1 // indirect
 	go.opencensus.io v0.22.3 // indirect
 	go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect
-	golang.org/x/crypto v0.14.0 // indirect
+	golang.org/x/crypto v0.17.0 // indirect
 	golang.org/x/net v0.17.0 // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
-	golang.org/x/sys v0.13.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
+	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 	google.golang.org/api v0.20.0 // indirect
 	google.golang.org/appengine v1.6.5 // indirect
diff --git a/go.sum b/go.sum
index 1a10599..0687af6 100644
--- a/go.sum
+++ b/go.sum
@@ -22,8 +22,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f h1:nMmwDgUIAWj9XQjzHz5unC3ZMfhhwHRk6rnuwLzdu1o=
-code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
+code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
+code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
@@ -318,7 +318,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
@@ -724,6 +723,7 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -771,9 +771,9 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -806,6 +806,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -843,7 +844,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -859,8 +862,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -917,12 +922,19 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -931,8 +943,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -983,6 +998,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs
 golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From 1a332c1d541ef51116423870d896aa00e89f0678 Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sat, 27 Apr 2024 22:55:32 +0200
Subject: [PATCH 106/196] fix CI triggers

---
 .woodpecker.yml | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/.woodpecker.yml b/.woodpecker.yml
index 370ffed..25beb00 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,6 +1,9 @@
 when:
-  branch: main
-  event: ["push", "pull_request"]
+  - event: [pull_request, tag, cron]
+  - event: push
+    branch:
+      - ${CI_REPO_DEFAULT_BRANCH}
+      - renovate/*
 
 steps:
   # use vendor to cache dependencies

From e45a354eef079ebb6fee27c41c690ab66c7da4d2 Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sat, 27 Apr 2024 21:26:21 +0000
Subject: [PATCH 107/196] CI modifications (#318)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/318
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 .woodpecker.yml => .woodpecker/build.yml | 60 ++++++++++++++----------
 .woodpecker/lint.yml                     | 26 ++++++++++
 2 files changed, 60 insertions(+), 26 deletions(-)
 rename .woodpecker.yml => .woodpecker/build.yml (70%)
 create mode 100644 .woodpecker/lint.yml

diff --git a/.woodpecker.yml b/.woodpecker/build.yml
similarity index 70%
rename from .woodpecker.yml
rename to .woodpecker/build.yml
index 25beb00..00097b2 100644
--- a/.woodpecker.yml
+++ b/.woodpecker/build.yml
@@ -5,6 +5,9 @@ when:
       - ${CI_REPO_DEFAULT_BRANCH}
       - renovate/*
 
+depends_on:
+  - lint
+
 steps:
   # use vendor to cache dependencies
   vendor:
@@ -12,20 +15,6 @@ steps:
     commands:
       - go mod vendor
 
-  lint:
-    image: golangci/golangci-lint:latest
-    depends_on: vendor
-    pull: true
-    commands:
-      - go version
-      - go install mvdan.cc/gofumpt@latest
-      - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
-      - golangci-lint run --timeout 5m --build-tags integration
-
-  editor-config:
-    depends_on: []
-    image: mstruebing/editorconfig-checker
-
   build:
     depends_on: vendor
     image: codeberg.org/6543/docker-images/golang_just
@@ -33,18 +22,24 @@ steps:
       - go version
       - just build
     when:
-      event: ["pull_request", "push"]
+      - event: [push, pull_request]
+        branch:
+          - ${CI_REPO_DEFAULT_BRANCH}
+          - renovate/*
 
   docker-dryrun:
     depends_on: vendor
-    image: plugins/kaniko
+    image: plugins/kaniko:1.8.8
     settings:
       dockerfile: Dockerfile
       no_push: true
       tags: latest
     when:
-      event: ["pull_request", "push"]
-      path: Dockerfile
+      - event: [push, pull_request]
+        branch:
+          - ${CI_REPO_DEFAULT_BRANCH}
+          - renovate/*
+        path: Dockerfile
 
   build-tag:
     depends_on: vendor
@@ -53,13 +48,19 @@ steps:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
     when:
-      event: ["tag"]
+      - event: ["tag"]
+        branch:
+          - ${CI_REPO_DEFAULT_BRANCH}
 
   test:
     depends_on: build
     image: codeberg.org/6543/docker-images/golang_just
     commands:
       - just test
+    when:
+      - event: pull_request
+      - event: push
+        branch: renovate/*
 
   integration-tests:
     depends_on: build
@@ -71,10 +72,14 @@ steps:
       - PAGES_DOMAIN=localhost.mock.directory
       - RAW_DOMAIN=raw.localhost.mock.directory
       - PORT=4430
+    when:
+      - event: pull_request
+      - event: push
+        branch: renovate/*
 
   release:
     depends_on: build
-    image: plugins/gitea-release
+    image: plugins/gitea-release:0.3.1
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite
@@ -87,11 +92,13 @@ steps:
       - CI_BUILD_EVENT=${CI_BUILD_EVENT}
       - CI_COMMIT_REF=${CI_COMMIT_REF}
     when:
-      event: ["tag"]
+      - event: ["tag"]
+        branch:
+          - ${CI_REPO_DEFAULT_BRANCH}
 
   docker-next:
     depends_on: vendor
-    image: plugins/kaniko
+    image: plugins/kaniko:1.8.8
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
@@ -102,12 +109,12 @@ steps:
       password:
         from_secret: bot_token
     when:
-      event: ["push"]
-      branch: ${CI_REPO_DEFAULT_BRANCH}
+      - event: ["push"]
+        branch: ${CI_REPO_DEFAULT_BRANCH}
 
   docker-tag:
     depends_on: vendor
-    image: plugins/kaniko
+    image: plugins/kaniko:1.8.8
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
@@ -118,4 +125,5 @@ steps:
       password:
         from_secret: bot_token
     when:
-      event: ["tag"]
+      - event: ["push"]
+        branch: ${CI_REPO_DEFAULT_BRANCH}
diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
new file mode 100644
index 0000000..02d9ec8
--- /dev/null
+++ b/.woodpecker/lint.yml
@@ -0,0 +1,26 @@
+when:
+  - event: pull_request
+  - event: push
+    branch: renovate/*
+
+steps:
+  lint:
+    depends_on: []
+    image: golangci/golangci-lint:v1.57.2
+    commands:
+      - go version
+      - go install mvdan.cc/gofumpt@latest
+      - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
+      - golangci-lint run --timeout 5m --build-tags integration
+    when:
+      - event: pull_request
+      - event: push
+        branch: renovate/*
+
+  editor-config:
+    depends_on: []
+    image: mstruebing/editorconfig-checker:2.7.2
+    when:
+      - event: pull_request
+      - event: push
+        branch: renovate/*

From 418afb7357097f22aa64c39dadf80d489236df38 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sat, 27 Apr 2024 21:40:40 +0000
Subject: [PATCH 108/196] add nix flake for dev env (#320)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/320
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 flake.lock | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 flake.nix  | 26 +++++++++++++++++++
 2 files changed, 99 insertions(+)
 create mode 100644 flake.lock
 create mode 100644 flake.nix

diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..b14ba48
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,73 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1710146030,
+        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+        "type": "github"
+      },
+      "original": {
+        "id": "flake-utils",
+        "type": "indirect"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1714030708,
+        "narHash": "sha256-JOGPOxa8N6ySzB7SQBsh0OVz+UXZriyahgvfNHMIY0Y=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "b0d52b31f7f4d80f8bf38f0253652125579c35ff",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "type": "indirect"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs",
+        "systems": "systems_2"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "systems_2": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "id": "systems",
+        "type": "indirect"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..f981ed1
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,26 @@
+{
+  outputs = {
+    self,
+    nixpkgs,
+    flake-utils,
+    systems,
+  }:
+    flake-utils.lib.eachSystem (import systems)
+    (system: let
+      pkgs = import nixpkgs {
+        inherit system;
+      };
+    in {
+      devShells.default = pkgs.mkShell {
+        buildInputs = with pkgs; [
+          gcc
+          go
+          gofumpt
+          gopls
+          gotools
+          go-tools
+          sqlite-interactive
+        ];
+      };
+    });
+}

From 89800d4f3660ea79bfc70ff9f179b08d7cae4f4e Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 28 Apr 2024 08:24:39 +0000
Subject: [PATCH 109/196] chore(deps): update plugins/gitea-release docker tag
 to v1 (#322)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [plugins/gitea-release](https://github.com/drone-plugins/drone-gitea-release) | major | `0.3.1` -> `1.1.0` |

---

### Release Notes

<details>
<summary>drone-plugins/drone-gitea-release (plugins/gitea-release)</summary>

### [`v1.1.0`](https://github.com/drone-plugins/drone-gitea-release/compare/v1.0.0...v1.1.0)

[Compare Source](https://github.com/drone-plugins/drone-gitea-release/compare/v1.0.0...v1.1.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zMjYuMSIsInVwZGF0ZWRJblZlciI6IjM3LjMyNi4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/322
Reviewed-by: Patrick Schratz <pat-s@noreply.codeberg.org>
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 00097b2..81821ca 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -79,7 +79,7 @@ steps:
 
   release:
     depends_on: build
-    image: plugins/gitea-release:0.3.1
+    image: plugins/gitea-release:1.1.0
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite

From f407fd3ae40cdb538d3014d1dc25dedb540c6c5c Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sun, 28 Apr 2024 20:47:04 +0000
Subject: [PATCH 110/196] Add prettier config and format files (#319)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/319
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 .prettierrc.json                        |  8 +++++++
 .woodpecker/build.yml                   | 10 ++++-----
 .woodpecker/lint.yml                    |  6 +++++
 FEATURES.md                             | 14 ++++++------
 README.md                               | 29 +++++++++++++------------
 examples/haproxy-sni/README.md          |  4 +++-
 examples/haproxy-sni/docker-compose.yml | 21 +++++++++---------
 html/templates/error.html               | 28 +++++-------------------
 8 files changed, 60 insertions(+), 60 deletions(-)
 create mode 100644 .prettierrc.json

diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..aed6467
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,8 @@
+{
+  "semi": true,
+  "trailingComma": "all",
+  "singleQuote": true,
+  "printWidth": 120,
+  "tabWidth": 2,
+  "endOfLine": "lf"
+}
diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 81821ca..a5ff820 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -48,7 +48,7 @@ steps:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
     when:
-      - event: ["tag"]
+      - event: ['tag']
         branch:
           - ${CI_REPO_DEFAULT_BRANCH}
 
@@ -92,7 +92,7 @@ steps:
       - CI_BUILD_EVENT=${CI_BUILD_EVENT}
       - CI_COMMIT_REF=${CI_COMMIT_REF}
     when:
-      - event: ["tag"]
+      - event: ['tag']
         branch:
           - ${CI_REPO_DEFAULT_BRANCH}
 
@@ -109,7 +109,7 @@ steps:
       password:
         from_secret: bot_token
     when:
-      - event: ["push"]
+      - event: ['push']
         branch: ${CI_REPO_DEFAULT_BRANCH}
 
   docker-tag:
@@ -119,11 +119,11 @@ steps:
       registry: codeberg.org
       dockerfile: Dockerfile
       repo: codeberg.org/codeberg/pages-server
-      tags: [latest, "${CI_COMMIT_TAG}"]
+      tags: [latest, '${CI_COMMIT_TAG}']
       username:
         from_secret: bot_user
       password:
         from_secret: bot_token
     when:
-      - event: ["push"]
+      - event: ['push']
         branch: ${CI_REPO_DEFAULT_BRANCH}
diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 02d9ec8..3b269f6 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -24,3 +24,9 @@ steps:
       - event: pull_request
       - event: push
         branch: renovate/*
+
+  prettier:
+    image: docker.io/woodpeckerci/plugin-prettier:0.1.0
+    depends_on: []
+    settings:
+      version: 3.2.5
diff --git a/FEATURES.md b/FEATURES.md
index 3d2f394..52b90e5 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -19,16 +19,16 @@ Redirects can be created with a `_redirects` file with the following format:
 from  to  [status]
 ```
 
-* Lines starting with `#` are ignored
-* `from` - the path to redirect from (Note: repository and branch names are removed from request URLs)
-* `to` - the path or URL to redirect to
-* `status` - status code to use when redirecting (default 301)
+- Lines starting with `#` are ignored
+- `from` - the path to redirect from (Note: repository and branch names are removed from request URLs)
+- `to` - the path or URL to redirect to
+- `status` - status code to use when redirecting (default 301)
 
 ### Status codes
 
-* `200` - returns content from specified path (no external URLs) without changing the URL (rewrite)
-* `301` - Moved Permanently (Permanent redirect)
-* `302` - Found (Temporary redirect)
+- `200` - returns content from specified path (no external URLs) without changing the URL (rewrite)
+- `301` - Moved Permanently (Permanent redirect)
+- `302` - Found (Temporary redirect)
 
 ### Examples
 
diff --git a/README.md b/README.md
index f47a196..c23d86a 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 [![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue)](https://opensource.org/license/eupl-1-2/)
 [![status-badge](https://ci.codeberg.org/api/badges/Codeberg/pages-server/status.svg)](https://ci.codeberg.org/Codeberg/pages-server)
 <a href="https://matrix.to/#/#gitea-pages-server:matrix.org" title="Join the Matrix room at https://matrix.to/#/#gitea-pages-server:matrix.org">
-  <img src="https://img.shields.io/matrix/gitea-pages-server:matrix.org?label=matrix">
+<img src="https://img.shields.io/matrix/gitea-pages-server:matrix.org?label=matrix">
 </a>
 
 Gitea lacks the ability to host static pages from Git.
@@ -21,19 +21,19 @@ and the [Codeberg Documentation](https://docs.codeberg.org/codeberg-pages/).
 This is the new Codeberg Pages server, a solution for serving static pages from Gitea repositories.
 Mapping custom domains is not static anymore, but can be done with DNS:
 
-1) add a `.domains` text file to your repository, containing the allowed domains, separated by new lines. The
-first line will be the canonical domain/URL; all other occurrences will be redirected to it.
+1. add a `.domains` text file to your repository, containing the allowed domains, separated by new lines. The
+   first line will be the canonical domain/URL; all other occurrences will be redirected to it.
 
-2) add a CNAME entry to your domain, pointing to `[[{branch}.]{repo}.]{owner}.codeberg.page` (repo defaults to
-"pages", "branch" defaults to the default branch if "repo" is "pages", or to "pages" if "repo" is something else.
-If the branch name contains slash characters, you need to replace "/" in the branch name to "~"):
-  `www.example.org. IN CNAME main.pages.example.codeberg.page.`
+2. add a CNAME entry to your domain, pointing to `[[{branch}.]{repo}.]{owner}.codeberg.page` (repo defaults to
+   "pages", "branch" defaults to the default branch if "repo" is "pages", or to "pages" if "repo" is something else.
+   If the branch name contains slash characters, you need to replace "/" in the branch name to "~"):
+   `www.example.org. IN CNAME main.pages.example.codeberg.page.`
 
-3) if a CNAME is set for "www.example.org", you can redirect there from the naked domain by adding an ALIAS record
-for "example.org" (if your provider allows ALIAS or similar records, otherwise use A/AAAA), together with a TXT
-record that points to your repo (just like the CNAME record):
-  `example.org IN ALIAS codeberg.page.`
-  `example.org IN TXT main.pages.example.codeberg.page.`
+3. if a CNAME is set for "www.example.org", you can redirect there from the naked domain by adding an ALIAS record
+   for "example.org" (if your provider allows ALIAS or similar records, otherwise use A/AAAA), together with a TXT
+   record that points to your repo (just like the CNAME record):
+   `example.org IN ALIAS codeberg.page.`
+   `example.org IN TXT main.pages.example.codeberg.page.`
 
 Certificates are generated, updated and cleaned up automatically via Let's Encrypt through a TLS challenge.
 
@@ -43,7 +43,8 @@ Certificates are generated, updated and cleaned up automatically via Let's Encry
 
 ## Deployment
 
-**Warning: Some Caveats Apply**  
+**Warning: Some Caveats Apply**
+
 > Currently, the deployment requires you to have some knowledge of system administration as well as understanding and building code,
 > so you can eventually edit non-configurable and codeberg-specific settings.
 > In the future, we'll try to reduce these and make hosting Codeberg Pages as easy as setting up Gitea.
@@ -74,7 +75,7 @@ and especially have a look at [this section of the haproxy.cfg](https://codeberg
 - `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging).  
   ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
 - `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
-- `ACME_EAB_KID` &  `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
+- `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
 - `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider.
 - `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.
 - `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
diff --git a/examples/haproxy-sni/README.md b/examples/haproxy-sni/README.md
index 8bb8f02..8165d6c 100644
--- a/examples/haproxy-sni/README.md
+++ b/examples/haproxy-sni/README.md
@@ -1,8 +1,9 @@
 # HAProxy with SNI & Host-based rules
 
-This is a proof of concept, enabling HAProxy to use *either* SNI to redirect to backends with their own HTTPS certificates (which are then fully exposed to the client; HAProxy only proxies on a TCP level in that case), *as well as* to terminate HTTPS and use the Host header to redirect to backends that use HTTP (or a new HTTPS connection).
+This is a proof of concept, enabling HAProxy to use _either_ SNI to redirect to backends with their own HTTPS certificates (which are then fully exposed to the client; HAProxy only proxies on a TCP level in that case), _as well as_ to terminate HTTPS and use the Host header to redirect to backends that use HTTP (or a new HTTPS connection).
 
 ## How it works
+
 1. The `http_redirect_frontend` is only there to listen on port 80 and redirect every request to HTTPS.
 2. The `https_sni_frontend` listens on port 443 and chooses a backend based on the SNI hostname of the TLS connection.
 3. The `https_termination_backend` passes all requests to a unix socket (using the plain TCP data).
@@ -11,6 +12,7 @@ This is a proof of concept, enabling HAProxy to use *either* SNI to redirect to
 In the example (see [haproxy.cfg](haproxy.cfg)), the `pages_backend` is listening via HTTPS and is providing its own HTTPS certificates, while the `gitea_backend` only provides HTTP.
 
 ## How to test
+
 ```bash
 docker-compose up &
 ./test.sh
diff --git a/examples/haproxy-sni/docker-compose.yml b/examples/haproxy-sni/docker-compose.yml
index 66ff52b..2cac2ad 100644
--- a/examples/haproxy-sni/docker-compose.yml
+++ b/examples/haproxy-sni/docker-compose.yml
@@ -1,22 +1,21 @@
-version: "3"
+version: '3'
 services:
   haproxy:
     image: haproxy
-    ports: ["443:443"]
+    ports: ['443:443']
     volumes:
-    - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
-    - ./dhparam.pem:/etc/ssl/dhparam.pem:ro
-    - ./haproxy-certificates:/etc/ssl/private/haproxy:ro
+      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
+      - ./dhparam.pem:/etc/ssl/dhparam.pem:ro
+      - ./haproxy-certificates:/etc/ssl/private/haproxy:ro
     cap_add:
-    - NET_ADMIN
+      - NET_ADMIN
   gitea:
     image: caddy
     volumes:
-    - ./gitea-www:/srv:ro
-    - ./gitea.Caddyfile:/etc/caddy/Caddyfile:ro
+      - ./gitea-www:/srv:ro
+      - ./gitea.Caddyfile:/etc/caddy/Caddyfile:ro
   pages:
     image: caddy
     volumes:
-    - ./pages-www:/srv:ro
-    - ./pages.Caddyfile:/etc/caddy/Caddyfile:ro
-
+      - ./pages-www:/srv:ro
+      - ./pages.Caddyfile:/etc/caddy/Caddyfile:ro
diff --git a/html/templates/error.html b/html/templates/error.html
index 6094a26..05a5d46 100644
--- a/html/templates/error.html
+++ b/html/templates/error.html
@@ -1,18 +1,12 @@
-<!DOCTYPE html>
+<!doctype html>
 <html class="codeberg-design">
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <title>{{.StatusText}}</title>
 
-    <link
-      rel="stylesheet"
-      href="https://design.codeberg.org/design-kit/codeberg.css"
-    />
-    <link
-      rel="stylesheet"
-      href="https://fonts.codeberg.org/dist/inter/Inter%20Web/inter.css"
-    />
+    <link rel="stylesheet" href="https://design.codeberg.org/design-kit/codeberg.css" />
+    <link rel="stylesheet" href="https://fonts.codeberg.org/dist/inter/Inter%20Web/inter.css" />
 
     <style>
       body {
@@ -34,12 +28,7 @@
     </style>
   </head>
   <body>
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      height="10em"
-      viewBox="0 0 24 24"
-      fill="var(--blue-color)"
-    >
+    <svg xmlns="http://www.w3.org/2000/svg" height="10em" viewBox="0 0 24 24" fill="var(--blue-color)">
       <path
         d="M 9 2 C 5.1458514 2 2 5.1458514 2 9 C 2 12.854149 5.1458514 16 9 16 C 10.747998 16 12.345009 15.348024 13.574219 14.28125 L 14 14.707031 L 14 16 L 19.585938 21.585938 C 20.137937 22.137937 21.033938 22.137938 21.585938 21.585938 C 22.137938 21.033938 22.137938 20.137938 21.585938 19.585938 L 16 14 L 14.707031 14 L 14.28125 13.574219 C 15.348024 12.345009 16 10.747998 16 9 C 16 5.1458514 12.854149 2 9 2 z M 9 4 C 11.773268 4 14 6.2267316 14 9 C 14 11.773268 11.773268 14 9 14 C 6.2267316 14 4 11.773268 4 9 C 4 6.2267316 6.2267316 4 9 4 z"
       />
@@ -50,18 +39,13 @@
       <p><b>"{{.Message}}"</b></p>
       <p>
         We hope this isn't a problem on our end ;) - Make sure to check the
-        <a
-          href="https://docs.codeberg.org/codeberg-pages/troubleshooting/"
-          target="_blank"
+        <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank"
           >troubleshooting section in the Docs</a
         >!
       </p>
     </h5>
     <small class="text-muted">
-      <img
-        src="https://design.codeberg.org/logo-kit/icon.svg"
-        class="align-top"
-      />
+      <img src="https://design.codeberg.org/logo-kit/icon.svg" class="align-top" />
       Static pages made easy -
       <a href="https://codeberg.page">Codeberg Pages</a>
     </small>

From 8cba7f9c8ad591b2c2ebef8f0978343ab3a14ead Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sun, 28 Apr 2024 21:48:07 +0000
Subject: [PATCH 111/196] Add yamllint (#321)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/321
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 .woodpecker/lint.yml | 10 ++++++++++
 .yamllint.yaml       | 19 +++++++++++++++++++
 2 files changed, 29 insertions(+)
 create mode 100644 .yamllint.yaml

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 3b269f6..8a90a15 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -25,6 +25,16 @@ steps:
       - event: push
         branch: renovate/*
 
+  yamllint:
+    image: pipelinecomponents/yamllint:0.31.1
+    depends_on: []
+    commands:
+      - yamllint .
+    when:
+      - event: pull_request
+      - event: push
+        branch: renovate/*
+
   prettier:
     image: docker.io/woodpeckerci/plugin-prettier:0.1.0
     depends_on: []
diff --git a/.yamllint.yaml b/.yamllint.yaml
new file mode 100644
index 0000000..2b1f87c
--- /dev/null
+++ b/.yamllint.yaml
@@ -0,0 +1,19 @@
+extends: default
+
+rules:
+  comments:
+    require-starting-space: false
+    ignore-shebangs: true
+    min-spaces-from-content: 1
+  braces:
+    min-spaces-inside: 1
+    max-spaces-inside: 1
+  document-start:
+    present: false
+  indentation:
+    spaces: 2
+    indent-sequences: true
+  line-length:
+    max: 256
+  new-lines:
+    type: unix

From d09c6e12180467d5811e5ec929e57f633861cd17 Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Mon, 29 Apr 2024 12:46:00 +0000
Subject: [PATCH 112/196] Multiarch images (#324)

taken from #214

Configured to only build multiarch when also publishing (when running in `main` or for a release)

## Build time

- amd64 & arm64: 07:42
- amd64 only: 04:26

Build time via kaniko building only amd64: ~ 6/7 min

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/324
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 .woodpecker/build.yml | 11 +++++++----
 Dockerfile            | 31 ++++++++++++++++++++++++++-----
 2 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index a5ff820..1be4885 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -29,10 +29,11 @@ steps:
 
   docker-dryrun:
     depends_on: vendor
-    image: plugins/kaniko:1.8.8
+    image: woodpeckerci/plugin-docker-buildx:3.2.1
     settings:
       dockerfile: Dockerfile
-      no_push: true
+      platforms: linux/amd64
+      dry-run: true
       tags: latest
     when:
       - event: [push, pull_request]
@@ -98,10 +99,11 @@ steps:
 
   docker-next:
     depends_on: vendor
-    image: plugins/kaniko:1.8.8
+    image: woodpeckerci/plugin-docker-buildx:3.2.1
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
+      platforms: linux/amd64,arm64
       repo: codeberg.org/codeberg/pages-server
       tags: next
       username:
@@ -114,10 +116,11 @@ steps:
 
   docker-tag:
     depends_on: vendor
-    image: plugins/kaniko:1.8.8
+    image: woodpeckerci/plugin-docker-buildx:3.2.1
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
+      platforms: linux/amd64,arm64
       repo: codeberg.org/codeberg/pages-server
       tags: [latest, '${CI_COMMIT_TAG}']
       username:
diff --git a/Dockerfile b/Dockerfile
index eec97de..9c05cda 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,35 @@
-FROM techknowlogick/xgo as build
+# Set the default Go version as a build argument
+ARG XGO="go-1.21.x"
 
-WORKDIR /workspace
+# Use xgo (a Go cross-compiler tool) as build image
+FROM --platform=$BUILDPLATFORM techknowlogick/xgo:${XGO} as build
 
-COPY . .
-RUN CGO_ENABLED=1 go build -tags 'sqlite sqlite_unlock_notify netgo' -ldflags '-s -w -extldflags "-static" -linkmode external' .
+# Set the working directory and copy the source code
+WORKDIR /go/src/codeberg.org/codeberg/pages
+COPY . /go/src/codeberg.org/codeberg/pages
 
+# Set the target architecture (can be set using --build-arg), buildx set it automatically
+ARG TARGETOS TARGETARCH
+
+# Build the binary using xgo
+RUN --mount=type=cache,target=/root/.cache/go-build \
+    --mount=type=cache,target=/go/pkg \
+    GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=1 \
+    xgo -x -v --targets=${TARGETOS}/${TARGETARCH} -tags='sqlite sqlite_unlock_notify netgo' -ldflags='-s -w -extldflags "-static" -linkmode external' -out pages .
+
+# Use a scratch image as the base image for the final container,
+# which will contain only the built binary and the CA certificates
 FROM scratch
-COPY --from=build /workspace/pages /pages
+
+# Copy the built binary and the CA certificates from the build container to the final container
+COPY --from=build /go/src/codeberg.org/codeberg/pages/ /pages
 COPY --from=build \
     /etc/ssl/certs/ca-certificates.crt \
     /etc/ssl/certs/ca-certificates.crt
 
+# Expose ports 80 and 443 for the built binary to listen on
+EXPOSE 80/tcp
+EXPOSE 443/tcp
+
+# Set the entrypoint for the container to the built binary
 ENTRYPOINT ["/pages"]

From ca9433e0eae1ced3f356c86413c589529be02bd7 Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Mon, 29 Apr 2024 14:48:00 +0200
Subject: [PATCH 113/196] run lint workflow on main branch to unblock build
 workflow

---
 .woodpecker/lint.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 8a90a15..0977645 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -1,7 +1,9 @@
 when:
   - event: pull_request
   - event: push
-    branch: renovate/*
+    branch:
+      - ${CI_REPO_DEFAULT_BRANCH}
+      - renovate/**
 
 steps:
   lint:

From 56d44609eaa23212c0bf09d5284c9d782aaeccfc Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Tue, 30 Apr 2024 19:50:03 +0000
Subject: [PATCH 114/196] Add option to start http server for profiling (#323)

https://rafallorenz.com/go/go-profiling-http-service-with-pprof-and-expvar/
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/323
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 Justfile            |  4 ++--
 README.md           | 15 +++++++++++++++
 cli/flags.go        | 12 ++++++++++++
 server/profiling.go | 21 +++++++++++++++++++++
 server/startup.go   |  8 ++++----
 5 files changed, 54 insertions(+), 6 deletions(-)
 create mode 100644 server/profiling.go

diff --git a/Justfile b/Justfile
index 0bf38a3..231df7f 100644
--- a/Justfile
+++ b/Justfile
@@ -1,13 +1,13 @@
 CGO_FLAGS := '-extldflags "-static" -linkmode external'
 TAGS      := 'sqlite sqlite_unlock_notify netgo'
 
-dev:
+dev *FLAGS:
     #!/usr/bin/env bash
     set -euxo pipefail
     set -a # automatically export all variables
     source .env-dev
     set +a
-    go run -tags '{{TAGS}}' .
+    go run -tags '{{TAGS}}' . {{FLAGS}}
 
 build:
     CGO_ENABLED=1 go build -tags '{{TAGS}}' -ldflags '-s -w {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./
diff --git a/README.md b/README.md
index c23d86a..a1d6424 100644
--- a/README.md
+++ b/README.md
@@ -124,3 +124,18 @@ now these pages should work:
 - <https://momar.localhost.mock.directory:4430/ci-testing/>
 - <https://momar.localhost.mock.directory:4430/pag/@master/>
 - <https://mock-pages.codeberg-test.org:4430/README.md>
+
+### Profiling
+
+> This section is just a collection of commands for quick reference. If you want to learn more about profiling read [this](https://go.dev/doc/diagnostics) article or google `golang profiling`.
+
+First enable profiling by supplying the cli arg `--enable-profiling` or using the environment variable `EENABLE_PROFILING`.
+
+Get cpu and mem stats:
+
+```bash
+go tool pprof -raw -output=cpu.txt 'http://localhost:9999/debug/pprof/profile?seconds=60' &
+curl -so mem.txt 'http://localhost:9999/debug/pprof/heap?seconds=60'
+```
+
+More endpoints are documented here: <https://pkg.go.dev/net/http/pprof>
diff --git a/cli/flags.go b/cli/flags.go
index 52e1c1c..f7a7dc8 100644
--- a/cli/flags.go
+++ b/cli/flags.go
@@ -139,6 +139,18 @@ var (
 			EnvVars: []string{"CONFIG_FILE"},
 		},
 
+		&cli.BoolFlag{
+			Name:    "enable-profiling",
+			Usage:   "enables the go http profiling endpoints",
+			EnvVars: []string{"ENABLE_PROFILING"},
+		},
+		&cli.StringFlag{
+			Name:    "profiling-address",
+			Usage:   "specify ip address and port the profiling server should listen on",
+			EnvVars: []string{"PROFILING_ADDRESS"},
+			Value:   "localhost:9999",
+		},
+
 		// ############################
 		// ### ACME Client Settings ###
 		// ############################
diff --git a/server/profiling.go b/server/profiling.go
new file mode 100644
index 0000000..7d20926
--- /dev/null
+++ b/server/profiling.go
@@ -0,0 +1,21 @@
+package server
+
+import (
+	"net/http"
+	_ "net/http/pprof"
+
+	"github.com/rs/zerolog/log"
+)
+
+func StartProfilingServer(listeningAddress string) {
+	server := &http.Server{
+		Addr:    listeningAddress,
+		Handler: http.DefaultServeMux,
+	}
+
+	log.Info().Msgf("Starting debug server on %s", listeningAddress)
+
+	go func() {
+		log.Fatal().Err(server.ListenAndServe()).Msg("Failed to start debug server")
+	}()
+}
diff --git a/server/startup.go b/server/startup.go
index 149a07d..fd89803 100644
--- a/server/startup.go
+++ b/server/startup.go
@@ -3,7 +3,6 @@ package server
 import (
 	"context"
 	"crypto/tls"
-	"encoding/json"
 	"fmt"
 	"net"
 	"net/http"
@@ -43,9 +42,6 @@ func Serve(ctx *cli.Context) error {
 	}
 	log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel)
 
-	foo, _ := json.Marshal(cfg)
-	log.Trace().RawJSON("config", foo).Msg("starting server with config")
-
 	listeningSSLAddress := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
 	listeningHTTPAddress := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.HttpPort)
 
@@ -133,6 +129,10 @@ func Serve(ctx *cli.Context) error {
 		}()
 	}
 
+	if ctx.IsSet("enable-profiling") {
+		StartProfilingServer(ctx.String("profiling-address"))
+	}
+
 	// Create ssl handler based on settings
 	sslHandler := handler.Handler(cfg.Server, giteaClient, dnsLookupCache, canonicalDomainCache, redirectsCache)
 

From eb08c46dcd85f5b93b4339d4a77ad669762ce1cb Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Thu, 2 May 2024 13:20:17 +0000
Subject: [PATCH 115/196] chore(deps): update golang docker tag to v1.22 (#326)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| golang | minor | `1.21` -> `1.22` |

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zMzMuMSIsInVwZGF0ZWRJblZlciI6IjM3LjMzMy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/326
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 1be4885..f2cb615 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -11,7 +11,7 @@ depends_on:
 steps:
   # use vendor to cache dependencies
   vendor:
-    image: golang:1.21
+    image: golang:1.22
     commands:
       - go mod vendor
 

From 7c49c4b967d47f74e88b5223e872ca6da750955c Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 5 May 2024 00:59:58 +0000
Subject: [PATCH 116/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.58.0 (#330)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 0977645..ee75113 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -8,7 +8,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.57.2
+    image: golangci/golangci-lint:v1.58.0
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From adf13bfdbcb7c7f60a1684395984611ab6e15ae0 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Tue, 7 May 2024 09:09:27 +0000
Subject: [PATCH 117/196] chore(deps): update mstruebing/editorconfig-checker
 docker tag to v3 (#329)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| mstruebing/editorconfig-checker | major | `2.7.2` -> `v3.0.1` |

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zNDAuNSIsInVwZGF0ZWRJblZlciI6IjM3LjM0MC41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/329
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index ee75113..cc361c0 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -21,7 +21,7 @@ steps:
 
   editor-config:
     depends_on: []
-    image: mstruebing/editorconfig-checker:2.7.2
+    image: mstruebing/editorconfig-checker:v3.0.1
     when:
       - event: pull_request
       - event: push

From d74f1fe8a4cedc348e25d15831a05b23cb15c358 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 12 May 2024 00:55:23 +0000
Subject: [PATCH 118/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.58.1 (#333)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index cc361c0..5efcca1 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -8,7 +8,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.58.0
+    image: golangci/golangci-lint:v1.58.1
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From c1df2f068b8fac7afaa98d63056945c5f7497c75 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 19 May 2024 01:11:30 +0000
Subject: [PATCH 119/196] fix(deps): update golang.org/x/exp digest to 9bf2ced
 (#335)

---
 go.mod |  2 +-
 go.sum | 17 ++++++++++-------
 2 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/go.mod b/go.mod
index e4d880d..518dff0 100644
--- a/go.mod
+++ b/go.mod
@@ -19,7 +19,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
+	golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index 0687af6..ae24fc3 100644
--- a/go.sum
+++ b/go.sum
@@ -254,8 +254,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -784,8 +784,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
-golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -807,8 +807,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -864,8 +865,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -999,8 +1001,9 @@ golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roY
 golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
+golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From b54cd38d0be564dad2dce2e25c82dffed7bc4428 Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sun, 19 May 2024 16:18:11 +0000
Subject: [PATCH 120/196] Fix Dockerfile binary location (#337)

I know get the following

```
docker run --rm pages-server:test
12:40PM ERR A fatal error occurred error="could not create new gitea client: Get \"/api/v1/version\": unsupported protocol scheme \"\""
```

which I am not sure is OK as doing the same with v5.1 results in

```
docker run --platform linux/amd64 --rm -it codeberg.org/codeberg/pages-server:v5.1

ACME client has wrong config: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory
```

The error is the same though what I get when building of 8cba7f9c8ad591b2c2ebef8f0978343ab3a14ead (just before merging the multi-arch PR).
Not sure if this ERR is expected but it should be unrelated to the multiarch approach.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/337
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 Dockerfile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index 9c05cda..6106317 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,13 +16,14 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
     --mount=type=cache,target=/go/pkg \
     GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=1 \
     xgo -x -v --targets=${TARGETOS}/${TARGETARCH} -tags='sqlite sqlite_unlock_notify netgo' -ldflags='-s -w -extldflags "-static" -linkmode external' -out pages .
+RUN mv -vf /build/pages-* /go/src/codeberg.org/codeberg/pages/pages
 
 # Use a scratch image as the base image for the final container,
 # which will contain only the built binary and the CA certificates
 FROM scratch
 
 # Copy the built binary and the CA certificates from the build container to the final container
-COPY --from=build /go/src/codeberg.org/codeberg/pages/ /pages
+COPY --from=build /go/src/codeberg.org/codeberg/pages/pages /pages
 COPY --from=build \
     /etc/ssl/certs/ca-certificates.crt \
     /etc/ssl/certs/ca-certificates.crt

From 69361c69c143ab3edce230269d8a5084b16fb580 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 20 May 2024 00:39:02 +0000
Subject: [PATCH 121/196] chore(deps): update pipelinecomponents/yamllint
 docker tag to v0.31.2 (#336)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 5efcca1..0dde00e 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -28,7 +28,7 @@ steps:
         branch: renovate/*
 
   yamllint:
-    image: pipelinecomponents/yamllint:0.31.1
+    image: pipelinecomponents/yamllint:0.31.2
     depends_on: []
     commands:
       - yamllint .

From 885cfac2ec4f04f5a62401a71f4e598b06445f74 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 26 May 2024 01:26:39 +0000
Subject: [PATCH 122/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.58.2 (#341)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 0dde00e..cf59edc 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -8,7 +8,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.58.1
+    image: golangci/golangci-lint:v1.58.2
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From eea009c7febce63ed21b8184af244181aa266a35 Mon Sep 17 00:00:00 2001
From: pat-s <patrick.schratz@gmail.com>
Date: Sun, 26 May 2024 14:34:03 +0000
Subject: [PATCH 123/196] Allow building PR images on demand (#340)

Triggered when the label is set in a PR.
Helps to test changes in PRs.

Co-authored-by: crapStone <codeberg@crapstone.dev>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/340
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
---
 .woodpecker/build.yml | 17 +++++++++++++++++
 README.md             | 11 +++++++----
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index f2cb615..a86c7fe 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -114,6 +114,23 @@ steps:
       - event: ['push']
         branch: ${CI_REPO_DEFAULT_BRANCH}
 
+  'Publish PR image':
+    image: woodpeckerci/plugin-docker-buildx:3.2.1
+    depends_on: test
+    settings:
+      registry: codeberg.org
+      dockerfile: Dockerfile
+      platforms: linux/amd64
+      repo: codeberg.org/codeberg/pages-server
+      tags: next
+      username:
+        from_secret: bot_user
+      password:
+        from_secret: bot_token
+    when:
+      evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_image"'
+      event: pull_request
+
   docker-tag:
     depends_on: vendor
     image: woodpeckerci/plugin-docker-buildx:3.2.1
diff --git a/README.md b/README.md
index a1d6424..34143f1 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,9 @@ but forward the requests on the IP level to the Pages Server.
 You can check out a proof of concept in the `examples/haproxy-sni` folder,
 and especially have a look at [this section of the haproxy.cfg](https://codeberg.org/Codeberg/pages-server/src/branch/main/examples/haproxy-sni/haproxy.cfg#L38).
 
+If you want to test a change, you can open a PR and ask for the label `build_pr_image` to be added.
+This will trigger a build of the PR which will build a docker image to be used for testing.
+
 ### Environment Variables
 
 - `HOST` & `PORT` (default: `[::]` & `443`): listen address.
@@ -72,14 +75,14 @@ and especially have a look at [this section of the haproxy.cfg](https://codeberg
 - `GITEA_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance.
 - `GITEA_API_TOKEN` (default: empty): API token for the Gitea instance to access non-public (e.g. limited) repos.
 - `RAW_INFO_PAGE` (default: <https://docs.codeberg.org/pages/raw-content/>): info page for raw resources, shown if no resource is provided.
-- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging).  
+- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging).
   ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
 - `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
 - `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
 - `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider.
 - `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.
 - `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
-- `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard.  
+- `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard.
   See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
 - `NO_DNS_01` (default: `false`): Disable the use of ACME DNS. This means that the wildcard certificate is self-signed and all domains and subdomains will have a distinct certificate. Because this may lead to a rate limit from the ACME provider, this option is not recommended for Gitea/Forgejo instances with open registrations or a great number of users/orgs.
 - `LOG_LEVEL` (default: warn): Set this to specify the level of logging.
@@ -104,7 +107,7 @@ Previous maintainers:
 
 ### First steps
 
-The code of this repository is split in several modules.  
+The code of this repository is split in several modules.
 The [Architecture is explained](https://codeberg.org/Codeberg/pages-server/wiki/Architecture) in the wiki.
 
 The `cmd` folder holds the data necessary for interacting with the service via the cli.
@@ -117,7 +120,7 @@ Thank you very much.
 
 Make sure you have [golang](https://go.dev) v1.21 or newer and [just](https://just.systems/man/en/) installed.
 
-run `just dev`  
+run `just dev`
 now these pages should work:
 
 - <https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg>

From 77a8439ea7b59e40b88598589ca4653e4959b24b Mon Sep 17 00:00:00 2001
From: crapStone <codeberg@crapstone.dev>
Date: Sun, 26 May 2024 14:45:03 +0000
Subject: [PATCH 124/196] Rename gitea to forge in cli args and env variables
 (#339)

This PR renames `gitea` in cli args to `forge` and `GITEA` in environment variables to `FORGE` and adds the gitea names as aliases for the forge names.

Also closes #311

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/339
---
 cli/flags.go                   | 24 ++++++-----
 config/assets/test_config.toml |  4 +-
 config/config.go               |  4 +-
 config/setup.go                | 12 +++---
 config/setup_test.go           | 73 +++++++++++++++++++++++-----------
 example_config.toml            |  2 +-
 server/gitea/client.go         |  9 +++--
 server/handler/handler_test.go |  2 +-
 server/startup.go              |  2 +-
 9 files changed, 81 insertions(+), 51 deletions(-)

diff --git a/cli/flags.go b/cli/flags.go
index f7a7dc8..934ef8d 100644
--- a/cli/flags.go
+++ b/cli/flags.go
@@ -22,29 +22,31 @@ var (
 
 	ServerFlags = append(CertStorageFlags, []cli.Flag{
 		// #############
-		// ### Gitea ###
+		// ### Forge ###
 		// #############
-		// GiteaRoot specifies the root URL of the Gitea instance, without a trailing slash.
+		// ForgeRoot specifies the root URL of the Forge 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"},
+			Name:    "forge-root",
+			Aliases: []string{"gitea-root"},
+			Usage:   "specifies the root URL of the Forgejo/Gitea instance, without a trailing slash.",
+			EnvVars: []string{"FORGE_ROOT", "GITEA_ROOT"},
 		},
-		// GiteaApiToken specifies an api token for the Gitea instance
+		// ForgeApiToken specifies an api token for the Forge instance
 		&cli.StringFlag{
-			Name:    "gitea-api-token",
-			Usage:   "specifies an api token for the Gitea instance",
-			EnvVars: []string{"GITEA_API_TOKEN"},
+			Name:    "forge-api-token",
+			Aliases: []string{"gitea-api-token"},
+			Usage:   "specifies an api token for the Forgejo/Gitea instance",
+			EnvVars: []string{"FORGE_API_TOKEN", "GITEA_API_TOKEN"},
 		},
 		&cli.BoolFlag{
 			Name:    "enable-lfs-support",
-			Usage:   "enable lfs support, require gitea >= v1.17.0 as backend",
+			Usage:   "enable lfs support, gitea must be version v1.17.0 or higher",
 			EnvVars: []string{"ENABLE_LFS_SUPPORT"},
 			Value:   false,
 		},
 		&cli.BoolFlag{
 			Name:    "enable-symlink-support",
-			Usage:   "follow symlinks if enabled, require gitea >= v1.18.0 as backend",
+			Usage:   "follow symlinks if enabled, gitea must be version v1.18.0 or higher",
 			EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
 			Value:   false,
 		},
diff --git a/config/assets/test_config.toml b/config/assets/test_config.toml
index 6a2f0d0..acb2c55 100644
--- a/config/assets/test_config.toml
+++ b/config/assets/test_config.toml
@@ -10,8 +10,8 @@ rawDomain = 'raw.codeberg.page'
 allowedCorsDomains = ['fonts.codeberg.org', 'design.codeberg.org']
 blacklistedPaths = ['do/not/use']
 
-[gitea]
-root = 'codeberg.org'
+[forge]
+root = 'https://codeberg.org'
 token = 'XXXXXXXX'
 lfsEnabled = true
 followSymlinks = true
diff --git a/config/config.go b/config/config.go
index 0146e0f..2accbf5 100644
--- a/config/config.go
+++ b/config/config.go
@@ -3,7 +3,7 @@ package config
 type Config struct {
 	LogLevel string `default:"warn"`
 	Server   ServerConfig
-	Gitea    GiteaConfig
+	Forge    ForgeConfig
 	Database DatabaseConfig
 	ACME     ACMEConfig
 }
@@ -20,7 +20,7 @@ type ServerConfig struct {
 	BlacklistedPaths   []string
 }
 
-type GiteaConfig struct {
+type ForgeConfig struct {
 	Root               string
 	Token              string
 	LFSEnabled         bool   `default:"false"`
diff --git a/config/setup.go b/config/setup.go
index 6a2aa62..f1388fe 100644
--- a/config/setup.go
+++ b/config/setup.go
@@ -51,7 +51,7 @@ func MergeConfig(ctx *cli.Context, config *Config) {
 	}
 
 	mergeServerConfig(ctx, &config.Server)
-	mergeGiteaConfig(ctx, &config.Gitea)
+	mergeForgeConfig(ctx, &config.Forge)
 	mergeDatabaseConfig(ctx, &config.Database)
 	mergeACMEConfig(ctx, &config.ACME)
 }
@@ -89,12 +89,12 @@ func mergeServerConfig(ctx *cli.Context, config *ServerConfig) {
 	config.BlacklistedPaths = append(config.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
 }
 
-func mergeGiteaConfig(ctx *cli.Context, config *GiteaConfig) {
-	if ctx.IsSet("gitea-root") {
-		config.Root = ctx.String("gitea-root")
+func mergeForgeConfig(ctx *cli.Context, config *ForgeConfig) {
+	if ctx.IsSet("forge-root") {
+		config.Root = ctx.String("forge-root")
 	}
-	if ctx.IsSet("gitea-api-token") {
-		config.Token = ctx.String("gitea-api-token")
+	if ctx.IsSet("forge-api-token") {
+		config.Token = ctx.String("forge-api-token")
 	}
 	if ctx.IsSet("enable-lfs-support") {
 		config.LFSEnabled = ctx.Bool("enable-lfs-support")
diff --git a/config/setup_test.go b/config/setup_test.go
index 1a32740..6ca9712 100644
--- a/config/setup_test.go
+++ b/config/setup_test.go
@@ -110,7 +110,7 @@ func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) {
 			}
 
 			expectedConfig.LogLevel = "debug"
-			expectedConfig.Gitea.Root = "not-codeberg.org"
+			expectedConfig.Forge.Root = "not-codeberg.org"
 			expectedConfig.ACME.AcceptTerms = true
 			expectedConfig.Server.Host = "172.17.0.2"
 			expectedConfig.Server.BlacklistedPaths = append(expectedConfig.Server.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
@@ -122,7 +122,7 @@ func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) {
 		[]string{
 			"--config-file", "assets/test_config.toml",
 			"--log-level", "debug",
-			"--gitea-root", "not-codeberg.org",
+			"--forge-root", "not-codeberg.org",
 			"--acme-accept-terms",
 			"--host", "172.17.0.2",
 		},
@@ -146,7 +146,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
 					AllowedCorsDomains: []string{"original"},
 					BlacklistedPaths:   []string{"original"},
 				},
-				Gitea: GiteaConfig{
+				Forge: ForgeConfig{
 					Root:               "original",
 					Token:              "original",
 					LFSEnabled:         false,
@@ -186,7 +186,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
 					AllowedCorsDomains: []string{"changed"},
 					BlacklistedPaths:   append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...),
 				},
-				Gitea: GiteaConfig{
+				Forge: ForgeConfig{
 					Root:               "changed",
 					Token:              "changed",
 					LFSEnabled:         true,
@@ -227,9 +227,9 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
 			"--port", "8443",
 			"--http-port", "443",
 			"--enable-http-server",
-			// Gitea
-			"--gitea-root", "changed",
-			"--gitea-api-token", "changed",
+			// Forge
+			"--forge-root", "changed",
+			"--forge-api-token", "changed",
 			"--enable-lfs-support",
 			"--enable-symlink-support",
 			"--default-mime-type", "changed",
@@ -366,11 +366,11 @@ func TestMergeServerConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgE
 	}
 }
 
-func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+func TestMergeForgeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
 	runApp(
 		t,
 		func(ctx *cli.Context) error {
-			cfg := &GiteaConfig{
+			cfg := &ForgeConfig{
 				Root:               "original",
 				Token:              "original",
 				LFSEnabled:         false,
@@ -379,9 +379,9 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
 				ForbiddenMimeTypes: []string{"original"},
 			}
 
-			mergeGiteaConfig(ctx, cfg)
+			mergeForgeConfig(ctx, cfg)
 
-			expectedConfig := &GiteaConfig{
+			expectedConfig := &ForgeConfig{
 				Root:               "changed",
 				Token:              "changed",
 				LFSEnabled:         true,
@@ -395,8 +395,8 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
 			return nil
 		},
 		[]string{
-			"--gitea-root", "changed",
-			"--gitea-api-token", "changed",
+			"--forge-root", "changed",
+			"--forge-api-token", "changed",
 			"--enable-lfs-support",
 			"--enable-symlink-support",
 			"--default-mime-type", "changed",
@@ -405,25 +405,25 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
 	)
 }
 
-func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+func TestMergeForgeConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
 	type testValuePair struct {
 		args     []string
-		callback func(*GiteaConfig)
+		callback func(*ForgeConfig)
 	}
 	testValuePairs := []testValuePair{
-		{args: []string{"--gitea-root", "changed"}, callback: func(gc *GiteaConfig) { gc.Root = "changed" }},
-		{args: []string{"--gitea-api-token", "changed"}, callback: func(gc *GiteaConfig) { gc.Token = "changed" }},
-		{args: []string{"--enable-lfs-support"}, callback: func(gc *GiteaConfig) { gc.LFSEnabled = true }},
-		{args: []string{"--enable-symlink-support"}, callback: func(gc *GiteaConfig) { gc.FollowSymlinks = true }},
-		{args: []string{"--default-mime-type", "changed"}, callback: func(gc *GiteaConfig) { gc.DefaultMimeType = "changed" }},
-		{args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *GiteaConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }},
+		{args: []string{"--forge-root", "changed"}, callback: func(gc *ForgeConfig) { gc.Root = "changed" }},
+		{args: []string{"--forge-api-token", "changed"}, callback: func(gc *ForgeConfig) { gc.Token = "changed" }},
+		{args: []string{"--enable-lfs-support"}, callback: func(gc *ForgeConfig) { gc.LFSEnabled = true }},
+		{args: []string{"--enable-symlink-support"}, callback: func(gc *ForgeConfig) { gc.FollowSymlinks = true }},
+		{args: []string{"--default-mime-type", "changed"}, callback: func(gc *ForgeConfig) { gc.DefaultMimeType = "changed" }},
+		{args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *ForgeConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }},
 	}
 
 	for _, pair := range testValuePairs {
 		runApp(
 			t,
 			func(ctx *cli.Context) error {
-				cfg := GiteaConfig{
+				cfg := ForgeConfig{
 					Root:               "original",
 					Token:              "original",
 					LFSEnabled:         false,
@@ -435,7 +435,7 @@ func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgEx
 				expectedConfig := cfg
 				pair.callback(&expectedConfig)
 
-				mergeGiteaConfig(ctx, &cfg)
+				mergeForgeConfig(ctx, &cfg)
 
 				expectedConfig.ForbiddenMimeTypes = fixArrayFromCtx(ctx, "forbidden-mime-types", expectedConfig.ForbiddenMimeTypes)
 
@@ -448,6 +448,33 @@ func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgEx
 	}
 }
 
+func TestMergeForgeConfigShouldReplaceValuesGivenGiteaOptionsExist(t *testing.T) {
+	runApp(
+		t,
+		func(ctx *cli.Context) error {
+			cfg := &ForgeConfig{
+				Root:  "original",
+				Token: "original",
+			}
+
+			mergeForgeConfig(ctx, cfg)
+
+			expectedConfig := &ForgeConfig{
+				Root:  "changed",
+				Token: "changed",
+			}
+
+			assert.Equal(t, expectedConfig, cfg)
+
+			return nil
+		},
+		[]string{
+			"--gitea-root", "changed",
+			"--gitea-api-token", "changed",
+		},
+	)
+}
+
 func TestMergeDatabaseConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
 	runApp(
 		t,
diff --git a/example_config.toml b/example_config.toml
index 30e77c4..c8dacb2 100644
--- a/example_config.toml
+++ b/example_config.toml
@@ -11,7 +11,7 @@ pagesBranches = ["pages"]
 allowedCorsDomains = []
 blacklistedPaths = []
 
-[gitea]
+[forge]
 root = 'https://codeberg.org'
 token = 'ASDF1234'
 lfsEnabled = true
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 5955bfb..3abb487 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -57,12 +57,13 @@ type Client struct {
 	defaultMimeType    string
 }
 
-func NewClient(cfg config.GiteaConfig, respCache cache.ICache) (*Client, error) {
-	rootURL, err := url.Parse(cfg.Root)
+func NewClient(cfg config.ForgeConfig, respCache cache.ICache) (*Client, error) {
+	// url.Parse returns valid on almost anything...
+	rootURL, err := url.ParseRequestURI(cfg.Root)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("invalid forgejo/gitea root url: %w", err)
 	}
-	giteaRoot := strings.Trim(rootURL.String(), "/")
+	giteaRoot := strings.TrimSuffix(rootURL.String(), "/")
 
 	stdClient := http.Client{Timeout: 10 * time.Second}
 
diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index 4cb859a..0ae7962 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -13,7 +13,7 @@ import (
 )
 
 func TestHandlerPerformance(t *testing.T) {
-	cfg := config.GiteaConfig{
+	cfg := config.ForgeConfig{
 		Root:           "https://codeberg.org",
 		Token:          "",
 		LFSEnabled:     false,
diff --git a/server/startup.go b/server/startup.go
index fd89803..95c3c5c 100644
--- a/server/startup.go
+++ b/server/startup.go
@@ -77,7 +77,7 @@ func Serve(ctx *cli.Context) error {
 	// clientResponseCache stores responses from the Gitea server
 	clientResponseCache := cache.NewInMemoryCache()
 
-	giteaClient, err := gitea.NewClient(cfg.Gitea, clientResponseCache)
+	giteaClient, err := gitea.NewClient(cfg.Forge, clientResponseCache)
 	if err != nil {
 		return fmt.Errorf("could not create new gitea client: %v", err)
 	}

From b9a9467dba4ee3d2b2004339075c3a8b86c04fe5 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 26 May 2024 17:11:00 +0000
Subject: [PATCH 125/196] chore(deps): update woodpeckerci/plugin-docker-buildx
 docker tag to v4 (#332)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [woodpeckerci/plugin-docker-buildx](https://codeberg.org/woodpecker-plugins/docker-buildx) ([source](https://codeberg.org/woodpecker-plugins/docker-buildx.git)) | major | `3.2.1` -> `4.0.0` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/docker-buildx (woodpeckerci/plugin-docker-buildx)</summary>

### [`v4.0.0`](https://codeberg.org/woodpecker-plugins/docker-buildx/releases/tag/v4.0.0)

[Compare Source](https://codeberg.org/woodpecker-plugins/docker-buildx/compare/v3.2.1...v4.0.0)

-   chore(deps): update docker docker tag to v26.1.0
-   chore(deps): update docker/buildx-bin docker tag to v0.14.0 ([#&#8203;155](https://github.com/woodpecker-plugins/docker-buildx/issues/155))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zNTQuNSIsInVwZGF0ZWRJblZlciI6IjM3LjM1NC41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Co-authored-by: crapStone <codeberg@crapstone.dev>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/332
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index a86c7fe..ea38374 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -29,7 +29,7 @@ steps:
 
   docker-dryrun:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:3.2.1
+    image: woodpeckerci/plugin-docker-buildx:4.0.0
     settings:
       dockerfile: Dockerfile
       platforms: linux/amd64
@@ -99,7 +99,7 @@ steps:
 
   docker-next:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:3.2.1
+    image: woodpeckerci/plugin-docker-buildx:4.0.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
@@ -133,7 +133,7 @@ steps:
 
   docker-tag:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:3.2.1
+    image: woodpeckerci/plugin-docker-buildx:4.0.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile

From 2c41e11f2ff0565456f9b2d74c79d961266226c7 Mon Sep 17 00:00:00 2001
From: crapStone <codeberg@crapstone.dev>
Date: Sun, 26 May 2024 20:05:46 +0000
Subject: [PATCH 126/196] Use hashicorp's LRU cache for DNS & certificates
 (#315)

Taken from #301

Co-authored-by: Moritz Marquardt <git@momar.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/315
---
 flake.lock                              |  6 ++---
 flake.nix                               |  1 +
 go.mod                                  |  1 +
 go.sum                                  |  2 ++
 server/certificates/certificates.go     | 34 +++++++++++++++++--------
 server/dns/dns.go                       | 20 +++++++++------
 server/handler/handler.go               |  4 +--
 server/handler/handler_custom_domain.go |  6 ++---
 server/handler/handler_test.go          |  2 +-
 server/startup.go                       |  7 ++---
 10 files changed, 50 insertions(+), 33 deletions(-)

diff --git a/flake.lock b/flake.lock
index b14ba48..c74fe33 100644
--- a/flake.lock
+++ b/flake.lock
@@ -19,11 +19,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1714030708,
-        "narHash": "sha256-JOGPOxa8N6ySzB7SQBsh0OVz+UXZriyahgvfNHMIY0Y=",
+        "lastModified": 1716715802,
+        "narHash": "sha256-usk0vE7VlxPX8jOavrtpOqphdfqEQpf9lgedlY/r66c=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "b0d52b31f7f4d80f8bf38f0253652125579c35ff",
+        "rev": "e2dd4e18cc1c7314e24154331bae07df76eb582f",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index f981ed1..61f3b55 100644
--- a/flake.nix
+++ b/flake.nix
@@ -16,6 +16,7 @@
           gcc
           go
           gofumpt
+          golangci-lint
           gopls
           gotools
           go-tools
diff --git a/go.mod b/go.mod
index 518dff0..bb3a05a 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/creasty/defaults v1.7.0
 	github.com/go-acme/lego/v4 v4.5.3
 	github.com/go-sql-driver/mysql v1.6.0
+	github.com/hashicorp/golang-lru/v2 v2.0.7
 	github.com/joho/godotenv v1.4.0
 	github.com/lib/pq v1.10.7
 	github.com/mattn/go-sqlite3 v1.14.16
diff --git a/go.sum b/go.sum
index ae24fc3..5875472 100644
--- a/go.sum
+++ b/go.sum
@@ -323,6 +323,8 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 67219dd..ff34775 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -14,6 +14,7 @@ import (
 	"github.com/go-acme/lego/v4/certificate"
 	"github.com/go-acme/lego/v4/challenge/tlsalpn01"
 	"github.com/go-acme/lego/v4/lego"
+	"github.com/hashicorp/golang-lru/v2/expirable"
 	"github.com/reugn/equalizer"
 	"github.com/rs/zerolog/log"
 
@@ -31,11 +32,14 @@ func TLSConfig(mainDomainSuffix string,
 	giteaClient *gitea.Client,
 	acmeClient *AcmeClient,
 	firstDefaultBranch string,
-	keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.ICache,
+	challengeCache, canonicalDomainCache cache.ICache,
 	certDB database.CertDB,
 	noDNS01 bool,
 	rawDomain string,
 ) *tls.Config {
+	// every cert is at most 24h in the cache and 7 days before expiry the cert is renewed
+	keyCache := expirable.NewLRU[string, *tls.Certificate](32, nil, 24*time.Hour)
+
 	return &tls.Config{
 		// check DNS name & get certificate from Let's Encrypt
 		GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
@@ -86,7 +90,7 @@ func TLSConfig(mainDomainSuffix string,
 				}
 			} else {
 				var targetRepo, targetBranch string
-				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
+				targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch)
 				if targetOwner == "" {
 					// DNS not set up, return main certificate to redirect to the docs
 					domain = mainDomainSuffix
@@ -107,7 +111,7 @@ func TLSConfig(mainDomainSuffix string,
 
 			if tlsCertificate, ok := keyCache.Get(domain); ok {
 				// we can use an existing certificate object
-				return tlsCertificate.(*tls.Certificate), nil
+				return tlsCertificate, nil
 			}
 
 			var tlsCertificate *tls.Certificate
@@ -132,9 +136,8 @@ func TLSConfig(mainDomainSuffix string,
 				}
 			}
 
-			if err := keyCache.Set(domain, tlsCertificate, 15*time.Minute); err != nil {
-				return nil, err
-			}
+			keyCache.Add(domain, tlsCertificate)
+
 			return tlsCertificate, nil
 		},
 		NextProtos: []string{
@@ -186,11 +189,10 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
 
 	// TODO: document & put into own function
 	if !strings.EqualFold(sni, mainDomainSuffix) {
-		tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
+		tlsCertificate.Leaf, err = leaf(&tlsCertificate)
 		if err != nil {
-			return nil, fmt.Errorf("error parsing leaf tlsCert: %w", err)
+			return nil, err
 		}
-
 		// renew certificates 7 days before they expire
 		if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
 			// TODO: use ValidTill of custom cert struct
@@ -291,6 +293,7 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew
 			}
 			leaf, err := leaf(&tlsCertificate)
 			if err == nil && leaf.NotAfter.After(time.Now()) {
+				tlsCertificate.Leaf = leaf
 				// avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at
 				renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
 				if err := keyDatabase.Put(name, renew); err != nil {
@@ -388,11 +391,20 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, acmeClient *Acm
 	}
 }
 
-// leaf returns the parsed leaf certificate, either from c.leaf or by parsing
+// leaf returns the parsed leaf certificate, either from c.Leaf or by parsing
 // the corresponding c.Certificate[0].
+// After successfully parsing the cert c.Leaf gets set to the parsed cert.
 func leaf(c *tls.Certificate) (*x509.Certificate, error) {
 	if c.Leaf != nil {
 		return c.Leaf, nil
 	}
-	return x509.ParseCertificate(c.Certificate[0])
+
+	leaf, err := x509.ParseCertificate(c.Certificate[0])
+	if err != nil {
+		return nil, fmt.Errorf("tlsCert - failed to parse leaf: %w", err)
+	}
+
+	c.Leaf = leaf
+
+	return leaf, err
 }
diff --git a/server/dns/dns.go b/server/dns/dns.go
index 970f0c0..e29e42c 100644
--- a/server/dns/dns.go
+++ b/server/dns/dns.go
@@ -5,22 +5,26 @@ import (
 	"strings"
 	"time"
 
-	"codeberg.org/codeberg/pages/server/cache"
+	"github.com/hashicorp/golang-lru/v2/expirable"
 )
 
-// lookupCacheTimeout specifies the timeout for the DNS lookup cache.
-var lookupCacheTimeout = 15 * time.Minute
+const (
+	lookupCacheValidity = 30 * time.Second
+	defaultPagesRepo    = "pages"
+)
 
-var defaultPagesRepo = "pages"
+// TODO(#316): refactor to not use global variables
+var lookupCache *expirable.LRU[string, string] = expirable.NewLRU[string, string](4096, nil, lookupCacheValidity)
 
 // GetTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix.
 // If everything is fine, it returns the target data.
-func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string, dnsLookupCache cache.ICache) (targetOwner, targetRepo, targetBranch string) {
+func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string) (targetOwner, targetRepo, targetBranch string) {
 	// Get CNAME or TXT
 	var cname string
 	var err error
-	if cachedName, ok := dnsLookupCache.Get(domain); ok {
-		cname = cachedName.(string)
+
+	if entry, ok := lookupCache.Get(domain); ok {
+		cname = entry
 	} else {
 		cname, err = net.LookupCNAME(domain)
 		cname = strings.TrimSuffix(cname, ".")
@@ -38,7 +42,7 @@ func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string, dnsLo
 				}
 			}
 		}
-		_ = dnsLookupCache.Set(domain, cname, lookupCacheTimeout)
+		_ = lookupCache.Add(domain, cname)
 	}
 	if cname == "" {
 		return
diff --git a/server/handler/handler.go b/server/handler/handler.go
index ffc3400..c038c2d 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -23,7 +23,7 @@ const (
 func Handler(
 	cfg config.ServerConfig,
 	giteaClient *gitea.Client,
-	dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
+	canonicalDomainCache, redirectsCache cache.ICache,
 ) http.HandlerFunc {
 	return func(w http.ResponseWriter, req *http.Request) {
 		log.Debug().Msg("\n----------------------------------------------------------")
@@ -108,7 +108,7 @@ func Handler(
 				trimmedHost,
 				pathElements,
 				cfg.PagesBranches[0],
-				dnsLookupCache, canonicalDomainCache, redirectsCache)
+				canonicalDomainCache, redirectsCache)
 		}
 	}
 }
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 82953f9..852001a 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -19,10 +19,10 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 	trimmedHost string,
 	pathElements []string,
 	firstDefaultBranch string,
-	dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
+	canonicalDomainCache, redirectsCache cache.ICache,
 ) {
 	// Serve pages from custom domains
-	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
+	targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch)
 	if targetOwner == "" {
 		html.ReturnErrorPage(ctx,
 			"could not obtain repo owner from custom domain",
@@ -53,7 +53,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 			return
 		} else if canonicalDomain != trimmedHost {
 			// only redirect if the target is also a codeberg page!
-			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
+			targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, firstDefaultBranch)
 			if targetOwner != "" {
 				ctx.Redirect("https://"+canonicalDomain+"/"+targetOpt.TargetPath, http.StatusTemporaryRedirect)
 				return
diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go
index 0ae7962..765b3b1 100644
--- a/server/handler/handler_test.go
+++ b/server/handler/handler_test.go
@@ -29,7 +29,7 @@ func TestHandlerPerformance(t *testing.T) {
 		AllowedCorsDomains: []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
 		PagesBranches:      []string{"pages"},
 	}
-	testHandler := Handler(serverCfg, giteaClient, cache.NewInMemoryCache(), cache.NewInMemoryCache(), cache.NewInMemoryCache())
+	testHandler := Handler(serverCfg, giteaClient, cache.NewInMemoryCache(), cache.NewInMemoryCache())
 
 	testCase := func(uri string, status int) {
 		t.Run(uri, func(t *testing.T) {
diff --git a/server/startup.go b/server/startup.go
index 95c3c5c..6642d83 100644
--- a/server/startup.go
+++ b/server/startup.go
@@ -66,12 +66,9 @@ func Serve(ctx *cli.Context) error {
 	}
 	defer closeFn()
 
-	keyCache := cache.NewInMemoryCache()
 	challengeCache := cache.NewInMemoryCache()
 	// canonicalDomainCache stores canonical domains
 	canonicalDomainCache := cache.NewInMemoryCache()
-	// dnsLookupCache stores DNS lookups for custom domains
-	dnsLookupCache := cache.NewInMemoryCache()
 	// redirectsCache stores redirects in _redirects files
 	redirectsCache := cache.NewInMemoryCache()
 	// clientResponseCache stores responses from the Gitea server
@@ -104,7 +101,7 @@ func Serve(ctx *cli.Context) error {
 		giteaClient,
 		acmeClient,
 		cfg.Server.PagesBranches[0],
-		keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
+		challengeCache, canonicalDomainCache,
 		certDB,
 		cfg.ACME.NoDNS01,
 		cfg.Server.RawDomain,
@@ -134,7 +131,7 @@ func Serve(ctx *cli.Context) error {
 	}
 
 	// Create ssl handler based on settings
-	sslHandler := handler.Handler(cfg.Server, giteaClient, dnsLookupCache, canonicalDomainCache, redirectsCache)
+	sslHandler := handler.Handler(cfg.Server, giteaClient, canonicalDomainCache, redirectsCache)
 
 	// Start the ssl listener
 	log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())

From 74cdf78044f7f215654ac3cc6118f4f9e2f6cb60 Mon Sep 17 00:00:00 2001
From: adagio <h759bkyo4@mozmail.com>
Date: Mon, 27 May 2024 15:00:10 +0000
Subject: [PATCH 127/196] Sorts the env var flags (#334)

Applies a sort for the various env flags for ease of user lookup.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/334
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: adagio <h759bkyo4@mozmail.com>
Co-committed-by: adagio <h759bkyo4@mozmail.com>
---
 README.md | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index 34143f1..98e16d9 100644
--- a/README.md
+++ b/README.md
@@ -69,23 +69,21 @@ This will trigger a build of the PR which will build a docker image to be used f
 
 ### Environment Variables
 
+- `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider.
+- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging). ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
+- `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
+- `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
+- `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.
+- `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard. See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
+- `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
+- `GITEA_API_TOKEN` (default: empty): API token for the Gitea instance to access non-public (e.g. limited) repos.
+- `GITEA_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance.
 - `HOST` & `PORT` (default: `[::]` & `443`): listen address.
+- `LOG_LEVEL` (default: warn): Set this to specify the level of logging.
+- `NO_DNS_01` (default: `false`): Disable the use of ACME DNS. This means that the wildcard certificate is self-signed and all domains and subdomains will have a distinct certificate. Because this may lead to a rate limit from the ACME provider, this option is not recommended for Gitea/Forgejo instances with open registrations or a great number of users/orgs.
 - `PAGES_DOMAIN` (default: `codeberg.page`): main domain for pages.
 - `RAW_DOMAIN` (default: `raw.codeberg.page`): domain for raw resources (must be subdomain of `PAGES_DOMAIN`).
-- `GITEA_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance.
-- `GITEA_API_TOKEN` (default: empty): API token for the Gitea instance to access non-public (e.g. limited) repos.
 - `RAW_INFO_PAGE` (default: <https://docs.codeberg.org/pages/raw-content/>): info page for raw resources, shown if no resource is provided.
-- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging).
-  ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
-- `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
-- `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
-- `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider.
-- `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.
-- `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
-- `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard.
-  See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
-- `NO_DNS_01` (default: `false`): Disable the use of ACME DNS. This means that the wildcard certificate is self-signed and all domains and subdomains will have a distinct certificate. Because this may lead to a rate limit from the ACME provider, this option is not recommended for Gitea/Forgejo instances with open registrations or a great number of users/orgs.
-- `LOG_LEVEL` (default: warn): Set this to specify the level of logging.
 
 ## Contributing to the development
 

From 3928cd7affb31980fa77dfd984a4a17b373365da Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Wed, 29 May 2024 00:33:03 +0000
Subject: [PATCH 128/196] fix(deps): update golang.org/x/exp digest to 4c93da0
 (#344)

---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index bb3a05a..e0bd51c 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
+	golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index 5875472..01c1e1b 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
-golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
+golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
+golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

From 05413e50c6780969b760849ccfac16b0f17d8d8f Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Thu, 30 May 2024 00:43:46 +0000
Subject: [PATCH 129/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.59.0 (#345)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index cf59edc..7e7ba51 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -8,7 +8,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.58.2
+    image: golangci/golangci-lint:v1.59.0
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From 4543033874424431a96166d39fd187f9ee20e675 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 2 Jun 2024 01:02:45 +0000
Subject: [PATCH 130/196] fix(deps): update golang.org/x/exp digest to fd00a4e
 (#346)

---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index e0bd51c..7ae5a5b 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d
+	golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index 01c1e1b..271416f 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
-golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
+golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
+golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

From e61ab7075ba92bb3051e2fc0db5e8816228455c7 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 9 Jun 2024 01:17:54 +0000
Subject: [PATCH 131/196] fix(deps): update golang.org/x/exp digest to fc45aab
 (#352)

---
 go.mod |  2 +-
 go.sum | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/go.mod b/go.mod
index 7ae5a5b..b821614 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc
+	golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index 271416f..bc63665 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
-golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
+golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
+golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -810,8 +810,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
+golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1004,8 +1004,8 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
-golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From 2c1701a657bbdd615b1fac10775026c92da47cd2 Mon Sep 17 00:00:00 2001
From: fnetX <otto@codeberg.org>
Date: Thu, 13 Jun 2024 13:31:27 +0000
Subject: [PATCH 132/196] Remove RAW_INFO_PAGE from README (#353)

https://codeberg.org/Codeberg/pages-server/pulls/261 removed that option, but not from the README.
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/353
Co-authored-by: fnetX <otto@codeberg.org>
Co-committed-by: fnetX <otto@codeberg.org>
---
 README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/README.md b/README.md
index 98e16d9..029ba9b 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,6 @@ This will trigger a build of the PR which will build a docker image to be used f
 - `NO_DNS_01` (default: `false`): Disable the use of ACME DNS. This means that the wildcard certificate is self-signed and all domains and subdomains will have a distinct certificate. Because this may lead to a rate limit from the ACME provider, this option is not recommended for Gitea/Forgejo instances with open registrations or a great number of users/orgs.
 - `PAGES_DOMAIN` (default: `codeberg.page`): main domain for pages.
 - `RAW_DOMAIN` (default: `raw.codeberg.page`): domain for raw resources (must be subdomain of `PAGES_DOMAIN`).
-- `RAW_INFO_PAGE` (default: <https://docs.codeberg.org/pages/raw-content/>): info page for raw resources, shown if no resource is provided.
 
 ## Contributing to the development
 

From adcd973ade505e5b19673001335b1fd8263c2533 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 16 Jun 2024 01:04:41 +0000
Subject: [PATCH 133/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.59.1 (#357)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 7e7ba51..a859606 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -8,7 +8,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.59.0
+    image: golangci/golangci-lint:v1.59.1
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From c043a887c22e15528452ec82d7ccf9119a9dbb6a Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 17 Jun 2024 00:53:42 +0000
Subject: [PATCH 134/196] fix(deps): update golang.org/x/exp digest to 7f521ea
 (#356)

---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index b821614..9a4c67c 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8
+	golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index bc63665..2a87883 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
-golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

From 4e44ea1d58521f001706ddca057136fc44669ff4 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 7 Jul 2024 01:09:14 +0000
Subject: [PATCH 135/196] chore(deps): update pipelinecomponents/yamllint
 docker tag to v0.31.3 (#361)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index a859606..25c8354 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -28,7 +28,7 @@ steps:
         branch: renovate/*
 
   yamllint:
-    image: pipelinecomponents/yamllint:0.31.2
+    image: pipelinecomponents/yamllint:0.31.3
     depends_on: []
     commands:
       - yamllint .

From d583587773781be1a660c4512e303a33fde5bf05 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 8 Jul 2024 00:52:53 +0000
Subject: [PATCH 136/196] chore(deps): update mstruebing/editorconfig-checker
 docker tag to v3.0.3 (#360)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 25c8354..0e3df1d 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -21,7 +21,7 @@ steps:
 
   editor-config:
     depends_on: []
-    image: mstruebing/editorconfig-checker:v3.0.1
+    image: mstruebing/editorconfig-checker:v3.0.3
     when:
       - event: pull_request
       - event: push

From 17530a065b7e3c8b862f0fef5efcca942ab1d1ed Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 14 Jul 2024 01:21:11 +0000
Subject: [PATCH 137/196] fix(deps): update golang.org/x/exp digest to 46b0784
 (#363)

---
 go.mod |  2 +-
 go.sum | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/go.mod b/go.mod
index 9a4c67c..7e71146 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
+	golang.org/x/exp v0.0.0-20240707233637-46b078467d37
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index 2a87883..d1acb45 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
-golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
+golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
+golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -810,8 +810,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
-golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
+golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1004,8 +1004,8 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
-golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
+golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
+golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From bc9111a05fef91e6912d068f3c4d50b4e1d244cd Mon Sep 17 00:00:00 2001
From: Peter Gerber <peter@arbitrary.ch>
Date: Tue, 23 Jul 2024 18:42:24 +0000
Subject: [PATCH 138/196] Use correct timestamp format for Last-Modified header
 (#365)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

HTTP uses GMT [1,2] rather than UTC as timezone for timestamps. However,
the Last-Modified header used UTC which confused at least wget.

Before, UTC was used:

$ wget --no-check-certificate -S --spider https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
...
  Last-Modified: Sun, 11 Sep 2022 08:37:42 UTC
...
Last-modified header invalid -- time-stamp ignored.
...

After, GMT is used:

$ wget --no-check-certificate -S --spider https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
...
  Last-Modified: Sun, 11 Sep 2022 08:37:42 GMT
...
(no last-modified-header-invalid warning)

[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified
[2]: https://www.rfc-editor.org/rfc/rfc9110#name-date-time-formats

Fixes #364

---

Whatt I noticed is that the If-Modified-Since header isn't accepted (neither with GMT nor with UTC):

```
$ wget --header "If-Modified-Since: Sun, 11 Sep 2022 08:37:42 GMT" --no-check-certificate -S --spider https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
Spider mode enabled. Check if remote file exists.
--2024-07-15 23:31:41--  https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
Resolving cb_pages_tests.localhost.mock.directory (cb_pages_tests.localhost.mock.directory)... 127.0.0.1
Connecting to cb_pages_tests.localhost.mock.directory (cb_pages_tests.localhost.mock.directory)|127.0.0.1|:4430... connected.
WARNING: The certificate of ‘cb_pages_tests.localhost.mock.directory’ is not trusted.
WARNING: The certificate of ‘cb_pages_tests.localhost.mock.directory’ doesn't have a known issuer.
HTTP request sent, awaiting response...
  HTTP/1.1 200 OK
  Allow: GET, HEAD, OPTIONS
  Cache-Control: public, max-age=600
  Content-Length: 124635
  Content-Type: image/jpeg
  Etag: "073af1960852e2a4ef446202c7974768b9881814"
  Last-Modified: Sun, 11 Sep 2022 08:37:42 GMT
  Referrer-Policy: strict-origin-when-cross-origin
  Server: pages-server
  Strict-Transport-Security: max-age=63072000; includeSubdomains; preload
  Date: Mon, 15 Jul 2024 21:31:42 GMT
Length: 124635 (122K) [image/jpeg]
Remote file exists
```

I would have expected a 304 (Not Modified) rather than a 200 (OK). I assume this is simply not supported and on production 304 is returned by a caching proxy in front of pages-server.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/365
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Peter Gerber <peter@arbitrary.ch>
Co-committed-by: Peter Gerber <peter@arbitrary.ch>
---
 server/upstream/header.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/upstream/header.go b/server/upstream/header.go
index 9575a3f..7b85df1 100644
--- a/server/upstream/header.go
+++ b/server/upstream/header.go
@@ -24,5 +24,5 @@ func (o *Options) setHeader(ctx *context.Context, header http.Header) {
 	} else {
 		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, mime)
 	}
-	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(time.RFC1123))
+	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(http.TimeFormat))
 }

From efd1adae0ff65b1b908181dd8e1ee47f09b23108 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Tue, 23 Jul 2024 19:50:54 +0000
Subject: [PATCH 139/196] fix woodpecker pipeline definition (#370)

use https://woodpecker-ci.org/plugins/Release instead of the old drone plugin

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/370
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .woodpecker/build.yml | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index ea38374..bff5c9b 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -18,6 +18,7 @@ steps:
   build:
     depends_on: vendor
     image: codeberg.org/6543/docker-images/golang_just
+    pull: true
     commands:
       - go version
       - just build
@@ -45,6 +46,7 @@ steps:
   build-tag:
     depends_on: vendor
     image: codeberg.org/6543/docker-images/golang_just
+    pull: true
     commands:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
@@ -56,6 +58,7 @@ steps:
   test:
     depends_on: build
     image: codeberg.org/6543/docker-images/golang_just
+    pull: true
     commands:
       - just test
     when:
@@ -66,6 +69,7 @@ steps:
   integration-tests:
     depends_on: build
     image: codeberg.org/6543/docker-images/golang_just
+    pull: true
     commands:
       - just integration
     environment:
@@ -80,18 +84,13 @@ steps:
 
   release:
     depends_on: build
-    image: plugins/gitea-release:1.1.0
+    image: woodpeckerci/plugin-release:0.1.0
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite
       files: build/codeberg-pages-server
       api_key:
         from_secret: bot_token
-    environment:
-      - CI_REPO_OWNER=${CI_REPO_OWNER}
-      - CI_REPO_NAME=${CI_REPO_NAME}
-      - CI_BUILD_EVENT=${CI_BUILD_EVENT}
-      - CI_COMMIT_REF=${CI_COMMIT_REF}
     when:
       - event: ['tag']
         branch:

From 6ce17461e6a6feb88cd95fd808d24ac61ba34daa Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Tue, 23 Jul 2024 20:01:02 +0000
Subject: [PATCH 140/196] chore(deps): update woodpeckerci/plugin-docker-buildx
 docker tag to v4.2.0 (#362)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [woodpeckerci/plugin-docker-buildx](https://codeberg.org/woodpecker-plugins/docker-buildx) ([source](https://codeberg.org/woodpecker-plugins/docker-buildx.git)) | minor | `4.0.0` -> `4.2.0` |
| [woodpeckerci/plugin-docker-buildx](https://codeberg.org/woodpecker-plugins/docker-buildx) ([source](https://codeberg.org/woodpecker-plugins/docker-buildx.git)) | major | `3.2.1` -> `4.2.0` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/docker-buildx (woodpeckerci/plugin-docker-buildx)</summary>

### [`v4.2.0`](https://codeberg.org/woodpecker-plugins/docker-buildx/releases/tag/v4.2.0)

[Compare Source](https://codeberg.org/woodpecker-plugins/docker-buildx/compare/v4.1.0...v4.2.0)

##### Features

-   Add dedicated setting to automatically handle proxy configuration ([#&#8203;171](https://github.com/woodpecker-plugins/docker-buildx/issues/171))

##### Dependencies

-   chore(deps): update docker/buildx-bin docker tag to v0.16.0
-   fix(deps): update module github.com/aws/aws-sdk-go to v1.54.19

### [`v4.1.0`](https://codeberg.org/woodpecker-plugins/docker-buildx/releases/tag/v4.1.0)

[Compare Source](https://codeberg.org/woodpecker-plugins/docker-buildx/compare/v4.0.0...v4.1.0)

##### Features

-   Pass Daemon.Mirror to Docker daemon ([#&#8203;165](https://github.com/woodpecker-plugins/docker-buildx/issues/165))

##### Bug Fixes

-   Respect repository mirrors without login credentials ([#&#8203;164](https://github.com/woodpecker-plugins/docker-buildx/issues/164))

##### Dependencies

-   chore(deps): update docker/buildx-bin docker tag to v0.15.1
-   chore(deps): update docker docker tag to v26.1.4

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MzEuMCIsInVwZGF0ZWRJblZlciI6IjM3LjQzMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/362
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index bff5c9b..a00b7d6 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -30,7 +30,7 @@ steps:
 
   docker-dryrun:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:4.0.0
+    image: woodpeckerci/plugin-docker-buildx:4.2.0
     settings:
       dockerfile: Dockerfile
       platforms: linux/amd64
@@ -98,7 +98,7 @@ steps:
 
   docker-next:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:4.0.0
+    image: woodpeckerci/plugin-docker-buildx:4.2.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
@@ -114,7 +114,7 @@ steps:
         branch: ${CI_REPO_DEFAULT_BRANCH}
 
   'Publish PR image':
-    image: woodpeckerci/plugin-docker-buildx:3.2.1
+    image: woodpeckerci/plugin-docker-buildx:4.2.0
     depends_on: test
     settings:
       registry: codeberg.org
@@ -132,7 +132,7 @@ steps:
 
   docker-tag:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:4.0.0
+    image: woodpeckerci/plugin-docker-buildx:4.2.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile

From abbebbbceef335d251608b1dfe9e831af19eef43 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Tue, 23 Jul 2024 20:19:44 +0000
Subject: [PATCH 141/196] fix(deps): update golang.org/x/exp digest to 8a7402a
 (#368)

---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index 7e71146..479d6a5 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240707233637-46b078467d37
+	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index d1acb45..9d9c871 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
-golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
+golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
+golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

From 68825a17272224efca0a15544cf7608fe0429272 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Tue, 23 Jul 2024 20:32:46 +0000
Subject: [PATCH 142/196] improve pipeline config (#371)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/371
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .woodpecker/build.yml | 35 ++++++++---------------------------
 .woodpecker/lint.yml  | 14 --------------
 2 files changed, 8 insertions(+), 41 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index a00b7d6..9bf20f9 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -1,9 +1,6 @@
 when:
-  - event: [pull_request, tag, cron]
-  - event: push
-    branch:
-      - ${CI_REPO_DEFAULT_BRANCH}
-      - renovate/*
+  - event: [push, pull_request, tag, cron]
+    branch: ${CI_REPO_DEFAULT_BRANCH}
 
 depends_on:
   - lint
@@ -24,9 +21,6 @@ steps:
       - just build
     when:
       - event: [push, pull_request]
-        branch:
-          - ${CI_REPO_DEFAULT_BRANCH}
-          - renovate/*
 
   docker-dryrun:
     depends_on: vendor
@@ -38,9 +32,6 @@ steps:
       tags: latest
     when:
       - event: [push, pull_request]
-        branch:
-          - ${CI_REPO_DEFAULT_BRANCH}
-          - renovate/*
         path: Dockerfile
 
   build-tag:
@@ -51,9 +42,7 @@ steps:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
     when:
-      - event: ['tag']
-        branch:
-          - ${CI_REPO_DEFAULT_BRANCH}
+      - event: [tag]
 
   test:
     depends_on: build
@@ -62,9 +51,7 @@ steps:
     commands:
       - just test
     when:
-      - event: pull_request
-      - event: push
-        branch: renovate/*
+      - event: [push, pull_request]
 
   integration-tests:
     depends_on: build
@@ -78,9 +65,7 @@ steps:
       - RAW_DOMAIN=raw.localhost.mock.directory
       - PORT=4430
     when:
-      - event: pull_request
-      - event: push
-        branch: renovate/*
+      - event: [push, pull_request]
 
   release:
     depends_on: build
@@ -92,9 +77,7 @@ steps:
       api_key:
         from_secret: bot_token
     when:
-      - event: ['tag']
-        branch:
-          - ${CI_REPO_DEFAULT_BRANCH}
+      - event: [tag]
 
   docker-next:
     depends_on: vendor
@@ -110,8 +93,7 @@ steps:
       password:
         from_secret: bot_token
     when:
-      - event: ['push']
-        branch: ${CI_REPO_DEFAULT_BRANCH}
+      - event: [push]
 
   'Publish PR image':
     image: woodpeckerci/plugin-docker-buildx:4.2.0
@@ -144,5 +126,4 @@ steps:
       password:
         from_secret: bot_token
     when:
-      - event: ['push']
-        branch: ${CI_REPO_DEFAULT_BRANCH}
+      - event: [push]
diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 0e3df1d..5699a0a 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -1,9 +1,7 @@
 when:
   - event: pull_request
-  - event: push
     branch:
       - ${CI_REPO_DEFAULT_BRANCH}
-      - renovate/**
 
 steps:
   lint:
@@ -14,28 +12,16 @@ steps:
       - go install mvdan.cc/gofumpt@latest
       - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
       - golangci-lint run --timeout 5m --build-tags integration
-    when:
-      - event: pull_request
-      - event: push
-        branch: renovate/*
 
   editor-config:
     depends_on: []
     image: mstruebing/editorconfig-checker:v3.0.3
-    when:
-      - event: pull_request
-      - event: push
-        branch: renovate/*
 
   yamllint:
     image: pipelinecomponents/yamllint:0.31.3
     depends_on: []
     commands:
       - yamllint .
-    when:
-      - event: pull_request
-      - event: push
-        branch: renovate/*
 
   prettier:
     image: docker.io/woodpeckerci/plugin-prettier:0.1.0

From 5265b3884b5f05e9000abeb7d6fc63a69c2b9b5c Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Tue, 23 Jul 2024 22:58:34 +0200
Subject: [PATCH 143/196] don't depend on lint for push to main

---
 .woodpecker/build.yml | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 9bf20f9..eb6af24 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -2,9 +2,6 @@ when:
   - event: [push, pull_request, tag, cron]
     branch: ${CI_REPO_DEFAULT_BRANCH}
 
-depends_on:
-  - lint
-
 steps:
   # use vendor to cache dependencies
   vendor:
@@ -31,7 +28,7 @@ steps:
       dry-run: true
       tags: latest
     when:
-      - event: [push, pull_request]
+      - event: [pull_request]
         path: Dockerfile
 
   build-tag:
@@ -51,7 +48,7 @@ steps:
     commands:
       - just test
     when:
-      - event: [push, pull_request]
+      - event: [pull_request]
 
   integration-tests:
     depends_on: build
@@ -65,7 +62,7 @@ steps:
       - RAW_DOMAIN=raw.localhost.mock.directory
       - PORT=4430
     when:
-      - event: [push, pull_request]
+      - event: [pull_request]
 
   release:
     depends_on: build

From c9be1ce75dd3c9efefd8ba92e327ad6ecba2d211 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Tue, 23 Jul 2024 21:18:06 +0000
Subject: [PATCH 144/196] fix docker latest tagging (#372)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/372
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .woodpecker/build.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index eb6af24..23d5d1d 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -109,7 +109,7 @@ steps:
       evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_image"'
       event: pull_request
 
-  docker-tag:
+  docker-release:
     depends_on: vendor
     image: woodpeckerci/plugin-docker-buildx:4.2.0
     settings:
@@ -123,4 +123,4 @@ steps:
       password:
         from_secret: bot_token
     when:
-      - event: [push]
+      - event: [tag]

From 9524b1eb12f77fa345cc8a220f67ae244da0ab12 Mon Sep 17 00:00:00 2001
From: Gnaaarwhal <gnaaarwhal@noreply.codeberg.org>
Date: Fri, 2 Aug 2024 12:52:44 +0000
Subject: [PATCH 145/196] Fix typo in README.md (#374)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/374
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Gnaaarwhal <gnaaarwhal@noreply.codeberg.org>
Co-committed-by: Gnaaarwhal <gnaaarwhal@noreply.codeberg.org>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 029ba9b..0051a9e 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ This will trigger a build of the PR which will build a docker image to be used f
 ### Environment Variables
 
 - `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider.
-- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging). ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
+- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.directory> to use invalid certificates without any verification (great for debugging). ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
 - `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
 - `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
 - `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.

From 6cff8d2ee922f1245de76b45c6d8bb7c6d79ea2e Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 19 Aug 2024 20:19:04 +0000
Subject: [PATCH 146/196] chore(deps): update woodpeckerci/plugin-release
 docker tag to v0.2.1 (#376)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [woodpeckerci/plugin-release](https://codeberg.org/woodpecker-plugins/release) ([source](https://codeberg.org/woodpecker-plugins/release.git)) | minor | `0.1.0` -> `0.2.1` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/release (woodpeckerci/plugin-release)</summary>

### [`v0.2.1`](https://codeberg.org/woodpecker-plugins/release/releases/tag/v0.2.1)

[Compare Source](https://codeberg.org/woodpecker-plugins/release/compare/v0.2.0...v0.2.1)

-   Fix infinite loop ([#&#8203;13](https://github.com/woodpecker-plugins/release/issues/13))

### [`v0.2.0`](https://codeberg.org/woodpecker-plugins/release/releases/tag/v0.2.0)

[Compare Source](https://codeberg.org/woodpecker-plugins/release/compare/v0.1.0...v0.2.0)

### Bugfix

-   Fix loading without releases ([#&#8203;9](https://github.com/woodpecker-plugins/release/issues/9))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/376
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 23d5d1d..9270c33 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -66,7 +66,7 @@ steps:
 
   release:
     depends_on: build
-    image: woodpeckerci/plugin-release:0.1.0
+    image: woodpeckerci/plugin-release:0.2.1
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite

From c76daaca4d4258f6939e53382fc45d0bd0905e77 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 19 Aug 2024 20:20:07 +0000
Subject: [PATCH 147/196] chore(deps): update golang docker tag to v1.23 (#380)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| golang | minor | `1.22` -> `1.23` |

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/380
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 9270c33..976e923 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -5,7 +5,7 @@ when:
 steps:
   # use vendor to cache dependencies
   vendor:
-    image: golang:1.22
+    image: golang:1.23
     commands:
       - go mod vendor
 

From 557a29573296dd629e642f9d37179c271c50ed23 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 19 Aug 2024 20:20:59 +0000
Subject: [PATCH 148/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.60.1 (#381)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [golangci/golangci-lint](https://github.com/golangci/golangci-lint) | minor | `v1.59.1` -> `v1.60.1` |

---

### Release Notes

<details>
<summary>golangci/golangci-lint (golangci/golangci-lint)</summary>

### [`v1.60.1`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1601)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.59.1...v1.60.1)

1.  Updated linters
    -   `errorlint`: from 1.5.2 to 1.6.0
    -   `exhaustruct`: from 3.2.0 to 3.3.0 (recognize custom error values in return)
    -   `fatcontext`: from 0.2.2 to 0.4.0 (fix false positives for context stored in structs)
    -   `gocognit`: from 1.1.2 to 1.1.3
    -   `gomodguard`: from 1.3.2 to 1.3.3
    -   `govet` (`printf`): report non-constant format, no args
    -   `lll`: advertise max line length instead of just reporting failure
    -   `revive`: from 1.3.7 to 1.3.9 (new rule: `comments-density`)
    -   `sloglint`: from 0.7.1 to 0.7.2
    -   `spancheck`: from 0.6.1 to 0.6.2
    -   `staticcheck`: from 0.4.7 to 0.5.0
    -   `tenv`: from 1.7.1 to 1.10.0 (remove reports on fuzzing)
    -   `testifylint`: from 1.3.1 to 1.4.3 (new options: `formatter`, `suite-broken-parallel`, `suite-subtest-run`)
    -   `tparallel`: from 0.3.1 to 0.3.2
    -   `usestdlibvars`: from 1.26.0 to 1.27.0 (fix false-positive with number used inside a mathematical operations)
    -   `wsl`: from 4.2.1 to 4.4.1
    -   ️⚠️ `unused`: remove `exported-is-used` option
2.  Fixes
    -   SARIF: sanitize level property
    -   ️⚠️ `typecheck` issues should never be ignored
3.  Documentation
    -   Add link on linter without configuration
    -   Remove 'trusted by' page
    -   `wsl` update documentation of the configuration
4.  misc.
    -   🎉 go1.23 support

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/381
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 5699a0a..1f281a4 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.59.1
+    image: golangci/golangci-lint:v1.60.1
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From 2d4152d18b0b42c513efea6f2a3c7ff17bffaaf5 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 29 Sep 2024 20:48:31 +0000
Subject: [PATCH 149/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.61.0 (#383)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [golangci/golangci-lint](https://github.com/golangci/golangci-lint) | minor | `v1.60.1` -> `v1.61.0` |

---

### Release Notes

<details>
<summary>golangci/golangci-lint (golangci/golangci-lint)</summary>

### [`v1.61.0`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1610)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.60.3...v1.61.0)

1.  Enhancements
    -   Add `junit-xml-extended` format
    -   Exclude Swagger Codegen files by default
2.  Updated linters
    -   `dupword`: from 0.0.14 to 0.1.1
    -   `fatcontext`: from 0.4.0 to 0.5.2
    -   `gci`: from 0.13.4 to 0.13.5 (new option `no-lex-order`)
    -   `go-ruleguard`: from 0.4.2 to [`0fe6f58`](https://github.com/golangci/golangci-lint/commit/0fe6f58b47b1) (fix panic with custom linters)
    -   `godot`: from 1.4.16 to 1.4.17
    -   `gomodguard`: from 1.3.3 to 1.3.5
    -   `gosec`: disable temporarily `G407`
    -   `gosec`: from [`ab3f6c1`](https://github.com/golangci/golangci-lint/commit/ab3f6c1c83a0) to 2.21.2 (partially fix `G115`)
    -   `intrange`: from 0.1.2 to 0.2.0
    -   `nolintlint`: remove the empty line in the directive replacement
3.  Misc.
    -   Improve runtime version parsing
4.  Documentation
    -   Add additional info about `typecheck`

### [`v1.60.3`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1603)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.60.2...v1.60.3)

1.  Updated linters
    -   `gosec`: from [`81cda2f`](https://github.com/golangci/golangci-lint/commit/81cda2f91fbe) to [`ab3f6c1`](https://github.com/golangci/golangci-lint/commit/ab3f6c1c83a0) (fix `G115` false positives)
2.  Misc.
    -   Check that the Go version use to build is greater or equals to the Go version of the project

### [`v1.60.2`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1602)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.60.1...v1.60.2)

1.  Updated linters

-   `gofmt`: update to HEAD (go1.22)
-   `gofumpt`: from 0.6.0 to 0.7.0
-   `gosec`: fix G602 analyzer
-   `gosec`: from [`5f0084e`](https://github.com/golangci/golangci-lint/commit/5f0084eb01a9) to [`81cda2f`](https://github.com/golangci/golangci-lint/commit/81cda2f91fbe) (adds `G115`, `G405`, `G406`, `G506`, `G507`)
-   `staticcheck`: from 0.5.0 to 0.5.1
-   `staticcheck`: propagate Go version
-   `wrapcheck`: from 2.8.3 to 2.9.0
-   ⚠️ `exportloopref`: deprecation

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/383
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 1f281a4..6ba262c 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.60.1
+    image: golangci/golangci-lint:v1.61.0
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From 549377aa01922dd19cefc1c849f4fb5550a59d8b Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 29 Sep 2024 20:49:56 +0000
Subject: [PATCH 150/196] chore(deps): update
 docker.io/woodpeckerci/plugin-prettier docker tag to v0.2.0 (#385)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [docker.io/woodpeckerci/plugin-prettier](https://github.com/woodpecker-ci/plugin-prettier) | minor | `0.1.0` -> `0.2.0` |

---

### Release Notes

<details>
<summary>woodpecker-ci/plugin-prettier (docker.io/woodpeckerci/plugin-prettier)</summary>

### [`v0.2.0`](https://github.com/woodpecker-ci/plugin-prettier/blob/HEAD/CHANGELOG.md#020---2024-08-26)

[Compare Source](https://github.com/woodpecker-ci/plugin-prettier/compare/0.1.0...0.2.0)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;6543](https://github.com/6543), [@&#8203;qwerty287](https://github.com/qwerty287)

##### 📈 Enhancement

-   Allow to read version from lockfile \[[#&#8203;20](https://github.com/woodpecker-ci/plugin-prettier/pull/20)]

##### Misc

-   Update pipeline conf \[[#&#8203;41](https://github.com/woodpecker-ci/plugin-prettier/pull/41)]
-   \[pre-commit.ci] pre-commit autoupdate \[[#&#8203;40](https://github.com/woodpecker-ci/plugin-prettier/pull/40)]
-   Update woodpeckerci/plugin-ready-release-go Docker tag to v1.2.0 \[[#&#8203;39](https://github.com/woodpecker-ci/plugin-prettier/pull/39)]
-   Update woodpeckerci/plugin-docker-buildx Docker tag to v4.2.0 \[[#&#8203;38](https://github.com/woodpecker-ci/plugin-prettier/pull/38)]
-   Update woodpeckerci/plugin-docker-buildx Docker tag to v4.1.0 \[[#&#8203;37](https://github.com/woodpecker-ci/plugin-prettier/pull/37)]
-   Update pre-commit hook igorshubovych/markdownlint-cli to v0.41.0 \[[#&#8203;34](https://github.com/woodpecker-ci/plugin-prettier/pull/34)]
-   Update woodpeckerci/plugin-ready-release-go Docker tag to v1.1.2 \[[#&#8203;33](https://github.com/woodpecker-ci/plugin-prettier/pull/33)]
-   Update woodpeckerci/plugin-docker-buildx Docker tag to v4 \[[#&#8203;32](https://github.com/woodpecker-ci/plugin-prettier/pull/32)]
-   Update pre-commit hook igorshubovych/markdownlint-cli to v0.40.0 \[[#&#8203;30](https://github.com/woodpecker-ci/plugin-prettier/pull/30)]
-   Update woodpeckerci/plugin-ready-release-go Docker tag to v1.1.1 \[[#&#8203;29](https://github.com/woodpecker-ci/plugin-prettier/pull/29)]
-   Update pre-commit hook pre-commit/pre-commit-hooks to v4.6.0 \[[#&#8203;28](https://github.com/woodpecker-ci/plugin-prettier/pull/28)]
-   Update woodpeckerci/plugin-docker-buildx Docker tag to v3.2.1 \[[#&#8203;27](https://github.com/woodpecker-ci/plugin-prettier/pull/27)]
-   Update woodpeckerci/plugin-docker-buildx Docker tag to v3.2.0 \[[#&#8203;26](https://github.com/woodpecker-ci/plugin-prettier/pull/26)]
-   Update woodpeckerci/plugin-docker-buildx Docker tag to v3.1.0 \[[#&#8203;25](https://github.com/woodpecker-ci/plugin-prettier/pull/25)]
-   \[pre-commit.ci] pre-commit autoupdate \[[#&#8203;24](https://github.com/woodpecker-ci/plugin-prettier/pull/24)]
-   Update woodpeckerci/plugin-docker-buildx Docker tag to v3.0.1 \[[#&#8203;23](https://github.com/woodpecker-ci/plugin-prettier/pull/23)]
-   Update woodpeckerci/plugin-ready-release-go Docker tag to v1.1.0 \[[#&#8203;22](https://github.com/woodpecker-ci/plugin-prettier/pull/22)]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/385
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 6ba262c..403ded7 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -24,7 +24,7 @@ steps:
       - yamllint .
 
   prettier:
-    image: docker.io/woodpeckerci/plugin-prettier:0.1.0
+    image: docker.io/woodpeckerci/plugin-prettier:0.2.0
     depends_on: []
     settings:
       version: 3.2.5

From e5320e1972b8abe83e4cbd09b4bf334ca04fb283 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 29 Sep 2024 20:50:14 +0000
Subject: [PATCH 151/196] chore(deps): update pipelinecomponents/yamllint
 docker tag to v0.32.1 (#389)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pipelinecomponents/yamllint](https://gitlab.com/pipeline-components/yamllint) | minor | `0.31.3` -> `0.32.1` |

---

### Release Notes

<details>
<summary>pipeline-components/yamllint (pipelinecomponents/yamllint)</summary>

### [`v0.32.1`](https://gitlab.com/pipeline-components/yamllint/compare/v0.32.0...v0.32.1)

[Compare Source](https://gitlab.com/pipeline-components/yamllint/compare/v0.32.0...v0.32.1)

### [`v0.32.0`](https://gitlab.com/pipeline-components/yamllint/compare/v0.31.3...v0.32.0)

[Compare Source](https://gitlab.com/pipeline-components/yamllint/compare/v0.31.3...v0.32.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/389
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 403ded7..3b7ca40 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -18,7 +18,7 @@ steps:
     image: mstruebing/editorconfig-checker:v3.0.3
 
   yamllint:
-    image: pipelinecomponents/yamllint:0.31.3
+    image: pipelinecomponents/yamllint:0.32.1
     depends_on: []
     commands:
       - yamllint .

From 5b120f0488182e1a2d47a32b2fff93bdfa1e68cd Mon Sep 17 00:00:00 2001
From: ltdk <usr@ltdk.xyz>
Date: Sun, 29 Sep 2024 21:00:54 +0000
Subject: [PATCH 152/196] Implement static serving of compressed files (#387)

This provides an option for #223 without fully resolving it. (I think.)

Essentially, it acts very similar to the `gzip_static` and similar options for nginx, where it will check for the existence of pre-compressed files and serve those instead if the client allows it. I couldn't find a pre-existing way to actually parse the Accept-Encoding header properly (admittedly didn't look very hard) and just implemented one on my own that should be fine.

This should hopefully not have the same DOS vulnerabilities as #302, since it relies on the existing caching system. Compressed versions of files will be cached just like any other files, and that includes cache for missing files as well.

The compressed files will also be accessible directly, and this won't automatically decompress them. So, if you have a `tar.gz` file that you access directly, it will still be downloaded as the gzipped version, although you will now gain the option to download the `.tar` directly and decompress it in transit. (Which doesn't affect the server at all, just the client's way of interpreting it.)

----

One key thing this change also adds is a short-circuit when accessing directories: these always return 404 via the API, although they'd try the cache anyway and go through that route, which was kind of slow. Adding in the additional encodings, it's going to try for .gz, .br, and .zst files in the worst case as well, which feels wrong. So, instead, it just always falls back to the index-check behaviour if the path ends in a slash or is empty. (Which is implicitly just a slash.)

----

For testing, I set up this repo: https://codeberg.org/clarfonthey/testrepo

I ended up realising that LFS wasn't supported by default with `just dev`, so, it ended up working until I made sure the files on the repo *didn't* use LFS.

Assuming you've run `just dev`, you can go directly to this page in the browser here: https://clarfonthey.localhost.mock.directory:4430/testrepo/
And also you can try a few cURL commands:

```shell
curl https://clarfonthey.localhost.mock.directory:4430/testrepo/ --verbose --insecure
curl -H 'Accept-Encoding: gz' https://clarfonthey.localhost.mock.directory:4430/testrepo/ --verbose --insecure | gunzip -
curl -H 'Accept-Encoding: br' https://clarfonthey.localhost.mock.directory:4430/testrepo/ --verbose --insecure | brotli --decompress -
curl -H 'Accept-Encoding: zst' https://clarfonthey.localhost.mock.directory:4430/testrepo/ --verbose --insecure | zstd --decompress -
```

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/387
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: ltdk <usr@ltdk.xyz>
Co-committed-by: ltdk <usr@ltdk.xyz>
---
 server/certificates/certificates.go |   2 +-
 server/gitea/cache.go               |  18 +++--
 server/gitea/client.go              |  47 +++++++++----
 server/upstream/header.go           |   3 +
 server/upstream/upstream.go         | 101 +++++++++++++++++++++++++++-
 5 files changed, 150 insertions(+), 21 deletions(-)

diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index ff34775..062f721 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -196,7 +196,7 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
 		// renew certificates 7 days before they expire
 		if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
 			// TODO: use ValidTill of custom cert struct
-			if res.CSR != nil && len(res.CSR) > 0 {
+			if len(res.CSR) > 0 {
 				// CSR stores the time when the renewal shall be tried again
 				nextTryUnix, err := strconv.ParseInt(string(res.CSR), 10, 64)
 				if err == nil && time.Now().Before(time.Unix(nextTryUnix, 0)) {
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index 97024a1..cfb7c2a 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -37,15 +37,20 @@ type FileResponse struct {
 	Exists    bool
 	IsSymlink bool
 	ETag      string
-	MimeType  string
-	Body      []byte
+
+	// uncompressed MIME type
+	MimeType string
+
+	// raw MIME type (if compressed, type of compression)
+	RawMime string
+	Body    []byte
 }
 
 func (f FileResponse) IsEmpty() bool {
 	return len(f.Body) == 0
 }
 
-func (f FileResponse) createHttpResponse(cacheKey string) (header http.Header, statusCode int) {
+func (f FileResponse) createHttpResponse(cacheKey string, decompress bool) (header http.Header, statusCode int) {
 	header = make(http.Header)
 
 	if f.Exists {
@@ -58,7 +63,12 @@ func (f FileResponse) createHttpResponse(cacheKey string) (header http.Header, s
 		header.Set(giteaObjectTypeHeader, objTypeSymlink)
 	}
 	header.Set(ETagHeader, f.ETag)
-	header.Set(ContentTypeHeader, f.MimeType)
+
+	if decompress {
+		header.Set(ContentTypeHeader, f.MimeType)
+	} else {
+		header.Set(ContentTypeHeader, f.RawMime)
+	}
 	header.Set(ContentLengthHeader, fmt.Sprintf("%d", len(f.Body)))
 	header.Set(PagesCacheIndicatorHeader, "true")
 
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 3abb487..9777043 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -39,9 +39,10 @@ const (
 	objTypeSymlink        = "symlink"
 
 	// std
-	ETagHeader          = "ETag"
-	ContentTypeHeader   = "Content-Type"
-	ContentLengthHeader = "Content-Length"
+	ETagHeader            = "ETag"
+	ContentTypeHeader     = "Content-Type"
+	ContentLengthHeader   = "Content-Length"
+	ContentEncodingHeader = "Content-Encoding"
 )
 
 type Client struct {
@@ -103,7 +104,7 @@ func (client *Client) ContentWebLink(targetOwner, targetRepo, branch, resource s
 }
 
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
-	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
+	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource, false)
 	if err != nil {
 		return nil, err
 	}
@@ -111,21 +112,21 @@ func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource str
 	return io.ReadAll(reader)
 }
 
-func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (io.ReadCloser, http.Header, int, error) {
+func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string, decompress bool) (io.ReadCloser, http.Header, int, error) {
 	cacheKey := fmt.Sprintf("%s/%s/%s|%s|%s", rawContentCacheKeyPrefix, targetOwner, targetRepo, ref, resource)
 	log := log.With().Str("cache_key", cacheKey).Logger()
 	log.Trace().Msg("try file in cache")
 	// handle if cache entry exist
 	if cache, ok := client.responseCache.Get(cacheKey); ok {
 		cache := cache.(FileResponse)
-		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey)
+		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey, decompress)
 		// TODO: check against some timestamp mismatch?!?
 		if cache.Exists {
 			log.Debug().Msg("[cache] exists")
 			if cache.IsSymlink {
 				linkDest := string(cache.Body)
 				log.Debug().Msgf("[cache] follow symlink from %q to %q", resource, linkDest)
-				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
 			} else if !cache.IsEmpty() {
 				log.Debug().Msgf("[cache] return %d bytes", len(cache.Body))
 				return io.NopCloser(bytes.NewReader(cache.Body)), cachedHeader, cachedStatusCode, nil
@@ -170,13 +171,17 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 					}
 
 					log.Debug().Msgf("follow symlink from %q to %q", resource, linkDest)
-					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
 				}
 			}
 
 			// now we are sure it's content so set the MIME type
-			mimeType := client.getMimeTypeByExtension(resource)
-			resp.Response.Header.Set(ContentTypeHeader, mimeType)
+			mimeType, rawType := client.getMimeTypeByExtension(resource)
+			if decompress {
+				resp.Response.Header.Set(ContentTypeHeader, mimeType)
+			} else {
+				resp.Response.Header.Set(ContentTypeHeader, rawType)
+			}
 
 			if !shouldRespBeSavedToCache(resp.Response) {
 				return reader, resp.Response.Header, resp.StatusCode, err
@@ -187,6 +192,7 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 				Exists:   true,
 				ETag:     resp.Header.Get(ETagHeader),
 				MimeType: mimeType,
+				RawMime:  rawType,
 			}
 			return fileResp.CreateCacheReader(reader, client.responseCache, cacheKey), resp.Response.Header, resp.StatusCode, nil
 
@@ -300,16 +306,31 @@ func (client *Client) GiteaCheckIfOwnerExists(owner string) (bool, error) {
 	return false, nil
 }
 
-func (client *Client) getMimeTypeByExtension(resource string) string {
-	mimeType := mime.TypeByExtension(path.Ext(resource))
+func (client *Client) extToMime(ext string) string {
+	mimeType := mime.TypeByExtension(ext)
 	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
 	if client.forbiddenMimeTypes[mimeTypeSplit[0]] || mimeType == "" {
 		mimeType = client.defaultMimeType
 	}
-	log.Trace().Msgf("probe mime of %q is %q", resource, mimeType)
 	return mimeType
 }
 
+func (client *Client) getMimeTypeByExtension(resource string) (mimeType, rawType string) {
+	rawExt := path.Ext(resource)
+	innerExt := rawExt
+	switch rawExt {
+	case ".gz", ".br", ".zst":
+		innerExt = path.Ext(resource[:len(resource)-len(rawExt)])
+	}
+	rawType = client.extToMime(rawExt)
+	mimeType = rawType
+	if innerExt != rawExt {
+		mimeType = client.extToMime(innerExt)
+	}
+	log.Trace().Msgf("probe mime of %q is (%q / raw %q)", resource, mimeType, rawType)
+	return mimeType, rawType
+}
+
 func shouldRespBeSavedToCache(resp *http.Response) bool {
 	if resp == nil {
 		return false
diff --git a/server/upstream/header.go b/server/upstream/header.go
index 7b85df1..3a218a1 100644
--- a/server/upstream/header.go
+++ b/server/upstream/header.go
@@ -24,5 +24,8 @@ func (o *Options) setHeader(ctx *context.Context, header http.Header) {
 	} else {
 		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, mime)
 	}
+	if encoding := header.Get(gitea.ContentEncodingHeader); encoding != "" && encoding != "identity" {
+		ctx.RespWriter.Header().Set(gitea.ContentEncodingHeader, encoding)
+	}
 	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(http.TimeFormat))
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index d9c131e..98137ba 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -1,10 +1,13 @@
 package upstream
 
 import (
+	"cmp"
 	"errors"
 	"fmt"
 	"io"
 	"net/http"
+	"slices"
+	"strconv"
 	"strings"
 	"time"
 
@@ -19,6 +22,8 @@ import (
 const (
 	headerLastModified    = "Last-Modified"
 	headerIfModifiedSince = "If-Modified-Since"
+	headerAcceptEncoding  = "Accept-Encoding"
+	headerContentEncoding = "Content-Encoding"
 
 	rawMime = "text/plain; charset=utf-8"
 )
@@ -52,6 +57,72 @@ type Options struct {
 	ServeRaw bool
 }
 
+// allowed encodings
+var allowedEncodings = map[string]string{
+	"gzip":     ".gz",
+	"br":       ".br",
+	"zstd":     ".zst",
+	"identity": "",
+}
+
+// parses Accept-Encoding header into a list of acceptable encodings
+func AcceptEncodings(header string) []string {
+	log.Trace().Msgf("got accept-encoding: %s", header)
+	encodings := []string{}
+	globQuality := 0.0
+	qualities := make(map[string]float64)
+
+	for _, encoding := range strings.Split(header, ",") {
+		name, quality_str, has_quality := strings.Cut(encoding, ";q=")
+		quality := 1.0
+
+		if has_quality {
+			var err error
+			quality, err = strconv.ParseFloat(quality_str, 64)
+			if err != nil || quality < 0 {
+				continue
+			}
+		}
+
+		name = strings.TrimSpace(name)
+
+		if name == "*" {
+			globQuality = quality
+		} else {
+			_, allowed := allowedEncodings[name]
+			if allowed {
+				qualities[name] = quality
+				if quality > 0 {
+					encodings = append(encodings, name)
+				}
+			}
+		}
+	}
+
+	if globQuality > 0 {
+		for encoding := range allowedEncodings {
+			_, exists := qualities[encoding]
+			if !exists {
+				encodings = append(encodings, encoding)
+				qualities[encoding] = globQuality
+			}
+		}
+	} else {
+		_, exists := qualities["identity"]
+		if !exists {
+			encodings = append(encodings, "identity")
+			qualities["identity"] = -1
+		}
+	}
+
+	slices.SortStableFunc(encodings, func(x, y string) int {
+		// sort in reverse order; big quality comes first
+		return cmp.Compare(qualities[y], qualities[x])
+	})
+	log.Trace().Msgf("decided encoding order: %v", encodings)
+	return encodings
+}
+
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
 func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.ICache) bool {
 	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
@@ -97,9 +168,33 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 
 	log.Debug().Msg("Preparing")
 
-	reader, header, statusCode, err := giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath)
-	if reader != nil {
-		defer reader.Close()
+	var reader io.ReadCloser
+	var header http.Header
+	var statusCode int
+	var err error
+
+	// pick first non-404 response for encoding, *only* if not root
+	if o.TargetPath == "" || strings.HasSuffix(o.TargetPath, "/") {
+		err = gitea.ErrorNotFound
+	} else {
+		for _, encoding := range AcceptEncodings(ctx.Req.Header.Get(headerAcceptEncoding)) {
+			log.Trace().Msgf("try %s encoding", encoding)
+
+			// add extension for encoding
+			path := o.TargetPath + allowedEncodings[encoding]
+			reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path, true)
+			if statusCode == 404 {
+				continue
+			}
+			log.Debug().Msgf("using %s encoding", encoding)
+			if encoding != "identity" {
+				header.Set(headerContentEncoding, encoding)
+			}
+			break
+		}
+		if reader != nil {
+			defer reader.Close()
+		}
 	}
 
 	log.Debug().Msg("Aquisting")

From 2f8bd38f5940be07024e679249a85e0a4f9e8e27 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sun, 29 Sep 2024 21:17:15 +0000
Subject: [PATCH 153/196] pin golang-just image (#390)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/390
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .woodpecker/build.yml | 12 ++++--------
 flake.lock            | 16 +++++++---------
 2 files changed, 11 insertions(+), 17 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 976e923..0da09a8 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -11,8 +11,7 @@ steps:
 
   build:
     depends_on: vendor
-    image: codeberg.org/6543/docker-images/golang_just
-    pull: true
+    image: codeberg.org/6543/docker-images/golang_just:go-1.22
     commands:
       - go version
       - just build
@@ -33,8 +32,7 @@ steps:
 
   build-tag:
     depends_on: vendor
-    image: codeberg.org/6543/docker-images/golang_just
-    pull: true
+    image: codeberg.org/6543/docker-images/golang_just:go-1.22
     commands:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
@@ -43,8 +41,7 @@ steps:
 
   test:
     depends_on: build
-    image: codeberg.org/6543/docker-images/golang_just
-    pull: true
+    image: codeberg.org/6543/docker-images/golang_just:go-1.22
     commands:
       - just test
     when:
@@ -52,8 +49,7 @@ steps:
 
   integration-tests:
     depends_on: build
-    image: codeberg.org/6543/docker-images/golang_just
-    pull: true
+    image: codeberg.org/6543/docker-images/golang_just:go-1.22
     commands:
       - just integration
     environment:
diff --git a/flake.lock b/flake.lock
index c74fe33..90d377f 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
         "systems": "systems"
       },
       "locked": {
-        "lastModified": 1710146030,
-        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+        "lastModified": 1726560853,
+        "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+        "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
         "type": "github"
       },
       "original": {
@@ -19,12 +19,10 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1716715802,
-        "narHash": "sha256-usk0vE7VlxPX8jOavrtpOqphdfqEQpf9lgedlY/r66c=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "e2dd4e18cc1c7314e24154331bae07df76eb582f",
-        "type": "github"
+        "lastModified": 0,
+        "narHash": "sha256-8fnFlXBgM/uSvBlLWjZ0Z0sOdRBesyNdH0+esxqizGc=",
+        "path": "/nix/store/rqicl40jqw5d93l43s1xq0bm6yy0gpx4-source",
+        "type": "path"
       },
       "original": {
         "id": "nixpkgs",

From d6f0ec39fcdf5ce1e26308a706d859ba04cb17f1 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 30 Sep 2024 00:26:29 +0000
Subject: [PATCH 154/196] fix(deps): update golang.org/x/exp digest to 0cdaa3a
 (#377)

---
 go.mod |  6 +++---
 go.sum | 16 ++++++++--------
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/go.mod b/go.mod
index 479d6a5..57989bf 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,8 @@
 module codeberg.org/codeberg/pages
 
-go 1.21
+go 1.22.0
 
-toolchain go1.21.4
+toolchain go1.23.1
 
 require (
 	code.gitea.io/sdk/gitea v0.17.1
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
+	golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index 9d9c871..a18667f 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
-golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -810,8 +810,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
-golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -868,8 +868,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1004,8 +1004,8 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
-golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
+golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
+golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From 831ce3d913015e856351dc4d3fc983ada826ef7e Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 7 Oct 2024 00:38:22 +0000
Subject: [PATCH 155/196] fix(deps): update golang.org/x/exp digest to 225e2ab
 (#391)

---
 go.mod | 2 +-
 go.sum | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/go.mod b/go.mod
index 57989bf..9ab2629 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
+	golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index a18667f..59f2529 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
+golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw=
+golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1004,8 +1004,8 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
-golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
+golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From 168bc9047b52efcbbbe0cdda95586ce9ac211d54 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Tue, 15 Oct 2024 00:51:32 +0000
Subject: [PATCH 156/196] fix(deps): update golang.org/x/exp digest to f66d83c
 (#396)

---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index 9ab2629..27dd9e6 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6
+	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
 	xorm.io/xorm v1.3.2
 )
 
diff --git a/go.sum b/go.sum
index 59f2529..dab56c6 100644
--- a/go.sum
+++ b/go.sum
@@ -786,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw=
-golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
+golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
+golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

From bb7cfbb37ceb9632e2325e3f6fcf592b4833f310 Mon Sep 17 00:00:00 2001
From: Julien Malka <julien@malka.sh>
Date: Tue, 29 Oct 2024 17:56:00 +0000
Subject: [PATCH 157/196] feat: add proxy protocol support (#394)

This can be useful when the pages-server is running behing a proxy, to
keep track of the originating ip.

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/394
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Julien Malka <julien@malka.sh>
Co-committed-by: Julien Malka <julien@malka.sh>
---
 cli/flags.go      | 7 +++++++
 config/config.go  | 1 +
 config/setup.go   | 4 ++++
 go.mod            | 7 ++++---
 go.sum            | 9 +++++++++
 server/startup.go | 4 ++++
 6 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/cli/flags.go b/cli/flags.go
index 934ef8d..542b6ca 100644
--- a/cli/flags.go
+++ b/cli/flags.go
@@ -109,6 +109,13 @@ var (
 			EnvVars: []string{"ENABLE_HTTP_SERVER"},
 			Value:   false,
 		},
+		&cli.BoolFlag{
+			Name:    "use-proxy-protocol",
+			Usage:   "use the proxy protocol",
+			EnvVars: []string{"USE_PROXY_PROTOCOL"},
+			Value:   false,
+		},
+
 		// Default branches to fetch assets from
 		&cli.StringSliceFlag{
 			Name:    "pages-branch",
diff --git a/config/config.go b/config/config.go
index 2accbf5..8db9336 100644
--- a/config/config.go
+++ b/config/config.go
@@ -13,6 +13,7 @@ type ServerConfig struct {
 	Port               uint16 `default:"443"`
 	HttpPort           uint16 `default:"80"`
 	HttpServerEnabled  bool   `default:"true"`
+	UseProxyProtocol   bool   `default:"false"`
 	MainDomain         string
 	RawDomain          string
 	PagesBranches      []string
diff --git a/config/setup.go b/config/setup.go
index f1388fe..0846314 100644
--- a/config/setup.go
+++ b/config/setup.go
@@ -69,6 +69,10 @@ func mergeServerConfig(ctx *cli.Context, config *ServerConfig) {
 	if ctx.IsSet("enable-http-server") {
 		config.HttpServerEnabled = ctx.Bool("enable-http-server")
 	}
+	if ctx.IsSet("use-proxy-protocol") {
+		config.UseProxyProtocol = ctx.Bool("use-proxy-protocol")
+	}
+
 	if ctx.IsSet("pages-domain") {
 		config.MainDomain = ctx.String("pages-domain")
 	}
diff --git a/go.mod b/go.mod
index 27dd9e6..5e42e1b 100644
--- a/go.mod
+++ b/go.mod
@@ -104,6 +104,7 @@ require (
 	github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
 	github.com/ovh/go-ovh v1.1.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+	github.com/pires/go-proxyproto v0.8.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pquerna/otp v1.3.0 // indirect
@@ -123,10 +124,10 @@ require (
 	github.com/vultr/govultr/v2 v2.7.1 // indirect
 	go.opencensus.io v0.22.3 // indirect
 	go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect
-	golang.org/x/crypto v0.17.0 // indirect
-	golang.org/x/net v0.17.0 // indirect
+	golang.org/x/crypto v0.21.0 // indirect
+	golang.org/x/net v0.23.0 // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
-	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/sys v0.18.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
 	google.golang.org/api v0.20.0 // indirect
diff --git a/go.sum b/go.sum
index dab56c6..6903528 100644
--- a/go.sum
+++ b/go.sum
@@ -578,6 +578,8 @@ github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
+github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -776,6 +778,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -852,6 +856,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -932,6 +938,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -939,6 +947,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/server/startup.go b/server/startup.go
index 6642d83..14c37aa 100644
--- a/server/startup.go
+++ b/server/startup.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/pires/go-proxyproto"
 	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
@@ -95,6 +96,9 @@ func Serve(ctx *cli.Context) error {
 		return fmt.Errorf("couldn't create listener: %v", err)
 	}
 
+	if cfg.Server.UseProxyProtocol {
+		listener = &proxyproto.Listener{Listener: listener}
+	}
 	// Setup listener for SSL connections
 	listener = tls.NewListener(listener, certificates.TLSConfig(
 		cfg.Server.MainDomain,

From 532e1af2fcc1b2686eba0be65d4f13bf3531828b Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Tue, 5 Nov 2024 18:52:45 +0000
Subject: [PATCH 158/196] chore(deps): update woodpeckerci/plugin-docker-buildx
 docker tag to v5 (#392)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [woodpeckerci/plugin-docker-buildx](https://codeberg.org/woodpecker-plugins/docker-buildx) ([source](https://codeberg.org/woodpecker-plugins/docker-buildx.git)) | major | `4.2.0` -> `5.0.0` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/docker-buildx (woodpeckerci/plugin-docker-buildx)</summary>

### [`v5.0.0`](https://codeberg.org/woodpecker-plugins/docker-buildx/blob/HEAD/CHANGELOG.md#500---2024-10-09)

[Compare Source](https://codeberg.org/woodpecker-plugins/docker-buildx/compare/v4.2.0...v5.0.0)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;6543](https://github.com/6543), [@&#8203;flyingscorpio](https://github.com/flyingscorpio), [@&#8203;pat-s](https://github.com/pat-s), [@&#8203;woodpecker-bot](https://github.com/woodpecker-bot)

##### 💥 Breaking changes

-   Hide insecure configuration options behind insecure tag \[[#&#8203;194](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/194)]
-   chore(deps): update docker docker tag to v27 \[[#&#8203;170](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/170)]

##### ✨ Features

-   Add `sbom` option \[[#&#8203;187](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/187)]

##### 📈 Enhancement

-   Optimize container layers \[[#&#8203;191](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/191)]
-   Dont let proxy config overwrite dedicated buildkit_driveropt proxy options if set \[[#&#8203;175](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/175)]

##### 🐛 Bug Fixes

-   Quote `no_proxy` \[[#&#8203;185](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/185)]
-   Update drone-plugin-lib to handle nil pointer \[[#&#8203;178](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/178)]

##### 📚 Documentation

-   update pipeline config example \[[#&#8203;180](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/180)]

##### 📦️ Dependency

-   chore(deps): update docker docker tag to v27.2.1 \[[#&#8203;183](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/183)]
-   fix(deps): update github.com/urfave/cli/v3 digest to [`31c5c84`](https://github.com/woodpecker-plugins/docker-buildx/commit/31c5c84) \[[#&#8203;182](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/182)]
-   Update to github.com/urfave/cli/v3 \[[#&#8203;173](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/173)]

##### Misc

-   dont ignore changelog ([f3f8166](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/f3f8166af3c420310ddd1dca69158a6bc70cb288))
-   Add nix flake \[[#&#8203;192](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/192)]
-   chore(deps): update dependency go to v1.23.2 ([9c4cea3](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/9c4cea3cccce7b3b668ed5a078d71e4e0b652ec4))
-   fix(deps): update github.com/urfave/cli/v3 digest to [`20ef97b`](https://github.com/woodpecker-plugins/docker-buildx/commit/20ef97b) ([b6a6f1c](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/b6a6f1c7728729882ce616a78aeb7bdda2de19be))
-   Add release config for release-helper plugin \[[#&#8203;190](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/190)]
-   Use ready-release-go plugin \[[#&#8203;189](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/189)]
-   add-remote-builders \[[#&#8203;179](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/179)]
-   chore(deps): update docker docker tag to v27.3.1 ([4fedea4](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/4fedea48552f8e7584abb39aa50a9e8e4d1aaea9))
-   chore(deps): update docker/buildx-bin docker tag to v0.17.1 ([b807eda](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/b807edaa48ff0aadd5f5e154c40ba74956e2e2a9))
-   chore(deps): update davidanson/markdownlint-cli2 docker tag to v0.14.0 ([8435819](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/84358199716d7e6b4529e454772dccf50ca5e067))
-   chore(deps): update dependency go to v1.23.1 ([3ee6b8b](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/3ee6b8bb9dd1170434f48b7f9d0a389e1c85a569))
-   chore(deps): update docker docker tag to v27.2.0 ([c661b7a](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/c661b7a48d39b4329944f435727c06af9336a6ee))
-   fix(deps): update github.com/urfave/cli/v3 digest to [`3d76e1b`](https://github.com/woodpecker-plugins/docker-buildx/commit/3d76e1b) ([ba44638](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/ba44638ddaba4aeff7fe9d59caa0f73e47f20a70))
-   fix(deps): update module github.com/pelletier/go-toml/v2 to v2.2.3 ([0e1e881](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/0e1e8818e72d6fae335635be701fa43e6c10ae6b))
-   fix(deps): update github.com/urfave/cli/v3 digest to [`5386081`](https://github.com/woodpecker-plugins/docker-buildx/commit/5386081) ([cf7d19c](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/cf7d19cbb1b1f94ce0d4cf868dde9d75c8b2aab5))
-   fix(deps): update module honnef.co/go/tools to v0.5.1 ([23b0f61](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/23b0f6125596bdf4877262676e59490f3ac577dd))
-   chore(deps): update golang docker tag to v1.23 ([b98307b](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/b98307bb3e495969737aba7dc861838720de3c21))
-   fix(deps): update github.com/urfave/cli/v3 digest to [`3110c0e`](https://github.com/woodpecker-plugins/docker-buildx/commit/3110c0e) ([13aa887](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/13aa8879a21410c5ac9a83515aa9a19ca75243cb))
-   chore(deps): update docker docker tag to v27.1.2 ([fe28f12](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/fe28f12be182c261560c10e6b21c487e6beaee2f))
-   chore(deps): update woodpeckerci/plugin-release docker tag to v0.2.1 ([ea32257](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/ea32257d1d634daf2132dae551473cd7d473df07))
-   fix(deps): update module github.com/aws/aws-sdk-go to v1.55.5 ([af71dcd](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/af71dcdbdef4a03cda1e6ac4779d75a0263293f8))
-   fix(deps): update module github.com/aws/aws-sdk-go to v1.55.3 ([1d5b8fd](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/1d5b8fdc17a39cb5469c05b392cedccf06437bd3))
-   chore(deps): update docker/buildx-bin docker tag to v0.16.2 ([c671088](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/c671088766b7cb467c3cb10f0d7b1069c59e853a))
-   chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v4.2.0 ([642f2b3](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/642f2b36d7736b0f875c985228a41ea4a8419078))
-   chore(deps): update docker/buildx-bin docker tag to v0.16.1 ([f8b58f5](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/f8b58f573b2b56b53466c4e7871032244adbc8ca))
-   fix(deps): update github.com/urfave/cli/v3 digest to [`127cf54`](https://github.com/woodpecker-plugins/docker-buildx/commit/127cf54) ([03cd73f](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/03cd73f06864bc0a26c66aaa2e66b07b0e9c81c5))
-   update version parse lib ([42395c5](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/42395c51eca5407325619b38a90669d480ca4ef9))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/392
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 0da09a8..10489fb 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -20,7 +20,7 @@ steps:
 
   docker-dryrun:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:4.2.0
+    image: woodpeckerci/plugin-docker-buildx:5.0.0
     settings:
       dockerfile: Dockerfile
       platforms: linux/amd64
@@ -74,7 +74,7 @@ steps:
 
   docker-next:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:4.2.0
+    image: woodpeckerci/plugin-docker-buildx:5.0.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
@@ -89,7 +89,7 @@ steps:
       - event: [push]
 
   'Publish PR image':
-    image: woodpeckerci/plugin-docker-buildx:4.2.0
+    image: woodpeckerci/plugin-docker-buildx:5.0.0
     depends_on: test
     settings:
       registry: codeberg.org
@@ -107,7 +107,7 @@ steps:
 
   docker-release:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:4.2.0
+    image: woodpeckerci/plugin-docker-buildx:5.0.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile

From bef866faae2b699dbbb4cfc3ec18adcbaed27b8b Mon Sep 17 00:00:00 2001
From: Bastian Greshake Tzovaras <bgreshake@googlemail.com>
Date: Tue, 5 Nov 2024 21:01:13 +0000
Subject: [PATCH 159/196] Improve error message when pages don't work (#398)

On Matrix there was a recent discussion about the quality of the error page that _Codeberg Pages_ displays. Pixelcode had posted this relevant points:

> Currently, the message reads as if written for the website's owner. I think it should be rephrased so that visitors who encounter the error understand that:
> 1. This page is an error message, not the actual website. (e.g. Error 424 instead of just 424)
> 2. The website is hosted on Codeberg Pages, not on a server controlled by the owner.
> 3. There's perhaps a configuration error or an internal server issue.
> 4. It might take a little until the website is up again.

 In particular the point about the "dual audience" of _Codeberg Pages_ user/website owner and website visitor seems important, to make sure that both audiences understand that there's an error.
 Also the error can be due to _Codeberg Pages_ being down or due to the user misconfiguring their page.

 I've taken a first stab at trying to cover those 4 points, but of course the exact improvements are up for debate, but thought it would be easiest to provide a concrete suggestion to get us started!

Co-authored-by: crapStone <me@crapstone.dev>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/398
Co-authored-by: Bastian Greshake Tzovaras <bgreshake@googlemail.com>
Co-committed-by: Bastian Greshake Tzovaras <bgreshake@googlemail.com>
---
 html/templates/error.html | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/html/templates/error.html b/html/templates/error.html
index 05a5d46..ccaa682 100644
--- a/html/templates/error.html
+++ b/html/templates/error.html
@@ -33,12 +33,17 @@
         d="M 9 2 C 5.1458514 2 2 5.1458514 2 9 C 2 12.854149 5.1458514 16 9 16 C 10.747998 16 12.345009 15.348024 13.574219 14.28125 L 14 14.707031 L 14 16 L 19.585938 21.585938 C 20.137937 22.137937 21.033938 22.137938 21.585938 21.585938 C 22.137938 21.033938 22.137938 20.137938 21.585938 19.585938 L 16 14 L 14.707031 14 L 14.28125 13.574219 C 15.348024 12.345009 16 10.747998 16 9 C 16 5.1458514 12.854149 2 9 2 z M 9 4 C 11.773268 4 14 6.2267316 14 9 C 14 11.773268 11.773268 14 9 14 C 6.2267316 14 4 11.773268 4 9 C 4 6.2267316 6.2267316 4 9 4 z"
       />
     </svg>
-    <h1 class="mb-0 text-primary">{{.StatusText}} ({{.StatusCode}})!</h1>
+    <h1 class="mb-0 text-primary">{{.StatusText}} (Error {{.StatusCode}})!</h1>
     <h5 class="text-center" style="max-width: 25em">
-      <p>Sorry, but this page couldn't be served.</p>
+      <p>Sorry, but this page couldn't be served:</p>
       <p><b>"{{.Message}}"</b></p>
       <p>
-        We hope this isn't a problem on our end ;) - Make sure to check the
+        The page you tried to reach is hosted on Codeberg Pages, which might currently be experiencing technical
+        difficulties. If that is the case, it could take a little while until this page is available again.
+      </p>
+      <p>
+        Otherwise, this page might also be unavailable due to a configuration error. If you are the owner of this
+        website, please make sure to check the
         <a href="https://docs.codeberg.org/codeberg-pages/troubleshooting/" target="_blank"
           >troubleshooting section in the Docs</a
         >!

From 23a8e83e80e72b11d99145cc2dfdac617ec77f14 Mon Sep 17 00:00:00 2001
From: Gnarwhal <git.aspect893@passmail.net>
Date: Sun, 17 Nov 2024 16:28:52 +0000
Subject: [PATCH 160/196] Allow serving custom error page (#393)

It might be useful for those self-hosting a Codeberg Pages instance to be able to serve a different error page than the one embedded with go:embed

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/393
Co-authored-by: Gnarwhal <git.aspect893@passmail.net>
Co-committed-by: Gnarwhal <git.aspect893@passmail.net>
---
 README.md    |  9 +++++++++
 flake.lock   | 10 +++++-----
 html/html.go | 20 +++++++++++++++++++-
 3 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index 0051a9e..4bcd393 100644
--- a/README.md
+++ b/README.md
@@ -84,6 +84,15 @@ This will trigger a build of the PR which will build a docker image to be used f
 - `PAGES_DOMAIN` (default: `codeberg.page`): main domain for pages.
 - `RAW_DOMAIN` (default: `raw.codeberg.page`): domain for raw resources (must be subdomain of `PAGES_DOMAIN`).
 
+### Custom Error Page
+
+A custom error page template can be served by creating `custom/error.html`.
+Data available to the template includes:
+
+- `{{ .StatusCode }}`: The HTTP status code (e.g. 404)
+- `{{ .StatusText }}`: The textual name associated with the status code (e.g. Not Found)
+- `{{ .Message }}`: The reason for the error
+
 ## Contributing to the development
 
 The Codeberg team is very open to your contribution.
diff --git a/flake.lock b/flake.lock
index 90d377f..861cc42 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
         "systems": "systems"
       },
       "locked": {
-        "lastModified": 1726560853,
-        "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
+        "lastModified": 1731533236,
+        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
+        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
         "type": "github"
       },
       "original": {
@@ -20,8 +20,8 @@
     "nixpkgs": {
       "locked": {
         "lastModified": 0,
-        "narHash": "sha256-8fnFlXBgM/uSvBlLWjZ0Z0sOdRBesyNdH0+esxqizGc=",
-        "path": "/nix/store/rqicl40jqw5d93l43s1xq0bm6yy0gpx4-source",
+        "narHash": "sha256-x07g4NcqGP6mQn6AISXJaks9sQYDjZmTMBlKIvajvyc=",
+        "path": "/nix/store/2w8kz6zh3aq80f1dypiin222fry1rv51-source",
         "type": "path"
       },
       "original": {
diff --git a/html/html.go b/html/html.go
index ff8b6b4..ffede7f 100644
--- a/html/html.go
+++ b/html/html.go
@@ -3,6 +3,8 @@ package html
 import (
 	_ "embed"
 	"net/http"
+	"os"
+	"path"
 	"text/template" // do not use html/template here, we sanitize the message before passing it to the template
 
 	"codeberg.org/codeberg/pages/server/context"
@@ -14,7 +16,7 @@ import (
 var errorPage string
 
 var (
-	errorTemplate = template.Must(template.New("error").Parse(errorPage))
+	errorTemplate = template.Must(template.New("error").Parse(loadCustomTemplateOrDefault()))
 	sanitizer     = createBlueMondayPolicy()
 )
 
@@ -51,3 +53,19 @@ func createBlueMondayPolicy() *bluemonday.Policy {
 
 	return p
 }
+
+func loadCustomTemplateOrDefault() string {
+	contents, err := os.ReadFile("custom/error.html")
+	if err != nil {
+		if !os.IsNotExist(err) {
+			wd, wdErr := os.Getwd()
+			if wdErr != nil {
+				log.Err(err).Msg("could not load custom error page 'custom/error.html'")
+			} else {
+				log.Err(err).Msgf("could not load custom error page '%v'", path.Join(wd, "custom/error.html"))
+			}
+		}
+		return errorPage
+	}
+	return string(contents)
+}

From 85059aa46ef0b04c76646c8a56a7860f5007f58a Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Thu, 21 Nov 2024 23:26:30 +0000
Subject: [PATCH 161/196] cache pages (#403)

taken from https://codeberg.org/Codeberg/pages-server/pulls/301

Co-authored-by: Moritz Marquardt <git@momar.de>

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/403
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 server/gitea/cache.go       |  61 +++++++------
 server/gitea/client.go      | 171 ++++++++++++++++++------------------
 server/upstream/header.go   |   3 -
 server/upstream/upstream.go |   2 +-
 4 files changed, 120 insertions(+), 117 deletions(-)

diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index cfb7c2a..c560a19 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -2,6 +2,7 @@ package gitea
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
@@ -34,23 +35,18 @@ const (
 )
 
 type FileResponse struct {
-	Exists    bool
-	IsSymlink bool
-	ETag      string
-
-	// uncompressed MIME type
-	MimeType string
-
-	// raw MIME type (if compressed, type of compression)
-	RawMime string
-	Body    []byte
+	Exists    bool   `json:"exists"`
+	IsSymlink bool   `json:"isSymlink"`
+	ETag      string `json:"eTag"`
+	MimeType  string `json:"mimeType"`
+	Body      []byte `json:"-"` // saved separately
 }
 
 func (f FileResponse) IsEmpty() bool {
 	return len(f.Body) == 0
 }
 
-func (f FileResponse) createHttpResponse(cacheKey string, decompress bool) (header http.Header, statusCode int) {
+func (f FileResponse) createHttpResponse(cacheKey string) (header http.Header, statusCode int) {
 	header = make(http.Header)
 
 	if f.Exists {
@@ -63,12 +59,7 @@ func (f FileResponse) createHttpResponse(cacheKey string, decompress bool) (head
 		header.Set(giteaObjectTypeHeader, objTypeSymlink)
 	}
 	header.Set(ETagHeader, f.ETag)
-
-	if decompress {
-		header.Set(ContentTypeHeader, f.MimeType)
-	} else {
-		header.Set(ContentTypeHeader, f.RawMime)
-	}
+	header.Set(ContentTypeHeader, f.MimeType)
 	header.Set(ContentLengthHeader, fmt.Sprintf("%d", len(f.Body)))
 	header.Set(PagesCacheIndicatorHeader, "true")
 
@@ -77,9 +68,9 @@ func (f FileResponse) createHttpResponse(cacheKey string, decompress bool) (head
 }
 
 type BranchTimestamp struct {
-	Branch    string
-	Timestamp time.Time
-	notFound  bool
+	NotFound  bool      `json:"notFound"`
+	Branch    string    `json:"branch,omitempty"`
+	Timestamp time.Time `json:"timestamp,omitempty"`
 }
 
 type writeCacheReader struct {
@@ -89,32 +80,46 @@ type writeCacheReader struct {
 	cacheKey       string
 	cache          cache.ICache
 	hasError       bool
+	doNotCache     bool
+	complete       bool
 }
 
 func (t *writeCacheReader) Read(p []byte) (n int, err error) {
 	log.Trace().Msgf("[cache] read %q", t.cacheKey)
 	n, err = t.originalReader.Read(p)
+	if err == io.EOF {
+		t.complete = true
+	}
 	if err != nil && err != io.EOF {
 		log.Trace().Err(err).Msgf("[cache] original reader for %q has returned an error", t.cacheKey)
 		t.hasError = true
 	} else if n > 0 {
-		_, _ = t.buffer.Write(p[:n])
+		if t.buffer.Len()+n > int(fileCacheSizeLimit) {
+			t.doNotCache = true
+			t.buffer.Reset()
+		} else {
+			_, _ = t.buffer.Write(p[:n])
+		}
 	}
 	return
 }
 
 func (t *writeCacheReader) Close() error {
-	doWrite := !t.hasError
+	doWrite := !t.hasError && !t.doNotCache && t.complete
 	fc := *t.fileResponse
 	fc.Body = t.buffer.Bytes()
-	if fc.IsEmpty() {
-		log.Trace().Msg("[cache] file response is empty")
-		doWrite = false
-	}
 	if doWrite {
-		err := t.cache.Set(t.cacheKey, fc, fileCacheTimeout)
+		jsonToCache, err := json.Marshal(fc)
 		if err != nil {
-			log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey)
+			log.Trace().Err(err).Msgf("[cache] marshaling json for %q has returned an error", t.cacheKey+"|Metadata")
+		}
+		err = t.cache.Set(t.cacheKey+"|Metadata", jsonToCache, fileCacheTimeout)
+		if err != nil {
+			log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey+"|Metadata")
+		}
+		err = t.cache.Set(t.cacheKey+"|Body", fc.Body, fileCacheTimeout)
+		if err != nil {
+			log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey+"|Body")
 		}
 	}
 	log.Trace().Msgf("cacheReader for %q saved=%t closed", t.cacheKey, doWrite)
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 9777043..ea57c14 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -2,6 +2,7 @@ package gitea
 
 import (
 	"bytes"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -39,10 +40,9 @@ const (
 	objTypeSymlink        = "symlink"
 
 	// std
-	ETagHeader            = "ETag"
-	ContentTypeHeader     = "Content-Type"
-	ContentLengthHeader   = "Content-Length"
-	ContentEncodingHeader = "Content-Encoding"
+	ETagHeader          = "ETag"
+	ContentTypeHeader   = "Content-Type"
+	ContentLengthHeader = "Content-Length"
 )
 
 type Client struct {
@@ -104,7 +104,7 @@ func (client *Client) ContentWebLink(targetOwner, targetRepo, branch, resource s
 }
 
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
-	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource, false)
+	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
 	if err != nil {
 		return nil, err
 	}
@@ -112,27 +112,42 @@ func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource str
 	return io.ReadAll(reader)
 }
 
-func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string, decompress bool) (io.ReadCloser, http.Header, int, error) {
+func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (io.ReadCloser, http.Header, int, error) {
 	cacheKey := fmt.Sprintf("%s/%s/%s|%s|%s", rawContentCacheKeyPrefix, targetOwner, targetRepo, ref, resource)
 	log := log.With().Str("cache_key", cacheKey).Logger()
 	log.Trace().Msg("try file in cache")
 	// handle if cache entry exist
-	if cache, ok := client.responseCache.Get(cacheKey); ok {
-		cache := cache.(FileResponse)
-		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey, decompress)
-		// TODO: check against some timestamp mismatch?!?
+	if cacheMetadata, ok := client.responseCache.Get(cacheKey + "|Metadata"); ok {
+		var cache FileResponse
+		err := json.Unmarshal(cacheMetadata.([]byte), &cache)
+		if err != nil {
+			log.Error().Err(err).Msgf("[cache] failed to unmarshal metadata for: %s", cacheKey)
+			return nil, nil, http.StatusNotFound, err
+		}
+
+		if !cache.Exists {
+			return nil, nil, http.StatusNotFound, ErrorNotFound
+		}
+
+		body, ok := client.responseCache.Get(cacheKey + "|Body")
+		if !ok {
+			log.Error().Msgf("[cache] failed to get body for: %s", cacheKey)
+			return nil, nil, http.StatusNotFound, ErrorNotFound
+		}
+		cache.Body = body.([]byte)
+
+		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey)
 		if cache.Exists {
-			log.Debug().Msg("[cache] exists")
 			if cache.IsSymlink {
 				linkDest := string(cache.Body)
 				log.Debug().Msgf("[cache] follow symlink from %q to %q", resource, linkDest)
-				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
-			} else if !cache.IsEmpty() {
+				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+			} else {
 				log.Debug().Msgf("[cache] return %d bytes", len(cache.Body))
 				return io.NopCloser(bytes.NewReader(cache.Body)), cachedHeader, cachedStatusCode, nil
-			} else if cache.IsEmpty() {
-				log.Debug().Msg("[cache] is empty")
 			}
+		} else {
+			return nil, nil, http.StatusNotFound, ErrorNotFound
 		}
 	}
 	log.Trace().Msg("file not in cache")
@@ -166,41 +181,40 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 						ETag:      resp.Header.Get(ETagHeader),
 					}
 					log.Trace().Msgf("file response has %d bytes", len(fileResponse.Body))
-					if err := client.responseCache.Set(cacheKey, fileResponse, fileCacheTimeout); err != nil {
+					jsonToCache, err := json.Marshal(fileResponse)
+					if err != nil {
+						log.Error().Err(err).Msgf("[cache] marshaling json metadata for %q has returned an error", cacheKey)
+					}
+					if err := client.responseCache.Set(cacheKey+"|Metadata", jsonToCache, fileCacheTimeout); err != nil {
+						log.Error().Err(err).Msg("[cache] error on cache write")
+					}
+					if err := client.responseCache.Set(cacheKey+"|Body", fileResponse.Body, fileCacheTimeout); err != nil {
 						log.Error().Err(err).Msg("[cache] error on cache write")
 					}
 
 					log.Debug().Msgf("follow symlink from %q to %q", resource, linkDest)
-					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
+					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
 				}
 			}
 
 			// now we are sure it's content so set the MIME type
-			mimeType, rawType := client.getMimeTypeByExtension(resource)
-			if decompress {
-				resp.Response.Header.Set(ContentTypeHeader, mimeType)
-			} else {
-				resp.Response.Header.Set(ContentTypeHeader, rawType)
-			}
-
-			if !shouldRespBeSavedToCache(resp.Response) {
-				return reader, resp.Response.Header, resp.StatusCode, err
-			}
+			mimeType := client.getMimeTypeByExtension(resource)
+			resp.Response.Header.Set(ContentTypeHeader, mimeType)
 
 			// now we write to cache and respond at the same time
 			fileResp := FileResponse{
 				Exists:   true,
 				ETag:     resp.Header.Get(ETagHeader),
 				MimeType: mimeType,
-				RawMime:  rawType,
 			}
 			return fileResp.CreateCacheReader(reader, client.responseCache, cacheKey), resp.Response.Header, resp.StatusCode, nil
 
 		case http.StatusNotFound:
-			if err := client.responseCache.Set(cacheKey, FileResponse{
-				Exists: false,
-				ETag:   resp.Header.Get(ETagHeader),
-			}, fileCacheTimeout); err != nil {
+			jsonToCache, err := json.Marshal(FileResponse{ETag: resp.Header.Get(ETagHeader)})
+			if err != nil {
+				log.Error().Err(err).Msgf("[cache] marshaling json metadata for %q has returned an error", cacheKey)
+			}
+			if err := client.responseCache.Set(cacheKey+"|Metadata", jsonToCache, fileCacheTimeout); err != nil {
 				log.Error().Err(err).Msg("[cache] error on cache write")
 			}
 
@@ -215,21 +229,36 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchName string) (*BranchTimestamp, error) {
 	cacheKey := fmt.Sprintf("%s/%s/%s/%s", branchTimestampCacheKeyPrefix, repoOwner, repoName, branchName)
 
-	if stamp, ok := client.responseCache.Get(cacheKey); ok && stamp != nil {
-		branchTimeStamp := stamp.(*BranchTimestamp)
-		if branchTimeStamp.notFound {
-			log.Trace().Msgf("[cache] use branch %q not found", branchName)
+	if stampRaw, ok := client.responseCache.Get(cacheKey); ok {
+		var stamp BranchTimestamp
+		err := json.Unmarshal(stampRaw.([]byte), &stamp)
+		if err != nil {
+			log.Error().Err(err).Bytes("stamp", stampRaw.([]byte)).Msgf("[cache] failed to unmarshal timestamp for: %s", cacheKey)
 			return &BranchTimestamp{}, ErrorNotFound
 		}
-		log.Trace().Msgf("[cache] use branch %q exist", branchName)
-		return branchTimeStamp, nil
+
+		if stamp.NotFound {
+			log.Trace().Msgf("[cache] branch %q does not exist", branchName)
+
+			return &BranchTimestamp{}, ErrorNotFound
+		} else {
+			log.Trace().Msgf("[cache] use branch %q exist", branchName)
+			// This comes from the refactoring of the caching library.
+			// The branch as reported by the API was stored in the cache, and I'm not sure if there are
+			// situations where it differs from the name in the request, hence this is left here.
+			return &stamp, nil
+		}
 	}
 
 	branch, resp, err := client.sdkClient.GetRepoBranch(repoOwner, repoName, branchName)
 	if err != nil {
 		if resp != nil && resp.StatusCode == http.StatusNotFound {
 			log.Trace().Msgf("[cache] set cache branch %q not found", branchName)
-			if err := client.responseCache.Set(cacheKey, &BranchTimestamp{Branch: branchName, notFound: true}, branchExistenceCacheTimeout); err != nil {
+			jsonToCache, err := json.Marshal(BranchTimestamp{NotFound: true})
+			if err != nil {
+				log.Error().Err(err).Msgf("[cache] marshaling empty timestamp for '%s' has returned an error", cacheKey)
+			}
+			if err := client.responseCache.Set(cacheKey, jsonToCache, branchExistenceCacheTimeout); err != nil {
 				log.Error().Err(err).Msg("[cache] error on cache write")
 			}
 			return &BranchTimestamp{}, ErrorNotFound
@@ -246,7 +275,11 @@ func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchNam
 	}
 
 	log.Trace().Msgf("set cache branch [%s] exist", branchName)
-	if err := client.responseCache.Set(cacheKey, stamp, branchExistenceCacheTimeout); err != nil {
+	jsonToCache, err := json.Marshal(stamp)
+	if err != nil {
+		log.Error().Err(err).Msgf("[cache] marshaling timestamp for %q has returned an error", cacheKey)
+	}
+	if err := client.responseCache.Set(cacheKey, jsonToCache, branchExistenceCacheTimeout); err != nil {
 		log.Error().Err(err).Msg("[cache] error on cache write")
 	}
 	return stamp, nil
@@ -255,8 +288,8 @@ func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchNam
 func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (string, error) {
 	cacheKey := fmt.Sprintf("%s/%s/%s", defaultBranchCacheKeyPrefix, repoOwner, repoName)
 
-	if branch, ok := client.responseCache.Get(cacheKey); ok && branch != nil {
-		return branch.(string), nil
+	if branch, ok := client.responseCache.Get(cacheKey); ok {
+		return string(branch.([]byte)), nil
 	}
 
 	repo, resp, err := client.sdkClient.GetRepo(repoOwner, repoName)
@@ -268,7 +301,7 @@ func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (str
 	}
 
 	branch := repo.DefaultBranch
-	if err := client.responseCache.Set(cacheKey, branch, defaultBranchCacheTimeout); err != nil {
+	if err := client.responseCache.Set(cacheKey, []byte(branch), defaultBranchCacheTimeout); err != nil {
 		log.Error().Err(err).Msg("[cache] error on cache write")
 	}
 	return branch, nil
@@ -277,13 +310,14 @@ func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (str
 func (client *Client) GiteaCheckIfOwnerExists(owner string) (bool, error) {
 	cacheKey := fmt.Sprintf("%s/%s", ownerExistenceKeyPrefix, owner)
 
-	if exist, ok := client.responseCache.Get(cacheKey); ok && exist != nil {
-		return exist.(bool), nil
+	if existRaw, ok := client.responseCache.Get(cacheKey); ok && existRaw != nil {
+		exist, err := strconv.ParseBool(existRaw.(string))
+		return exist, err
 	}
 
 	_, resp, err := client.sdkClient.GetUserInfo(owner)
 	if resp.StatusCode == http.StatusOK && err == nil {
-		if err := client.responseCache.Set(cacheKey, true, ownerExistenceCacheTimeout); err != nil {
+		if err := client.responseCache.Set(cacheKey, []byte("true"), ownerExistenceCacheTimeout); err != nil {
 			log.Error().Err(err).Msg("[cache] error on cache write")
 		}
 		return true, nil
@@ -293,59 +327,26 @@ func (client *Client) GiteaCheckIfOwnerExists(owner string) (bool, error) {
 
 	_, resp, err = client.sdkClient.GetOrg(owner)
 	if resp.StatusCode == http.StatusOK && err == nil {
-		if err := client.responseCache.Set(cacheKey, true, ownerExistenceCacheTimeout); err != nil {
+		if err := client.responseCache.Set(cacheKey, []byte("true"), ownerExistenceCacheTimeout); err != nil {
 			log.Error().Err(err).Msg("[cache] error on cache write")
 		}
 		return true, nil
 	} else if resp.StatusCode != http.StatusNotFound {
 		return false, err
 	}
-	if err := client.responseCache.Set(cacheKey, false, ownerExistenceCacheTimeout); err != nil {
+	if err := client.responseCache.Set(cacheKey, []byte("false"), ownerExistenceCacheTimeout); err != nil {
 		log.Error().Err(err).Msg("[cache] error on cache write")
 	}
 	return false, nil
 }
 
-func (client *Client) extToMime(ext string) string {
-	mimeType := mime.TypeByExtension(ext)
+func (client *Client) getMimeTypeByExtension(resource string) string {
+	mimeType := mime.TypeByExtension(path.Ext(resource))
 	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
 	if client.forbiddenMimeTypes[mimeTypeSplit[0]] || mimeType == "" {
 		mimeType = client.defaultMimeType
 	}
+	log.Trace().Msgf("probe mime of %q is %q", resource, mimeType)
+
 	return mimeType
 }
-
-func (client *Client) getMimeTypeByExtension(resource string) (mimeType, rawType string) {
-	rawExt := path.Ext(resource)
-	innerExt := rawExt
-	switch rawExt {
-	case ".gz", ".br", ".zst":
-		innerExt = path.Ext(resource[:len(resource)-len(rawExt)])
-	}
-	rawType = client.extToMime(rawExt)
-	mimeType = rawType
-	if innerExt != rawExt {
-		mimeType = client.extToMime(innerExt)
-	}
-	log.Trace().Msgf("probe mime of %q is (%q / raw %q)", resource, mimeType, rawType)
-	return mimeType, rawType
-}
-
-func shouldRespBeSavedToCache(resp *http.Response) bool {
-	if resp == nil {
-		return false
-	}
-
-	contentLengthRaw := resp.Header.Get(ContentLengthHeader)
-	if contentLengthRaw == "" {
-		return false
-	}
-
-	contentLength, err := strconv.ParseInt(contentLengthRaw, 10, 64)
-	if err != nil {
-		log.Error().Err(err).Msg("could not parse content length")
-	}
-
-	// if content to big or could not be determined we not cache it
-	return contentLength > 0 && contentLength < fileCacheSizeLimit
-}
diff --git a/server/upstream/header.go b/server/upstream/header.go
index 3a218a1..7b85df1 100644
--- a/server/upstream/header.go
+++ b/server/upstream/header.go
@@ -24,8 +24,5 @@ func (o *Options) setHeader(ctx *context.Context, header http.Header) {
 	} else {
 		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, mime)
 	}
-	if encoding := header.Get(gitea.ContentEncodingHeader); encoding != "" && encoding != "identity" {
-		ctx.RespWriter.Header().Set(gitea.ContentEncodingHeader, encoding)
-	}
 	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(http.TimeFormat))
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 98137ba..2f1751b 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -182,7 +182,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 
 			// add extension for encoding
 			path := o.TargetPath + allowedEncodings[encoding]
-			reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path, true)
+			reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path)
 			if statusCode == 404 {
 				continue
 			}

From 044c684a47853af53c660e454328348a49277c9c Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Mon, 25 Nov 2024 12:21:55 +0000
Subject: [PATCH 162/196] Fix compression (#405)

closes #404

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/405
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 flake.lock                  |  4 ++--
 flake.nix                   |  2 +-
 server/gitea/cache.go       | 15 ++++++++----
 server/gitea/client.go      | 47 +++++++++++++++++++++++++++----------
 server/upstream/header.go   |  3 +++
 server/upstream/upstream.go |  2 +-
 6 files changed, 53 insertions(+), 20 deletions(-)

diff --git a/flake.lock b/flake.lock
index 861cc42..5eb7802 100644
--- a/flake.lock
+++ b/flake.lock
@@ -20,8 +20,8 @@
     "nixpkgs": {
       "locked": {
         "lastModified": 0,
-        "narHash": "sha256-x07g4NcqGP6mQn6AISXJaks9sQYDjZmTMBlKIvajvyc=",
-        "path": "/nix/store/2w8kz6zh3aq80f1dypiin222fry1rv51-source",
+        "narHash": "sha256-29QfSvJwpjNwppFnU33nnyedAWpaaDBSlDJZzJhg97s=",
+        "path": "/nix/store/1703v0vkmk136sca5rf1861jrn2ndajr-source",
         "type": "path"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index 61f3b55..36a545e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -13,7 +13,7 @@
     in {
       devShells.default = pkgs.mkShell {
         buildInputs = with pkgs; [
-          gcc
+          glibc.static
           go
           gofumpt
           golangci-lint
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index c560a19..276358c 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -38,15 +38,16 @@ type FileResponse struct {
 	Exists    bool   `json:"exists"`
 	IsSymlink bool   `json:"isSymlink"`
 	ETag      string `json:"eTag"`
-	MimeType  string `json:"mimeType"`
-	Body      []byte `json:"-"` // saved separately
+	MimeType  string `json:"mimeType"` // uncompressed MIME type
+	RawMime   string `json:"rawMime"`  // raw MIME type (if compressed, type of compression)
+	Body      []byte `json:"-"`        // saved separately
 }
 
 func (f FileResponse) IsEmpty() bool {
 	return len(f.Body) == 0
 }
 
-func (f FileResponse) createHttpResponse(cacheKey string) (header http.Header, statusCode int) {
+func (f FileResponse) createHttpResponse(cacheKey string, decompress bool) (header http.Header, statusCode int) {
 	header = make(http.Header)
 
 	if f.Exists {
@@ -59,7 +60,13 @@ func (f FileResponse) createHttpResponse(cacheKey string) (header http.Header, s
 		header.Set(giteaObjectTypeHeader, objTypeSymlink)
 	}
 	header.Set(ETagHeader, f.ETag)
-	header.Set(ContentTypeHeader, f.MimeType)
+
+	if decompress {
+		header.Set(ContentTypeHeader, f.MimeType)
+	} else {
+		header.Set(ContentTypeHeader, f.RawMime)
+	}
+
 	header.Set(ContentLengthHeader, fmt.Sprintf("%d", len(f.Body)))
 	header.Set(PagesCacheIndicatorHeader, "true")
 
diff --git a/server/gitea/client.go b/server/gitea/client.go
index ea57c14..5a12111 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -40,9 +40,10 @@ const (
 	objTypeSymlink        = "symlink"
 
 	// std
-	ETagHeader          = "ETag"
-	ContentTypeHeader   = "Content-Type"
-	ContentLengthHeader = "Content-Length"
+	ETagHeader            = "ETag"
+	ContentTypeHeader     = "Content-Type"
+	ContentLengthHeader   = "Content-Length"
+	ContentEncodingHeader = "Content-Encoding"
 )
 
 type Client struct {
@@ -104,7 +105,7 @@ func (client *Client) ContentWebLink(targetOwner, targetRepo, branch, resource s
 }
 
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
-	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
+	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource, false)
 	if err != nil {
 		return nil, err
 	}
@@ -112,7 +113,7 @@ func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource str
 	return io.ReadAll(reader)
 }
 
-func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (io.ReadCloser, http.Header, int, error) {
+func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string, decompress bool) (io.ReadCloser, http.Header, int, error) {
 	cacheKey := fmt.Sprintf("%s/%s/%s|%s|%s", rawContentCacheKeyPrefix, targetOwner, targetRepo, ref, resource)
 	log := log.With().Str("cache_key", cacheKey).Logger()
 	log.Trace().Msg("try file in cache")
@@ -136,12 +137,12 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 		}
 		cache.Body = body.([]byte)
 
-		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey)
+		cachedHeader, cachedStatusCode := cache.createHttpResponse(cacheKey, decompress)
 		if cache.Exists {
 			if cache.IsSymlink {
 				linkDest := string(cache.Body)
 				log.Debug().Msgf("[cache] follow symlink from %q to %q", resource, linkDest)
-				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
 			} else {
 				log.Debug().Msgf("[cache] return %d bytes", len(cache.Body))
 				return io.NopCloser(bytes.NewReader(cache.Body)), cachedHeader, cachedStatusCode, nil
@@ -193,19 +194,25 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 					}
 
 					log.Debug().Msgf("follow symlink from %q to %q", resource, linkDest)
-					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
+					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
 				}
 			}
 
 			// now we are sure it's content so set the MIME type
-			mimeType := client.getMimeTypeByExtension(resource)
+			mimeType, rawType := client.getMimeTypeByExtension(resource)
 			resp.Response.Header.Set(ContentTypeHeader, mimeType)
+			if decompress {
+				resp.Response.Header.Set(ContentTypeHeader, mimeType)
+			} else {
+				resp.Response.Header.Set(ContentTypeHeader, rawType)
+			}
 
 			// now we write to cache and respond at the same time
 			fileResp := FileResponse{
 				Exists:   true,
 				ETag:     resp.Header.Get(ETagHeader),
 				MimeType: mimeType,
+				RawMime:  rawType,
 			}
 			return fileResp.CreateCacheReader(reader, client.responseCache, cacheKey), resp.Response.Header, resp.StatusCode, nil
 
@@ -340,13 +347,29 @@ func (client *Client) GiteaCheckIfOwnerExists(owner string) (bool, error) {
 	return false, nil
 }
 
-func (client *Client) getMimeTypeByExtension(resource string) string {
-	mimeType := mime.TypeByExtension(path.Ext(resource))
+func (client *Client) extToMime(ext string) string {
+	mimeType := mime.TypeByExtension(path.Ext(ext))
 	mimeTypeSplit := strings.SplitN(mimeType, ";", 2)
 	if client.forbiddenMimeTypes[mimeTypeSplit[0]] || mimeType == "" {
 		mimeType = client.defaultMimeType
 	}
-	log.Trace().Msgf("probe mime of %q is %q", resource, mimeType)
+	log.Trace().Msgf("probe mime of extension '%q' is '%q'", ext, mimeType)
 
 	return mimeType
 }
+
+func (client *Client) getMimeTypeByExtension(resource string) (mimeType, rawType string) {
+	rawExt := path.Ext(resource)
+	innerExt := rawExt
+	switch rawExt {
+	case ".gz", ".br", ".zst":
+		innerExt = path.Ext(resource[:len(resource)-len(rawExt)])
+	}
+	rawType = client.extToMime(rawExt)
+	mimeType = rawType
+	if innerExt != rawExt {
+		mimeType = client.extToMime(innerExt)
+	}
+	log.Trace().Msgf("probe mime of %q is (%q / raw %q)", resource, mimeType, rawType)
+	return mimeType, rawType
+}
diff --git a/server/upstream/header.go b/server/upstream/header.go
index 7b85df1..3a218a1 100644
--- a/server/upstream/header.go
+++ b/server/upstream/header.go
@@ -24,5 +24,8 @@ func (o *Options) setHeader(ctx *context.Context, header http.Header) {
 	} else {
 		ctx.RespWriter.Header().Set(gitea.ContentTypeHeader, mime)
 	}
+	if encoding := header.Get(gitea.ContentEncodingHeader); encoding != "" && encoding != "identity" {
+		ctx.RespWriter.Header().Set(gitea.ContentEncodingHeader, encoding)
+	}
 	ctx.RespWriter.Header().Set(headerLastModified, o.BranchTimestamp.In(time.UTC).Format(http.TimeFormat))
 }
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 2f1751b..98137ba 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -182,7 +182,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 
 			// add extension for encoding
 			path := o.TargetPath + allowedEncodings[encoding]
-			reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path)
+			reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path, true)
 			if statusCode == 404 {
 				continue
 			}

From c9f1624afd6b15c64745b778bdd7c7584af4f74b Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 25 Nov 2024 12:26:32 +0000
Subject: [PATCH 163/196] fix(deps): update golang.org/x/exp digest to 2d47ceb
 (#401)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/exp | require | digest | `f66d83c` -> `2d47ceb` |

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/401
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 go.mod |  4 ++--
 go.sum | 22 +++++++++-------------
 2 files changed, 11 insertions(+), 15 deletions(-)

diff --git a/go.mod b/go.mod
index 5e42e1b..4f1666f 100644
--- a/go.mod
+++ b/go.mod
@@ -16,11 +16,12 @@ require (
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/microcosm-cc/bluemonday v1.0.26
 	github.com/pelletier/go-toml/v2 v2.1.0
+	github.com/pires/go-proxyproto v0.8.0
 	github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
 	github.com/rs/zerolog v1.27.0
 	github.com/stretchr/testify v1.8.4
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
+	golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
 	xorm.io/xorm v1.3.2
 )
 
@@ -104,7 +105,6 @@ require (
 	github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
 	github.com/ovh/go-ovh v1.1.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
-	github.com/pires/go-proxyproto v0.8.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pquerna/otp v1.3.0 // indirect
diff --git a/go.sum b/go.sum
index 6903528..7c4b251 100644
--- a/go.sum
+++ b/go.sum
@@ -776,7 +776,6 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
 golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
@@ -790,8 +789,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
-golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
+golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
+golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -814,8 +813,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
-golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
+golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -854,8 +853,6 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
 golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -874,8 +871,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
+golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -936,7 +933,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
 golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@@ -945,9 +941,9 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1013,8 +1009,8 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
-golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
+golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From e079ce4cf40c0609feec61ae6b4dbef2bc279c97 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Mon, 25 Nov 2024 12:26:54 +0000
Subject: [PATCH 164/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.62.0 (#402)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [golangci/golangci-lint](https://github.com/golangci/golangci-lint) | minor | `v1.61.0` -> `v1.62.0` |

---

### Release Notes

<details>
<summary>golangci/golangci-lint (golangci/golangci-lint)</summary>

### [`v1.62.0`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1620)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.61.0...v1.62.0)

1.  New linters
    -   Add `recvcheck` linter https://github.com/raeperd/recvcheck
    -   Add `iface` linter https://github.com/uudashr/iface
2.  Updated linters
    -   ⚠️ `execinquery`: deprecation step 2
    -   ⚠️ `gomnd`: deprecation step 2 (replaced by `mnd`)
    -   `bidichk`: from 0.2.7 to 0.3.2 (important performance improvement)
    -   `canonicalheader`: from 1.1.1 to 1.1.2
    -   `cyclop`: from 1.2.1 to 1.2.3
    -   `dupword`: from 0.1.1 to 0.1.3
    -   `errcheck`: from 1.7.0 to 1.8.0
    -   `errchkjson`: from 0.3.6 to 0.4.0
    -   `errname`: from 0.1.13 to 1.0.0
    -   `ginkgolinter`: from 0.17.0 to 0.18.0 (new option: `force-succeed`)
    -   `go-check-sumtype`: from 0.1.4 to 0.2.0 (new option: `default-signifies-exhaustive`)
    -   `go-critic`: from 0.11.4 to 0.11.5
    -   `go-printf-func-name`: from [`7558a9e`](https://github.com/golangci/golangci-lint/commit/7558a9eaa5af) to v0.1.0
    -   `godot`: from 1.4.17 to 1.4.18
    -   `gosec`: from 2.21.2 to 2.21.4
    -   `intrange`: from 0.2.0 to 0.2.1
    -   `loggercheck`: from 0.9.4 to 0.10.1 (`log/slog` support)
    -   `musttag`: from 0.12.2 to 0.13.0
    -   `nakedret`: from 2.0.4 to 2.0.5
    -   `nilnil`: from 0.1.9 to 1.0.0 (new option: `detect-opposite`)
    -   `noctx`: from 0.0.2 to 0.1.0
    -   `protogetter`: from 0.3.6 to 0.3.8
    -   `revive`: from 1.3.9 to 1.5.0 (new rules: `filename-format`, and `file-length-limit`)
    -   `tenv`: from 1.10.0 to 1.12.1 (handle dot import)
    -   `testifylint`: from 1.4.3 to 1.5.0 (new checkers: `contains`, `encoded-compare`, `regexp`)
3.  Misc.
    -   Type sizing when cross-compiling (32-bit).
    -   code-climate: add check_name field
    -   Improve Go version detection
    -   Fix Go version propagation
4.  Documentation
    -   Adds a section about `exclude-dirs-use-default`
    -   Improve 'install from sources' section
    -   Improve FAQ about Go versions
    -   Improve linter/rule/check docs
    -   Improve new linter section
    -   Improve `forbidigo` pattern examples for built-in functions

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - "before 4am" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/402
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 3b7ca40..d8ef836 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.61.0
+    image: golangci/golangci-lint:v1.62.0
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From 2438de0eb2d843a99d7e2a0fb6a71db8c9c70e2e Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sun, 29 Dec 2024 15:43:30 +0000
Subject: [PATCH 165/196] fix panic when upstream fails (#412)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/412
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 flake.lock                  | 4 ++--
 server/upstream/upstream.go | 5 ++++-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/flake.lock b/flake.lock
index 5eb7802..1062fa4 100644
--- a/flake.lock
+++ b/flake.lock
@@ -20,8 +20,8 @@
     "nixpkgs": {
       "locked": {
         "lastModified": 0,
-        "narHash": "sha256-29QfSvJwpjNwppFnU33nnyedAWpaaDBSlDJZzJhg97s=",
-        "path": "/nix/store/1703v0vkmk136sca5rf1861jrn2ndajr-source",
+        "narHash": "sha256-UA56kDQSztRWbpsG9bK+jIGyZ0YZAcIsYAhXDHIr8zs=",
+        "path": "/nix/store/np3i3qi6hq6d1p5xr36cjmr0xwd2j289-source",
         "type": "path"
       },
       "original": {
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 98137ba..2abd0b9 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -183,9 +183,12 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 			// add extension for encoding
 			path := o.TargetPath + allowedEncodings[encoding]
 			reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path, true)
-			if statusCode == 404 {
+			if statusCode == http.StatusNotFound {
 				continue
 			}
+			if err != nil {
+				break
+			}
 			log.Debug().Msgf("using %s encoding", encoding)
 			if encoding != "identity" {
 				header.Set(headerContentEncoding, encoding)

From ef7e2cd7bbdaaad90347791050d9c287629caa0a Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sun, 29 Dec 2024 18:49:25 +0000
Subject: [PATCH 166/196] Add reqId to all logging messages I could find (#413)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/413
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .gitignore                              |  1 +
 flake.lock                              |  4 ++--
 go.mod                                  |  1 +
 go.sum                                  |  2 ++
 server/certificates/certificates.go     | 28 +++++++++++++++----------
 server/context/context.go               | 10 +++++++++
 server/gitea/cache.go                   | 19 ++++++++++-------
 server/gitea/client.go                  | 15 ++++++-------
 server/handler/handler.go               |  4 ++--
 server/handler/handler_custom_domain.go |  6 +++---
 server/handler/handler_raw_domain.go    |  4 ++--
 server/handler/handler_sub_domain.go    | 10 ++++-----
 server/handler/try.go                   | 12 ++++++++---
 server/startup.go                       |  9 ++++----
 server/upstream/domains.go              |  5 +++--
 server/upstream/redirects.go            |  4 ++--
 server/upstream/upstream.go             |  8 +++----
 17 files changed, 88 insertions(+), 54 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3035107..66e7084 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ vendor/
 pages
 certs.sqlite
 .bash_history
+pkg/
diff --git a/flake.lock b/flake.lock
index 1062fa4..1fdb364 100644
--- a/flake.lock
+++ b/flake.lock
@@ -20,8 +20,8 @@
     "nixpkgs": {
       "locked": {
         "lastModified": 0,
-        "narHash": "sha256-UA56kDQSztRWbpsG9bK+jIGyZ0YZAcIsYAhXDHIr8zs=",
-        "path": "/nix/store/np3i3qi6hq6d1p5xr36cjmr0xwd2j289-source",
+        "narHash": "sha256-zUfNgGopg1+/xYRnFaI6PAhlF2JofdEG789CsKclYSw=",
+        "path": "/nix/store/0ijldwygklvrzinxx7scsxsvqhx0hqgz-source",
         "type": "path"
       },
       "original": {
diff --git a/go.mod b/go.mod
index 4f1666f..1bc1de6 100644
--- a/go.mod
+++ b/go.mod
@@ -72,6 +72,7 @@ require (
 	github.com/gorilla/css v1.0.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
 	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
+	github.com/hashicorp/go-uuid v1.0.3 // indirect
 	github.com/hashicorp/go-version v1.6.0 // indirect
 	github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
 	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
diff --git a/go.sum b/go.sum
index 7c4b251..6aef8a6 100644
--- a/go.sum
+++ b/go.sum
@@ -317,6 +317,8 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go
index 062f721..aeb619f 100644
--- a/server/certificates/certificates.go
+++ b/server/certificates/certificates.go
@@ -16,9 +16,11 @@ import (
 	"github.com/go-acme/lego/v4/lego"
 	"github.com/hashicorp/golang-lru/v2/expirable"
 	"github.com/reugn/equalizer"
+	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	psContext "codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/database"
 	dnsutils "codeberg.org/codeberg/pages/server/dns"
 	"codeberg.org/codeberg/pages/server/gitea"
@@ -43,7 +45,11 @@ func TLSConfig(mainDomainSuffix string,
 	return &tls.Config{
 		// check DNS name & get certificate from Let's Encrypt
 		GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
+			ctx := psContext.New(nil, nil)
+			log := log.With().Str("ReqId", ctx.ReqId).Logger()
+
 			domain := strings.ToLower(strings.TrimSpace(info.ServerName))
+			log.Debug().Str("domain", domain).Msg("start: get tls certificate")
 			if len(domain) < 1 {
 				return nil, errors.New("missing domain info via SNI (RFC 4366, Section 3.1)")
 			}
@@ -100,7 +106,7 @@ func TLSConfig(mainDomainSuffix string,
 						TargetRepo:   targetRepo,
 						TargetBranch: targetBranch,
 					}
-					_, valid := targetOpt.CheckCanonicalDomain(giteaClient, domain, mainDomainSuffix, canonicalDomainCache)
+					_, valid := targetOpt.CheckCanonicalDomain(ctx, giteaClient, domain, mainDomainSuffix, canonicalDomainCache)
 					if !valid {
 						// We shouldn't obtain a certificate when we cannot check if the
 						// repository has specified this domain in the `.domains` file.
@@ -116,7 +122,7 @@ func TLSConfig(mainDomainSuffix string,
 
 			var tlsCertificate *tls.Certificate
 			var err error
-			if tlsCertificate, err = acmeClient.retrieveCertFromDB(domain, mainDomainSuffix, false, certDB); err != nil {
+			if tlsCertificate, err = acmeClient.retrieveCertFromDB(log, domain, mainDomainSuffix, false, certDB); err != nil {
 				if !errors.Is(err, database.ErrNotFound) {
 					return nil, err
 				}
@@ -130,7 +136,7 @@ func TLSConfig(mainDomainSuffix string,
 					return nil, fmt.Errorf("won't request certificate for %q", domain)
 				}
 
-				tlsCertificate, err = acmeClient.obtainCert(acmeClient.legoClient, []string{domain}, nil, targetOwner, false, mainDomainSuffix, certDB)
+				tlsCertificate, err = acmeClient.obtainCert(log, acmeClient.legoClient, []string{domain}, nil, targetOwner, false, mainDomainSuffix, certDB)
 				if err != nil {
 					return nil, err
 				}
@@ -173,7 +179,7 @@ func (c *AcmeClient) checkUserLimit(user string) error {
 	return nil
 }
 
-func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProvider bool, certDB database.CertDB) (*tls.Certificate, error) {
+func (c *AcmeClient) retrieveCertFromDB(log zerolog.Logger, sni, mainDomainSuffix string, useDnsProvider bool, certDB database.CertDB) (*tls.Certificate, error) {
 	// parse certificate from database
 	res, err := certDB.Get(sni)
 	if err != nil {
@@ -206,7 +212,7 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
 			// TODO: make a queue ?
 			go (func() {
 				res.CSR = nil // acme client doesn't like CSR to be set
-				if _, err := c.obtainCert(c.legoClient, []string{sni}, res, "", useDnsProvider, mainDomainSuffix, certDB); err != nil {
+				if _, err := c.obtainCert(log, c.legoClient, []string{sni}, res, "", useDnsProvider, mainDomainSuffix, certDB); err != nil {
 					log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
 				}
 			})()
@@ -216,7 +222,7 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
 	return &tlsCertificate, nil
 }
 
-func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user string, useDnsProvider bool, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
+func (c *AcmeClient) obtainCert(log zerolog.Logger, acmeClient *lego.Client, domains []string, renew *certificate.Resource, user string, useDnsProvider bool, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
 	name := strings.TrimPrefix(domains[0], "*")
 
 	// lock to avoid simultaneous requests
@@ -226,7 +232,7 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew
 			time.Sleep(100 * time.Millisecond)
 			_, working = c.obtainLocks.Load(name)
 		}
-		cert, err := c.retrieveCertFromDB(name, mainDomainSuffix, useDnsProvider, keyDatabase)
+		cert, err := c.retrieveCertFromDB(log, name, mainDomainSuffix, useDnsProvider, keyDatabase)
 		if err != nil {
 			return nil, fmt.Errorf("certificate failed in synchronous request: %w", err)
 		}
@@ -320,7 +326,7 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew
 	return &tlsCertificate, nil
 }
 
-func SetupMainDomainCertificates(mainDomainSuffix string, acmeClient *AcmeClient, certDB database.CertDB) error {
+func SetupMainDomainCertificates(log zerolog.Logger, mainDomainSuffix string, acmeClient *AcmeClient, certDB database.CertDB) error {
 	// getting main cert before ACME account so that we can fail here without hitting rate limits
 	mainCertBytes, err := certDB.Get(mainDomainSuffix)
 	if err != nil && !errors.Is(err, database.ErrNotFound) {
@@ -328,7 +334,7 @@ func SetupMainDomainCertificates(mainDomainSuffix string, acmeClient *AcmeClient
 	}
 
 	if mainCertBytes == nil {
-		_, err = acmeClient.obtainCert(acmeClient.dnsChallengerLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, nil, "", true, mainDomainSuffix, certDB)
+		_, err = acmeClient.obtainCert(log, acmeClient.dnsChallengerLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, nil, "", true, mainDomainSuffix, certDB)
 		if err != nil {
 			log.Error().Err(err).Msg("Couldn't renew main domain certificate, continuing with mock certs only")
 		}
@@ -337,7 +343,7 @@ func SetupMainDomainCertificates(mainDomainSuffix string, acmeClient *AcmeClient
 	return nil
 }
 
-func MaintainCertDB(ctx context.Context, interval time.Duration, acmeClient *AcmeClient, mainDomainSuffix string, certDB database.CertDB) {
+func MaintainCertDB(log zerolog.Logger, ctx context.Context, interval time.Duration, acmeClient *AcmeClient, mainDomainSuffix string, certDB database.CertDB) {
 	for {
 		// delete expired certs that will be invalid until next clean up
 		threshold := time.Now().Add(interval)
@@ -375,7 +381,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, acmeClient *Acm
 			} else if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
 				// renew main certificate 30 days before it expires
 				go (func() {
-					_, err = acmeClient.obtainCert(acmeClient.dnsChallengerLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", true, mainDomainSuffix, certDB)
+					_, err = acmeClient.obtainCert(log, acmeClient.dnsChallengerLegoClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", true, mainDomainSuffix, certDB)
 					if err != nil {
 						log.Error().Err(err).Msg("Couldn't renew certificate for main domain")
 					}
diff --git a/server/context/context.go b/server/context/context.go
index 6650164..e695ab7 100644
--- a/server/context/context.go
+++ b/server/context/context.go
@@ -5,19 +5,29 @@ import (
 	"net/http"
 
 	"codeberg.org/codeberg/pages/server/utils"
+	"github.com/hashicorp/go-uuid"
+	"github.com/rs/zerolog/log"
 )
 
 type Context struct {
 	RespWriter http.ResponseWriter
 	Req        *http.Request
 	StatusCode int
+	ReqId      string
 }
 
 func New(w http.ResponseWriter, r *http.Request) *Context {
+	req_uuid, err := uuid.GenerateUUID()
+	if err != nil {
+		log.Error().Err(err).Msg("Failed to generate request id, assigning error value")
+		req_uuid = "ERROR"
+	}
+
 	return &Context{
 		RespWriter: w,
 		Req:        r,
 		StatusCode: http.StatusOK,
+		ReqId:      req_uuid,
 	}
 }
 
diff --git a/server/gitea/cache.go b/server/gitea/cache.go
index 276358c..03f40a9 100644
--- a/server/gitea/cache.go
+++ b/server/gitea/cache.go
@@ -8,9 +8,11 @@ import (
 	"net/http"
 	"time"
 
+	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
 )
 
 const (
@@ -89,16 +91,17 @@ type writeCacheReader struct {
 	hasError       bool
 	doNotCache     bool
 	complete       bool
+	log            zerolog.Logger
 }
 
 func (t *writeCacheReader) Read(p []byte) (n int, err error) {
-	log.Trace().Msgf("[cache] read %q", t.cacheKey)
+	t.log.Trace().Msgf("[cache] read %q", t.cacheKey)
 	n, err = t.originalReader.Read(p)
 	if err == io.EOF {
 		t.complete = true
 	}
 	if err != nil && err != io.EOF {
-		log.Trace().Err(err).Msgf("[cache] original reader for %q has returned an error", t.cacheKey)
+		t.log.Trace().Err(err).Msgf("[cache] original reader for %q has returned an error", t.cacheKey)
 		t.hasError = true
 	} else if n > 0 {
 		if t.buffer.Len()+n > int(fileCacheSizeLimit) {
@@ -118,22 +121,23 @@ func (t *writeCacheReader) Close() error {
 	if doWrite {
 		jsonToCache, err := json.Marshal(fc)
 		if err != nil {
-			log.Trace().Err(err).Msgf("[cache] marshaling json for %q has returned an error", t.cacheKey+"|Metadata")
+			t.log.Trace().Err(err).Msgf("[cache] marshaling json for %q has returned an error", t.cacheKey+"|Metadata")
 		}
 		err = t.cache.Set(t.cacheKey+"|Metadata", jsonToCache, fileCacheTimeout)
 		if err != nil {
-			log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey+"|Metadata")
+			t.log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey+"|Metadata")
 		}
 		err = t.cache.Set(t.cacheKey+"|Body", fc.Body, fileCacheTimeout)
 		if err != nil {
-			log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey+"|Body")
+			t.log.Trace().Err(err).Msgf("[cache] writer for %q has returned an error", t.cacheKey+"|Body")
 		}
 	}
-	log.Trace().Msgf("cacheReader for %q saved=%t closed", t.cacheKey, doWrite)
+	t.log.Trace().Msgf("cacheReader for %q saved=%t closed", t.cacheKey, doWrite)
 	return t.originalReader.Close()
 }
 
-func (f FileResponse) CreateCacheReader(r io.ReadCloser, cache cache.ICache, cacheKey string) io.ReadCloser {
+func (f FileResponse) CreateCacheReader(ctx *context.Context, r io.ReadCloser, cache cache.ICache, cacheKey string) io.ReadCloser {
+	log := log.With().Str("ReqId", ctx.ReqId).Logger()
 	if r == nil || cache == nil || cacheKey == "" {
 		log.Error().Msg("could not create CacheReader")
 		return nil
@@ -145,5 +149,6 @@ func (f FileResponse) CreateCacheReader(r io.ReadCloser, cache cache.ICache, cac
 		fileResponse:   &f,
 		cache:          cache,
 		cacheKey:       cacheKey,
+		log:            log,
 	}
 }
diff --git a/server/gitea/client.go b/server/gitea/client.go
index 5a12111..0cf6e6c 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -19,6 +19,7 @@ import (
 
 	"codeberg.org/codeberg/pages/config"
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/version"
 )
 
@@ -104,8 +105,8 @@ func (client *Client) ContentWebLink(targetOwner, targetRepo, branch, resource s
 	return path.Join(client.giteaRoot, targetOwner, targetRepo, "src/branch", branch, resource)
 }
 
-func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
-	reader, _, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource, false)
+func (client *Client) GiteaRawContent(ctx *context.Context, targetOwner, targetRepo, ref, resource string) ([]byte, error) {
+	reader, _, _, err := client.ServeRawContent(ctx, targetOwner, targetRepo, ref, resource, false)
 	if err != nil {
 		return nil, err
 	}
@@ -113,9 +114,9 @@ func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource str
 	return io.ReadAll(reader)
 }
 
-func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string, decompress bool) (io.ReadCloser, http.Header, int, error) {
+func (client *Client) ServeRawContent(ctx *context.Context, targetOwner, targetRepo, ref, resource string, decompress bool) (io.ReadCloser, http.Header, int, error) {
 	cacheKey := fmt.Sprintf("%s/%s/%s|%s|%s", rawContentCacheKeyPrefix, targetOwner, targetRepo, ref, resource)
-	log := log.With().Str("cache_key", cacheKey).Logger()
+	log := log.With().Str("ReqId", ctx.ReqId).Str("cache_key", cacheKey).Logger()
 	log.Trace().Msg("try file in cache")
 	// handle if cache entry exist
 	if cacheMetadata, ok := client.responseCache.Get(cacheKey + "|Metadata"); ok {
@@ -142,7 +143,7 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 			if cache.IsSymlink {
 				linkDest := string(cache.Body)
 				log.Debug().Msgf("[cache] follow symlink from %q to %q", resource, linkDest)
-				return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
+				return client.ServeRawContent(ctx, targetOwner, targetRepo, ref, linkDest, decompress)
 			} else {
 				log.Debug().Msgf("[cache] return %d bytes", len(cache.Body))
 				return io.NopCloser(bytes.NewReader(cache.Body)), cachedHeader, cachedStatusCode, nil
@@ -194,7 +195,7 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 					}
 
 					log.Debug().Msgf("follow symlink from %q to %q", resource, linkDest)
-					return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest, decompress)
+					return client.ServeRawContent(ctx, targetOwner, targetRepo, ref, linkDest, decompress)
 				}
 			}
 
@@ -214,7 +215,7 @@ func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource str
 				MimeType: mimeType,
 				RawMime:  rawType,
 			}
-			return fileResp.CreateCacheReader(reader, client.responseCache, cacheKey), resp.Response.Header, resp.StatusCode, nil
+			return fileResp.CreateCacheReader(ctx, reader, client.responseCache, cacheKey), resp.Response.Header, resp.StatusCode, nil
 
 		case http.StatusNotFound:
 			jsonToCache, err := json.Marshal(FileResponse{ETag: resp.Header.Get(ETagHeader)})
diff --git a/server/handler/handler.go b/server/handler/handler.go
index c038c2d..437697a 100644
--- a/server/handler/handler.go
+++ b/server/handler/handler.go
@@ -26,9 +26,9 @@ func Handler(
 	canonicalDomainCache, redirectsCache cache.ICache,
 ) http.HandlerFunc {
 	return func(w http.ResponseWriter, req *http.Request) {
-		log.Debug().Msg("\n----------------------------------------------------------")
-		log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
 		ctx := context.New(w, req)
+		log := log.With().Str("ReqId", ctx.ReqId).Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
+		log.Debug().Msg("\n----------------------------------------------------------")
 
 		ctx.RespWriter.Header().Set("Server", "pages-server")
 
diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go
index 852001a..8a5f9d7 100644
--- a/server/handler/handler_custom_domain.go
+++ b/server/handler/handler_custom_domain.go
@@ -47,7 +47,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 		TargetBranch:  targetBranch,
 		TargetPath:    path.Join(pathParts...),
 	}, canonicalLink); works {
-		canonicalDomain, valid := targetOpt.CheckCanonicalDomain(giteaClient, trimmedHost, mainDomainSuffix, canonicalDomainCache)
+		canonicalDomain, valid := targetOpt.CheckCanonicalDomain(ctx, giteaClient, trimmedHost, mainDomainSuffix, canonicalDomainCache)
 		if !valid {
 			html.ReturnErrorPage(ctx, "domain not specified in <code>.domains</code> file", http.StatusMisdirectedRequest)
 			return
@@ -63,8 +63,8 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
 			return
 		}
 
-		log.Debug().Msg("tryBranch, now trying upstream 7")
-		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+		log.Debug().Str("url", trimmedHost).Msg("tryBranch, now trying upstream")
+		tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		return
 	}
 
diff --git a/server/handler/handler_raw_domain.go b/server/handler/handler_raw_domain.go
index f48e8e4..bbbf7da 100644
--- a/server/handler/handler_raw_domain.go
+++ b/server/handler/handler_raw_domain.go
@@ -45,7 +45,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 			TargetPath:   path.Join(pathElements[3:]...),
 		}, true); works {
 			log.Trace().Msg("tryUpstream: serve raw domain with specified branch")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+			tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 			return
 		}
 		log.Debug().Msg("missing branch info")
@@ -62,7 +62,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
 		TargetPath:    path.Join(pathElements[2:]...),
 	}, true); works {
 		log.Trace().Msg("tryUpstream: serve raw domain with default branch")
-		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+		tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 	} else {
 		html.ReturnErrorPage(ctx,
 			fmt.Sprintf("raw domain could not find repo <code>%s/%s</code> or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo),
diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go
index 806fe7f..e335019 100644
--- a/server/handler/handler_sub_domain.go
+++ b/server/handler/handler_sub_domain.go
@@ -53,7 +53,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			TargetPath:    path.Join(pathElements[2:]...),
 		}, true); works {
 			log.Trace().Msg("tryUpstream: serve with specified repo and branch")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+			tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		} else {
 			html.ReturnErrorPage(
 				ctx,
@@ -85,7 +85,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			TargetPath:    path.Join(pathElements[1:]...),
 		}, true); works {
 			log.Trace().Msg("tryUpstream: serve default pages repo with specified branch")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+			tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		} else {
 			html.ReturnErrorPage(
 				ctx,
@@ -110,7 +110,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 				TargetPath:    path.Join(pathElements[1:]...),
 			}, false); works {
 				log.Debug().Msg("tryBranch, now trying upstream 5")
-				tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+				tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 				return
 			}
 		}
@@ -126,7 +126,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 			TargetPath:    path.Join(pathElements...),
 		}, false); works {
 			log.Debug().Msg("tryBranch, now trying upstream 6")
-			tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+			tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 			return
 		}
 	}
@@ -141,7 +141,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
 		TargetPath:    path.Join(pathElements...),
 	}, false); works {
 		log.Debug().Msg("tryBranch, now trying upstream 6")
-		tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
+		tryUpstream(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
 		return
 	}
 
diff --git a/server/handler/try.go b/server/handler/try.go
index e76891d..e5fc49b 100644
--- a/server/handler/try.go
+++ b/server/handler/try.go
@@ -15,7 +15,7 @@ import (
 )
 
 // tryUpstream forwards the target request to the Gitea API, and shows an error page on failure.
-func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
+func tryUpstream(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
 	mainDomainSuffix, trimmedHost string,
 	options *upstream.Options,
 	canonicalDomainCache cache.ICache,
@@ -23,7 +23,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 ) {
 	// check if a canonical domain exists on a request on MainDomain
 	if strings.HasSuffix(trimmedHost, mainDomainSuffix) && !options.ServeRaw {
-		canonicalDomain, _ := options.CheckCanonicalDomain(giteaClient, "", mainDomainSuffix, canonicalDomainCache)
+		canonicalDomain, _ := options.CheckCanonicalDomain(ctx, giteaClient, "", mainDomainSuffix, canonicalDomainCache)
 		if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix) {
 			canonicalPath := ctx.Req.RequestURI
 			if options.TargetRepo != defaultPagesRepo {
@@ -32,7 +32,12 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 					canonicalPath = "/" + path[2]
 				}
 			}
-			ctx.Redirect("https://"+canonicalDomain+canonicalPath, http.StatusTemporaryRedirect)
+
+			redirect_to := "https://" + canonicalDomain + canonicalPath
+
+			log.Debug().Str("to", redirect_to).Msg("redirecting")
+
+			ctx.Redirect(redirect_to, http.StatusTemporaryRedirect)
 			return
 		}
 	}
@@ -41,6 +46,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
 	options.Host = trimmedHost
 
 	// Try to request the file from the Gitea API
+	log.Debug().Msg("requesting from upstream")
 	if !options.Upstream(ctx, giteaClient, redirectsCache) {
 		html.ReturnErrorPage(ctx, fmt.Sprintf("Forge returned %d %s", ctx.StatusCode, http.StatusText(ctx.StatusCode)), ctx.StatusCode)
 	}
diff --git a/server/startup.go b/server/startup.go
index 14c37aa..4ae26c1 100644
--- a/server/startup.go
+++ b/server/startup.go
@@ -27,7 +27,7 @@ import (
 // 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)
+	log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Caller().Logger().Level(zerolog.TraceLevel)
 
 	cfg, err := config.ReadConfig(ctx)
 	if err != nil {
@@ -41,7 +41,8 @@ func Serve(ctx *cli.Context) error {
 	if err != nil {
 		return err
 	}
-	log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel)
+	fmt.Printf("Setting log level to: %s\n", logLevel)
+	log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Caller().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)
@@ -85,7 +86,7 @@ func Serve(ctx *cli.Context) error {
 		return err
 	}
 
-	if err := certificates.SetupMainDomainCertificates(cfg.Server.MainDomain, acmeClient, certDB); err != nil {
+	if err := certificates.SetupMainDomainCertificates(log.Logger, cfg.Server.MainDomain, acmeClient, certDB); err != nil {
 		return err
 	}
 
@@ -114,7 +115,7 @@ func Serve(ctx *cli.Context) error {
 	interval := 12 * time.Hour
 	certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background())
 	defer cancelCertMaintain()
-	go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, cfg.Server.MainDomain, certDB)
+	go certificates.MaintainCertDB(log.Logger, certMaintainCtx, interval, acmeClient, cfg.Server.MainDomain, certDB)
 
 	if cfg.Server.HttpServerEnabled {
 		// Create handler for http->https redirect and http acme challenges
diff --git a/server/upstream/domains.go b/server/upstream/domains.go
index d53d586..f68a02b 100644
--- a/server/upstream/domains.go
+++ b/server/upstream/domains.go
@@ -8,6 +8,7 @@ import (
 	"github.com/rs/zerolog/log"
 
 	"codeberg.org/codeberg/pages/server/cache"
+	"codeberg.org/codeberg/pages/server/context"
 	"codeberg.org/codeberg/pages/server/gitea"
 )
 
@@ -17,7 +18,7 @@ var canonicalDomainCacheTimeout = 15 * time.Minute
 const canonicalDomainConfig = ".domains"
 
 // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file).
-func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.ICache) (domain string, valid bool) {
+func (o *Options) CheckCanonicalDomain(ctx *context.Context, giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainCache cache.ICache) (domain string, valid bool) {
 	// Check if this request is cached.
 	if cachedValue, ok := canonicalDomainCache.Get(o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch); ok {
 		domains := cachedValue.([]string)
@@ -30,7 +31,7 @@ func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain,
 		return domains[0], valid
 	}
 
-	body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, canonicalDomainConfig)
+	body, err := giteaClient.GiteaRawContent(ctx, o.TargetOwner, o.TargetRepo, o.TargetBranch, canonicalDomainConfig)
 	if err != nil && !errors.Is(err, gitea.ErrorNotFound) {
 		log.Error().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo)
 	}
diff --git a/server/upstream/redirects.go b/server/upstream/redirects.go
index ddde375..3ed5010 100644
--- a/server/upstream/redirects.go
+++ b/server/upstream/redirects.go
@@ -44,7 +44,7 @@ var redirectsCacheTimeout = 10 * time.Minute
 const redirectsConfig = "_redirects"
 
 // getRedirects returns redirects specified in the _redirects file.
-func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.ICache) []Redirect {
+func (o *Options) getRedirects(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.ICache) []Redirect {
 	var redirects []Redirect
 	cacheKey := o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch
 
@@ -53,7 +53,7 @@ func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.I
 		redirects = cachedValue.([]Redirect)
 	} else {
 		// Get _redirects file and parse
-		body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, redirectsConfig)
+		body, err := giteaClient.GiteaRawContent(ctx, o.TargetOwner, o.TargetRepo, o.TargetBranch, redirectsConfig)
 		if err == nil {
 			for _, line := range strings.Split(string(body), "\n") {
 				redirectArr := strings.Fields(line)
diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 2abd0b9..9aac271 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -125,7 +125,7 @@ func AcceptEncodings(header string) []string {
 
 // Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
 func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.ICache) bool {
-	log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
+	log := log.With().Str("ReqId", ctx.ReqId).Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
 
 	log.Debug().Msg("Start")
 
@@ -182,7 +182,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 
 			// add extension for encoding
 			path := o.TargetPath + allowedEncodings[encoding]
-			reader, header, statusCode, err = giteaClient.ServeRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, path, true)
+			reader, header, statusCode, err = giteaClient.ServeRawContent(ctx, o.TargetOwner, o.TargetRepo, o.TargetBranch, path, true)
 			if statusCode == http.StatusNotFound {
 				continue
 			}
@@ -206,7 +206,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 	if err != nil && errors.Is(err, gitea.ErrorNotFound) {
 		log.Debug().Msg("Handling not found error")
 		// Get and match redirects
-		redirects := o.getRedirects(giteaClient, redirectsCache)
+		redirects := o.getRedirects(ctx, giteaClient, redirectsCache)
 		if o.matchRedirects(ctx, giteaClient, redirects, redirectsCache) {
 			log.Trace().Msg("redirect")
 			return true
@@ -234,7 +234,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redi
 			}
 		}
 
-		log.Trace().Msg("not found")
+		log.Debug().Msg("not found")
 
 		ctx.StatusCode = http.StatusNotFound
 		if o.TryIndexPages {

From 6376bfd2e0e0b64744731aaa8519c1c19fd04853 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sun, 29 Dec 2024 19:01:08 +0000
Subject: [PATCH 167/196] Fix stack overflow on root redirects (#414)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/414
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 server/upstream/redirects.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/server/upstream/redirects.go b/server/upstream/redirects.go
index 3ed5010..b0762d5 100644
--- a/server/upstream/redirects.go
+++ b/server/upstream/redirects.go
@@ -92,8 +92,9 @@ func (o *Options) matchRedirects(ctx *context.Context, giteaClient *gitea.Client
 
 	for _, redirect := range redirects {
 		if dstURL, ok := redirect.rewriteURL(reqURL); ok {
-			// do rewrite if status code is 200
-			if redirect.StatusCode == 200 {
+			if o.TargetPath == dstURL { // recursion base case, rewrite directly when paths are the same
+				return true
+			} else if redirect.StatusCode == 200 { // do rewrite if status code is 200
 				o.TargetPath = dstURL
 				o.Upstream(ctx, giteaClient, redirectsCache)
 			} else {

From 229851b75e888ce504c703e306e3af7fd2c43b1d Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 29 Dec 2024 20:10:27 +0000
Subject: [PATCH 168/196] chore(deps): update woodpeckerci/plugin-release
 docker tag to v0.2.2 (#409)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [woodpeckerci/plugin-release](https://codeberg.org/woodpecker-plugins/release) ([source](https://codeberg.org/woodpecker-plugins/release.git)) | patch | `0.2.1` -> `0.2.2` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/release (woodpeckerci/plugin-release)</summary>

### [`v0.2.2`](https://codeberg.org/woodpecker-plugins/release/blob/HEAD/CHANGELOG.md#022---2024-11-27)

[Compare Source](https://codeberg.org/woodpecker-plugins/release/compare/v0.2.1...v0.2.2)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;6543](https://github.com/6543), [@&#8203;woodpecker-bot](https://github.com/woodpecker-bot)

##### 📦️ Dependency

-   fix(deps): update module github.com/google/go-github/v65 to v66 \[[#&#8203;20](https://codeberg.org/woodpecker-plugins/release/pulls/20)]
-   chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v5 \[[#&#8203;19](https://codeberg.org/woodpecker-plugins/release/pulls/19)]
-   fix(deps): update module github.com/google/go-github/v64 to v65 \[[#&#8203;16](https://codeberg.org/woodpecker-plugins/release/pulls/16)]
-   fix(deps): update module github.com/google/go-github/v63 to v64 \[[#&#8203;15](https://codeberg.org/woodpecker-plugins/release/pulls/15)]
-   chore(deps): update dependency go to v1.23.0 \[[#&#8203;14](https://codeberg.org/woodpecker-plugins/release/pulls/14)]

##### Misc

-   Use ready-release-go plugin \[[#&#8203;17](https://codeberg.org/woodpecker-plugins/release/pulls/17)]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM5LjgyLjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/409
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 10489fb..07d25b5 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -62,7 +62,7 @@ steps:
 
   release:
     depends_on: build
-    image: woodpeckerci/plugin-release:0.2.1
+    image: woodpeckerci/plugin-release:0.2.2
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite

From 6fd9474075697e640da3c04fdd001cc50f01c308 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 29 Dec 2024 20:10:49 +0000
Subject: [PATCH 169/196] chore(deps): update pipelinecomponents/yamllint
 docker tag to v0.33.0 (#410)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pipelinecomponents/yamllint](https://gitlab.com/pipeline-components/yamllint) | minor | `0.32.1` -> `0.33.0` |

---

### Release Notes

<details>
<summary>pipeline-components/yamllint (pipelinecomponents/yamllint)</summary>

### [`v0.33.0`](https://gitlab.com/pipeline-components/yamllint/compare/v0.32.1...v0.33.0)

[Compare Source](https://gitlab.com/pipeline-components/yamllint/compare/v0.32.1...v0.33.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM5LjgyLjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/410
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index d8ef836..7b6c479 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -18,7 +18,7 @@ steps:
     image: mstruebing/editorconfig-checker:v3.0.3
 
   yamllint:
-    image: pipelinecomponents/yamllint:0.32.1
+    image: pipelinecomponents/yamllint:0.33.0
     depends_on: []
     commands:
       - yamllint .

From 905e76deedae3414858a286e83337cb611ad6c77 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 29 Dec 2024 20:16:51 +0000
Subject: [PATCH 170/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.62.2 (#408)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [golangci/golangci-lint](https://github.com/golangci/golangci-lint) | patch | `v1.62.0` -> `v1.62.2` |

---

### Release Notes

<details>
<summary>golangci/golangci-lint (golangci/golangci-lint)</summary>

### [`v1.62.2`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1622)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.62.0...v1.62.2)

1.  Updated linters
    -   `fatcontext`: from 0.5.2 to 0.5.3
    -   `ginkgolinter`: from 0.18.0 to 0.18.3
    -   `go-errorlint`: from 1.6.0 to 1.7.0
    -   `iface`: from 1.2.0 to 1.2.1
    -   `revive`: from 1.5.0 to 1.5.1
    -   `testifylint`: from 1.5.0 to 1.5.2
2.  Misc.
    -   fix: ignore cache error when file not found

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM5LjgyLjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/408
Reviewed-by: crapStone <codeberg@crapstone.dev>
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 7b6c479..8b18851 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.62.0
+    image: golangci/golangci-lint:v1.62.2
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From e8f9ec9ce28ee8c5acc3e181d85ab5566941934b Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sun, 29 Dec 2024 20:54:45 +0000
Subject: [PATCH 171/196] Update dependencies (#406)

closes #299 #395

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/406
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .woodpecker/build.yml |    8 +-
 go.mod                |  289 +++--
 go.sum                | 2333 +++++++++++++++++++++++++++++++----------
 3 files changed, 1984 insertions(+), 646 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 07d25b5..60f3311 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -11,7 +11,7 @@ steps:
 
   build:
     depends_on: vendor
-    image: codeberg.org/6543/docker-images/golang_just:go-1.22
+    image: codeberg.org/6543/docker-images/golang_just:go-1.23
     commands:
       - go version
       - just build
@@ -32,7 +32,7 @@ steps:
 
   build-tag:
     depends_on: vendor
-    image: codeberg.org/6543/docker-images/golang_just:go-1.22
+    image: codeberg.org/6543/docker-images/golang_just:go-1.23
     commands:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
@@ -41,7 +41,7 @@ steps:
 
   test:
     depends_on: build
-    image: codeberg.org/6543/docker-images/golang_just:go-1.22
+    image: codeberg.org/6543/docker-images/golang_just:go-1.23
     commands:
       - just test
     when:
@@ -49,7 +49,7 @@ steps:
 
   integration-tests:
     depends_on: build
-    image: codeberg.org/6543/docker-images/golang_just:go-1.22
+    image: codeberg.org/6543/docker-images/golang_just:go-1.23
     commands:
       - just integration
     environment:
diff --git a/go.mod b/go.mod
index 1bc1de6..13e40df 100644
--- a/go.mod
+++ b/go.mod
@@ -1,145 +1,238 @@
 module codeberg.org/codeberg/pages
 
-go 1.22.0
+go 1.23.0
 
-toolchain go1.23.1
+toolchain go1.23.3
 
 require (
-	code.gitea.io/sdk/gitea v0.17.1
+	code.gitea.io/sdk/gitea v0.19.0
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
-	github.com/creasty/defaults v1.7.0
-	github.com/go-acme/lego/v4 v4.5.3
-	github.com/go-sql-driver/mysql v1.6.0
+	github.com/creasty/defaults v1.8.0
+	github.com/go-acme/lego/v4 v4.21.0
+	github.com/go-sql-driver/mysql v1.8.1
+	github.com/hashicorp/go-uuid v1.0.3
 	github.com/hashicorp/golang-lru/v2 v2.0.7
-	github.com/joho/godotenv v1.4.0
-	github.com/lib/pq v1.10.7
-	github.com/mattn/go-sqlite3 v1.14.16
-	github.com/microcosm-cc/bluemonday v1.0.26
-	github.com/pelletier/go-toml/v2 v2.1.0
+	github.com/joho/godotenv v1.5.1
+	github.com/lib/pq v1.10.9
+	github.com/mattn/go-sqlite3 v1.14.24
+	github.com/microcosm-cc/bluemonday v1.0.27
+	github.com/pelletier/go-toml/v2 v2.2.3
 	github.com/pires/go-proxyproto v0.8.0
 	github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
-	github.com/rs/zerolog v1.27.0
-	github.com/stretchr/testify v1.8.4
-	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
-	xorm.io/xorm v1.3.2
+	github.com/rs/zerolog v1.33.0
+	github.com/stretchr/testify v1.10.0
+	github.com/urfave/cli/v2 v2.27.5
+	golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
+	xorm.io/xorm v1.3.9
 )
 
 require (
-	cloud.google.com/go v0.54.0 // indirect
-	github.com/Azure/azure-sdk-for-go v32.4.0+incompatible // indirect
+	cloud.google.com/go/auth v0.13.0 // indirect
+	cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
+	cloud.google.com/go/compute/metadata v0.6.0 // indirect
+	filippo.io/edwards25519 v1.1.0 // indirect
+	github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
+	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
-	github.com/Azure/go-autorest/autorest v0.11.19 // indirect
-	github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
-	github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
-	github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
+	github.com/Azure/go-autorest/autorest v0.11.29 // indirect
+	github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect
+	github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
+	github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
 	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
 	github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
-	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
 	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
+	github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
 	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
-	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
-	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 // indirect
-	github.com/aws/aws-sdk-go v1.39.0 // indirect
+	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
+	github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect
+	github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect
+	github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
+	github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 // indirect
+	github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
+	github.com/aws/smithy-go v1.22.1 // indirect
 	github.com/aymerick/douceur v0.2.0 // indirect
-	github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
-	github.com/cenkalti/backoff/v4 v4.1.1 // indirect
-	github.com/cloudflare/cloudflare-go v0.20.0 // indirect
+	github.com/benbjohnson/clock v1.3.5 // indirect
+	github.com/boombuler/barcode v1.0.2 // indirect
+	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+	github.com/civo/civogo v0.3.89 // indirect
+	github.com/cloudflare/cloudflare-go v0.112.0 // indirect
 	github.com/cpu/goacmedns v0.1.1 // indirect
-	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
-	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/davidmz/go-pageant v1.0.2 // indirect
-	github.com/deepmap/oapi-codegen v1.6.1 // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
-	github.com/dnsimple/dnsimple-go v0.70.1 // indirect
-	github.com/exoscale/egoscale v0.67.0 // indirect
+	github.com/dnsimple/dnsimple-go v1.7.0 // indirect
+	github.com/exoscale/egoscale/v3 v3.1.7 // indirect
 	github.com/fatih/structs v1.1.0 // indirect
-	github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
-	github.com/go-errors/errors v1.0.1 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/fsnotify/fsnotify v1.8.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.7 // indirect
+	github.com/ghodss/yaml v1.0.0 // indirect
+	github.com/go-errors/errors v1.5.1 // indirect
 	github.com/go-fed/httpsig v1.1.0 // indirect
-	github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
-	github.com/goccy/go-json v0.8.1 // indirect
-	github.com/gofrs/uuid v4.0.0+incompatible // indirect
-	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/go-jose/go-jose/v4 v4.0.4 // indirect
+	github.com/go-logr/logr v1.4.2 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.23.0 // indirect
+	github.com/go-resty/resty/v2 v2.16.2 // indirect
+	github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
+	github.com/goccy/go-json v0.10.4 // indirect
+	github.com/gofrs/flock v0.12.1 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
+	github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
-	github.com/google/uuid v1.3.0 // indirect
-	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
-	github.com/gophercloud/gophercloud v0.16.0 // indirect
-	github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
-	github.com/gorilla/css v1.0.0 // indirect
-	github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
-	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
-	github.com/hashicorp/go-uuid v1.0.3 // indirect
-	github.com/hashicorp/go-version v1.6.0 // indirect
+	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/google/s2a-go v0.1.8 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
+	github.com/googleapis/gax-go/v2 v2.14.1 // indirect
+	github.com/gophercloud/gophercloud v1.14.1 // indirect
+	github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
+	github.com/gorilla/css v1.0.1 // indirect
+	github.com/hashicorp/errwrap v1.1.0 // indirect
+	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+	github.com/hashicorp/go-multierror v1.1.1 // indirect
+	github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
+	github.com/hashicorp/go-version v1.7.0 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129 // indirect
 	github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
 	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
-	github.com/jarcoal/httpmock v1.0.6 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
-	github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
-	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
+	github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
+	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
 	github.com/labbsr0x/goh v1.0.1 // indirect
-	github.com/linode/linodego v0.31.1 // indirect
-	github.com/liquidweb/go-lwApi v0.0.5 // indirect
-	github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
-	github.com/liquidweb/liquidweb-go v1.6.3 // indirect
-	github.com/mattn/go-colorable v0.1.12 // indirect
-	github.com/mattn/go-isatty v0.0.14 // indirect
-	github.com/miekg/dns v1.1.43 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/linode/linodego v1.44.0 // indirect
+	github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
+	github.com/liquidweb/liquidweb-go v1.6.4 // indirect
+	github.com/magiconair/properties v1.8.9 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/miekg/dns v1.1.62 // indirect
+	github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
-	github.com/mitchellh/mapstructure v1.4.1 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
-	github.com/nrdcg/auroradns v1.0.1 // indirect
-	github.com/nrdcg/desec v0.6.0 // indirect
+	github.com/nrdcg/auroradns v1.1.0 // indirect
+	github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
+	github.com/nrdcg/desec v0.10.0 // indirect
 	github.com/nrdcg/dnspod-go v0.4.0 // indirect
-	github.com/nrdcg/freemyip v0.2.0 // indirect
-	github.com/nrdcg/goinwx v0.8.1 // indirect
+	github.com/nrdcg/freemyip v0.3.0 // indirect
+	github.com/nrdcg/goinwx v0.10.0 // indirect
+	github.com/nrdcg/mailinabox v0.2.0 // indirect
 	github.com/nrdcg/namesilo v0.2.1 // indirect
-	github.com/nrdcg/porkbun v0.1.1 // indirect
-	github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
-	github.com/ovh/go-ovh v1.1.0 // indirect
+	github.com/nrdcg/nodion v0.1.0 // indirect
+	github.com/nrdcg/porkbun v0.4.0 // indirect
+	github.com/nzdjb/go-metaname v1.0.0 // indirect
+	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
+	github.com/oracle/oci-go-sdk/v65 v65.81.1 // indirect
+	github.com/ovh/go-ovh v1.6.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+	github.com/peterhellberg/link v1.2.0 // indirect
+	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
 	github.com/pkg/errors v0.9.1 // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/pquerna/otp v1.3.0 // indirect
-	github.com/russross/blackfriday/v2 v2.0.1 // indirect
-	github.com/sacloud/libsacloud v1.36.2 // indirect
-	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f // indirect
-	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
-	github.com/sirupsen/logrus v1.4.2 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/pquerna/otp v1.4.0 // indirect
+	github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/sacloud/api-client-go v0.2.10 // indirect
+	github.com/sacloud/go-http v0.1.9 // indirect
+	github.com/sacloud/iaas-api-go v1.14.0 // indirect
+	github.com/sacloud/packages-go v0.0.11 // indirect
+	github.com/sagikazarmark/locafero v0.6.0 // indirect
+	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
+	github.com/selectel/domains-go v1.1.0 // indirect
+	github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect
+	github.com/shopspring/decimal v1.4.0 // indirect
+	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
-	github.com/softlayer/softlayer-go v1.0.3 // indirect
+	github.com/softlayer/softlayer-go v1.1.7 // indirect
 	github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
-	github.com/spf13/cast v1.3.1 // indirect
-	github.com/stretchr/objx v0.5.0 // indirect
+	github.com/sony/gobreaker v1.0.0 // indirect
+	github.com/sourcegraph/conc v0.3.0 // indirect
+	github.com/spf13/afero v1.11.0 // indirect
+	github.com/spf13/cast v1.7.1 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/spf13/viper v1.19.0 // indirect
+	github.com/stretchr/objx v0.5.2 // indirect
+	github.com/subosito/gotenv v1.6.0 // indirect
 	github.com/syndtr/goleveldb v1.0.0 // indirect
-	github.com/transip/gotransip/v6 v6.6.1 // indirect
-	github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 // indirect
-	github.com/vultr/govultr/v2 v2.7.1 // indirect
-	go.opencensus.io v0.22.3 // indirect
-	go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect
-	golang.org/x/crypto v0.21.0 // indirect
-	golang.org/x/net v0.23.0 // indirect
-	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
-	golang.org/x/sys v0.18.0 // indirect
-	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
-	google.golang.org/api v0.20.0 // indirect
-	google.golang.org/appengine v1.6.5 // indirect
-	google.golang.org/genproto v0.0.0-20200305110556-506484158171 // indirect
-	google.golang.org/grpc v1.27.1 // indirect
-	google.golang.org/protobuf v1.26.0 // indirect
-	gopkg.in/ini.v1 v1.62.0 // indirect
-	gopkg.in/ns1/ns1-go.v2 v2.6.2 // indirect
-	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071 // indirect
+	github.com/tjfoc/gmsm v1.4.1 // indirect
+	github.com/transip/gotransip/v6 v6.26.0 // indirect
+	github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
+	github.com/vinyldns/go-vinyldns v0.9.16 // indirect
+	github.com/volcengine/volc-sdk-golang v1.0.190 // indirect
+	github.com/vultr/govultr/v3 v3.13.0 // indirect
+	github.com/x448/float16 v0.8.4 // indirect
+	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
+	github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
+	github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
+	go.mongodb.org/mongo-driver v1.17.1 // indirect
+	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
+	go.opentelemetry.io/otel v1.33.0 // indirect
+	go.opentelemetry.io/otel/metric v1.33.0 // indirect
+	go.opentelemetry.io/otel/trace v1.33.0 // indirect
+	go.uber.org/atomic v1.11.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/ratelimit v0.3.1 // indirect
+	golang.org/x/crypto v0.31.0 // indirect
+	golang.org/x/mod v0.22.0 // indirect
+	golang.org/x/net v0.33.0 // indirect
+	golang.org/x/oauth2 v0.24.0 // indirect
+	golang.org/x/sync v0.10.0 // indirect
+	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/time v0.8.0 // indirect
+	golang.org/x/tools v0.28.0 // indirect
+	google.golang.org/api v0.214.0 // indirect
+	google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
+	google.golang.org/grpc v1.69.2 // indirect
+	google.golang.org/protobuf v1.36.1 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	xorm.io/builder v0.3.12 // indirect
+	k8s.io/api v0.32.0 // indirect
+	k8s.io/apimachinery v0.32.0 // indirect
+	k8s.io/klog/v2 v2.130.1 // indirect
+	k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
+	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
+	sigs.k8s.io/yaml v1.4.0 // indirect
+	xorm.io/builder v0.3.13 // indirect
 )
diff --git a/go.sum b/go.sum
index 6aef8a6..f8ab2be 100644
--- a/go.sum
+++ b/go.sum
@@ -3,508 +3,1280 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
 cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
 cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
 cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
 cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=
 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
+cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
+cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
+cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
+cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
+cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
+cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
+cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
+cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
+cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
+cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=
+cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE=
+cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM=
+cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ=
+cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
+cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
+cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg=
+cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ=
+cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k=
+cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw=
+cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
+cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
+cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M=
+cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE=
+cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE=
+cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk=
+cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=
+cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8=
+cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc=
+cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04=
+cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8=
+cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY=
+cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM=
+cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc=
+cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU=
+cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI=
+cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8=
+cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno=
+cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak=
+cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84=
+cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A=
+cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E=
+cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
+cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
+cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY=
+cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k=
+cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
+cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
+cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0=
+cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=
+cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI=
+cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ=
+cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI=
+cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08=
+cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
+cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
+cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=
+cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ=
+cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY=
+cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo=
+cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg=
+cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw=
+cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
+cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
+cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=
+cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
+cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
+cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
+cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
+cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
+cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
+cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
+cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
+cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
+cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=
+cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM=
+cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU=
+cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc=
+cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI=
+cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss=
+cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE=
+cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE=
+cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g=
+cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4=
+cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8=
+cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM=
+cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
+cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw=
+cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=
+cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E=
+cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac=
+cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q=
+cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU=
+cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
+cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
+cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=
+cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y=
+cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss=
+cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc=
+cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
+cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
+cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0=
+cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk=
+cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q=
+cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg=
+cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590=
+cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8=
+cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk=
+cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk=
+cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE=
+cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU=
+cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U=
+cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=
+cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M=
+cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg=
+cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s=
+cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=
+cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=
+cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=
+cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
+cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
+cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4=
+cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI=
+cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y=
+cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
+cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
+cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
+cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=
+cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
+cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
+cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
+cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
+cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
+cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
+cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
+cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=
+cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
+cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=
+cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg=
+cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo=
+cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4=
+cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM=
+cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA=
+cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
+cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
+cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI=
+cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s=
+cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
+cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
+cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
+cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE=
+cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=
+cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M=
+cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0=
+cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8=
+cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
+cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
+cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE=
+cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
+cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
+cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0=
+cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA=
+cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE=
+cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38=
+cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w=
+cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8=
+cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
+cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
+cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM=
+cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA=
+cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A=
+cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ=
+cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs=
+cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s=
+cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI=
+cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4=
+cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
+cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
+cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM=
+cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c=
+cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
+cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
+cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g=
+cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4=
+cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs=
+cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww=
+cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c=
+cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s=
+cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI=
+cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ=
+cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
+cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
+cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=
+cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek=
+cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0=
+cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM=
+cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4=
+cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE=
+cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM=
+cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q=
+cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4=
+cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
+cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
+cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k=
+cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4=
+cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM=
+cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs=
+cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
+cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
+cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE=
+cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
+cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=
+cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc=
+cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY=
+cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
+cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI=
+cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8=
+cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M=
+cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc=
+cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw=
+cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw=
+cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY=
+cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w=
+cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI=
+cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs=
+cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
+cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
+cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
+cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY=
+cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08=
+cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw=
+cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA=
+cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c=
+cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
+cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
+cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w=
+cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM=
+cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0=
+cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60=
+cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo=
+cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg=
+cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
+cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
+cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw=
+cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
+cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
+cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E=
+cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw=
+cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA=
+cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI=
+cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y=
+cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
+cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM=
+cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o=
+cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo=
+cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
+cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
+cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=
+cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
+cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY=
+cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY=
+cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
+cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=
+cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=
+cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk=
+cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo=
+cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74=
+cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM=
+cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY=
+cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4=
+cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs=
+cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g=
+cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o=
+cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE=
+cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
+cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
+cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
+cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=
+cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=
+cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=
+cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=
+cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
+cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
+cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
+cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8=
+cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY=
+cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
+cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
+cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo=
+cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=
+cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=
+cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=
+cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
+cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
+cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=
+cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=
+cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=
+cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI=
+cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw=
+cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY=
+cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
+cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
+cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I=
+cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
+cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
+cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA=
+cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY=
+cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM=
+cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
+cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
+cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8=
+cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI=
+cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo=
+cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk=
+cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=
+cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=
+cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw=
+cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
+cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
+cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=
+cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8=
+cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E=
+cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM=
+cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8=
+cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4=
+cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY=
+cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
+cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
+cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k=
+cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU=
+cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
+cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
+cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA=
+cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0=
+cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE=
+cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ=
+cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4=
+cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs=
+cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI=
+cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA=
+cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk=
+cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ=
+cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE=
+cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc=
+cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc=
+cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
+cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
+cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo=
+cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw=
+cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw=
+cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
+cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
+cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70=
+cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo=
+cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs=
+cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
+cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
+cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk=
+cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg=
+cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE=
+cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw=
+cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc=
+cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
+cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
+cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg=
+cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI=
+cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=
+cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8=
+cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4=
+cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=
+cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k=
+cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM=
+cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
+cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
+cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
+cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=
+cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE=
+cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U=
+cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA=
+cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c=
+cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
+cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
+cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac=
+cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
+cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
+cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs=
+cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70=
+cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ=
+cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
+cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
+cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA=
+cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM=
+cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ=
+cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA=
+cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0=
+cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots=
+cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo=
+cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI=
+cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU=
+cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=
+cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA=
+cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
+cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
+cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc=
+cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y=
+cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14=
+cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do=
+cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo=
+cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM=
+cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg=
+cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
+cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
+cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk=
+cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44=
+cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc=
+cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc=
+cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
+cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4=
+cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4=
+cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU=
+cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
+cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
+cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
+cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q=
+cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=
+cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8=
+cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0=
+cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
+cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
+cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk=
+cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk=
+cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0=
+cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag=
+cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU=
+cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=
+cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA=
+cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc=
+cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk=
+cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
+cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
+cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4=
+cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U=
+cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY=
+cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s=
+cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco=
+cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo=
+cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc=
+cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4=
+cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E=
+cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU=
+cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec=
+cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA=
+cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4=
+cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw=
+cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A=
+cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
+cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
+cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
+cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
+cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
+cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
+cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco=
+cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0=
+cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
-code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
+cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
+cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
+cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
+cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
+cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
+cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
+cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
+cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
+cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw=
+cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
+cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
+cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM=
+cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA=
+cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c=
+cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8=
+cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4=
+cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc=
+cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ=
+cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg=
+cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM=
+cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28=
+cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=
+cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=
+cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk=
+cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=
+cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=
+cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0=
+cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=
+cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=
+cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk=
+cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=
+cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg=
+cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk=
+cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=
+cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=
+cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
+cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
+cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M=
+cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU=
+cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU=
+cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
+cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
+cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
+cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY=
+cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E=
+cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY=
+cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0=
+cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE=
+cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g=
+cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc=
+cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY=
+cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208=
+cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8=
+cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY=
+cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w=
+cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8=
+cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes=
+cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
+cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
+cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc=
+cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A=
+cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg=
+cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo=
+cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ=
+cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng=
+cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
+cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
+cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=
+cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
+cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
+code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y=
+code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
+git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
-gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
-github.com/Azure/azure-sdk-for-go v32.4.0+incompatible h1:1JP8SKfroEakYiQU2ZyPDosh8w2Tg9UopKt88VyQPt4=
-github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE=
+github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
+github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
+github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 h1:yzrctSl9GMIQ5lHu7jc8olOsGjWDCsBpJhWqfGa/YIM=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0/go.mod h1:GE4m0rnnfwLGX0Y9A9A25Zx5N/90jneT5ABevqzhuFQ=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 h1:zLzoX5+W2l95UJoVwiyNS4dX8vHyQ6x2xRLoBBL9wMk=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
-github.com/Azure/go-autorest/autorest v0.11.19 h1:7/IqD2fEYVha1EPeaiytVKhzmPV223pfkRIQUGOK2IE=
-github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
-github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
-github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=
-github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY=
-github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
+github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
+github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
+github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
+github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
+github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4=
+github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
 github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
 github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
 github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
-github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
-github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
 github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a h1:Cf4CrDeyrIcuIiJZEZJAH5dapqQ6J3OmP/vHPbDjaFA=
 github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a/go.mod h1:ig6eVXkYn/9dz0Vm8UdLf+E0u1bE6kBSn3n2hqk6jas=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
-github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 h1:bLzehmpyCwQiqCE1Qe9Ny6fbFqs7hPlmo9vKv2orUxs=
-github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8=
+github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
+github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 h1:dkj8/dxOQ4L1XpwCzRLqukvUBbxuNdz3FeyvHFnRjmo=
-github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
-github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 h1:mg/+23+/gAw6zdxv9I5dPCj666WJPLk8S1nXm0dOumQ=
+github.com/aliyun/alibaba-cloud-sdk-go v1.63.76/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
+github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
+github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
+github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
-github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
-github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo=
-github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
+github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
+github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
+github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
+github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
+github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=
+github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
+github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 h1:+lmJoqxuUoPlSfGk5JYQQivd9YFjUvRZR6RPY+Wcx48=
+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8/go.mod h1:Gg8/myP4+rgRi4+j9gQdbOEnMtwMAUUIeXo+nKCFVj8=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 h1:0jMtawybbfpFEIMy4wvfyW2Z4YLr7mnuzT0fhR67Nrc=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4/go.mod h1:xlMODgumb0Pp8bzfpojqelDrf8SL9rb5ovwmwKJl+oU=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc=
+github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
+github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
+github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
+github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
+github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
-github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
-github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
+github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY=
+github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
 github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/civo/civogo v0.3.89 h1:g+I4NGVa5t0L2Z9+QbnEAqxE/3OCDUYvepje3oUkKVo=
+github.com/civo/civogo v0.3.89/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
+github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/cloudflare-go v0.20.0 h1:y2a6KwYHTFxhw+8PLhz0q5hpTGj6Un3W1pbpQLhzFaE=
-github.com/cloudflare/cloudflare-go v0.20.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
-github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
+github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g=
+github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
 github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
-github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
-github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
-github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
+github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
 github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
-github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpAMecnw=
-github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
-github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
-github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
-github.com/dnsimple/dnsimple-go v0.70.1 h1:cSZndVjttLpgplDuesY4LFIvfKf/zRA1J7mCATBbzSM=
-github.com/dnsimple/dnsimple-go v0.70.1/go.mod h1:F9WHww9cC76hrnwGFfAfrqdW99j3MOYasQcIwTS/aUk=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
+github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
+github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/exoscale/egoscale v0.67.0 h1:qgWh7T5IZGrNWtg6ib4dr+76WThvB+odTtGG+DGbXF8=
-github.com/exoscale/egoscale v0.67.0/go.mod h1:wi0myUxPsV8SdEtdJHQJxFLL/wEw9fiw9Gs1PWRkvkM=
+github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
+github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
+github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
+github.com/exoscale/egoscale/v3 v3.1.7 h1:Q6p9tOVY0IiOW0fUpaPQWY7ggGEuSPZLAGxFgDd2sCE=
+github.com/exoscale/egoscale/v3 v3.1.7/go.mod h1:GHKucK/J26v8PGWztGdhxWNMjrjG9PbelxKCJ4YI11Q=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
+github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
+github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
+github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-acme/lego/v4 v4.5.3 h1:v5RSN8l+RAeNHKTSL80eqLiec6q6UNaFpl2Df5x/5tM=
-github.com/go-acme/lego/v4 v4.5.3/go.mod h1:mL1DY809LzjvRuaxINNxsI26f5oStVhBGTpJMiinkZM=
-github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
+github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
+github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
 github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
-github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
+github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
 github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
 github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
+github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
+github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
+github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
+github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
+github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
+github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
+github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
-github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
-github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
+github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
+github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
-github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
-github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
-github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
+github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
+github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
+github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
-github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
+github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
+github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
+github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
-github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
+github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
+github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
+github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
+github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
+github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
+github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
-github.com/gophercloud/gophercloud v0.16.0 h1:sWjPfypuzxRxjVbk3/MsU4H8jS0NNlyauZtIUl78BPU=
-github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
-github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0=
-github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
+github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
+github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
+github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
+github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
+github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
+github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
+github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
+github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
+github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw=
+github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
+github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE=
+github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
-github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
+github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
+github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
+github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
+github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
-github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
+github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
-github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
-github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
+github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
 github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
 github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
-github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
 github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129 h1:+4ocCqiQFWTODV2Q94q/DMGrmoXFIrimPn+SZZPCbBs=
+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
+github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
+github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
 github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
-github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
-github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
-github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
-github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
-github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
-github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
-github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
-github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
-github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
-github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
-github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
-github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
-github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
-github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
-github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
-github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
-github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
-github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
-github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
-github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
-github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
-github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
-github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
-github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
-github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE=
-github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
-github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
-github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
-github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
-github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
-github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
-github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
-github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
-github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
-github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g=
-github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
-github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc=
-github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
+github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
+github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk=
 github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
 github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk=
 github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
-github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
-github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
-github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
-github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/linode/linodego v0.31.1 h1:dBtjKo7J9UhNFhTOclEXb12RRyQDaRBxISdONVuU+DA=
-github.com/linode/linodego v0.31.1/go.mod h1:BR0gVkCJffEdIGJSl6bHR80Ty+Uvg/2jkjmrWaFectM=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/linode/linodego v1.44.0 h1:JZLLWzCAx3CmHSV9NmCoXisuqKtrmPhfY9MrgvaHMUY=
+github.com/linode/linodego v1.44.0/go.mod h1:umdoNOmtbqAdGQbmQnPFZ2YS4US+/mU/1bA7MjoKAvg=
 github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
-github.com/liquidweb/go-lwApi v0.0.5 h1:CT4cdXzJXmo0bon298kS7NeSk+Gt8/UHpWBBol1NGCA=
 github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
-github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM=
 github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
-github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4=
-github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
-github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
+github.com/liquidweb/liquidweb-cli v0.7.0 h1:7j1r1U0MZa1TXiWo3IMU5V1YQwnBHMVxU+xLsgDcu6Q=
+github.com/liquidweb/liquidweb-cli v0.7.0/go.mod h1:+uU7L6BhaQtgo4cYKhhsP5UNCq/imNvjBjlf76Vqpb0=
+github.com/liquidweb/liquidweb-go v1.6.4 h1:6S0m3hHSpiLqGD7AFSb7lH/W/qr1wx+tKil9fgIbjMc=
+github.com/liquidweb/liquidweb-go v1.6.4/go.mod h1:B934JPIIcdA+uTq2Nz5PgOtG6CuCaEvQKe/Ge/5GgZ4=
+github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
+github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
+github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
+github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
-github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
-github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
-github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
+github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
+github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
-github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
+github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
+github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
 github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
+github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
+github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
+github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34=
+github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M=
+github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
+github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
+github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
+github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -512,289 +1284,426 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
-github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
-github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
-github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
-github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
-github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
-github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
+github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY=
+github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g=
+github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
+github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
+github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
-github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y=
-github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
-github.com/nrdcg/desec v0.6.0 h1:kZ9JtsYEW3LNfuPIM+2tXoxoQlF9koWfQTWTQsA7Sr8=
-github.com/nrdcg/desec v0.6.0/go.mod h1:wybWg5cRrNmtXLYpUCPCLvz4jfFNEGZQEnoUiX9WqcY=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
+github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
+github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI=
+github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8=
+github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM=
+github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs=
 github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
 github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
-github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8=
-github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4=
-github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
-github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
+github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc=
+github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM=
+github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
+github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
+github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
+github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc=
 github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
 github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
-github.com/nrdcg/porkbun v0.1.1 h1:gxVzQYfFUGXhnBax/aVugoE3OIBAdHgrJgyMPyY5Sjo=
-github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk+CPoA=
+github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw=
+github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms=
+github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
+github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/nzdjb/go-metaname v1.0.0 h1:sNASlZC1RM3nSudtBTE1a3ZVTDyTpjqI5WXRPrdZ9Hg=
+github.com/nzdjb/go-metaname v1.0.0/go.mod h1:0GR0LshZax1Lz4VrOrfNSE4dGvTp7HGjiemdczXT2H4=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
-github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
+github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
-github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
-github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
-github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
-github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
-github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
-github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk=
-github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
-github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
+github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
+github.com/oracle/oci-go-sdk/v65 v65.81.1 h1:JYc47bk8n/MUchA2KHu1ggsCQzlJZQLJ+tTKfOho00E=
+github.com/oracle/oci-go-sdk/v65 v65.81.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
+github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
+github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
-github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
-github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM=
+github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
+github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
+github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
+github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
 github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
 github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
-github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
+github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE=
+github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad h1:WtSUHi5zthjudjIi3L6QmL/V9vpJPbc/j/F2u55d3fs=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad/go.mod h1:h0+DiDRe2Y+6iHTjIq/9HzUq7NII/Nffp0HkFrsAKq4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
-github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
-github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
-github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
-github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
+github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
+github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI=
-github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
-github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f h1:WSnaD0/cvbKJgSTYbjAPf4RJXVvNNDAwVm+W8wEmnGE=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRzhp3q66c=
+github.com/sacloud/api-client-go v0.2.10/go.mod h1:Jj3CTy2+O4bcMedVDXlbHuqqche85HEPuVXoQFhLaRc=
+github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE=
+github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE=
+github.com/sacloud/iaas-api-go v1.14.0 h1:xjkFWqdo4ilTrKPNNYBNWR/CZ/kVRsJrdAHAad6J/AQ=
+github.com/sacloud/iaas-api-go v1.14.0/go.mod h1:C8os2Mnj0TOmMdSllwhaDWKMVG2ysFnpe69kyA4M3V0=
+github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo=
+github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8=
+github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ=
+github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
+github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
-github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo=
+github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA=
+github.com/selectel/go-selvpcclient/v3 v3.2.1 h1:ny6WIAMiHzKxOgOEnwcWE79wIQij1AHHylzPA41MXCw=
+github.com/selectel/go-selvpcclient/v3 v3.2.1/go.mod h1:3EfSf8aEWyhspOGbvZ6mvnFg7JN5uckxNyBFPGWsXNQ=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
-github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
-github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
-github.com/softlayer/softlayer-go v1.0.3 h1:9FONm5xzQ9belQtbdryR6gBg4EF6hX6lrjNKi0IvZkU=
-github.com/softlayer/softlayer-go v1.0.3/go.mod h1:6HepcfAXROz0Rf63krk5hPZyHT6qyx2MNvYyHof7ik4=
+github.com/softlayer/softlayer-go v1.1.7 h1:SgTL+pQZt1h+5QkAhVmHORM/7N9c1X0sljJhuOIHxWE=
+github.com/softlayer/softlayer-go v1.1.7/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw=
 github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
 github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
+github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
 github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
+github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
+github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
+github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
+github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
+github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
+github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071 h1:Q/Ue/yRv4HSpaiFAnXIshoDjxzwyhwezEidXU49Boa4=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071 h1:1P0aq3ss/sku5n2qq18qTeyApQVMcy9/b7qbXPejCSc=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071/go.mod h1:wIqmfnhHUG/GMIK4OfsvXA29+Ei4Gmz5K3SWftLVxFQ=
+github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
+github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/transip/gotransip/v6 v6.6.1 h1:nsCU1ErZS5G0FeOpgGXc4FsWvBff9GPswSMggsC4564=
-github.com/transip/gotransip/v6 v6.6.1/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
-github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
-github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
+github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
+github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI=
+github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
-github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
-github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 h1:TFXGGMHmml4rs29PdPisC/aaCzOxUu1Vsh9on/IpUfE=
-github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
-github.com/vultr/govultr/v2 v2.7.1 h1:uF9ERet++Gb+7Cqs3p1P6b6yebeaZqVd7t5P2uZCaJU=
-github.com/vultr/govultr/v2 v2.7.1/go.mod h1:BvOhVe6/ZpjwcoL6/unkdQshmbS9VGbowI4QT+3DGVU=
+github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
+github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
+github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
+github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
+github.com/volcengine/volc-sdk-golang v1.0.190 h1:bva2jbMuce9LIqWRlfWbZcyriRs17ZaSoLZulMpk6K8=
+github.com/volcengine/volc-sdk-golang v1.0.190/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE=
+github.com/vultr/govultr/v3 v3.13.0 h1:Tq26syAju/cTlgorJJX391ZTbhILbe4WzIatyeStXCM=
+github.com/vultr/govultr/v3 v3.13.0/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
+github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
+github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c h1:Rnr+lDYXVkP+3eT8/d68iq4G/UeIhyCQk+HKa8toTvg=
+github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
+github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 h1:qmpz0Kvr9GAng8LAhRcKIpY71CEAcL3EBkftVlsP5Cw=
+github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134/go.mod h1:KgZCJrxdhdw/sKhTQ/M3S9WOLri2PCnBlc4C3s+PfKY=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
-github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
-go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
+go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
+go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
+go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s=
+go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
+go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
+go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
+go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
+go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
+go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
+go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
+go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
+go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
+go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
+go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
+go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 h1:d9qaMM+ODpCq+9We41//fu/sHsTnXcrqd1en3x+GKy4=
-go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
+go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
 golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
-golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
+golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -805,6 +1714,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@@ -813,8 +1724,15 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
 golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -825,7 +1743,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -836,46 +1753,117 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
+golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
+golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
+golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
-golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -883,11 +1871,10 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -896,56 +1883,121 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -955,40 +2007,50 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
-golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
+golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -997,7 +2059,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -1005,21 +2066,57 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
-golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
-golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
+gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
+gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
+gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1029,21 +2126,69 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
+google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
+google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
+google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
+google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
+google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=
+google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
+google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
+google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
+google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
+google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
+google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
+google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
+google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
+google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
+google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
+google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA=
+google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
@@ -1056,54 +2201,213 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx
 google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
 google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
+google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
+google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
+google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
+google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
+google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
+google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=
+google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
+google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
+google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
+google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
+google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
+google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 h1:e26eS1K69yxjjNNHYqjN49y95kcaQLJ3TL5h68dcA1E=
+google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:i5btTErZyoKCCubju3HS5LVho4nZd3yFnEp6moqeUjE=
+google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0=
+google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
+google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
+google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
+google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
+google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
+google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
 gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
-gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
-gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ns1/ns1-go.v2 v2.6.2 h1:tC+gRSN6fmnb9l9cVrIysXyuRO0wV6cZrjDqlMB0gGc=
-gopkg.in/ns1/ns1-go.v2 v2.6.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48=
+gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
-gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@@ -1111,137 +2415,78 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
+k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
+k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
+k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
+k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
+k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
+k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
+k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
-modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
-modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
-modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
-modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
-modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
-modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
-modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
-modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
-modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
-modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
-modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
-modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
-modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
-modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
-modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
-modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
-modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
-modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
-modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
-modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
-modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
-modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
-modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
-modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
-modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
-modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
-modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
-modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
-modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
-modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
-modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
-modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
-modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
-modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
-modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
-modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
-modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
-modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
+modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
+modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
+modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
+modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
+modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
+modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
 modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
-modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
-modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
-modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
-modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
-modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
-modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
-modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
-modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
-modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
-modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
-modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
-modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
-modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
-modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
-modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
-modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
-modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
-modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
-modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
-modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
-modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
-modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
-modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
-modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
-modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
-modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
-modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
-modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
-modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
-modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
-modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
-modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
-modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
-modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
-modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
-modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
-modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
+modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
+modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
+modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
+modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
+modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
+modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
 modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
 modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
-modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
-modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
-modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
+modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
+modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
 modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
-modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
-modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
+modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
 modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
-modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
-modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
+modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
+modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
 modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
+modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
-xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
-xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
-xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
-xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
-xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
+sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk=
+sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
+xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
+xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=

From 2f6960b88aa7a875218f61d3cef9018ab66663f4 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sun, 29 Dec 2024 21:12:17 +0000
Subject: [PATCH 172/196] fix woodpecker (#415)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/415
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .woodpecker/build.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 60f3311..19f9cc4 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -53,10 +53,10 @@ steps:
     commands:
       - just integration
     environment:
-      - ACME_API=https://acme.mock.directory
-      - PAGES_DOMAIN=localhost.mock.directory
-      - RAW_DOMAIN=raw.localhost.mock.directory
-      - PORT=4430
+      ACME_API: https://acme.mock.directory
+      PAGES_DOMAIN: localhost.mock.directory
+      RAW_DOMAIN: raw.localhost.mock.directory
+      PORT: 4430
     when:
       - event: [pull_request]
 

From 079fd09c43e7a5b432054ffe1dc4c68a497dfc84 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Fri, 17 Jan 2025 19:16:56 +0000
Subject: [PATCH 173/196] chore(deps): update woodpeckerci/plugin-docker-buildx
 docker tag to v5.1.0 (#421)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [woodpeckerci/plugin-docker-buildx](https://codeberg.org/woodpecker-plugins/docker-buildx) ([source](https://codeberg.org/woodpecker-plugins/docker-buildx.git)) | minor | `5.0.0` -> `5.1.0` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/docker-buildx (woodpeckerci/plugin-docker-buildx)</summary>

### [`v5.1.0`](https://codeberg.org/woodpecker-plugins/docker-buildx/blob/HEAD/CHANGELOG.md#510---2024-12-07)

[Compare Source](https://codeberg.org/woodpecker-plugins/docker-buildx/compare/v5.0.0...v5.1.0)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;6543](https://github.com/6543), [@&#8203;pat-s](https://github.com/pat-s), [@&#8203;woodpecker-bot](https://github.com/woodpecker-bot)

##### 📈 Enhancement

-   Allow build-args to get json \[[#&#8203;202](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/202)]

##### 📚 Documentation

-   Clarify insecure tag \[[#&#8203;200](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/200)]

##### 📦️ Dependency

-   fix(deps): update module github.com/urfave/cli/v3 to v3.0.0-alpha9.1 \[[#&#8203;199](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/199)]
-   chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v5.0.0 \[[#&#8203;198](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/198)]

##### Misc

-   chore(deps): update docker/buildx-bin docker tag to v0.19.1 ([5ff83b1](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/5ff83b1454f72ce243b95140fa5f7d8d1193d625))
-   fix(deps): update module github.com/urfave/cli/v3 to v3.0.0-beta1 ([62b3316](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/62b3316cb606bc404a063e705382b9f6a410b45e))
-   chore(deps): update woodpeckerci/plugin-release docker tag to v0.2.2 ([2ff549c](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/2ff549c3c6243863fae9aca26fd6b3a19dee6540))
-   fix(deps): update module github.com/stretchr/testify to v1.10.0 ([ed1d55f](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/ed1d55f533cc6e49c65656adf2bb8484f93f5b5c))
-   chore(deps): update docker/buildx-bin docker tag to v0.18.0 ([43bbfaa](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/43bbfaa0af080b89ae18075d11828d8d7776d0b3))
-   chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v2.1.1 ([faf4a88](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/faf4a889f6f167a23d7a95db99e174b8fa776614))
-   fix(deps): update module github.com/urfave/cli/v3 to v3.0.0-alpha9.4 ([1d723a3](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/1d723a337972a4cc6dc8e13748831cc5c055c7e1))
-   chore(deps): update davidanson/markdownlint-cli2 docker tag to v0.15.0 ([f6bbd6c](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/f6bbd6c1c2a49d5c883ce9cbe76a543ab4762a14))
-   fix(deps): update module github.com/urfave/cli/v3 to v3.0.0-alpha9.3 ([8d3d558](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/8d3d558c0975c074c34af88717a8facc13cfbd55))
-   chore(deps): update dependency go to v1.23.3 ([be45ede](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/be45ede5004f5d4cc7249fb8590acaf6942ff8c9))
-   fix(deps): update golang.org/x/lint digest to [`818c5a8`](https://github.com/woodpecker-plugins/docker-buildx/commit/818c5a8) ([cbf2022](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/cbf20222b0c343a827ae19c9f52b33d602a67adf))
-   Add vendor target for make \[[#&#8203;201](https://codeberg.org/woodpecker-plugins/docker-buildx/pulls/201)]
-   remove pat-s from maintainers ([b664c37](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/b664c37d62e2bd12683f7769324050942678534b))
-   CI: Dont lint on tag event ([1f4e506](https://codeberg.org/woodpecker-plugins/docker-buildx/src/commit/1f4e506a190a102c611769e2482442a14e4e131e))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45MS4zIiwidXBkYXRlZEluVmVyIjoiMzkuOTEuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/421
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 19f9cc4..26ba76f 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -20,7 +20,7 @@ steps:
 
   docker-dryrun:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:5.0.0
+    image: woodpeckerci/plugin-docker-buildx:5.1.0
     settings:
       dockerfile: Dockerfile
       platforms: linux/amd64
@@ -74,7 +74,7 @@ steps:
 
   docker-next:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:5.0.0
+    image: woodpeckerci/plugin-docker-buildx:5.1.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
@@ -89,7 +89,7 @@ steps:
       - event: [push]
 
   'Publish PR image':
-    image: woodpeckerci/plugin-docker-buildx:5.0.0
+    image: woodpeckerci/plugin-docker-buildx:5.1.0
     depends_on: test
     settings:
       registry: codeberg.org
@@ -107,7 +107,7 @@ steps:
 
   docker-release:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:5.0.0
+    image: woodpeckerci/plugin-docker-buildx:5.1.0
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile

From b62c6bdd2d71b828d601b0c7b072079204c52e3e Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Fri, 17 Jan 2025 19:17:14 +0000
Subject: [PATCH 174/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.63.4 (#419)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [golangci/golangci-lint](https://github.com/golangci/golangci-lint) | minor | `v1.62.2` -> `v1.63.4` |

---

### Release Notes

<details>
<summary>golangci/golangci-lint (golangci/golangci-lint)</summary>

### [`v1.63.4`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1634)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.63.3...v1.63.4)

1.  Linters bug fixes
    -   `dupl`, `gomodguard`, `revive`: keep only Go-files.

### [`v1.63.3`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1633)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.63.2...v1.63.3)

1.  Linters bug fixes
    -   `gofmt`, `gofumpt`, `goimports`, `gci`: panic with several trailing EOL
    -   `goheader`: skip issues with invalid positions

### [`v1.63.2`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1632)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.63.1...v1.63.2)

1.  Linters bug fixes
    -   `gofmt`, `gofumpt`, `goimports`, `gci`: panic with missing trailing EOL

### [`v1.63.1`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1631)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.63.0...v1.63.1)

1.  Linters bug fixes
    -   `cgi`: invalid reports with cgo
    -   `gofumpt`: panic with autofix and cgo

### [`v1.63.0`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1630)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.62.2...v1.63.0)

1.  Enhancements
    -   Add support for SuggestedFixes 🎉 (35 linters can "autofix" reports).
    -   Formatters (`gofmt`, `goimports`, `gofumpt`, `gci`) are applied after the suggested fixes.
2.  New linters
    -   Add `exptostd` linter https://github.com/ldez/exptostd
    -   Add `nilnesserr` linter https://github.com/alingse/nilnesserr
    -   Add `usetesting` linter https://github.com/ldez/usetesting
3.  Linters new features
    -   `gci`: new options: `no-inline-comments`, `no-prefix-comments`
    -   `gomoddirectives`: from 0.2.4 to 0.6.0 (new options: `go-version-pattern`, `toolchain-pattern`,`toolchain-forbidden`, `tool-forbidden`, `go-debug-forbidden`)
    -   `govet`: new `stdversion`, `waitgroup` analyzers
    -   `importas`: allow multiple empty aliases
    -   `loggercheck`: new `slog` option
    -   `recvcheck`: from 0.1.2 to 0.2.0 (new options: `disable-builtin`, `exclusions`)
    -   `tagliatelle`: from 0.5.0 to 0.7.1 (new options: `ignored-fields`, `extended-rules`,`overrides`, `pkg`, `ignore`)
    -   `usestdlibvars`: from 1.27.0 to 1.28.0 (autofix)
    -   `wrapcheck`: from 2.9.0 to 2.10.0 (new option: `extra-ignore-sigs`)
4.  Linters bug fixes
    -   `asciicheck`: from 0.2.0 to 0.3.0
    -   `bodyclose`: from [`5742072`](https://github.com/golangci/golangci-lint/commit/574207250966) to [`ed6a65f`](https://github.com/golangci/golangci-lint/commit/ed6a65f985e)
    -   `funlen`: from 0.1.0 to 0.2.0
    -   `ginkgolinter`: from 0.18.3 to 0.18.4
    -   `gochecksumtype`: from 0.2.0 to 0.3.1
    -   `gocognit`: from 1.1.3 to 1.2.0
    -   `godot`: from 1.4.18 to 1.4.20
    -   `goheader`: report position improvement
    -   `gosec`: handling of global nosec option when it is false
    -   `iface`: from 1.2.1 to 1.3.0
    -   `importas`: from 0.1.0 to 0.2.0
    -   `intrange`: from 0.2.1 to 0.3.0
    -   `makezero`: from 1.1.1 to 1.2.0
    -   `mirror`: from 1.2.0 to 1.3.0
    -   `nilnil`: from 1.0.0 to 1.0.1
    -   `nosprintfhostport`: from 0.1.1 to 0.2.0
    -   `reassign`: from 0.2.0 to 0.3.0
    -   `spancheck`: from 0.6.2 to 0.6.4
    -   `tagalign`: from 1.3.4 to 1.4.1
    -   `wastedassign`: from 2.0.7 to 2.1.0
    -   `whitespace`: from 0.1.1 to 0.2.0
    -   `wsl`: from 4.4.1 to 4.5.0
5.  Deprecations
    -   ⚠️ `output.uniq-by-line` is deprecated and replaced by `issues.uniq-by-line`.
6.  Misc.
    -   Improvements of the help command (color and JSON support).
    -   Removes `decoder`, `sloglint`, `tagalin` from `format` preset.
    -   Enables paths with junction inside Windows.
    -   The timeout is disabled if `run.timeout` <= 0.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45MS4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTEuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/419
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 8b18851..7e4e94a 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.62.2
+    image: golangci/golangci-lint:v1.63.4
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From 7fad16b5d6f85fcad10fbf3041874a7c990cae88 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Fri, 17 Jan 2025 19:18:13 +0000
Subject: [PATCH 175/196] chore(deps): update
 docker.io/woodpeckerci/plugin-prettier docker tag to v1 (#417)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [docker.io/woodpeckerci/plugin-prettier](https://codeberg.org/woodpecker-plugins/prettier) ([source](https://codeberg.org/woodpecker-plugins/prettier.git)) | major | `0.2.0` -> `1.1.0` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/prettier (docker.io/woodpeckerci/plugin-prettier)</summary>

### [`v1.1.0`](https://codeberg.org/woodpecker-plugins/prettier/compare/1.0.0...1.1.0)

[Compare Source](https://codeberg.org/woodpecker-plugins/prettier/compare/1.0.0...1.1.0)

### [`v1.0.0`](https://codeberg.org/woodpecker-plugins/prettier/blob/HEAD/CHANGELOG.md#100---2024-11-20)

[Compare Source](https://codeberg.org/woodpecker-plugins/prettier/compare/0.2.0...1.0.0)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;qwerty287](https://github.com/qwerty287), [@&#8203;woodpecker-bot](https://github.com/woodpecker-bot)

##### 💥 Breaking changes

-   Update node.js to v22 and remove ppc64le and arm images \[[#&#8203;2](https://codeberg.org/woodpecker-plugins/prettier/pulls/2)]

##### 📦️ Dependency

-   chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v2.1.1 \[[#&#8203;5](https://codeberg.org/woodpecker-plugins/prettier/pulls/5)]

##### Misc

-   Add CODEOWNERS \[[#&#8203;3](https://codeberg.org/woodpecker-plugins/prettier/pulls/3)]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45MC40IiwidXBkYXRlZEluVmVyIjoiMzkuOTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/417
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 7e4e94a..0d43a8f 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -24,7 +24,7 @@ steps:
       - yamllint .
 
   prettier:
-    image: docker.io/woodpeckerci/plugin-prettier:0.2.0
+    image: docker.io/woodpeckerci/plugin-prettier:1.1.0
     depends_on: []
     settings:
       version: 3.2.5

From ead959adf7812675b95144157b440031fe124098 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Fri, 17 Jan 2025 19:18:28 +0000
Subject: [PATCH 176/196] fix(deps): update golang.org/x/exp digest to 7588d65
 (#418)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/exp | require | digest | `b2144cd` -> `7588d65` |

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45MS4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTEuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/418
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 go.mod |  10 +++---
 go.sum | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 106 insertions(+), 15 deletions(-)

diff --git a/go.mod b/go.mod
index 13e40df..c329493 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,7 @@ require (
 	github.com/rs/zerolog v1.33.0
 	github.com/stretchr/testify v1.10.0
 	github.com/urfave/cli/v2 v2.27.5
-	golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
+	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
 	xorm.io/xorm v1.3.9
 )
 
@@ -207,15 +207,15 @@ require (
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/ratelimit v0.3.1 // indirect
-	golang.org/x/crypto v0.31.0 // indirect
+	golang.org/x/crypto v0.32.0 // indirect
 	golang.org/x/mod v0.22.0 // indirect
-	golang.org/x/net v0.33.0 // indirect
+	golang.org/x/net v0.34.0 // indirect
 	golang.org/x/oauth2 v0.24.0 // indirect
 	golang.org/x/sync v0.10.0 // indirect
-	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/sys v0.29.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
 	golang.org/x/time v0.8.0 // indirect
-	golang.org/x/tools v0.28.0 // indirect
+	golang.org/x/tools v0.29.0 // indirect
 	google.golang.org/api v0.214.0 // indirect
 	google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect
diff --git a/go.sum b/go.sum
index f8ab2be..b765807 100644
--- a/go.sum
+++ b/go.sum
@@ -605,6 +605,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
 git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
 github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE=
 github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
@@ -614,14 +615,20 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BL
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 h1:yzrctSl9GMIQ5lHu7jc8olOsGjWDCsBpJhWqfGa/YIM=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0/go.mod h1:GE4m0rnnfwLGX0Y9A9A25Zx5N/90jneT5ABevqzhuFQ=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 h1:zLzoX5+W2l95UJoVwiyNS4dX8vHyQ6x2xRLoBBL9wMk=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
@@ -638,6 +645,7 @@ github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdE
 github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
 github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
 github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
 github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
@@ -645,6 +653,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
+github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -750,10 +760,13 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -802,12 +815,15 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
 github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
 github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
 github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
 github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@@ -835,6 +851,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -846,6 +864,8 @@ github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZ
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@@ -894,6 +914,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
 github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@@ -906,9 +928,14 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
 github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
 github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
 github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
+github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
+github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
 github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
 github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
@@ -964,6 +991,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -987,6 +1016,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -1015,6 +1046,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
+github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
 github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
@@ -1068,6 +1101,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
@@ -1085,6 +1119,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S
 github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
+github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -1141,6 +1177,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod
 github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
 github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
 github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
+github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
+github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
 github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
 github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
@@ -1149,6 +1187,7 @@ github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aW
 github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
@@ -1170,7 +1209,10 @@ github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
+github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -1189,9 +1231,11 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
@@ -1246,6 +1290,8 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW
 github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
+github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
 github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -1267,6 +1313,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
+github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@@ -1319,6 +1367,7 @@ github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZ
 github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
 github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/nzdjb/go-metaname v1.0.0 h1:sNASlZC1RM3nSudtBTE1a3ZVTDyTpjqI5WXRPrdZ9Hg=
 github.com/nzdjb/go-metaname v1.0.0/go.mod h1:0GR0LshZax1Lz4VrOrfNSE4dGvTp7HGjiemdczXT2H4=
@@ -1327,15 +1376,20 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
+github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
+github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
@@ -1416,8 +1470,11 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
+github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
 github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE=
 github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad h1:WtSUHi5zthjudjIi3L6QmL/V9vpJPbc/j/F2u55d3fs=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad/go.mod h1:h0+DiDRe2Y+6iHTjIq/9HzUq7NII/Nffp0HkFrsAKq4=
@@ -1426,6 +1483,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
 github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
@@ -1466,9 +1525,13 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
+github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
+github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
 github.com/softlayer/softlayer-go v1.1.7 h1:SgTL+pQZt1h+5QkAhVmHORM/7N9c1X0sljJhuOIHxWE=
 github.com/softlayer/softlayer-go v1.1.7/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw=
 github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
@@ -1546,7 +1609,9 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
 github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
 github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
 github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI=
 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss=
@@ -1616,6 +1681,10 @@ go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
 go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
 go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
 go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
+go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
+go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
+go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
+go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
 go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
 go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
@@ -1624,6 +1693,7 @@ go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
@@ -1672,8 +1742,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1689,8 +1759,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
-golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
-golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
+golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -1813,8 +1883,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
-golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
-golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1981,8 +2051,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1998,6 +2068,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
+golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2099,8 +2171,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
-golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
-golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
+golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -2392,10 +2464,12 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
+gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
 gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
@@ -2408,6 +2482,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
 gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48=
 gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@@ -2442,16 +2517,21 @@ k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
 k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
 lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
 modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
 modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
 modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
+modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
 modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
 modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
 modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
 modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
 modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
 modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
+modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
+modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
 modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
 modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
 modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
@@ -2461,19 +2541,30 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
 modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
 modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
 modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
+modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
+modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
 modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
 modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
 modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
 modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
+modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
 modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
 modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
 modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
+modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
+modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
 modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
+modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
 modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
 modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
 modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
+modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
 modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

From ebc1444efb14cd7554f572d69baadc6579727c63 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Fri, 17 Jan 2025 20:21:32 +0100
Subject: [PATCH 177/196] update go version in docker file

---
 Dockerfile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 6106317..7cc21c5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,8 @@
 # Set the default Go version as a build argument
-ARG XGO="go-1.21.x"
+ARG XGO="go-1.23.x"
 
 # Use xgo (a Go cross-compiler tool) as build image
-FROM --platform=$BUILDPLATFORM techknowlogick/xgo:${XGO} as build
+FROM --platform=$BUILDPLATFORM techknowlogick/xgo:${XGO} AS build
 
 # Set the working directory and copy the source code
 WORKDIR /go/src/codeberg.org/codeberg/pages

From 7ca77716bffbe9f8cb3462ea9dae5e2f77432c75 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Fri, 17 Jan 2025 20:21:56 +0100
Subject: [PATCH 178/196] update flake

---
 flake.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/flake.lock b/flake.lock
index 1fdb364..b3837d9 100644
--- a/flake.lock
+++ b/flake.lock
@@ -20,8 +20,8 @@
     "nixpkgs": {
       "locked": {
         "lastModified": 0,
-        "narHash": "sha256-zUfNgGopg1+/xYRnFaI6PAhlF2JofdEG789CsKclYSw=",
-        "path": "/nix/store/0ijldwygklvrzinxx7scsxsvqhx0hqgz-source",
+        "narHash": "sha256-1/WZ7juTZp6oWAFb14WeANN2veJFGZ4AFSNRpk4Jiw8=",
+        "path": "/nix/store/prhs96gxbb9bcr4ia6ql2hsjga50mrdl-source",
         "type": "path"
       },
       "original": {

From b69d09e9e1cb49ef71628dc860d7324c50b5f832 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Fri, 17 Jan 2025 20:29:41 +0100
Subject: [PATCH 179/196] update dependencies

---
 go.mod | 101 +++++++++++++++++++++++++++--------------------------
 go.sum | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 159 insertions(+), 50 deletions(-)

diff --git a/go.mod b/go.mod
index c329493..1555baa 100644
--- a/go.mod
+++ b/go.mod
@@ -2,10 +2,10 @@ module codeberg.org/codeberg/pages
 
 go 1.23.0
 
-toolchain go1.23.3
+toolchain go1.23.5
 
 require (
-	code.gitea.io/sdk/gitea v0.19.0
+	code.gitea.io/sdk/gitea v0.20.0
 	github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
 	github.com/creasty/defaults v1.8.0
 	github.com/go-acme/lego/v4 v4.21.0
@@ -27,14 +27,15 @@ require (
 )
 
 require (
-	cloud.google.com/go/auth v0.13.0 // indirect
-	cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
+	cloud.google.com/go/auth v0.14.0 // indirect
+	cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
 	cloud.google.com/go/compute/metadata v0.6.0 // indirect
 	filippo.io/edwards25519 v1.1.0 // indirect
+	github.com/42wim/httpsig v1.2.2 // indirect
 	github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
 	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
@@ -51,40 +52,40 @@ require (
 	github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
 	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
 	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
-	github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 // indirect
-	github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect
-	github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect
-	github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
+	github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.33.0 // indirect
+	github.com/aws/aws-sdk-go-v2/config v1.29.0 // indirect
+	github.com/aws/aws-sdk-go-v2/credentials v1.17.53 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
-	github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 // indirect
-	github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.10 // indirect
+	github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.24.10 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sts v1.33.8 // indirect
 	github.com/aws/smithy-go v1.22.1 // indirect
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/benbjohnson/clock v1.3.5 // indirect
 	github.com/boombuler/barcode v1.0.2 // indirect
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
-	github.com/civo/civogo v0.3.89 // indirect
-	github.com/cloudflare/cloudflare-go v0.112.0 // indirect
+	github.com/civo/civogo v0.3.92 // indirect
+	github.com/cloudflare/cloudflare-go v0.114.0 // indirect
 	github.com/cpu/goacmedns v0.1.1 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/davidmz/go-pageant v1.0.2 // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
 	github.com/dnsimple/dnsimple-go v1.7.0 // indirect
-	github.com/exoscale/egoscale/v3 v3.1.7 // indirect
+	github.com/exoscale/egoscale/v3 v3.1.8 // indirect
 	github.com/fatih/structs v1.1.0 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fsnotify/fsnotify v1.8.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
-	github.com/gabriel-vasile/mimetype v1.4.7 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/ghodss/yaml v1.0.0 // indirect
 	github.com/go-errors/errors v1.5.1 // indirect
 	github.com/go-fed/httpsig v1.1.0 // indirect
@@ -93,8 +94,8 @@ require (
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
-	github.com/go-playground/validator/v10 v10.23.0 // indirect
-	github.com/go-resty/resty/v2 v2.16.2 // indirect
+	github.com/go-playground/validator/v10 v10.24.0 // indirect
+	github.com/go-resty/resty/v2 v2.16.3 // indirect
 	github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
 	github.com/goccy/go-json v0.10.4 // indirect
 	github.com/gofrs/flock v0.12.1 // indirect
@@ -104,7 +105,7 @@ require (
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
-	github.com/google/s2a-go v0.1.8 // indirect
+	github.com/google/s2a-go v0.1.9 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
 	github.com/googleapis/gax-go/v2 v2.14.1 // indirect
@@ -117,7 +118,7 @@ require (
 	github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
 	github.com/hashicorp/go-version v1.7.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
-	github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129 // indirect
+	github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132 // indirect
 	github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
 	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -128,11 +129,11 @@ require (
 	github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
 	github.com/labbsr0x/goh v1.0.1 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
-	github.com/linode/linodego v1.44.0 // indirect
+	github.com/linode/linodego v1.46.0 // indirect
 	github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
 	github.com/liquidweb/liquidweb-go v1.6.4 // indirect
 	github.com/magiconair/properties v1.8.9 // indirect
-	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-colorable v0.1.14 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/miekg/dns v1.1.62 // indirect
 	github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
@@ -153,7 +154,7 @@ require (
 	github.com/nrdcg/porkbun v0.4.0 // indirect
 	github.com/nzdjb/go-metaname v1.0.0 // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
-	github.com/oracle/oci-go-sdk/v65 v65.81.1 // indirect
+	github.com/oracle/oci-go-sdk/v65 v65.81.2 // indirect
 	github.com/ovh/go-ovh v1.6.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/peterhellberg/link v1.2.0 // indirect
@@ -167,7 +168,7 @@ require (
 	github.com/sacloud/go-http v0.1.9 // indirect
 	github.com/sacloud/iaas-api-go v1.14.0 // indirect
 	github.com/sacloud/packages-go v0.0.11 // indirect
-	github.com/sagikazarmark/locafero v0.6.0 // indirect
+	github.com/sagikazarmark/locafero v0.7.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
 	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
 	github.com/selectel/domains-go v1.1.0 // indirect
@@ -179,56 +180,56 @@ require (
 	github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
 	github.com/sony/gobreaker v1.0.0 // indirect
 	github.com/sourcegraph/conc v0.3.0 // indirect
-	github.com/spf13/afero v1.11.0 // indirect
+	github.com/spf13/afero v1.12.0 // indirect
 	github.com/spf13/cast v1.7.1 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/viper v1.19.0 // indirect
 	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
 	github.com/syndtr/goleveldb v1.0.0 // indirect
-	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071 // indirect
-	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1084 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	github.com/transip/gotransip/v6 v6.26.0 // indirect
 	github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
 	github.com/vinyldns/go-vinyldns v0.9.16 // indirect
-	github.com/volcengine/volc-sdk-golang v1.0.190 // indirect
-	github.com/vultr/govultr/v3 v3.13.0 // indirect
+	github.com/volcengine/volc-sdk-golang v1.0.193 // indirect
+	github.com/vultr/govultr/v3 v3.14.1 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
 	github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
 	github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
-	go.mongodb.org/mongo-driver v1.17.1 // indirect
+	go.mongodb.org/mongo-driver v1.17.2 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
-	go.opentelemetry.io/otel v1.33.0 // indirect
-	go.opentelemetry.io/otel/metric v1.33.0 // indirect
-	go.opentelemetry.io/otel/trace v1.33.0 // indirect
+	go.opentelemetry.io/otel v1.34.0 // indirect
+	go.opentelemetry.io/otel/metric v1.34.0 // indirect
+	go.opentelemetry.io/otel/trace v1.34.0 // indirect
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/ratelimit v0.3.1 // indirect
 	golang.org/x/crypto v0.32.0 // indirect
 	golang.org/x/mod v0.22.0 // indirect
 	golang.org/x/net v0.34.0 // indirect
-	golang.org/x/oauth2 v0.24.0 // indirect
+	golang.org/x/oauth2 v0.25.0 // indirect
 	golang.org/x/sync v0.10.0 // indirect
 	golang.org/x/sys v0.29.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
-	golang.org/x/time v0.8.0 // indirect
+	golang.org/x/time v0.9.0 // indirect
 	golang.org/x/tools v0.29.0 // indirect
-	google.golang.org/api v0.214.0 // indirect
-	google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
-	google.golang.org/grpc v1.69.2 // indirect
-	google.golang.org/protobuf v1.36.1 // indirect
+	google.golang.org/api v0.217.0 // indirect
+	google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
+	google.golang.org/grpc v1.69.4 // indirect
+	google.golang.org/protobuf v1.36.3 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/api v0.32.0 // indirect
-	k8s.io/apimachinery v0.32.0 // indirect
+	k8s.io/api v0.32.1 // indirect
+	k8s.io/apimachinery v0.32.1 // indirect
 	k8s.io/klog/v2 v2.130.1 // indirect
 	k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
 	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
diff --git a/go.sum b/go.sum
index b765807..426079f 100644
--- a/go.sum
+++ b/go.sum
@@ -36,6 +36,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
 cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
 cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
 cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
+cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ=
+cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM=
 cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
 cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
 cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -99,8 +101,12 @@ cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM
 cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
 cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
 cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
+cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM=
+cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A=
 cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
 cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
+cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
+cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
 cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
 cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
 cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=
@@ -175,6 +181,8 @@ cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvj
 cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
 cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
+cloud.google.com/go/compute v1.31.1 h1:SObuy8Fs6woazArpXp1fsHCw+ZH4iJ/8dGGTxUhHZQA=
+cloud.google.com/go/compute v1.31.1/go.mod h1:hyOponWhXviDptJCJSoEh89XO1cfv616wbwbkde1/+8=
 cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
 cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
@@ -600,6 +608,8 @@ cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT
 cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
 code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y=
 code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
+code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU=
+code.gitea.io/sdk/gitea v0.20.0/go.mod h1:faouBHC/zyx5wLgjmRKR62ydyvMzwWf3QnU0bH7Cw6U=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
@@ -607,16 +617,23 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zum
 git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
+github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA=
+github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY=
 github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE=
 github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
@@ -689,6 +706,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 h1:mg/+23+/gAw6zdxv9I5dPCj666WJPLk8S1nXm0dOumQ=
 github.com/aliyun/alibaba-cloud-sdk-go v1.63.76/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
+github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 h1:YBkf7H5CSgrlb3C1aWcpDt7Vk8UEGFPeD2OOirtt6IM=
+github.com/aliyun/alibaba-cloud-sdk-go v1.63.83/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
@@ -705,16 +724,28 @@ github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm
 github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
 github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
 github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
+github.com/aws/aws-sdk-go-v2 v1.33.0 h1:Evgm4DI9imD81V0WwD+TN4DCwjUMdc94TrduMLbgZJs=
+github.com/aws/aws-sdk-go-v2 v1.33.0/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
 github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=
 github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M=
+github.com/aws/aws-sdk-go-v2/config v1.29.0 h1:Vk/u4jof33or1qAQLdofpjKV7mQQT7DcUpnYx8kdmxY=
+github.com/aws/aws-sdk-go-v2/config v1.29.0/go.mod h1:iXAZK3Gxvpq3tA+B9WaDYpZis7M8KFgdrDPMmHrgbJM=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.53 h1:lwrVhiEDW5yXsuVKlFVUnR2R50zt2DklhOyeLETqDuE=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.53/go.mod h1:CkqM1bIw/xjEpBMhBnvqUXYZbpCFuj6dnCAyDk2AtAY=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 h1:5grmdTdMsovn9kPZPI23Hhvp0ZyNm5cRO+IZFIYiAfw=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24/go.mod h1:zqi7TVKTswH3Ozq28PkmBmgzG1tona7mo9G2IJg4Cis=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 h1:igORFSiH3bfq4lxKFkTSYDhJEUCYo6C8VKiWJjYwQuQ=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28/go.mod h1:3So8EA/aAYm36L7XIvCVwLa0s5N0P7o2b1oqnx/2R4g=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 h1:1mOW9zAUMhTSrMDssEHS/ajx8JcAj/IcftzcmNlmVLI=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28/go.mod h1:kGlXVIWDfvt2Ox5zEaNglmq0hXPHgQFNMix33Tw22jA=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
@@ -722,16 +753,28 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhv
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 h1:TQmKDyETFGiXVhZfQ/I0cCFziqqX58pi4tKJGYGFSz0=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9/go.mod h1:HVLPK2iHQBUx7HfZeOQSEu3v2ubZaAY2YPbAm5/WUyY=
 github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 h1:+lmJoqxuUoPlSfGk5JYQQivd9YFjUvRZR6RPY+Wcx48=
 github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8/go.mod h1:Gg8/myP4+rgRi4+j9gQdbOEnMtwMAUUIeXo+nKCFVj8=
+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.10 h1:D8L0rXdD2L5/DoxkC3sWfBfXbYnabB6F6f4XmqgMQPo=
+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.10/go.mod h1:UIgmS/dicuAwwgEbzEbpyXiPxMW5bU3yvxtZJmNuxFQ=
 github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 h1:0jMtawybbfpFEIMy4wvfyW2Z4YLr7mnuzT0fhR67Nrc=
 github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4/go.mod h1:xlMODgumb0Pp8bzfpojqelDrf8SL9rb5ovwmwKJl+oU=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 h1:njgAP7Rtt4DGdTGFPhJ4gaZXCD1CDj/SZDa5W4ZgSTs=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1/go.mod h1:TN4PcCL0lvqmYcv+AV8iZFC4Sd0FM06QDaoBXrFEftU=
 github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g=
 github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.10 h1:DyZUj3xSw3FR3TXSwDhPhuZkkT14QHBiacdbUVcD0Dg=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.10/go.mod h1:Ro744S4fKiCCuZECXgOi760TiYylUM8ZBf6OGiZzJtY=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.9 h1:I1TsPEs34vbpOnR81GIcAq4/3Ud+jRHVGwx6qLQUHLs=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.9/go.mod h1:Fzsj6lZEb8AkTE5S68OhcbBqeWPsR8RnGuKPr8Todl8=
 github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY=
 github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.8 h1:pqEJQtlKWvnv3B6VRt60ZmsHy3SotlEBvfUBPB1KVcM=
+github.com/aws/aws-sdk-go-v2/service/sts v1.33.8/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw=
 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
 github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
 github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
@@ -774,10 +817,14 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
 github.com/civo/civogo v0.3.89 h1:g+I4NGVa5t0L2Z9+QbnEAqxE/3OCDUYvepje3oUkKVo=
 github.com/civo/civogo v0.3.89/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
+github.com/civo/civogo v0.3.92 h1:fiA9nsyvEU9vVwA8ssIDSGt6gsWs6meqgaKrimAEaI0=
+github.com/civo/civogo v0.3.92/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
 github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g=
 github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0=
+github.com/cloudflare/cloudflare-go v0.114.0 h1:ucoti4/7Exo0XQ+rzpn1H+IfVVe++zgiM+tyKtf0HUA=
+github.com/cloudflare/cloudflare-go v0.114.0/go.mod h1:O7fYfFfA6wKqKFn2QIR9lhj7FDw6VQCGOY6hd2TBtd0=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -847,6 +894,8 @@ github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0+
 github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
 github.com/exoscale/egoscale/v3 v3.1.7 h1:Q6p9tOVY0IiOW0fUpaPQWY7ggGEuSPZLAGxFgDd2sCE=
 github.com/exoscale/egoscale/v3 v3.1.7/go.mod h1:GHKucK/J26v8PGWztGdhxWNMjrjG9PbelxKCJ4YI11Q=
+github.com/exoscale/egoscale/v3 v3.1.8 h1:LrZ7cAk3Wum9ZncKR1gRPpXsdvCoQb7KoUi3+ZxSTvY=
+github.com/exoscale/egoscale/v3 v3.1.8/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
@@ -876,6 +925,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
 github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
 github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
 github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
@@ -922,8 +973,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
 github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
+github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
 github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
 github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
+github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E=
+github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
 github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
 github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -1052,6 +1107,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
 github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
 github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
 github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
+github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
+github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -1165,6 +1222,8 @@ github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfE
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129 h1:+4ocCqiQFWTODV2Q94q/DMGrmoXFIrimPn+SZZPCbBs=
 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132 h1:5LqzrJa8LADcY0sDEdV35e8nbwI7RoUQEt+KXWvWoY0=
+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
 github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
 github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -1249,6 +1308,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/linode/linodego v1.44.0 h1:JZLLWzCAx3CmHSV9NmCoXisuqKtrmPhfY9MrgvaHMUY=
 github.com/linode/linodego v1.44.0/go.mod h1:umdoNOmtbqAdGQbmQnPFZ2YS4US+/mU/1bA7MjoKAvg=
+github.com/linode/linodego v1.46.0 h1:+uOG4SD2MIrhbrLrvOD5HrbdLN3D19Wgn3MgdUNQjeU=
+github.com/linode/linodego v1.46.0/go.mod h1:vyklQRzZUWhFVBZdYx4dcYJU/gG9yKB9VUcUs6ub0Lk=
 github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
 github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
 github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
@@ -1273,6 +1334,8 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
@@ -1397,6 +1460,8 @@ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mo
 github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
 github.com/oracle/oci-go-sdk/v65 v65.81.1 h1:JYc47bk8n/MUchA2KHu1ggsCQzlJZQLJ+tTKfOho00E=
 github.com/oracle/oci-go-sdk/v65 v65.81.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
+github.com/oracle/oci-go-sdk/v65 v65.81.2 h1:yhdu9xphYOJed+MPQP4OHAtPlSm0dxKY2L/lf5ntJbU=
+github.com/oracle/oci-go-sdk/v65 v65.81.2/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
 github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
 github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -1472,12 +1537,15 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
 github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
+github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
 github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE=
 github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad h1:WtSUHi5zthjudjIi3L6QmL/V9vpJPbc/j/F2u55d3fs=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad/go.mod h1:h0+DiDRe2Y+6iHTjIq/9HzUq7NII/Nffp0HkFrsAKq4=
+github.com/reugn/equalizer v0.2.0 h1:kJy7909B2SYFLKnpMpfPK//B+IWhx7kEq/N8AUrt27w=
+github.com/reugn/equalizer v0.2.0/go.mod h1:Jxi79RdAJ9Ro4nET7fANm0v7TehzmyyAkv2w0TPBSZQ=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -1506,6 +1574,8 @@ github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZO
 github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ=
 github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
 github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
+github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
+github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
 github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
 github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770=
@@ -1552,6 +1622,8 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD
 github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
 github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
 github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
+github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
@@ -1601,8 +1673,12 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071 h1:Q/Ue/yRv4HSpaiFAnXIshoDjxzwyhwezEidXU49Boa4=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1084 h1:kVMffrA57uPQvNaPBXSp1NJPiz+Y0a/bxqiEVBjrZAI=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1084/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071 h1:1P0aq3ss/sku5n2qq18qTeyApQVMcy9/b7qbXPejCSc=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071/go.mod h1:wIqmfnhHUG/GMIK4OfsvXA29+Ei4Gmz5K3SWftLVxFQ=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 h1:kwctN0WQYt8/iKP+iRCTCwdzEMIXsXklbRIib5rjeQ8=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084/go.mod h1:qE67ApiBzeRvzeDsV+GxyIDbVIDemsKpHXllQATz/Vw=
 github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
 github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -1622,8 +1698,12 @@ github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4
 github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
 github.com/volcengine/volc-sdk-golang v1.0.190 h1:bva2jbMuce9LIqWRlfWbZcyriRs17ZaSoLZulMpk6K8=
 github.com/volcengine/volc-sdk-golang v1.0.190/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE=
+github.com/volcengine/volc-sdk-golang v1.0.193 h1:mL1rlk+m9SaqF2MSGFWfigEaz10ZVJiYDnFuWfj65Ww=
+github.com/volcengine/volc-sdk-golang v1.0.193/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE=
 github.com/vultr/govultr/v3 v3.13.0 h1:Tq26syAju/cTlgorJJX391ZTbhILbe4WzIatyeStXCM=
 github.com/vultr/govultr/v3 v3.13.0/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w=
+github.com/vultr/govultr/v3 v3.14.1 h1:9BpyZgsWasuNoR39YVMcq44MSaF576Z4D+U3ro58eJQ=
+github.com/vultr/govultr/v3 v3.14.1/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -1665,6 +1745,8 @@ go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQa
 go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
 go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
 go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
+go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
+go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1679,14 +1761,20 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEj
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
 go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
 go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
+go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
+go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
 go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
 go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
+go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
+go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
 go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
 go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
 go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
 go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
 go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
 go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
+go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
+go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
 go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
@@ -1916,6 +2004,8 @@ golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw
 golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
 golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
+golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -2102,6 +2192,8 @@ golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
 golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
+golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -2249,6 +2341,8 @@ google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45
 google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
 google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA=
 google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE=
+google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU=
+google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -2391,10 +2485,16 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
 google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 h1:e26eS1K69yxjjNNHYqjN49y95kcaQLJ3TL5h68dcA1E=
 google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:i5btTErZyoKCCubju3HS5LVho4nZd3yFnEp6moqeUjE=
+google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f h1:387Y+JbxF52bmesc8kq1NyYIp33dnxCw6eiA7JMsTmw=
+google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:0joYwWwLQh18AOj8zMYeZLjzuqcYTU3/nC5JdCvC3JI=
 google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0=
 google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4=
+google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
+google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -2439,6 +2539,8 @@ google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3
 google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
 google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
 google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
+google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
+google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -2459,6 +2561,8 @@ google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
 google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
+google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -2510,8 +2614,12 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
 k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
 k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
+k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
+k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
 k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
 k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
+k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
+k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=

From cce656ec4e158d0853c01b843dfce7381739392f Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Fri, 17 Jan 2025 20:32:11 +0100
Subject: [PATCH 180/196] ignore failure of integration tests

---
 .woodpecker/build.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 26ba76f..7a99a42 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -48,6 +48,7 @@ steps:
       - event: [pull_request]
 
   integration-tests:
+    failure: ignore
     depends_on: build
     image: codeberg.org/6543/docker-images/golang_just:go-1.23
     commands:

From a77e2d944065bfdb25bff9353bdb029db37e81c8 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Fri, 17 Jan 2025 19:49:16 +0000
Subject: [PATCH 181/196] fix: timeout issue of gitea sdk client (#422)

closes #420

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/422
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 server/gitea/client.go | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/server/gitea/client.go b/server/gitea/client.go
index 0cf6e6c..5633bf2 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -49,6 +49,7 @@ const (
 
 type Client struct {
 	sdkClient     *gitea.Client
+	sdkFileClient *gitea.Client
 	responseCache cache.ICache
 
 	giteaRoot string
@@ -68,8 +69,6 @@ func NewClient(cfg config.ForgeConfig, respCache cache.ICache) (*Client, error)
 	}
 	giteaRoot := strings.TrimSuffix(rootURL.String(), "/")
 
-	stdClient := http.Client{Timeout: 10 * time.Second}
-
 	forbiddenMimeTypes := make(map[string]bool, len(cfg.ForbiddenMimeTypes))
 	for _, mimeType := range cfg.ForbiddenMimeTypes {
 		forbiddenMimeTypes[mimeType] = true
@@ -80,15 +79,26 @@ func NewClient(cfg config.ForgeConfig, respCache cache.ICache) (*Client, error)
 		defaultMimeType = "application/octet-stream"
 	}
 
-	sdk, err := gitea.NewClient(
+	sdkClient, err := gitea.NewClient(
 		giteaRoot,
-		gitea.SetHTTPClient(&stdClient),
+		gitea.SetHTTPClient(&http.Client{Timeout: 10 * time.Second}),
+		gitea.SetToken(cfg.Token),
+		gitea.SetUserAgent("pages-server/"+version.Version),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	sdkFileClient, err := gitea.NewClient(
+		giteaRoot,
+		gitea.SetHTTPClient(&http.Client{Timeout: 1 * time.Hour}),
 		gitea.SetToken(cfg.Token),
 		gitea.SetUserAgent("pages-server/"+version.Version),
 	)
 
 	return &Client{
-		sdkClient:     sdk,
+		sdkClient:     sdkClient,
+		sdkFileClient: sdkFileClient,
 		responseCache: respCache,
 
 		giteaRoot: giteaRoot,
@@ -154,7 +164,7 @@ func (client *Client) ServeRawContent(ctx *context.Context, targetOwner, targetR
 	}
 	log.Trace().Msg("file not in cache")
 	// not in cache, open reader via gitea api
-	reader, resp, err := client.sdkClient.GetFileReader(targetOwner, targetRepo, ref, resource, client.supportLFS)
+	reader, resp, err := client.sdkFileClient.GetFileReader(targetOwner, targetRepo, ref, resource, client.supportLFS)
 	if resp != nil {
 		switch resp.StatusCode {
 		case http.StatusOK:

From 9450415545d945e3e2061b60cb3da5f79f6f305d Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Tue, 21 Jan 2025 00:25:00 +0000
Subject: [PATCH 182/196] chore(deps): update woodpeckerci/plugin-release
 docker tag to v0.2.3 (#424)

---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 7a99a42..2860f81 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -63,7 +63,7 @@ steps:
 
   release:
     depends_on: build
-    image: woodpeckerci/plugin-release:0.2.2
+    image: woodpeckerci/plugin-release:0.2.3
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite

From 0b7687684a888f23dda4ff3d445eb4b05201f1db Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Fri, 7 Feb 2025 10:33:44 +0000
Subject: [PATCH 183/196] chore(deps): update mstruebing/editorconfig-checker
 docker tag to v3.1.2 (#423)

---
 .woodpecker/lint.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 0d43a8f..f2dee1f 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -11,11 +11,11 @@ steps:
       - go version
       - go install mvdan.cc/gofumpt@latest
       - "[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }"
-      - golangci-lint run --timeout 5m --build-tags integration
+      - golangci-lint run --timeout 10m --build-tags integration
 
   editor-config:
     depends_on: []
-    image: mstruebing/editorconfig-checker:v3.0.3
+    image: mstruebing/editorconfig-checker:v3.2.0
 
   yamllint:
     image: pipelinecomponents/yamllint:0.33.0

From a761f1284978f57082670406096f4959d7849c89 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Fri, 7 Feb 2025 11:00:09 +0000
Subject: [PATCH 184/196] chore(deps): update woodpeckerci/plugin-docker-buildx
 docker tag to v5.2.0 (#426)

---
 .woodpecker/build.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 2860f81..2b8b7eb 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -20,7 +20,7 @@ steps:
 
   docker-dryrun:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:5.1.0
+    image: woodpeckerci/plugin-docker-buildx:5.2.1
     settings:
       dockerfile: Dockerfile
       platforms: linux/amd64
@@ -75,7 +75,7 @@ steps:
 
   docker-next:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:5.1.0
+    image: woodpeckerci/plugin-docker-buildx:5.2.1
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile
@@ -90,7 +90,7 @@ steps:
       - event: [push]
 
   'Publish PR image':
-    image: woodpeckerci/plugin-docker-buildx:5.1.0
+    image: woodpeckerci/plugin-docker-buildx:5.2.1
     depends_on: test
     settings:
       registry: codeberg.org
@@ -108,7 +108,7 @@ steps:
 
   docker-release:
     depends_on: vendor
-    image: woodpeckerci/plugin-docker-buildx:5.1.0
+    image: woodpeckerci/plugin-docker-buildx:5.2.1
     settings:
       registry: codeberg.org
       dockerfile: Dockerfile

From 7f7ef63e842173548fcf0fe011700b43a8127b39 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sat, 8 Feb 2025 09:52:19 +0000
Subject: [PATCH 185/196] fix(deps): update golang.org/x/exp digest to e0ece0d
 (#427)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/exp | require | digest | `7588d65` -> `e0ece0d` |

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNTYuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE1Ni4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Co-authored-by: Patrick Schratz <pat-s@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/427
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 go.mod |   2 +-
 go.sum | 113 ++-------------------------------------------------------
 2 files changed, 5 insertions(+), 110 deletions(-)

diff --git a/go.mod b/go.mod
index 1555baa..9442210 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,7 @@ require (
 	github.com/rs/zerolog v1.33.0
 	github.com/stretchr/testify v1.10.0
 	github.com/urfave/cli/v2 v2.27.5
-	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
+	golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
 	xorm.io/xorm v1.3.9
 )
 
diff --git a/go.sum b/go.sum
index 426079f..b3f83de 100644
--- a/go.sum
+++ b/go.sum
@@ -36,8 +36,6 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
 cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
 cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
 cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
-cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ=
-cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM=
 cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
 cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
 cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -99,12 +97,8 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo
 cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
 cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
 cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
-cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
-cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
 cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM=
 cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A=
-cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
-cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
 cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
 cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
 cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
@@ -181,8 +175,6 @@ cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvj
 cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
 cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
-cloud.google.com/go/compute v1.31.1 h1:SObuy8Fs6woazArpXp1fsHCw+ZH4iJ/8dGGTxUhHZQA=
-cloud.google.com/go/compute v1.31.1/go.mod h1:hyOponWhXviDptJCJSoEh89XO1cfv616wbwbkde1/+8=
 cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
 cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
@@ -606,8 +598,6 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS
 cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=
 cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
 cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
-code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y=
-code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
 code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU=
 code.gitea.io/sdk/gitea v0.20.0/go.mod h1:faouBHC/zyx5wLgjmRKR62ydyvMzwWf3QnU0bH7Cw6U=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@@ -623,17 +613,12 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs
 github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
@@ -704,8 +689,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/aliyun/alibaba-cloud-sdk-go v1.63.76 h1:mg/+23+/gAw6zdxv9I5dPCj666WJPLk8S1nXm0dOumQ=
-github.com/aliyun/alibaba-cloud-sdk-go v1.63.76/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
 github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 h1:YBkf7H5CSgrlb3C1aWcpDt7Vk8UEGFPeD2OOirtt6IM=
 github.com/aliyun/alibaba-cloud-sdk-go v1.63.83/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
@@ -722,28 +705,16 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
 github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
 github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
-github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
-github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
 github.com/aws/aws-sdk-go-v2 v1.33.0 h1:Evgm4DI9imD81V0WwD+TN4DCwjUMdc94TrduMLbgZJs=
 github.com/aws/aws-sdk-go-v2 v1.33.0/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
-github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=
-github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M=
 github.com/aws/aws-sdk-go-v2/config v1.29.0 h1:Vk/u4jof33or1qAQLdofpjKV7mQQT7DcUpnYx8kdmxY=
 github.com/aws/aws-sdk-go-v2/config v1.29.0/go.mod h1:iXAZK3Gxvpq3tA+B9WaDYpZis7M8KFgdrDPMmHrgbJM=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.53 h1:lwrVhiEDW5yXsuVKlFVUnR2R50zt2DklhOyeLETqDuE=
 github.com/aws/aws-sdk-go-v2/credentials v1.17.53/go.mod h1:CkqM1bIw/xjEpBMhBnvqUXYZbpCFuj6dnCAyDk2AtAY=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 h1:5grmdTdMsovn9kPZPI23Hhvp0ZyNm5cRO+IZFIYiAfw=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24/go.mod h1:zqi7TVKTswH3Ozq28PkmBmgzG1tona7mo9G2IJg4Cis=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 h1:igORFSiH3bfq4lxKFkTSYDhJEUCYo6C8VKiWJjYwQuQ=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28/go.mod h1:3So8EA/aAYm36L7XIvCVwLa0s5N0P7o2b1oqnx/2R4g=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 h1:1mOW9zAUMhTSrMDssEHS/ajx8JcAj/IcftzcmNlmVLI=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28/go.mod h1:kGlXVIWDfvt2Ox5zEaNglmq0hXPHgQFNMix33Tw22jA=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
@@ -751,28 +722,16 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJN
 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 h1:TQmKDyETFGiXVhZfQ/I0cCFziqqX58pi4tKJGYGFSz0=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9/go.mod h1:HVLPK2iHQBUx7HfZeOQSEu3v2ubZaAY2YPbAm5/WUyY=
-github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 h1:+lmJoqxuUoPlSfGk5JYQQivd9YFjUvRZR6RPY+Wcx48=
-github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8/go.mod h1:Gg8/myP4+rgRi4+j9gQdbOEnMtwMAUUIeXo+nKCFVj8=
 github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.10 h1:D8L0rXdD2L5/DoxkC3sWfBfXbYnabB6F6f4XmqgMQPo=
 github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.10/go.mod h1:UIgmS/dicuAwwgEbzEbpyXiPxMW5bU3yvxtZJmNuxFQ=
-github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 h1:0jMtawybbfpFEIMy4wvfyW2Z4YLr7mnuzT0fhR67Nrc=
-github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4/go.mod h1:xlMODgumb0Pp8bzfpojqelDrf8SL9rb5ovwmwKJl+oU=
 github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 h1:njgAP7Rtt4DGdTGFPhJ4gaZXCD1CDj/SZDa5W4ZgSTs=
 github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1/go.mod h1:TN4PcCL0lvqmYcv+AV8iZFC4Sd0FM06QDaoBXrFEftU=
-github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g=
-github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg=
 github.com/aws/aws-sdk-go-v2/service/sso v1.24.10 h1:DyZUj3xSw3FR3TXSwDhPhuZkkT14QHBiacdbUVcD0Dg=
 github.com/aws/aws-sdk-go-v2/service/sso v1.24.10/go.mod h1:Ro744S4fKiCCuZECXgOi760TiYylUM8ZBf6OGiZzJtY=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.9 h1:I1TsPEs34vbpOnR81GIcAq4/3Ud+jRHVGwx6qLQUHLs=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.9/go.mod h1:Fzsj6lZEb8AkTE5S68OhcbBqeWPsR8RnGuKPr8Todl8=
-github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY=
-github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc=
 github.com/aws/aws-sdk-go-v2/service/sts v1.33.8 h1:pqEJQtlKWvnv3B6VRt60ZmsHy3SotlEBvfUBPB1KVcM=
 github.com/aws/aws-sdk-go-v2/service/sts v1.33.8/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw=
 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
@@ -815,14 +774,10 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
-github.com/civo/civogo v0.3.89 h1:g+I4NGVa5t0L2Z9+QbnEAqxE/3OCDUYvepje3oUkKVo=
-github.com/civo/civogo v0.3.89/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
 github.com/civo/civogo v0.3.92 h1:fiA9nsyvEU9vVwA8ssIDSGt6gsWs6meqgaKrimAEaI0=
 github.com/civo/civogo v0.3.92/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
 github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g=
-github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0=
 github.com/cloudflare/cloudflare-go v0.114.0 h1:ucoti4/7Exo0XQ+rzpn1H+IfVVe++zgiM+tyKtf0HUA=
 github.com/cloudflare/cloudflare-go v0.114.0/go.mod h1:O7fYfFfA6wKqKFn2QIR9lhj7FDw6VQCGOY6hd2TBtd0=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@@ -892,8 +847,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
 github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
 github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
-github.com/exoscale/egoscale/v3 v3.1.7 h1:Q6p9tOVY0IiOW0fUpaPQWY7ggGEuSPZLAGxFgDd2sCE=
-github.com/exoscale/egoscale/v3 v3.1.7/go.mod h1:GHKucK/J26v8PGWztGdhxWNMjrjG9PbelxKCJ4YI11Q=
 github.com/exoscale/egoscale/v3 v3.1.8 h1:LrZ7cAk3Wum9ZncKR1gRPpXsdvCoQb7KoUi3+ZxSTvY=
 github.com/exoscale/egoscale/v3 v3.1.8/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -923,8 +876,6 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
 github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
 github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
-github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
-github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
 github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
 github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -971,12 +922,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
-github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
 github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
-github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
-github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
 github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E=
 github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
 github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
@@ -1105,8 +1052,6 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
 github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
-github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
-github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
 github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
 github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -1220,8 +1165,6 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
 github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
 github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129 h1:+4ocCqiQFWTODV2Q94q/DMGrmoXFIrimPn+SZZPCbBs=
-github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.129/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132 h1:5LqzrJa8LADcY0sDEdV35e8nbwI7RoUQEt+KXWvWoY0=
 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.132/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
 github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
@@ -1306,8 +1249,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/linode/linodego v1.44.0 h1:JZLLWzCAx3CmHSV9NmCoXisuqKtrmPhfY9MrgvaHMUY=
-github.com/linode/linodego v1.44.0/go.mod h1:umdoNOmtbqAdGQbmQnPFZ2YS4US+/mU/1bA7MjoKAvg=
 github.com/linode/linodego v1.46.0 h1:+uOG4SD2MIrhbrLrvOD5HrbdLN3D19Wgn3MgdUNQjeU=
 github.com/linode/linodego v1.46.0/go.mod h1:vyklQRzZUWhFVBZdYx4dcYJU/gG9yKB9VUcUs6ub0Lk=
 github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
@@ -1332,7 +1273,6 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
 github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -1458,8 +1398,6 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
 github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
-github.com/oracle/oci-go-sdk/v65 v65.81.1 h1:JYc47bk8n/MUchA2KHu1ggsCQzlJZQLJ+tTKfOho00E=
-github.com/oracle/oci-go-sdk/v65 v65.81.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
 github.com/oracle/oci-go-sdk/v65 v65.81.2 h1:yhdu9xphYOJed+MPQP4OHAtPlSm0dxKY2L/lf5ntJbU=
 github.com/oracle/oci-go-sdk/v65 v65.81.2/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
 github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
@@ -1535,17 +1473,14 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
-github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
 github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
+github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
 github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE=
 github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad h1:WtSUHi5zthjudjIi3L6QmL/V9vpJPbc/j/F2u55d3fs=
 github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad/go.mod h1:h0+DiDRe2Y+6iHTjIq/9HzUq7NII/Nffp0HkFrsAKq4=
-github.com/reugn/equalizer v0.2.0 h1:kJy7909B2SYFLKnpMpfPK//B+IWhx7kEq/N8AUrt27w=
-github.com/reugn/equalizer v0.2.0/go.mod h1:Jxi79RdAJ9Ro4nET7fANm0v7TehzmyyAkv2w0TPBSZQ=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -1572,8 +1507,6 @@ github.com/sacloud/iaas-api-go v1.14.0/go.mod h1:C8os2Mnj0TOmMdSllwhaDWKMVG2ysFn
 github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo=
 github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8=
 github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ=
-github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
-github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
 github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
 github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
 github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
@@ -1620,8 +1553,6 @@ github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
 github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
-github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
-github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
 github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
 github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@@ -1671,12 +1602,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071 h1:Q/Ue/yRv4HSpaiFAnXIshoDjxzwyhwezEidXU49Boa4=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1071/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1084 h1:kVMffrA57uPQvNaPBXSp1NJPiz+Y0a/bxqiEVBjrZAI=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1084/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071 h1:1P0aq3ss/sku5n2qq18qTeyApQVMcy9/b7qbXPejCSc=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1071/go.mod h1:wIqmfnhHUG/GMIK4OfsvXA29+Ei4Gmz5K3SWftLVxFQ=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 h1:kwctN0WQYt8/iKP+iRCTCwdzEMIXsXklbRIib5rjeQ8=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084/go.mod h1:qE67ApiBzeRvzeDsV+GxyIDbVIDemsKpHXllQATz/Vw=
 github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
@@ -1696,12 +1623,8 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
 github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
 github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
 github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
-github.com/volcengine/volc-sdk-golang v1.0.190 h1:bva2jbMuce9LIqWRlfWbZcyriRs17ZaSoLZulMpk6K8=
-github.com/volcengine/volc-sdk-golang v1.0.190/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE=
 github.com/volcengine/volc-sdk-golang v1.0.193 h1:mL1rlk+m9SaqF2MSGFWfigEaz10ZVJiYDnFuWfj65Ww=
 github.com/volcengine/volc-sdk-golang v1.0.193/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE=
-github.com/vultr/govultr/v3 v3.13.0 h1:Tq26syAju/cTlgorJJX391ZTbhILbe4WzIatyeStXCM=
-github.com/vultr/govultr/v3 v3.13.0/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w=
 github.com/vultr/govultr/v3 v3.14.1 h1:9BpyZgsWasuNoR39YVMcq44MSaF576Z4D+U3ro58eJQ=
 github.com/vultr/govultr/v3 v3.14.1/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@@ -1743,8 +1666,6 @@ go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq
 go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
 go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
 go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
-go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
-go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
 go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
 go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -1759,20 +1680,14 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
-go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
-go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
 go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
 go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
-go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
-go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
 go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
 go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
 go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
 go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
 go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
 go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
-go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
-go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
 go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
 go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
@@ -1847,8 +1762,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
-golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
-golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
+golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
+golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -2002,8 +1917,6 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec
 golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
 golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
 golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
-golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
-golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
 golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -2190,8 +2103,6 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
-golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
 golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -2339,8 +2250,6 @@ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60c
 google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
 google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
 google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
-google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA=
-google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE=
 google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU=
 google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -2483,16 +2392,10 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl
 google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
 google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
-google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 h1:e26eS1K69yxjjNNHYqjN49y95kcaQLJ3TL5h68dcA1E=
-google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:i5btTErZyoKCCubju3HS5LVho4nZd3yFnEp6moqeUjE=
 google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f h1:387Y+JbxF52bmesc8kq1NyYIp33dnxCw6eiA7JMsTmw=
 google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:0joYwWwLQh18AOj8zMYeZLjzuqcYTU3/nC5JdCvC3JI=
-google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0=
-google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4=
 google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
 google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -2537,8 +2440,6 @@ google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v
 google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
 google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
 google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
-google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
-google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
 google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
 google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
@@ -2559,8 +2460,6 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
-google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
 google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -2612,12 +2511,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
-k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
-k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
 k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
 k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
-k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
-k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
 k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
 k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=

From 91c4577a43de01451ba314b4725fbf2acd084105 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sat, 15 Feb 2025 00:05:42 +0000
Subject: [PATCH 186/196] fix(deps): update golang.org/x/exp digest to 939b2ce
 (#430)

---
 go.mod | 16 ++++++++--------
 go.sum | 36 ++++++++++++++++++------------------
 2 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/go.mod b/go.mod
index 9442210..5df680c 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,7 @@ require (
 	github.com/rs/zerolog v1.33.0
 	github.com/stretchr/testify v1.10.0
 	github.com/urfave/cli/v2 v2.27.5
-	golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
+	golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
 	xorm.io/xorm v1.3.9
 )
 
@@ -208,15 +208,15 @@ require (
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/ratelimit v0.3.1 // indirect
-	golang.org/x/crypto v0.32.0 // indirect
-	golang.org/x/mod v0.22.0 // indirect
-	golang.org/x/net v0.34.0 // indirect
+	golang.org/x/crypto v0.33.0 // indirect
+	golang.org/x/mod v0.23.0 // indirect
+	golang.org/x/net v0.35.0 // indirect
 	golang.org/x/oauth2 v0.25.0 // indirect
-	golang.org/x/sync v0.10.0 // indirect
-	golang.org/x/sys v0.29.0 // indirect
-	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/sync v0.11.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
 	golang.org/x/time v0.9.0 // indirect
-	golang.org/x/tools v0.29.0 // indirect
+	golang.org/x/tools v0.30.0 // indirect
 	google.golang.org/api v0.217.0 // indirect
 	google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
diff --git a/go.sum b/go.sum
index b3f83de..6ba2d51 100644
--- a/go.sum
+++ b/go.sum
@@ -1745,8 +1745,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
-golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
+golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1762,8 +1762,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
-golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
-golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
+golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
+golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -1806,8 +1806,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
-golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
+golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1886,8 +1886,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
-golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
-golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1935,8 +1935,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -2054,8 +2054,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -2071,8 +2071,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
-golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
-golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2093,8 +2093,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -2174,8 +2174,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
-golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
-golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
+golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
+golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From c7fddf202db8b136076d043ff37d519edc7a6033 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sat, 15 Feb 2025 19:08:08 +0000
Subject: [PATCH 187/196] chore: update golang (#431)

ping #428

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/431
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .woodpecker/build.yml | 10 +++++-----
 .woodpecker/lint.yml  |  2 +-
 Dockerfile            |  2 +-
 flake.lock            |  4 ++--
 go.mod                |  4 +---
 5 files changed, 10 insertions(+), 12 deletions(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 2b8b7eb..20e47eb 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -5,13 +5,13 @@ when:
 steps:
   # use vendor to cache dependencies
   vendor:
-    image: golang:1.23
+    image: golang:1.24
     commands:
       - go mod vendor
 
   build:
     depends_on: vendor
-    image: codeberg.org/6543/docker-images/golang_just:go-1.23
+    image: codeberg.org/6543/docker-images/golang_just:go-1.24
     commands:
       - go version
       - just build
@@ -32,7 +32,7 @@ steps:
 
   build-tag:
     depends_on: vendor
-    image: codeberg.org/6543/docker-images/golang_just:go-1.23
+    image: codeberg.org/6543/docker-images/golang_just:go-1.24
     commands:
       - go version
       - just build-tag ${CI_COMMIT_TAG##v}
@@ -41,7 +41,7 @@ steps:
 
   test:
     depends_on: build
-    image: codeberg.org/6543/docker-images/golang_just:go-1.23
+    image: codeberg.org/6543/docker-images/golang_just:go-1.24
     commands:
       - just test
     when:
@@ -50,7 +50,7 @@ steps:
   integration-tests:
     failure: ignore
     depends_on: build
-    image: codeberg.org/6543/docker-images/golang_just:go-1.23
+    image: codeberg.org/6543/docker-images/golang_just:go-1.24
     commands:
       - just integration
     environment:
diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index f2dee1f..bdb6fb0 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.63.4
+    image: golangci/golangci-lint:v1.64.5
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest
diff --git a/Dockerfile b/Dockerfile
index 7cc21c5..dfec6d0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 # Set the default Go version as a build argument
-ARG XGO="go-1.23.x"
+ARG XGO="go-1.24.x"
 
 # Use xgo (a Go cross-compiler tool) as build image
 FROM --platform=$BUILDPLATFORM techknowlogick/xgo:${XGO} AS build
diff --git a/flake.lock b/flake.lock
index b3837d9..ef63308 100644
--- a/flake.lock
+++ b/flake.lock
@@ -20,8 +20,8 @@
     "nixpkgs": {
       "locked": {
         "lastModified": 0,
-        "narHash": "sha256-1/WZ7juTZp6oWAFb14WeANN2veJFGZ4AFSNRpk4Jiw8=",
-        "path": "/nix/store/prhs96gxbb9bcr4ia6ql2hsjga50mrdl-source",
+        "narHash": "sha256-WFZDy4bG2RkkCQloIEG8BXEvzyKklFVJbAismOJsIp4=",
+        "path": "/nix/store/c77dsgfxjywplw8bk8s8jlkdsr7a1bi9-source",
         "type": "path"
       },
       "original": {
diff --git a/go.mod b/go.mod
index 5df680c..bff6b77 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,6 @@
 module codeberg.org/codeberg/pages
 
-go 1.23.0
-
-toolchain go1.23.5
+go 1.24.0
 
 require (
 	code.gitea.io/sdk/gitea v0.20.0

From a597e40a556a2e6fa69f1942526725f7c7dda2f8 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Sat, 15 Feb 2025 19:32:29 +0000
Subject: [PATCH 188/196] setup direnv (#433)

Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/433
Co-authored-by: crapStone <me@crapstone.dev>
Co-committed-by: crapStone <me@crapstone.dev>
---
 .envrc     | 1 +
 .gitignore | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 .envrc

diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..c4b17d7
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use_flake
diff --git a/.gitignore b/.gitignore
index 66e7084..fb8d483 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ pages
 certs.sqlite
 .bash_history
 pkg/
+.direnv/

From f123d226a1efc397a54b98c66aedf8d8a305b2e2 Mon Sep 17 00:00:00 2001
From: crapStone <me@crapstone.dev>
Date: Wed, 19 Feb 2025 23:20:06 +0100
Subject: [PATCH 189/196] fix: build docker on release

---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 20e47eb..37e2dfe 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -16,7 +16,7 @@ steps:
       - go version
       - just build
     when:
-      - event: [push, pull_request]
+      - event: [push, pull_request, tag]
 
   docker-dryrun:
     depends_on: vendor

From 4b1faa5ebb678d544f887632c1e8e1624a3bcd9e Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Sun, 2 Mar 2025 00:14:55 +0000
Subject: [PATCH 190/196] fix(deps): update golang.org/x/exp digest to dead583
 (#436)

---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index bff6b77..38ffc0f 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.33.0
 	github.com/stretchr/testify v1.10.0
 	github.com/urfave/cli/v2 v2.27.5
-	golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
+	golang.org/x/exp v0.0.0-20250228200357-dead58393ab7
 	xorm.io/xorm v1.3.9
 )
 
diff --git a/go.sum b/go.sum
index 6ba2d51..1364eed 100644
--- a/go.sum
+++ b/go.sum
@@ -1762,8 +1762,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
+golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c=
+golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=

From 4efb5a7f3b243478c92f93b2339b3f06ae830d81 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Wed, 12 Mar 2025 17:06:57 +0000
Subject: [PATCH 191/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.64.7 (#439)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [golangci/golangci-lint](https://github.com/golangci/golangci-lint) | patch | `v1.64.5` -> `v1.64.7` |

---

### Release Notes

<details>
<summary>golangci/golangci-lint (golangci/golangci-lint)</summary>

### [`v1.64.7`](https://github.com/golangci/golangci-lint/releases/tag/v1.64.7)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.64.6...v1.64.7)

`golangci-lint` is a free and open-source project built by volunteers.

If you value it, consider supporting us, the [maintainers](https://opencollective.com/golangci-lint) and [linter authors](https://golangci-lint.run/product/thanks/).

We appreciate it! :heart:

For key updates, see the [changelog](https://golangci-lint.run/product/changelog/#&#8203;1647).

#### Changelog

-   [`94946f3`](https://github.com/golangci/golangci-lint/commit/94946f3c52a89774b2c7d255311c34f17f8fac7a) build(deps): bump github.com/OpenPeeDeeP/depguard/v2 from 2.2.0 to 2.2.1 ([#&#8203;5509](https://github.com/golangci/golangci-lint/issues/5509))
-   [`132365e`](https://github.com/golangci/golangci-lint/commit/132365e252c985a191c6eaea3f0cc01ca9120ccc) build(deps): bump github.com/golangci/dupl from [`3e9179a`](https://github.com/golangci/golangci-lint/commit/3e9179ac440a) to [`f665c8d`](https://github.com/golangci/golangci-lint/commit/f665c8d69b32) ([#&#8203;5512](https://github.com/golangci/golangci-lint/issues/5512))
-   [`bddd1bc`](https://github.com/golangci/golangci-lint/commit/bddd1bcedbc2f3d767e2362be91ff9eb481493cd) build(deps): bump github.com/securego/gosec/v2 from 2.22.1 to 2.22.2 ([#&#8203;5515](https://github.com/golangci/golangci-lint/issues/5515))
-   [`624fb4e`](https://github.com/golangci/golangci-lint/commit/624fb4e717dc89f1b55d6a84a69355fe64d60036) build(deps): bump golang.org/x/mod from 0.23.0 to 0.24.0 ([#&#8203;5507](https://github.com/golangci/golangci-lint/issues/5507))
-   [`8cffdb7`](https://github.com/golangci/golangci-lint/commit/8cffdb7d21e5b2b89f163f70c60ac9686c9d6180) build(deps): bump golang.org/x/oauth2 from 0.27.0 to 0.28.0 in /scripts/gen_github_action_config in the scripts group ([#&#8203;5521](https://github.com/golangci/golangci-lint/issues/5521))
-   [`7a3f3d7`](https://github.com/golangci/golangci-lint/commit/7a3f3d7c941475ee01edca221c44ea5ba59290a3) build(deps): bump golang.org/x/tools from 0.30.0 to 0.31.0 ([#&#8203;5508](https://github.com/golangci/golangci-lint/issues/5508))
-   [`c13fd5b`](https://github.com/golangci/golangci-lint/commit/c13fd5b7627c436246f36044a575990b5ec75c7d) build(deps): bump honnef.co/go/tools from 0.6.0 to 0.6.1 ([#&#8203;5510](https://github.com/golangci/golangci-lint/issues/5510))

### [`v1.64.6`](https://github.com/golangci/golangci-lint/blob/HEAD/CHANGELOG.md#v1646)

[Compare Source](https://github.com/golangci/golangci-lint/compare/v1.64.5...v1.64.6)

1.  Linters bug fixes
    -   `asciicheck`: from 0.4.0 to 0.4.1
    -   `contextcheck`: from 1.1.5 to 1.1.6
    -   `errcheck`: from 1.8.0 to 1.9.0
    -   `exptostd`: from 0.4.1 to 0.4.2
    -   `ginkgolinter`: from 0.19.0 to 0.19.1
    -   `go-exhaustruct`: from 3.3.0 to 3.3.1
    -   `gocheckcompilerdirectives`: from 1.2.1 to 1.3.0
    -   `godot`: from 1.4.20 to 1.5.0
    -   `perfsprint`: from 0.8.1 to 0.8.2
    -   `revive`: from 1.6.1 to 1.7.0
    -   `tagalign`: from 1.4.1 to 1.4.2

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xOTEuNCIsInVwZGF0ZWRJblZlciI6IjM5LjE5NS4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/439
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index bdb6fb0..d7cf141 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.64.5
+    image: golangci/golangci-lint:v1.64.7
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From e2395a057405a10285bfa583dc9c968a7d99b254 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Wed, 12 Mar 2025 22:18:46 +0000
Subject: [PATCH 192/196] fix(deps): update golang.org/x/exp digest to 054e65f
 (#438)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/exp | require | digest | `dead583` -> `054e65f` |

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xOTEuNCIsInVwZGF0ZWRJblZlciI6IjM5LjE5MS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/438
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 go.mod | 16 ++++++++--------
 go.sum | 36 ++++++++++++++++++------------------
 2 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/go.mod b/go.mod
index 38ffc0f..d5c8e24 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/rs/zerolog v1.33.0
 	github.com/stretchr/testify v1.10.0
 	github.com/urfave/cli/v2 v2.27.5
-	golang.org/x/exp v0.0.0-20250228200357-dead58393ab7
+	golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
 	xorm.io/xorm v1.3.9
 )
 
@@ -206,15 +206,15 @@ require (
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/ratelimit v0.3.1 // indirect
-	golang.org/x/crypto v0.33.0 // indirect
-	golang.org/x/mod v0.23.0 // indirect
-	golang.org/x/net v0.35.0 // indirect
+	golang.org/x/crypto v0.36.0 // indirect
+	golang.org/x/mod v0.24.0 // indirect
+	golang.org/x/net v0.37.0 // indirect
 	golang.org/x/oauth2 v0.25.0 // indirect
-	golang.org/x/sync v0.11.0 // indirect
-	golang.org/x/sys v0.30.0 // indirect
-	golang.org/x/text v0.22.0 // indirect
+	golang.org/x/sync v0.12.0 // indirect
+	golang.org/x/sys v0.31.0 // indirect
+	golang.org/x/text v0.23.0 // indirect
 	golang.org/x/time v0.9.0 // indirect
-	golang.org/x/tools v0.30.0 // indirect
+	golang.org/x/tools v0.31.0 // indirect
 	google.golang.org/api v0.217.0 // indirect
 	google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
diff --git a/go.sum b/go.sum
index 1364eed..9e78abb 100644
--- a/go.sum
+++ b/go.sum
@@ -1745,8 +1745,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
-golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1762,8 +1762,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
-golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c=
-golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -1806,8 +1806,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
-golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
+golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1886,8 +1886,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
-golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
-golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1935,8 +1935,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
-golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -2054,8 +2054,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -2071,8 +2071,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
-golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2093,8 +2093,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -2174,8 +2174,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
-golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
-golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
+golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
+golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From a0d202ef55427cd063e2526384046a0c9374c704 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Wed, 12 Mar 2025 22:19:23 +0000
Subject: [PATCH 193/196] chore(deps): update woodpeckerci/plugin-release
 docker tag to v0.2.4 (#434)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [woodpeckerci/plugin-release](https://codeberg.org/woodpecker-plugins/release) ([source](https://codeberg.org/woodpecker-plugins/release.git)) | patch | `0.2.3` -> `0.2.4` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/release (woodpeckerci/plugin-release)</summary>

### [`v0.2.4`](https://codeberg.org/woodpecker-plugins/release/blob/HEAD/CHANGELOG.md#024---2025-02-14)

[Compare Source](https://codeberg.org/woodpecker-plugins/release/compare/v0.2.3...v0.2.4)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;qwerty287](https://github.com/qwerty287), [@&#8203;woodpecker-bot](https://github.com/woodpecker-bot)

##### 🐛 Bug Fixes

-   Fix attachment fetching \[[#&#8203;33](https://codeberg.org/woodpecker-plugins/release/pulls/33)]

##### 📦️ Dependency

-   fix(deps): update module github.com/google/go-github/v68 to v69 \[[#&#8203;30](https://codeberg.org/woodpecker-plugins/release/pulls/30)]
-   chore(deps): update woodpeckerci/plugin-release docker tag to v0.2.3 \[[#&#8203;28](https://codeberg.org/woodpecker-plugins/release/pulls/28)]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE3My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/434
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 37e2dfe..7a1f957 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -63,7 +63,7 @@ steps:
 
   release:
     depends_on: build
-    image: woodpeckerci/plugin-release:0.2.3
+    image: woodpeckerci/plugin-release:0.2.4
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite

From 4672cdb54d437a1b2c9881f12b95204b8720094f Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Wed, 12 Mar 2025 23:16:56 +0000
Subject: [PATCH 194/196] chore(deps): update
 docker.io/woodpeckerci/plugin-prettier docker tag to v1.3.0 (#435)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [docker.io/woodpeckerci/plugin-prettier](https://codeberg.org/woodpecker-plugins/prettier) ([source](https://codeberg.org/woodpecker-plugins/prettier.git)) | minor | `1.1.0` -> `1.3.0` |

---

### Release Notes

<details>
<summary>woodpecker-plugins/prettier (docker.io/woodpeckerci/plugin-prettier)</summary>

### [`v1.3.0`](https://codeberg.org/woodpecker-plugins/prettier/blob/HEAD/CHANGELOG.md#130---2025-02-18)

[Compare Source](https://codeberg.org/woodpecker-plugins/prettier/compare/1.2.1...1.3.0)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;OCram85](https://github.com/OCram85)

##### 📈 Enhancement

-   adds output parameter for prettier results \[[#&#8203;25](https://codeberg.org/woodpecker-plugins/prettier/pulls/25)]

### [`v1.2.1`](https://codeberg.org/woodpecker-plugins/prettier/blob/HEAD/CHANGELOG.md#121---2025-02-13)

[Compare Source](https://codeberg.org/woodpecker-plugins/prettier/compare/1.1.0...1.2.1)

##### ❤️ Thanks to all contributors! ❤️

[@&#8203;woodpecker-bot](https://github.com/woodpecker-bot)

##### 📦️ Dependency

-   chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v5.2.1 \[[#&#8203;24](https://codeberg.org/woodpecker-plugins/prettier/pulls/24)]
-   chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v5.2.0 \[[#&#8203;22](https://codeberg.org/woodpecker-plugins/prettier/pulls/22)]
-   chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.1.3 \[[#&#8203;21](https://codeberg.org/woodpecker-plugins/prettier/pulls/21)]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "* * * * 0,6" (UTC), Automerge - "* 0-3 * * *" (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE3Ni40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: woodpecker-bot <woodpecker-bot@obermui.de>
Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/435
Co-authored-by: Dependency bot <renovate-bot@noreply.codeberg.org>
Co-committed-by: Dependency bot <renovate-bot@noreply.codeberg.org>
---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index d7cf141..ffa57c3 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -24,7 +24,7 @@ steps:
       - yamllint .
 
   prettier:
-    image: docker.io/woodpeckerci/plugin-prettier:1.1.0
+    image: docker.io/woodpeckerci/plugin-prettier:1.3.0
     depends_on: []
     settings:
       version: 3.2.5

From 0e873d97836b676a824f2ae631ad6ab7d5d92ce3 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Tue, 25 Mar 2025 00:04:28 +0000
Subject: [PATCH 195/196] chore(deps): update golangci/golangci-lint docker tag
 to v1.64.8 (#447)

---
 .woodpecker/lint.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index ffa57c3..f973eb8 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -6,7 +6,7 @@ when:
 steps:
   lint:
     depends_on: []
-    image: golangci/golangci-lint:v1.64.7
+    image: golangci/golangci-lint:v1.64.8
     commands:
       - go version
       - go install mvdan.cc/gofumpt@latest

From d7deecf5f9202d559fb830eb0fdf412119dc7273 Mon Sep 17 00:00:00 2001
From: Dependency bot <renovate-bot@noreply.codeberg.org>
Date: Fri, 28 Mar 2025 00:04:20 +0000
Subject: [PATCH 196/196] chore(deps): update woodpeckerci/plugin-release
 docker tag to v0.2.5 (#449)

---
 .woodpecker/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 7a1f957..a872106 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -63,7 +63,7 @@ steps:
 
   release:
     depends_on: build
-    image: woodpeckerci/plugin-release:0.2.4
+    image: woodpeckerci/plugin-release:0.2.5
     settings:
       base_url: https://codeberg.org
       file_exists: overwrite