Skip to content

Add ingress.http_address for plain-HTTP ingress on a custom address#789

Closed
jcasimir wants to merge 3 commits into
mirendev:mainfrom
jcasimir:jcasimir/configurable-http-ingress
Closed

Add ingress.http_address for plain-HTTP ingress on a custom address#789
jcasimir wants to merge 3 commits into
mirendev:mainfrom
jcasimir:jcasimir/configurable-http-ingress

Conversation

@jcasimir
Copy link
Copy Markdown
Contributor

@jcasimir jcasimir commented May 7, 2026

Standing up a Miren server on a host that already serves another application on 80/443 currently leaves no way to put the Miren ingress somewhere else. Setting --serve-tls=false falls back to a hard-coded :80, which still collides. The intended deployment shape is the standard one — TLS-terminating proxy out front, plain HTTP to Miren on a loopback port inward — but Miren can't bind anywhere except 0.0.0.0.

This PR introduces a new [ingress] config section with one field, http_address. When set, Miren binds plain HTTP on that address and skips the standard 80/443 binding entirely. tls.* settings have no effect in this mode; a warnIngressTLSOverride helper logs one warning per non-default tls.* field, naming each so operators can grep and clean up. tls.standard_tls is intentionally not in the warn list — it defaults to true, so warning whenever it's "set" would fire on every default config.

The listener binds synchronously so a port conflict surfaces as a startup error instead of an orphaned goroutine after the supervisor thinks we're up. The accept loop and graceful shutdown run in background goroutines wired to the parent context.

When ingress.http_address is empty (default), all existing paths are unchanged.

Non-goals

HTTPS on a custom port is a related but distinct change — a follow-up PR adds ingress.https_address. A third PR introduces a selectIngressMode resolver that rejects setting both ingress addresses simultaneously, once there are two fields to be exclusive with.

Testing

  • TestWarnIngressTLSOverride in cli/commands/server_test.go table-tests each warn-trigger field, including the silent-on-default standard_tls case.
  • Verified end-to-end on Linux in a containerized standalone-mode boot: curl http://127.0.0.1:18080/ against the custom address returns Miren's 404; ss -tln confirms no listeners on 80 or 443; the warn line fires as expected when --self-signed-tls is set alongside.

Introduce a new [ingress] section for ingress-listener settings and
add its first field, http_address. When set, it directs a plain-HTTP
ingress to a host:port — intended for deployments behind a
TLS-terminating proxy (nginx, Caddy, an ALB) where Miren itself
shouldn't speak TLS. Schema-only; no behavior change yet — the field
is plumbed through TOML, env (MIREN_INGRESS_HTTP_ADDRESS), and CLI
(--ingress-http-address), and validated as host:port at config load.
The runtime branch that consumes it lands in the next commit.

The codegen output (*.gen.go) is regenerated from schema.yml via
go generate; do not hand-edit.
@jcasimir jcasimir requested a review from a team as a code owner May 7, 2026 01:30
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 91e85a06-137d-4c1e-bf48-3492365fff44

📥 Commits

Reviewing files that changed from the base of the PR and between 15d4bac and 95306bb.

📒 Files selected for processing (3)
  • cli/commands/server.go
  • cli/commands/server_test.go
  • docs/docs/server-config.md
✅ Files skipped from review due to trivial changes (1)
  • docs/docs/server-config.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/commands/server_test.go
  • cli/commands/server.go

📝 Walkthrough

Walkthrough

Adds an ingress HTTP option and wiring throughout the codebase: a new [ingress] config with http_address, CLI flag and env var, schema and defaults, validation, and loader mapping. Server startup now prefers a plain-HTTP listener when ingress.http_address is set, emits warnings for ignored TLS fields, and includes a test and documentation updates.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
cli/commands/server_test.go (1)

14-98: ⚡ Quick win

Add a test case asserting tls.standard_tls does not produce a warning.

The PR description explicitly calls out: "tls.standard_tls is intentionally excluded from warnings." None of the table cases verifies this invariant. If the exclusion is accidentally dropped from warnIngressTLSOverride, every existing test would still pass.

