+5
-2
conf/config.example.toml
+5
-2
conf/config.example.toml
···
16
index-repos = ["<user>.codeberg.page", "pages"]
17
index-repo-branch = "main"
18
authorization = "forgejo"
19
-
fallback-proxy-to = "https://codeberg.page"
20
21
[storage]
22
type = "fs"
···
24
[storage.fs]
25
root = "./data"
26
27
-
[storage.s3] # non-default bucket configuration
28
endpoint = "play.min.io"
29
access-key-id = "Q3AM3UQ867SPQQA43P2F"
30
secret-access-key = "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
···
16
index-repos = ["<user>.codeberg.page", "pages"]
17
index-repo-branch = "main"
18
authorization = "forgejo"
19
+
20
+
[fallback] # non-default section
21
+
proxy-to = "https://codeberg.page"
22
+
insecure = false
23
24
[storage]
25
type = "fs"
···
27
[storage.fs]
28
root = "./data"
29
30
+
[storage.s3] # non-default section
31
endpoint = "play.min.io"
32
access-key-id = "Q3AM3UQ867SPQQA43P2F"
33
secret-access-key = "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
+33
-25
src/caddy.go
+33
-25
src/caddy.go
···
1
package git_pages
2
3
import (
4
"crypto/tls"
5
"fmt"
6
"net"
···
34
// Pages v2, which would under some circumstances return certificates with subjectAltName
35
// not valid for the SNI. Go's TLS stack makes `tls.Dial` return an error for these,
36
// thankfully making it unnecessary to examine X.509 certificates manually here.)
37
-
for _, wildcardConfig := range config.Wildcard {
38
-
if wildcardConfig.FallbackProxyTo == "" {
39
-
continue
40
-
}
41
-
fallbackURL, err := url.Parse(wildcardConfig.FallbackProxyTo)
42
-
if err != nil {
43
-
continue
44
-
}
45
-
if fallbackURL.Scheme != "https" {
46
-
continue
47
-
}
48
-
connectHost := fallbackURL.Host
49
-
if fallbackURL.Port() != "" {
50
-
connectHost += ":" + fallbackURL.Port()
51
-
} else {
52
-
connectHost += ":443"
53
-
}
54
-
logc.Printf(r.Context(), "caddy: check TLS %s", fallbackURL)
55
-
connection, err := tls.Dial("tcp", connectHost, &tls.Config{ServerName: domain})
56
-
if err != nil {
57
-
continue
58
-
}
59
-
connection.Close()
60
-
found = true
61
-
break
62
}
63
}
64
···
74
fmt.Fprintln(w, err)
75
}
76
}
···
1
package git_pages
2
3
import (
4
+
"context"
5
"crypto/tls"
6
"fmt"
7
"net"
···
35
// Pages v2, which would under some circumstances return certificates with subjectAltName
36
// not valid for the SNI. Go's TLS stack makes `tls.Dial` return an error for these,
37
// thankfully making it unnecessary to examine X.509 certificates manually here.)
38
+
found, err = tryDialWithSNI(r.Context(), domain)
39
+
if err != nil {
40
+
logc.Printf(r.Context(), "caddy err: check SNI: %s\n", err)
41
}
42
}
43
···
53
fmt.Fprintln(w, err)
54
}
55
}
56
+
57
+
func tryDialWithSNI(ctx context.Context, domain string) (bool, error) {
58
+
if config.Fallback.ProxyTo == "" {
59
+
return false, nil
60
+
}
61
+
62
+
fallbackURL, err := url.Parse(config.Fallback.ProxyTo)
63
+
if err != nil {
64
+
return false, err
65
+
}
66
+
if fallbackURL.Scheme != "https" {
67
+
return false, nil
68
+
}
69
+
70
+
connectHost := fallbackURL.Host
71
+
if fallbackURL.Port() != "" {
72
+
connectHost += ":" + fallbackURL.Port()
73
+
} else {
74
+
connectHost += ":443"
75
+
}
76
+
77
+
logc.Printf(ctx, "caddy: check TLS %s", fallbackURL)
78
+
connection, err := tls.Dial("tcp", connectHost, &tls.Config{ServerName: domain})
79
+
if err != nil {
80
+
return false, err
81
+
}
82
+
connection.Close()
83
+
return true, nil
84
+
}
+11
-7
src/config.go
+11
-7
src/config.go
···
41
LogLevel string `toml:"log-level" default:"info"`
42
Server ServerConfig `toml:"server"`
43
Wildcard []WildcardConfig `toml:"wildcard"`
44
Storage StorageConfig `toml:"storage"`
45
Limits LimitsConfig `toml:"limits"`
46
Observability ObservabilityConfig `toml:"observability"`
···
53
}
54
55
type WildcardConfig struct {
56
-
Domain string `toml:"domain"`
57
-
CloneURL string `toml:"clone-url"`
58
-
IndexRepos []string `toml:"index-repos" default:"[]"`
59
-
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
60
-
Authorization string `toml:"authorization"`
61
-
FallbackProxyTo string `toml:"fallback-proxy-to"`
62
-
FallbackInsecure bool `toml:"fallback-insecure"`
63
}
64
65
type CacheConfig struct {
···
41
LogLevel string `toml:"log-level" default:"info"`
42
Server ServerConfig `toml:"server"`
43
Wildcard []WildcardConfig `toml:"wildcard"`
44
+
Fallback FallbackConfig `toml:"fallback"`
45
Storage StorageConfig `toml:"storage"`
46
Limits LimitsConfig `toml:"limits"`
47
Observability ObservabilityConfig `toml:"observability"`
···
54
}
55
56
type WildcardConfig struct {
57
+
Domain string `toml:"domain"`
58
+
CloneURL string `toml:"clone-url"`
59
+
IndexRepos []string `toml:"index-repos" default:"[]"`
60
+
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
61
+
Authorization string `toml:"authorization"`
62
+
}
63
+
64
+
type FallbackConfig struct {
65
+
ProxyTo string `toml:"proxy-to"`
66
+
Insecure bool `toml:"insecure"`
67
}
68
69
type CacheConfig struct {
+30
src/main.go
+30
src/main.go
···
2
3
import (
4
"context"
5
"errors"
6
"flag"
7
"fmt"
···
10
"log/slog"
11
"net"
12
"net/http"
13
"net/url"
14
"os"
15
"runtime/debug"
···
22
23
var config *Config
24
var wildcards []*WildcardPattern
25
var backend Backend
26
27
func configureFeatures() (err error) {
···
63
}
64
}
65
66
func listen(name string, listen string) net.Listener {
67
if listen == "-" {
68
return nil
···
230
configureFeatures(),
231
configureMemLimit(),
232
configureWildcards(),
233
); err != nil {
234
log.Fatalln(err)
235
}
···
392
configureFeatures(),
393
configureMemLimit(),
394
configureWildcards(),
395
); err != nil {
396
// At this point the configuration is in an in-between, corrupted state, so
397
// the only reasonable choice is to crash.
···
2
3
import (
4
"context"
5
+
"crypto/tls"
6
"errors"
7
"flag"
8
"fmt"
···
11
"log/slog"
12
"net"
13
"net/http"
14
+
"net/http/httputil"
15
"net/url"
16
"os"
17
"runtime/debug"
···
24
25
var config *Config
26
var wildcards []*WildcardPattern
27
+
var fallback http.Handler
28
var backend Backend
29
30
func configureFeatures() (err error) {
···
66
}
67
}
68
69
+
func configureFallback() (err error) {
70
+
if config.Fallback.ProxyTo != "" {
71
+
var fallbackURL *url.URL
72
+
fallbackURL, err = url.Parse(config.Fallback.ProxyTo)
73
+
if err != nil {
74
+
err = fmt.Errorf("fallback: %w", err)
75
+
return
76
+
}
77
+
78
+
fallback = &httputil.ReverseProxy{
79
+
Rewrite: func(r *httputil.ProxyRequest) {
80
+
r.SetURL(fallbackURL)
81
+
r.Out.Host = r.In.Host
82
+
r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
83
+
},
84
+
Transport: &http.Transport{
85
+
TLSClientConfig: &tls.Config{
86
+
InsecureSkipVerify: config.Fallback.Insecure,
87
+
},
88
+
},
89
+
}
90
+
}
91
+
return
92
+
}
93
+
94
func listen(name string, listen string) net.Listener {
95
if listen == "-" {
96
return nil
···
258
configureFeatures(),
259
configureMemLimit(),
260
configureWildcards(),
261
+
configureFallback(),
262
); err != nil {
263
log.Fatalln(err)
264
}
···
421
configureFeatures(),
422
configureMemLimit(),
423
configureWildcards(),
424
+
configureFallback(),
425
); err != nil {
426
// At this point the configuration is in an in-between, corrupted state, so
427
// the only reasonable choice is to crash.
+4
-2
src/pages.go
+4
-2
src/pages.go
···
136
result := <-indexManifestCh
137
manifest, manifestMtime, err = result.manifest, result.manifestMtime, result.err
138
if manifest == nil && errors.Is(err, ErrObjectNotFound) {
139
-
if found, fallbackErr := HandleWildcardFallback(w, r); found {
140
-
return fallbackErr
141
} else {
142
w.WriteHeader(http.StatusNotFound)
143
fmt.Fprintf(w, "site not found\n")
···
136
result := <-indexManifestCh
137
manifest, manifestMtime, err = result.manifest, result.manifestMtime, result.err
138
if manifest == nil && errors.Is(err, ErrObjectNotFound) {
139
+
if fallback != nil {
140
+
logc.Printf(r.Context(), "fallback: %s via %s", host, config.Fallback.ProxyTo)
141
+
fallback.ServeHTTP(w, r)
142
+
return nil
143
} else {
144
w.WriteHeader(http.StatusNotFound)
145
fmt.Fprintf(w, "site not found\n")
-54
src/wildcard.go
-54
src/wildcard.go
···
1
package git_pages
2
3
import (
4
-
"crypto/tls"
5
"fmt"
6
-
"net/http"
7
-
"net/http/httputil"
8
-
"net/url"
9
"slices"
10
"strings"
11
···
18
IndexRepos []*fasttemplate.Template
19
IndexBranch string
20
Authorization bool
21
-
FallbackURL *url.URL
22
-
Fallback http.Handler
23
}
24
25
func (pattern *WildcardPattern) GetHost() string {
···
78
return repoURLs, branch
79
}
80
81
-
func (pattern *WildcardPattern) IsFallbackFor(host string) bool {
82
-
if pattern.Fallback == nil {
83
-
return false
84
-
}
85
-
_, found := pattern.Matches(host)
86
-
return found
87
-
}
88
-
89
-
func HandleWildcardFallback(w http.ResponseWriter, r *http.Request) (bool, error) {
90
-
host, err := GetHost(r)
91
-
if err != nil {
92
-
return false, err
93
-
}
94
-
95
-
for _, pattern := range wildcards {
96
-
if pattern.IsFallbackFor(host) {
97
-
logc.Printf(r.Context(), "proxy: %s via %s", pattern.GetHost(), pattern.FallbackURL)
98
-
pattern.Fallback.ServeHTTP(w, r)
99
-
return true, nil
100
-
}
101
-
}
102
-
return false, nil
103
-
}
104
-
105
func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
106
var wildcardPatterns []*WildcardPattern
107
for _, config := range configs {
···
134
}
135
}
136
137
-
var fallbackURL *url.URL
138
-
var fallback http.Handler
139
-
if config.FallbackProxyTo != "" {
140
-
fallbackURL, err = url.Parse(config.FallbackProxyTo)
141
-
if err != nil {
142
-
return nil, fmt.Errorf("wildcard pattern: fallback URL: %w", err)
143
-
}
144
-
145
-
fallback = &httputil.ReverseProxy{
146
-
Rewrite: func(r *httputil.ProxyRequest) {
147
-
r.SetURL(fallbackURL)
148
-
r.Out.Host = r.In.Host
149
-
r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
150
-
},
151
-
Transport: &http.Transport{
152
-
TLSClientConfig: &tls.Config{
153
-
InsecureSkipVerify: config.FallbackInsecure,
154
-
},
155
-
},
156
-
}
157
-
}
158
-
159
wildcardPatterns = append(wildcardPatterns, &WildcardPattern{
160
Domain: strings.Split(config.Domain, "."),
161
CloneURL: cloneURLTemplate,
162
IndexRepos: indexRepoTemplates,
163
IndexBranch: indexRepoBranch,
164
Authorization: authorization,
165
-
FallbackURL: fallbackURL,
166
-
Fallback: fallback,
167
})
168
}
169
return wildcardPatterns, nil
···
1
package git_pages
2
3
import (
4
"fmt"
5
"slices"
6
"strings"
7
···
14
IndexRepos []*fasttemplate.Template
15
IndexBranch string
16
Authorization bool
17
}
18
19
func (pattern *WildcardPattern) GetHost() string {
···
72
return repoURLs, branch
73
}
74
75
func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
76
var wildcardPatterns []*WildcardPattern
77
for _, config := range configs {
···
104
}
105
}
106
107
wildcardPatterns = append(wildcardPatterns, &WildcardPattern{
108
Domain: strings.Split(config.Domain, "."),
109
CloneURL: cloneURLTemplate,
110
IndexRepos: indexRepoTemplates,
111
IndexBranch: indexRepoBranch,
112
Authorization: authorization,
113
})
114
}
115
return wildcardPatterns, nil