marky

Lightweight bookmarking + plaintext archive
Log | Files | Refs | README | LICENSE

commit 5899940ff3e6fce18358d385458149b36feac0bf
parent 0b3f97b6ae7fc927c0299d66def01b804ecede0d
Author: David Eisinger <[email protected]>
Date:   Wed,  8 Oct 2025 15:20:05 -0400

Add --base-url option

Diffstat:
MREADME.md | 9+++++++++
Mcmd/marky.go | 6+++++-
Minternal/api.go | 24++++++++++++++++++++++++
3 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md @@ -231,6 +231,15 @@ curl -s http://localhost:8080/bookmarks/1/archive - Archiver: `internal/archiver.go` (pure-Go HTML-to-text) - Embedded web UI: `internal/web/` (served at `/`) +### Reverse proxy / HTTPS + +- When running behind a TLS-terminating proxy (e.g., Cloudflare, nginx), the app + infers the public origin from standard headers and generates correct `https://` + links for the bookmarklet and feeds. +- Recognized headers: `X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Forwarded-Scheme`, + `X-Forwarded-Ssl`, `Forwarded`, and Cloudflare's `Cf-Visitor`. +- To force a specific origin, set `--base-url` (e.g., `marky --base-url https://bookmarks.example.com`) or the env var `MARKY_BASE_URL`. + --- ## Acknowledgments diff --git a/cmd/marky.go b/cmd/marky.go @@ -29,6 +29,7 @@ func main() { addr := flag.String("addr", ":8080", "listen address (e.g. :8080, 127.0.0.1:8080)") dbPath := flag.String("db", defaultDBPath, "path to SQLite DB file (created if missing)") disableSignup := flag.Bool("disable-signup", false, "disable signup endpoint and hide from UI") + baseURL := flag.String("base-url", "", "public base URL (e.g. https://bookmarks.example.com)") // Also accept explicit --help to exit 0 with usage. for _, a := range os.Args[1:] { @@ -51,7 +52,10 @@ func main() { defer db.Close() // Build the shared HTTP handler (API + UI) - h, err := dbpkg.NewHandlerWithConfig(db, dbpkg.HandlerConfig{DisableSignup: *disableSignup}) + h, err := dbpkg.NewHandlerWithConfig(db, dbpkg.HandlerConfig{ + DisableSignup: *disableSignup, + BaseURL: *baseURL, + }) if err != nil { log.Fatalf("build handler: %v", err) } diff --git a/internal/api.go b/internal/api.go @@ -9,6 +9,7 @@ import ( "log" "net/http" "net/url" + "os" "path" "strconv" "strings" @@ -18,13 +19,19 @@ import ( // HandlerConfig controls optional features exposed by the HTTP handler. type HandlerConfig struct { DisableSignup bool + BaseURL string } // NewHandler constructs the HTTP API handler with default settings. func NewHandler(db *DB) (http.Handler, error) { return NewHandlerWithConfig(db, HandlerConfig{}) } // NewHandlerWithConfig constructs the HTTP API handler, including the embedded web UI. +var baseURLOverride string + func NewHandlerWithConfig(db *DB, cfg HandlerConfig) (http.Handler, error) { + if strings.TrimSpace(cfg.BaseURL) != "" { + baseURLOverride = strings.TrimRight(strings.TrimSpace(cfg.BaseURL), "/") + } mux := http.NewServeMux() // Auth endpoints @@ -839,6 +846,14 @@ func NewHandlerWithConfig(db *DB, cfg HandlerConfig) (http.Handler, error) { // standard reverse-proxy headers when present. This supports deployments // behind TLS-terminating proxies like Cloudflare or nginx. func originFromRequest(r *http.Request) string { + // CLI-configured override takes highest precedence + if baseURLOverride != "" { + return baseURLOverride + } + // Explicit override via environment, e.g. MARKY_BASE_URL=https://example.com + if base := strings.TrimSpace(os.Getenv("MARKY_BASE_URL")); base != "" { + return strings.TrimRight(base, "/") + } // Default scheme based on connection scheme := "http" if r.TLS != nil { @@ -859,6 +874,15 @@ func originFromRequest(r *http.Request) string { } else if strings.Contains(l, "http") { scheme = "http" } + } else if xs := r.Header.Get("X-Forwarded-Scheme"); xs != "" { // some proxies + first := strings.ToLower(strings.TrimSpace(strings.Split(xs, ",")[0])) + if first == "https" || first == "http" { + scheme = first + } + } else if xfssl := r.Header.Get("X-Forwarded-Ssl"); xfssl != "" { // nginx/common: on/off + if strings.EqualFold(strings.TrimSpace(strings.Split(xfssl, ",")[0]), "on") { + scheme = "https" + } } else if fwd := r.Header.Get("Forwarded"); fwd != "" { // Parse Forwarded header (RFC 7239), look for proto= // Example: Forwarded: for=1.2.3.4; proto=https; host=example.com