From 77a23825d373fb137a225bbfb20e5200b089f996 Mon Sep 17 00:00:00 2001 From: Blasius Patrick Date: Wed, 24 Jun 2026 16:13:50 +0700 Subject: [PATCH 1/2] docs: add HTTP proxy support info to README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document that hermes-node respects HTTPS_PROXY/HTTP_PROXY env vars via gorilla/websocket's DefaultDialer — no config changes needed. Includes FAQ entry on Basic auth and NTLM workaround. Signed-off-by: Blasius Patrick --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5b32555..7d58288 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,10 @@ log_level = "debug" backoff_initial = "1s" # default; Go duration, e.g. "500ms", "5s" backoff_max = "60s" # default; maximum delay between retries backoff_factor = 2.0 # default; multiplier per retry + +# Proxy: hermes-node respects HTTPS_PROXY / HTTP_PROXY / NO_PROXY +# env vars automatically. No config field needed — just set them +# in the shell before running `hermes-node run`. ``` ### `[server]` section @@ -329,6 +333,8 @@ Quick summary: - **Q: Can I reload config without restarting?** A: Send `SIGHUP` to the daemon process to reload `log_level`. Other changes require a restart. +- **Q: Does the node support HTTP proxies?** A: Yes. The WebSocket client respects the standard `HTTPS_PROXY`, `https_proxy`, `HTTP_PROXY`, `http_proxy`, and `NO_PROXY` environment variables automatically. For Basic auth, include credentials in the URL: `http://user:password@proxy:port`. For NTLM/Kerberos proxies, use a local authenticating proxy bridge (e.g. `cntlm`). + - **Q: What does `--version` show?** A: The version, Go version, commit SHA, and build date. Example: `hermes-node v0.1.0 go1.26.3 abc12345 2026-06-22`. ## Related From 5ae6613ae71b9c93026eca7ad7338f186e8999ea Mon Sep 17 00:00:00 2001 From: Blasius Patrick Date: Thu, 25 Jun 2026 12:21:53 +0700 Subject: [PATCH 2/2] feat: add proxy_url config field for built-in proxy support Adds a proxy_url field to [node] in config.toml so operators can configure an HTTP(S) proxy without relying on environment variables. - config.go: add ProxyURL field to NodeConfig - client.go: add ProxyURL to DialOptions, wire into gorilla/websocket dialer via http.ProxyURL - main.go: pass cfg.Node.ProxyURL in dialer options - main.go (validate): validate proxy_url when set - README.md: document proxy_url alongside existing env-var approach Signed-off-by: Blasius Patrick --- README.md | 14 +++++++++----- cmd/hermes-node/main.go | 17 +++++++++++++++++ internal/config/config.go | 1 + internal/wire/client.go | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7d58288..234f693 100644 --- a/README.md +++ b/README.md @@ -229,14 +229,18 @@ log_path = "/home/user/.hermes-nodes/audit.log" # One of: debug, info, warn, error log_level = "debug" -# Reconnect backoff (defaults shown) +# Proxy: optional HTTP(S) proxy for the WebSocket connection. +# When set, overrides HTTPS_PROXY / HTTP_PROXY env vars. +proxy_url = "http://proxy.corp.example:8080" + +# Reconnect backoff: fine-tune the exponential backoff for +# transient network drops. The defaults work for most setups. backoff_initial = "1s" # default; Go duration, e.g. "500ms", "5s" backoff_max = "60s" # default; maximum delay between retries backoff_factor = 2.0 # default; multiplier per retry -# Proxy: hermes-node respects HTTPS_PROXY / HTTP_PROXY / NO_PROXY -# env vars automatically. No config field needed — just set them -# in the shell before running `hermes-node run`. +# Proxy: set proxy_url above, or use HTTPS_PROXY / HTTP_PROXY / NO_PROXY +# env vars for env-level config. ``` ### `[server]` section @@ -333,7 +337,7 @@ Quick summary: - **Q: Can I reload config without restarting?** A: Send `SIGHUP` to the daemon process to reload `log_level`. Other changes require a restart. -- **Q: Does the node support HTTP proxies?** A: Yes. The WebSocket client respects the standard `HTTPS_PROXY`, `https_proxy`, `HTTP_PROXY`, `http_proxy`, and `NO_PROXY` environment variables automatically. For Basic auth, include credentials in the URL: `http://user:password@proxy:port`. For NTLM/Kerberos proxies, use a local authenticating proxy bridge (e.g. `cntlm`). +- **Q: Does the node support HTTP proxies?** A: Yes. Set `proxy_url` in `config.toml` under `[node]`, or use the standard `HTTPS_PROXY`/`HTTP_PROXY` env vars. For Basic auth, include credentials in the URL: `http://user:password@proxy:port`. For NTLM/Kerberos proxies, use a local authenticating proxy bridge (e.g. `cntlm`). - **Q: What does `--version` show?** A: The version, Go version, commit SHA, and build date. Example: `hermes-node v0.1.0 go1.26.3 abc12345 2026-06-22`. diff --git a/cmd/hermes-node/main.go b/cmd/hermes-node/main.go index 59eb06b..80730c7 100644 --- a/cmd/hermes-node/main.go +++ b/cmd/hermes-node/main.go @@ -32,6 +32,7 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "os/exec" "os/signal" @@ -699,6 +700,21 @@ func runValidate(args []string, configPath string, stdout, stderr io.Writer) int } // If no TLS settings are configured, that's fine — no check needed. + // 5. Proxy URL — validate the URL when set. + if cfg.Node.ProxyURL != "" { + proxyURL, err := url.Parse(cfg.Node.ProxyURL) + if err != nil { + fmt.Fprintf(stdout, " [FAIL] proxy_url: %v\n", err) + failed++ + } else if proxyURL.Scheme != "http" && proxyURL.Scheme != "https" { + fmt.Fprintf(stdout, " [FAIL] proxy_url: scheme %q must be http or https\n", proxyURL.Scheme) + failed++ + } else { + fmt.Fprintf(stdout, " [OK] proxy_url: %s\n", cfg.Node.ProxyURL) + passed++ + } + } + if failed == 0 { fmt.Fprintf(stdout, "\nhermes-node: config is valid (%d checks passed).\n", passed) return 0 @@ -809,6 +825,7 @@ func runRun(ctx context.Context, configPath string, stdout, stderr io.Writer) in Arch: runtime.GOARCH, Capabilities: []string{"exec", "read", "write"}, TLSConfig: tlsCfg, + ProxyURL: cfg.Node.ProxyURL, }) }, // Setup is invoked once per (re)connect. We build a fresh diff --git a/internal/config/config.go b/internal/config/config.go index 995bf77..c562f38 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -35,6 +35,7 @@ type NodeConfig struct { AllowedPaths []string `toml:"allowed_paths"` LogPath string `toml:"log_path"` LogLevel string `toml:"log_level"` + ProxyURL string `toml:"proxy_url"` BackoffInitial string `toml:"backoff_initial"` BackoffMax string `toml:"backoff_max"` BackoffFactor float64 `toml:"backoff_factor"` diff --git a/internal/wire/client.go b/internal/wire/client.go index 2fa4318..af7538d 100644 --- a/internal/wire/client.go +++ b/internal/wire/client.go @@ -10,6 +10,7 @@ import ( "crypto/tls" "errors" "fmt" + "net/http" "net/url" "time" @@ -97,6 +98,13 @@ type DialOptions struct { // handed over. A future maintainer should not construct a // per-call closure here. TLSConfig *tls.Config + + // ProxyURL is an optional HTTP(S) proxy URL for the WebSocket + // dialer. When set, it overrides the HTTP_PROXY / HTTPS_PROXY + // environment variables. Leave empty to use the default + // proxy-from-environment behaviour (ProxyFromEnvironment). + // Example: "http://proxy.corp.example:8080". + ProxyURL string } // withDefaults returns a copy of opts with zero-valued fields filled @@ -166,6 +174,13 @@ func Connect(ctx context.Context, opts DialOptions) (*Client, error) { if opts.TLSConfig != nil { dialer.TLSClientConfig = opts.TLSConfig } + if opts.ProxyURL != "" { + proxyURL, err := url.Parse(opts.ProxyURL) + if err != nil { + return nil, fmt.Errorf("wire: parse proxy_url: %w", err) + } + dialer.Proxy = http.ProxyURL(proxyURL) + } conn, _, err := dialer.DialContext(wsCtx, opts.ServerURL, nil) if err != nil { return nil, fmt.Errorf("wire: dial %s: %w", opts.ServerURL, err)