update: reorganize imports, add URL rewriting support, and minor refactoring

- cmd/lerproxy/reverse/proxy.go
  - Reorganized imports for logical grouping.

- cmd/lerproxy/main.go
  - Added URL rewriting capability and updated command-line usage documentation.
  - Reorganized imports for consistency.
  - Replaced `context.T` with `context.Context` for standardization.
  - Updated timeout handling logic to use `context.WithTimeout`.

- pkg/protocol/ws/connection.go
  - Replaced `fmt.Errorf` with `errorf.E` for error formatting.

- cmd/lerproxy/util/util.go
  - Renamed file for better clarity.
  - Removed unnecessary package documentation.

- cmd/lerproxy/hsts/proxy.go
  - Removed redundant package comments.

- cmd/lerproxy/tcpkeepalive/listener.go
  - Removed redundant package comments.
  - Adjusted import order.

- cmd/lerproxy/buf/bufpool.go
  - Removed unnecessary package comments.

- cmd/lerproxy/README.md
  - Updated package usage examples and installation instructions.
  - Removed outdated and unnecessary instructions.
This commit is contained in:
2025-08-18 20:13:14 +01:00
parent a928294234
commit a51e86f4c4
10 changed files with 40 additions and 60 deletions

0
cmd/lerproxy/LICENSE Normal file → Executable file
View File

21
cmd/lerproxy/README.md Normal file → Executable file
View File

@@ -6,12 +6,12 @@ DNS verification [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.
## Install ## Install
go install lerproxy.mleku.dev@latest go install mleku.dev/lerproxy@latest
## Run ## Run
``` ```
Usage: lerproxy.mleku.dev [--listen LISTEN] [--map MAP] [--rewrites REWRITES] [--cachedir CACHEDIR] [--hsts] [--email EMAIL] [--http HTTP] [--rto RTO] [--wto WTO] [--idle IDLE] [--cert CERT] Usage: mleku.dev/lerproxy [--listen LISTEN] [--map MAP] [--rewrites REWRITES] [--cachedir CACHEDIR] [--hsts] [--email EMAIL] [--http HTTP] [--rto RTO] [--wto WTO] [--idle IDLE] [--cert CERT]
Options: Options:
--listen LISTEN, -l LISTEN --listen LISTEN, -l LISTEN
@@ -49,7 +49,7 @@ as:
* in the launch parameters for `lerproxy` you can now add any number of `--cert` parameters with * in the launch parameters for `lerproxy` you can now add any number of `--cert` parameters with
the domain (including for wildcards), and the path to the `.crt`/`.key` files: the domain (including for wildcards), and the path to the `.crt`/`.key` files:
lerproxy.mleku.dev --cert <domain>:/path/to/TLS_cert mleku.dev/lerproxy --cert <domain>:/path/to/TLS_cert
this will then, if found, load and parse the TLS certificate and secret key if the suffix of this will then, if found, load and parse the TLS certificate and secret key if the suffix of
the domain matches. The certificate path is expanded to two files with the above filename the domain matches. The certificate path is expanded to two files with the above filename
@@ -58,17 +58,6 @@ as:
> Note that the match is greedy, so you can explicitly separately give a subdomain > Note that the match is greedy, so you can explicitly separately give a subdomain
certificate and it will be selected even if there is a wildcard that also matches. certificate and it will be selected even if there is a wildcard that also matches.
# IMPORTANT
With Comodo SSL (sectigo RSA) certificates you also need to append the intermediate certificate
to the `.crt` file in order to get it to work properly with openssl library based tools like
wget, curl and the go tool, which is quite important if you want to do subdomains on a wildcard
certificate.
Probably the same applies to some of the other certificate authorities. If you sometimes get
issues with CLI tools refusing to accept these certificates on your web server or other, this
may be the problem.
## example mapping.txt ## example mapping.txt
nostr.example.com: /path/to/nostr.json nostr.example.com: /path/to/nostr.json
@@ -96,7 +85,7 @@ Description=lerproxy
[Service] [Service]
Type=simple Type=simple
User=username User=username
ExecStart=/usr/local/bin/lerproxy.mleku.dev -m /path/to/mapping.txt -l xxx.xxx.xxx.xxx:443 --http xxx.xxx.xxx.6:80 -m /path/to/mapping.txt -e email@example.com -c /path/to/letsencrypt/cache --cert example.com:/path/to/tls/certs ExecStart=/usr/local/bin/mleku.dev/lerproxy -m /path/to/mapping.txt -l xxx.xxx.xxx.xxx:443 --http xxx.xxx.xxx.6:80 -m /path/to/mapping.txt -e email@example.com -c /path/to/letsencrypt/cache --cert example.com:/path/to/tls/certs
Restart=on-failure Restart=on-failure
Wants=network-online.target Wants=network-online.target
After=network.target network-online.target wg-quick@wg0.service After=network.target network-online.target wg-quick@wg0.service
@@ -114,7 +103,7 @@ a tunnel, such as your dev machine (something I do for nostr relay development)
The simplest way to allow `lerproxy` to bind to port 80 and 443 is as follows: The simplest way to allow `lerproxy` to bind to port 80 and 443 is as follows:
setcap 'cap_net_bind_service=+ep' /path/to/lerproxy.mleku.dev setcap 'cap_net_bind_service=+ep' /path/to/mleku.dev/lerproxy
## todo ## todo

View File

@@ -1,4 +1,3 @@
// Package buf implements a simple concurrent safe buffer pool for raw bytes.
package buf package buf
import "sync" import "sync"

View File

@@ -1,4 +1,3 @@
// Package hsts implements a HTTP handler that enforces HSTS.
package hsts package hsts
import "net/http" import "net/http"

View File

@@ -1,10 +1,10 @@
// Command lerproxy implements https reverse proxy with automatic LetsEncrypt // Command lerproxy implements https reverse proxy with automatic LetsEncrypt
// usage for multiple hostnames/backends,your own SSL certificates, nostr NIP-05 // usage for multiple hostnames/backends, and URL rewriting capability.
// DNS verification hosting and Go vanity redirects.
package main package main
import ( import (
"bufio" "bufio"
"context"
"crypto/tls" "crypto/tls"
_ "embed" _ "embed"
"encoding/json" "encoding/json"
@@ -15,14 +15,6 @@ import (
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"orly.dev/cmd/lerproxy/buf"
"orly.dev/cmd/lerproxy/hsts"
"orly.dev/cmd/lerproxy/reverse"
"orly.dev/cmd/lerproxy/tcpkeepalive"
"orly.dev/cmd/lerproxy/util"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
"orly.dev/pkg/utils/log"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@@ -34,14 +26,22 @@ import (
"github.com/alexflint/go-arg" "github.com/alexflint/go-arg"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"orly.dev/cmd/lerproxy/buf"
"orly.dev/cmd/lerproxy/hsts"
"orly.dev/cmd/lerproxy/reverse"
"orly.dev/cmd/lerproxy/tcpkeepalive"
"orly.dev/cmd/lerproxy/util"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/log"
) )
//go:embed favicon.ico //go:embed favicon.ico
var defaultFavicon []byte var defaultFavicon []byte
type runArgs struct { type runArgs struct {
Addr string `arg:"-l,--listen" default:":https" help:"address to listen at"` Addr string `arg:"-l,--listen" default:":https" help:"address to listen at"`
Conf string `arg:"-m,--map" default:"mapping.txt" help:"file with host/backend mapping"` Conf string `arg:"-m,--map" default:"mapping.txt" help:"file with host/backend mapping"`
// Rewrites string `arg:"-r,--rewrites" default:"rewrites.txt"`
Cache string `arg:"-c,--cachedir" default:"/var/cache/letsencrypt" help:"path to directory to cache key and certificates"` Cache string `arg:"-c,--cachedir" default:"/var/cache/letsencrypt" help:"path to directory to cache key and certificates"`
HSTS bool `arg:"-h,--hsts" help:"add Strict-Transport-Security header"` HSTS bool `arg:"-h,--hsts" help:"add Strict-Transport-Security header"`
Email string `arg:"-e,--email" help:"contact email address presented to letsencrypt CA"` Email string `arg:"-e,--email" help:"contact email address presented to letsencrypt CA"`
@@ -49,22 +49,21 @@ type runArgs struct {
RTO time.Duration `arg:"-r,--rto" default:"1m" help:"maximum duration before timing out read of the request"` RTO time.Duration `arg:"-r,--rto" default:"1m" help:"maximum duration before timing out read of the request"`
WTO time.Duration `arg:"-w,--wto" default:"5m" help:"maximum duration before timing out write of the response"` WTO time.Duration `arg:"-w,--wto" default:"5m" help:"maximum duration before timing out write of the response"`
Idle time.Duration `arg:"-i,--idle" help:"how long idle connection is kept before closing (set rto, wto to 0 to use this)"` Idle time.Duration `arg:"-i,--idle" help:"how long idle connection is kept before closing (set rto, wto to 0 to use this)"`
Certs []string `arg:"--cert,separate" help:"certificates and the domain they match: eg: orly.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"` Certs []string `arg:"--cert,separate" help:"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"`
// Rewrites string `arg:"-r,--rewrites" default:"rewrites.txt"`
} }
var args runArgs var args runArgs
func main() { func main() {
arg.MustParse(&args) arg.MustParse(&args)
ctx, cancel := signal.NotifyContext(context.Bg(), os.Interrupt) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel() defer cancel()
if err := run(ctx, args); chk.T(err) { if err := run(ctx, args); err != nil {
log.F.Ln(err) log.F.Ln(err)
} }
} }
func run(c context.T, args runArgs) (err error) { func run(ctx context.Context, args runArgs) (err error) {
if args.Cache == "" { if args.Cache == "" {
err = log.E.Err("no cache specified") err = log.E.Err("no cache specified")
@@ -83,7 +82,7 @@ func run(c context.T, args runArgs) (err error) {
if args.WTO > 0 { if args.WTO > 0 {
srv.WriteTimeout = args.WTO srv.WriteTimeout = args.WTO
} }
group, ctx := errgroup.WithContext(c) group, ctx := errgroup.WithContext(ctx)
if args.HTTP != "" { if args.HTTP != "" {
httpServer := http.Server{ httpServer := http.Server{
Addr: args.HTTP, Addr: args.HTTP,
@@ -100,8 +99,8 @@ func run(c context.T, args runArgs) (err error) {
group.Go( group.Go(
func() error { func() error {
<-ctx.Done() <-ctx.Done()
ctx, cancel := context.Timeout( ctx, cancel := context.WithTimeout(
context.Bg(), context.Background(),
time.Second, time.Second,
) )
defer cancel() defer cancel()
@@ -137,7 +136,9 @@ func run(c context.T, args runArgs) (err error) {
group.Go( group.Go(
func() error { func() error {
<-ctx.Done() <-ctx.Done()
ctx, cancel := context.Timeout(context.Bg(), time.Second) ctx, cancel := context.WithTimeout(
context.Background(), time.Second,
)
defer cancel() defer cancel()
return srv.Shutdown(ctx) return srv.Shutdown(ctx)
}, },
@@ -328,13 +329,12 @@ func setProxy(mapping map[string]string) (h http.Handler, err error) {
) )
fin := hn + "/favicon.ico" fin := hn + "/favicon.ico"
var fi []byte var fi []byte
if fi, err = os.ReadFile(fin); chk.E(err) { if fi, err = os.ReadFile(fin); !chk.E(err) {
fi = defaultFavicon fi = defaultFavicon
} }
mux.HandleFunc( mux.HandleFunc(
hn+"/favicon.ico", hn+"/favicon.ico",
func(writer http.ResponseWriter, request *http.Request) { func(writer http.ResponseWriter, request *http.Request) {
log.T.F("serving favicon to %s", hn)
if _, err = writer.Write(fi); chk.E(err) { if _, err = writer.Write(fi); chk.E(err) {
return return
} }
@@ -376,12 +376,12 @@ func setProxy(mapping map[string]string) (h http.Handler, err error) {
) )
// req.Header.Set("Access-Control-Allow-Credentials", "true") // req.Header.Set("Access-Control-Allow-Credentials", "true")
req.Header.Set("Access-Control-Allow-Origin", "*") req.Header.Set("Access-Control-Allow-Origin", "*")
log.I.Ln(req.URL, req.RemoteAddr) log.D.Ln(req.URL, req.RemoteAddr)
}, },
Transport: &http.Transport{ Transport: &http.Transport{
DialContext: func(c context.T, n, addr string) ( DialContext: func(
net.Conn, error, ctx context.Context, n, addr string,
) { ) (net.Conn, error) {
return net.DialTimeout(network, ba, 5*time.Second) return net.DialTimeout(network, ba, 5*time.Second)
}, },
}, },

View File

@@ -1,13 +1,12 @@
// Package reverse is a copy of httputil.NewSingleHostReverseProxy with addition
// of "X-Forwarded-Proto" header.
package reverse package reverse
import ( import (
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"orly.dev/cmd/lerproxy/util"
"orly.dev/pkg/utils/log" "orly.dev/pkg/utils/log"
"orly.dev/cmd/lerproxy/util"
) )
// NewSingleHostReverseProxy is a copy of httputil.NewSingleHostReverseProxy // NewSingleHostReverseProxy is a copy of httputil.NewSingleHostReverseProxy

View File

@@ -1,12 +1,11 @@
// Package tcpkeepalive implements a net.TCPListener with a singleton set period
// for a default 3 minute keep-aline.
package tcpkeepalive package tcpkeepalive
import ( import (
"net" "net"
"orly.dev/cmd/lerproxy/timeout"
"orly.dev/pkg/utils/chk" "orly.dev/pkg/utils/chk"
"time" "time"
"orly.dev/cmd/lerproxy/timeout"
) )
// Period can be changed prior to opening a Listener to alter its' // Period can be changed prior to opening a Listener to alter its'

View File

@@ -1,5 +1,3 @@
// Package timeout provides a simple extension of a net.TCPConn with a
// configurable read/write deadline.
package timeout package timeout
import ( import (

View File

@@ -1,6 +1,3 @@
// Package util provides some helpers for lerproxy, a tool to convert maps of
// strings to slices of the same strings, and a helper to avoid putting two / in
// a URL.
package util package util
import "strings" import "strings"

View File

@@ -3,10 +3,10 @@ package ws
import ( import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"orly.dev/pkg/utils/context" "orly.dev/pkg/utils/context"
"orly.dev/pkg/utils/errorf"
"orly.dev/pkg/utils/units" "orly.dev/pkg/utils/units"
"time" "time"
@@ -40,7 +40,7 @@ func (c *Connection) WriteMessage(
ctx context.T, data []byte, ctx context.T, data []byte,
) (err error) { ) (err error) {
if err = c.conn.Write(ctx, ws.MessageText, data); err != nil { if err = c.conn.Write(ctx, ws.MessageText, data); err != nil {
err = fmt.Errorf("failed to write message: %w", err) err = errorf.E("failed to write message: %w", err)
return return
} }
return nil return nil
@@ -52,11 +52,11 @@ func (c *Connection) ReadMessage(
) (err error) { ) (err error) {
var reader io.Reader var reader io.Reader
if _, reader, err = c.conn.Reader(ctx); err != nil { if _, reader, err = c.conn.Reader(ctx); err != nil {
err = fmt.Errorf("failed to get reader: %w", err) err = errorf.E("failed to get reader: %w", err)
return return
} }
if _, err = io.Copy(buf, reader); err != nil { if _, err = io.Copy(buf, reader); err != nil {
err = fmt.Errorf("failed to read message: %w", err) err = errorf.E("failed to read message: %w", err)
return return
} }
return return