✅ Proposed additional test case
 		{
 			name: "all warn together, each named individually",
 			...
 		},
+		{
+			name:           "standard_tls set: no warning emitted",
+			notWantContain: []string{"standard_tls", "ignored"},
+			// StandardTLS is set directly below after TLSConfig construction
+		},

Then in the loop body, add a branch for the new case (or expose standardTLS bool in the table struct) that calls tls.SetStandardTLS(true) and asserts no warning is emitted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/commands/server_test.go` around lines 14 - 98, Add a test case to ensure
tls.standard_tls is excluded from warnings: extend the test table in
TestWarnIngressTLSOverride with a case (e.g., name "standard_tls silent") that
sets standardTLS true, then in the t.Run setup call tls.SetStandardTLS(true)
before invoking warnIngressTLSOverride(log, tls) and assert the output does NOT
contain "tls.standard_tls" (add it to notWantContain); this ensures
warnIngressTLSOverride still omits standard_tls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/commands/server.go`:
- Around line 832-853: The ingress server goroutines are started detached and
can keep serving after startup failures; replace the two detached go funcs with
eg.Go calls so their lifecycles propagate through the errgroup `eg`: use eg.Go
to run the Serve call for `ingressServer` (returning the error unless it's
http.ErrServerClosed) and use another eg.Go to wait on `sub.Done()` and call
`ingressServer.Shutdown(shutdownCtx)` (returning any shutdown error),
referencing `ingressServer`, `ln`, `ingressAddr`, `sub`, and `eg` so the
errgroup observes Serve/shutdown errors instead of only logging them.

---

Nitpick comments:
In `@cli/commands/server_test.go`:
- Around line 14-98: Add a test case to ensure tls.standard_tls is excluded from
warnings: extend the test table in TestWarnIngressTLSOverride with a case (e.g.,
name "standard_tls silent") that sets standardTLS true, then in the t.Run setup
call tls.SetStandardTLS(true) before invoking warnIngressTLSOverride(log, tls)
and assert the output does NOT contain "tls.standard_tls" (add it to
notWantContain); this ensures warnIngressTLSOverride still omits standard_tls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e467fb41-8df7-4029-8249-38cf633d8f1e

📥 Commits

Reviewing files that changed from the base of the PR and between af4f87c and 15d4bac.

📒 Files selected for processing (10)
  • cli/commands/server.go
  • cli/commands/server_test.go
  • docs/docs/server-config.md
  • pkg/serverconfig/cli.gen.go
  • pkg/serverconfig/config.gen.go
  • pkg/serverconfig/defaults.gen.go
  • pkg/serverconfig/env.gen.go
  • pkg/serverconfig/loader.gen.go
  • pkg/serverconfig/schema.yml
  • pkg/serverconfig/validation.gen.go

Comment thread cli/commands/server.go Outdated
jcasimir added 2 commits May 6, 2026 21:36
When ingress.http_address is set, take a new branch in Server() that
binds plain HTTP on the configured address — bypassing both the TLS
path and the legacy bare-:80 fallback. Listen synchronously so a
port conflict surfaces as a startup error instead of an orphaned
goroutine; the supervisor needs to know if the ingress isn't really
up. The accept loop runs in a goroutine after a successful bind, and
a sibling goroutine waits on context cancellation to call
server.Shutdown so in-flight requests drain cleanly on SIGTERM.

A small warnIngressTLSOverride helper logs one warning per non-default
tls.* field that has no effect in this mode (self_signed, acme_email,
acme_dns_provider, additional_ips, additional_names). standard_tls is
intentionally skipped: it defaults to true, so warning whenever it's
"set" would cry wolf on every default config. Each warning names the
specific field so operators can grep and remove without ambiguity.

When ingress.http_address is empty, the existing standard_tls and
plain-HTTP paths are unchanged.
Add a [ingress] section to the server config reference covering
the use case (Miren behind nginx/Caddy/ALB), the configuration
shape, and the precedence list — ingress.http_address overrides
both standard_tls and the legacy bare-:80 fallback when set.
@jcasimir jcasimir force-pushed the jcasimir/configurable-http-ingress branch from 15d4bac to 95306bb Compare May 7, 2026 03:37
@jcasimir
Copy link
Copy Markdown
Contributor Author

