Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
fe83bd5b71
|
|||
|
456a0ce108
|
|||
|
81fbc9b2a4
|
|||
|
9baf63915c
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -87,6 +87,7 @@ node_modules/**
|
||||
!.gitignore
|
||||
!version
|
||||
!out.jsonl
|
||||
!Dockerfile*
|
||||
# ...even if they are in subdirectories
|
||||
!*/
|
||||
/blocklist.json
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
= reverse
|
||||
|
||||
simple reverse proxy with letsencrypt, nostr nip-05 and go vanity redirects
|
||||
simple reverse proxy with letsencrypt, nostr nip-05 and go vanity redirects
|
||||
|
||||
after compiling, you need to set the cap_net_bind_service capability to allow it to bind to 80 and 443
|
||||
|
||||
setcap 'cap_net_bind_service=+ep' /path/to/reverse
|
||||
54
main.go
54
main.go
@@ -41,6 +41,9 @@ var orlyFavicon []byte
|
||||
// faviconData holds the favicon data to serve
|
||||
var faviconData []byte
|
||||
|
||||
// faviconContentType holds the content type for the favicon
|
||||
var faviconContentType string
|
||||
|
||||
func main() {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
@@ -60,7 +63,7 @@ type C struct {
|
||||
WTo time.Duration `env:"REVERSE_WTO" usage:"maximum duration before timing out write of the response" default:"5m"`
|
||||
Idle time.Duration `env:"REVERSE_IDLE" usage:"how long idle connection is kept before closing (set rto, wto to 0 to use this)"`
|
||||
Certs []string `env:"REVERSE_CERTS" usage:"certificates and the domain they match: eg: mleku.dev:/path/to/cert - this will indicate to load two, one with extension .key and one with .crt, each expected to be PEM encoded TLS private and public keys, respectively"`
|
||||
Favicon string `env:"REVERSE_FAVICON" usage:"path to custom favicon file to serve as favicon.ico (overrides default orly.png)"`
|
||||
Favicon string `env:"REVERSE_FAVICON" usage:"path to custom favicon file to serve as favicon.ico (overrides default favicon.ico)"`
|
||||
}
|
||||
|
||||
// GetEnv checks if the first command line argument is "env" and returns
|
||||
@@ -475,6 +478,14 @@ func setProxy(mapping map[string]string) (http.Handler, error) {
|
||||
rp := newSingleHostReverseProxy(u)
|
||||
rp.ErrorLog = log2.New(io.Discard, "", 0)
|
||||
rp.BufferPool = bufPool{}
|
||||
rp.ModifyResponse = func(resp *http.Response) error {
|
||||
// Add CORS headers to all proxied responses
|
||||
resp.Header.Set("Access-Control-Allow-Origin", "*")
|
||||
resp.Header.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
resp.Header.Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol, Sec-WebSocket-Extensions")
|
||||
resp.Header.Set("Access-Control-Max-Age", "86400")
|
||||
return nil
|
||||
}
|
||||
mux.Handle(hn+"/", rp)
|
||||
addFaviconHandler(hn, mux)
|
||||
continue
|
||||
@@ -488,17 +499,23 @@ func setProxy(mapping map[string]string) (http.Handler, error) {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
clientIP := remoteIP(req.RemoteAddr)
|
||||
split := strings.Split(clientIP, ",")
|
||||
log.I.S(split)
|
||||
req.Header.Set("X-Forwarded-For", split[0])
|
||||
// Standard RFC 7239 Forwarded header
|
||||
appendForwardedHeader(req)
|
||||
log.I.S(req.Header)
|
||||
},
|
||||
Transport: &http.Transport{
|
||||
Dial: func(netw, addr string) (net.Conn, error) {
|
||||
return net.DialTimeout(network, ba, 5*time.Second)
|
||||
},
|
||||
},
|
||||
ModifyResponse: func(resp *http.Response) error {
|
||||
// Add CORS headers to all proxied responses
|
||||
resp.Header.Set("Access-Control-Allow-Origin", "*")
|
||||
resp.Header.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
resp.Header.Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol, Sec-WebSocket-Extensions")
|
||||
resp.Header.Set("Access-Control-Max-Age", "86400")
|
||||
return nil
|
||||
},
|
||||
ErrorLog: log2.New(os.Stderr, "reverse", 0),
|
||||
BufferPool: bufPool{},
|
||||
}
|
||||
@@ -654,11 +671,9 @@ func newSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
clientIP := remoteIP(req.RemoteAddr)
|
||||
split := strings.Split(clientIP, ",")
|
||||
log.I.S(split)
|
||||
req.Header.Set("X-Forwarded-For", split[0])
|
||||
// Standard RFC 7239 Forwarded header
|
||||
appendForwardedHeader(req)
|
||||
log.I.S(req.Header)
|
||||
}
|
||||
return &httputil.ReverseProxy{Director: director}
|
||||
}
|
||||
@@ -807,7 +822,6 @@ type NostrJSON struct {
|
||||
// - The handler serves the parsed Nostr DNS data with appropriate HTTP headers
|
||||
// set for CORS and content type.
|
||||
func NostrDNS(hn, ba string, mux *http.ServeMux) (err error) {
|
||||
log.T.Ln(hn, ba)
|
||||
var fb []byte
|
||||
if fb, err = os.ReadFile(ba); chk.E(err) {
|
||||
return
|
||||
@@ -849,7 +863,6 @@ func NostrDNS(hn, ba string, mux *http.ServeMux) (err error) {
|
||||
mux.HandleFunc(
|
||||
hn+"/favicon.ico",
|
||||
func(writer http.ResponseWriter, request *http.Request) {
|
||||
log.I.F("favicon")
|
||||
if _, err = writer.Write(fi); chk.E(err) {
|
||||
return
|
||||
}
|
||||
@@ -861,8 +874,9 @@ func NostrDNS(hn, ba string, mux *http.ServeMux) (err error) {
|
||||
// loadFavicon loads the favicon data from the configured file or uses the default
|
||||
func loadFavicon(faviconPath string) error {
|
||||
if faviconPath == "" {
|
||||
// Use default orly.png
|
||||
faviconData = orlyFavicon
|
||||
// Use default embedded favicon.ico
|
||||
faviconData = defaultFavicon
|
||||
faviconContentType = "image/x-icon"
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -876,18 +890,36 @@ func loadFavicon(faviconPath string) error {
|
||||
}
|
||||
|
||||
faviconData = data
|
||||
|
||||
// Determine content type based on file extension
|
||||
ext := strings.ToLower(filepath.Ext(faviconPath))
|
||||
switch ext {
|
||||
case ".ico":
|
||||
faviconContentType = "image/x-icon"
|
||||
case ".png":
|
||||
faviconContentType = "image/png"
|
||||
case ".gif":
|
||||
faviconContentType = "image/gif"
|
||||
case ".jpg", ".jpeg":
|
||||
faviconContentType = "image/jpeg"
|
||||
case ".svg":
|
||||
faviconContentType = "image/svg+xml"
|
||||
default:
|
||||
faviconContentType = "image/x-icon" // default fallback
|
||||
}
|
||||
|
||||
log.I.Ln("loaded custom favicon from", faviconPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// addFaviconHandler adds a favicon handler for the given hostname that serves
|
||||
// the configured favicon as favicon.ico
|
||||
// the configured favicon (default favicon.ico or custom file) as favicon.ico
|
||||
func addFaviconHandler(hostname string, mux *http.ServeMux) {
|
||||
mux.HandleFunc(
|
||||
hostname+"/favicon.ico",
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
log.I.Ln("serving favicon for", hostname)
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Content-Type", faviconContentType)
|
||||
w.Header().Set("Content-Length", fmt.Sprint(len(faviconData)))
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000") // 1 year cache
|
||||
if _, err := w.Write(faviconData); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user