jcasimir commented May 7, 2026

Thanks @coderabbitai — both findings addressed in the latest force-push:

  • Test gap (standard_tls=true exclusion): added a table case "standard_tls true: silent (excluded by design)" that asserts no warning fires when standard_tls is set. The case is the second entry in the table now, so the design invariant gets explicit coverage.

  • Detached goroutines: switched both Serve and Shutdown goroutines from go func() to eg.Go(...), returning wrapped errors so the parent errgroup observes them. A fatal serve error now terminates the process via eg.Wait() instead of being logged-and-forgotten. The same change is mirrored in the helper extracted by the stacked PR Reject conflicting ingress addresses and consolidate mode selection #791 (serveCustomHTTPIngress now takes the errgroup and uses eg.Go(...) internally) so the fix carries through the consolidation.

Verified end-to-end on the local containerized smoke runner: all five smoke scenarios (validation paths + full standalone-mode boot + listener probe + warn-helper assertion) still pass against the updated branch.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@jcasimir: Thank you for the thorough follow-up! Both fixes look well-considered:

  • The "standard_tls true: silent (excluded by design)" table case cleanly documents the intentional exclusion as a first-class invariant — that's exactly the right approach.
  • Migrating both Serve and Shutdown to eg.Go(...) ensures fatal listener errors propagate through eg.Wait() and terminate the process cleanly rather than being silently swallowed. Carrying the same pattern into serveCustomHTTPIngress in #791 is a good call to keep the consolidation consistent.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@evanphx
Copy link
Copy Markdown
Contributor

evanphx commented May 7, 2026

Thanks for the PR @jcasimir! I'm 100% behind the idea that our default http listener port needs to be configurable. Where I think we need to figure out the right balance is that this ALSO disables TLS entirely. It seems like it would be better if it were 2 flags, one to set the http ingress port and another to disable TLS listening.

@jcasimir
Copy link
Copy Markdown
Contributor Author

jcasimir commented May 8, 2026

Ok, so just to clarify you're saying you might want to enable TLS and non-TLS at the same time? I could see that being interesting.

@phinze
Copy link
Copy Markdown
Contributor

phinze commented May 11, 2026

Closing in favor of #799, which folds this work into the broader config-schema reshape described in RFD-84. Full context in the comment thread on #791.

@phinze phinze closed this May 11, 2026
evanphx pushed a commit to jcasimir/runtime that referenced this pull request May 18, 2026
…FD-84)

Replaces the GetStandardTLS branching in server.go with a switch on
cfg.Ingress.GetMode(). Each non-autoprovision mode resolves its bind
address from cfg.Ingress.GetAddress() (with a localhost default), and
dispatches to the right serve function:

- tls-autoprovision: existing ServeTLSWithController or
  ServeTLSSelfSigned (binds :443 + :80 as before)
- behind-proxy-https: new ServeTLSWithControllerOnAddr or
  ServeTLSSelfSignedOnAddr (single TLS listener at the configured
  address; adopted from Jeff Casimir's PR mirendev#790 essentially unchanged)
- behind-proxy-http: plain http.ListenAndServe at the configured
  address

Also drops the Host-based exception in ServeTLSWithController's :80
handler that used to route localhost / 127.0.0.1 / ::1 / raw-IP Host
requests directly to the default route app over plain HTTP. That was
a dev-convenience hack with production security smell. Operators who
want plain-HTTP access for dev workflow now pick behind-proxy-http
explicitly; the autoprovision :80 listener now does only what it
says (redirect + ACME).

ValidateIngressCoherence runs right after Load() in both Server()
and ServerConfigValidate() so config errors surface before any
listener starts.

Thanks to Jeff Casimir (https://github.com/jcasimir) for surfacing
the use case in PRs mirendev#789/mirendev#790/mirendev#791. The autotls helpers in this
commit are the OnAddr variants from his PR mirendev#790 essentially
unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants