Skip to content

chore: subdirectory routing, configurable domain, and deployment guidelines#241

Closed
sheepdestroyer wants to merge 14 commits into
masterfrom
chore/learn-deployment-guidelines
Closed

chore: subdirectory routing, configurable domain, and deployment guidelines#241
sheepdestroyer wants to merge 14 commits into
masterfrom
chore/learn-deployment-guidelines

Conversation

@sheepdestroyer

@sheepdestroyer sheepdestroyer commented Jul 8, 2026

Copy link
Copy Markdown
Owner

This PR resolves review comments from PR #240, syncs recent production changes from boy, and introduces subdirectory proxy routing.

Key Changes

  1. Subdirectory Routing & Dynamic Resolution:
    • Router dashboard URLs dynamically map /llm-routing/litellm, /llm-routing/langfuse, and /llm-routing/llama/ subdirectories depending on request headers/domain.
    • Fixed LiteLLM SERVER_ROOT_PATH and added PROXY_BASE_URL behind HAProxy.
    • Refactored hostname resolution to cleanly validate hostnames and port mappings while preventing HTML injection/XSS.
  2. Configurable Domain:
    • Extracted the production domain configuration to ROUTING_DOMAIN env variable in router/main.py (default: vendeuvre.lan).
  3. LiteLLM Health Probes:
    • Configured livenessProbe and readinessProbe to use direct in-pod endpoints (/ping and /health/readiness) rather than the reverse proxy paths.
    • Increased readiness probe delay to 45 seconds to prevent false startup failures.
  4. Synced Prod Updates from boy:
    • Re-enabled local-qwen-3.6 model on port 8081 in litellm/config.yaml.
    • Updated openrouter free model roster lists.
  5. Deployment guidelines (.agents/AGENTS.md):
    • Added production checklist for deployer agents under boy.
    • Cleaned up credentials, documented self-signed SSL cert verify commands, and added missing ssh boy prefixes.

Summary by Sourcery

Add subdirectory-aware dashboard routing and production-oriented configuration updates for the LLM routing stack.

Enhancements:

  • Make the router dashboard links dynamically resolve Langfuse, LiteLLM, and Llama endpoints based on the external host, routing domain, and request context instead of hardcoded localhost URLs.
  • Update stack startup messages to advertise the external HTTPS entrypoints and dashboard/Admin UI URLs fronted by the production reverse proxy.
  • Re-enable the local-qwen-3.6 model in the LiteLLM configuration, now pointing at the llama.cpp service on port 8081.

Deployment:

  • Document a detailed production deployment checklist for the boy host user, including prerequisites, stack restart steps, HAProxy setup, and operational notes.
  • Configure LiteLLM container environment to support operation behind a subdirectory reverse proxy via SERVER_ROOT_PATH and PROXY_BASE_URL, and relax readiness probe timing for more reliable startup.

Documentation:

  • Retarget the internal NotebookLM reference in the agent documentation to the LLM-Routing knowledge base notebook.

@sourcery-ai

sourcery-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements domain- and environment-aware dashboard routing for Langfuse/LiteLLM/Llama UIs under an /llm-routing subdirectory, reconfigures LiteLLM pod settings and health checks for HAProxy, re-enables a local model, and documents a concrete production deployment flow for the 'boy' host.

Sequence diagram for domain-aware dashboard routing

sequenceDiagram
    actor User
    participant FastAPI_app
    participant get_dashboard
    participant Env

    User->>FastAPI_app: GET /dashboard
    FastAPI_app->>get_dashboard: get_dashboard(request)
    get_dashboard->>Env: os.getenv(BASEURL)
    Env-->>get_dashboard: external_host_env
    alt [external_host_env is set]
        get_dashboard->>get_dashboard: urlparse(external_host_env)
        get_dashboard->>get_dashboard: validate external_host, external_netloc
    else [external_host_env is not set]
        get_dashboard->>get_dashboard: use request.base_url
        get_dashboard->>get_dashboard: validate external_host, external_netloc
    end
    get_dashboard->>Env: os.getenv(ROUTING_DOMAIN)
    Env-->>get_dashboard: domain
    alt [domain in external_netloc]
        get_dashboard->>get_dashboard: build https://{external_netloc}/llm-routing/... URLs
    else [domain in request.base_url.hostname]
        get_dashboard->>get_dashboard: build {scheme}://{netloc}/llm-routing/... URLs
    else [fallback]
        get_dashboard->>get_dashboard: build http://{external_host}:port URLs
    end
    get_dashboard->>get_dashboard: get_dashboard_data()
    get_dashboard-->>User: dashboard HTML with langfuse_url, litellm_url, llama_url
Loading

File-Level Changes

Change Details Files
Make dashboard links dynamically resolve Langfuse, LiteLLM, and Llama UIs based on BASEURL/ROUTING_DOMAIN and request host while validating hostnames.
  • Change /dashboard handler to accept the Request object and derive external host/netloc either from BASEURL/BASE_URL env vars or request.base_url
  • Introduce ROUTING_DOMAIN env (default vendeuvre.lan) and use regex-based validation of hostname/netloc strings to avoid malformed/unsafe values
  • Branch URL construction to prefer /llm-routing/* subdirectory paths when the routing domain matches, otherwise fall back to direct host:port URLs
  • Replace hardcoded localhost URLs in the dashboard HTML template with langfuse_url, litellm_url, and llama_url placeholders
router/main.py
Align deployment messaging and LiteLLM pod configuration with HAProxy /llm-routing subdirectory routing and improve probe behavior.
  • Update stack success messages to advertise HTTPS entrypoints and dashboard/Admin URLs under /llm-routing while still showing local http://localhost fallbacks
  • Set LiteLLM SERVER_ROOT_PATH and PROXY_BASE_URL env vars for subdirectory mounting behind HAProxy
  • Point liveness and readiness probes at direct in-pod HTTP endpoints instead of reverse-proxied paths
  • Increase readiness probe initial delay to 45 seconds to reduce false negatives on startup
start-stack.sh
pod.yaml
Re-enable the local-qwen-3.6 model via llama.cpp on a dedicated port and keep free model rosters in sync with production.
  • Restore a local-qwen-3.6 entry using api_base http://127.0.0.1:8081/v1, with an updated model identifier and alias
  • Synchronize free model roster JSON with the current production configuration
litellm/config.yaml
router/free_models_roster.json
Add and refine production deployment documentation for the 'boy' host and update the primary NotebookLM knowledge base reference.
  • Document one-time host prerequisites, fresh deploy steps, HAProxy + agy daemon startup, and verification commands for the boy user
  • Clarify behavior around self-signed cert verification, sudo password file handling, fast vs full rebuilds, and GitHub CLI authentication
  • Rename the primary NotebookLM notebook reference from TriageGate-Architect-KB to LLM-Routing-KB without changing its ID or URL
.agents/AGENTS.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@sheepdestroyer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35ecad8e-0506-4ddf-bb4d-8f74cdac6f0b

📥 Commits

Reviewing files that changed from the base of the PR and between 454593c and 0404eff.

📒 Files selected for processing (6)
  • .agents/AGENTS.md
  • litellm/config.yaml
  • pod.yaml
  • router/free_models_roster.json
  • router/main.py
  • start-stack.sh
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/learn-deployment-guidelines

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.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • There are multiple hard-coded host/domain references (e.g., https://x570.vendeuvre.lan/... in start-stack.sh and PROXY_BASE_URL in pod.yaml) while ROUTING_DOMAIN is configurable in router/main.py; consider centralizing the base domain/host configuration so these URLs are derived from env/config rather than baked into scripts.
  • The new /dashboard handler does repeated urlparse imports and compiles regexes on every request; you could move the imports and regex patterns to module scope (and deduplicate the urlparse logic) to simplify the function and avoid per-request overhead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There are multiple hard-coded host/domain references (e.g., `https://x570.vendeuvre.lan/...` in `start-stack.sh` and `PROXY_BASE_URL` in `pod.yaml`) while `ROUTING_DOMAIN` is configurable in `router/main.py`; consider centralizing the base domain/host configuration so these URLs are derived from env/config rather than baked into scripts.
- The new `/dashboard` handler does repeated `urlparse` imports and compiles regexes on every request; you could move the imports and regex patterns to module scope (and deduplicate the `urlparse` logic) to simplify the function and avoid per-request overhead.

## Individual Comments

### Comment 1
<location path="start-stack.sh" line_range="602-606" />
<code_context>
         echo "🎉 SUCCESS: LLM Triage Gateway restarted!"
-        echo "📍 Entry endpoint  : http://localhost:5000/v1"
-        echo "⚙️  Dashboard URL  : http://localhost:5000/dashboard"
+        echo "📍 Entry endpoint  : https://x570.vendeuvre.lan/llm-routing/v1"
+        echo "   (local)          : http://localhost:5000/v1"
+        echo "⚙️  Dashboard URL  : https://x570.vendeuvre.lan/llm-routing/dashboard"
         echo "🔑 Gateway API Key : gateway-pass"
-        echo "🔐 LiteLLM Admin UI: http://localhost:4000/ui"
+        echo "🔐 LiteLLM Admin UI: https://x570.vendeuvre.lan/llm-routing/litellm/ui"
         echo "   Username: admin  |  Password: $LITELLM_MASTER_KEY"
         echo "========================================================================="
</code_context>
<issue_to_address>
**suggestion:** Avoid hardcoding hostnames and paths in status output; derive them from configuration

These success messages now embed `https://x570.vendeuvre.lan/llm-routing/...` directly. That will break as soon as the hostname, routing prefix, or TLS setup changes, or if this script is reused in another environment.

Please build these URLs from configuration/env (e.g. `ROUTING_DOMAIN`, a `PUBLIC_BASE_URL`, etc.) with reasonable defaults so the printed endpoints always match the actual router setup across environments.

Suggested implementation:

```
        # Derive base URLs from configuration/env with sensible defaults
        PUBLIC_BASE_URL="${PUBLIC_BASE_URL:-https://x570.vendeuvre.lan/llm-routing}"
        LOCAL_BASE_URL="${LOCAL_BASE_URL:-http://localhost:5000}"

        echo ""
        echo "========================================================================="
        echo "🎉 SUCCESS: LLM Triage Gateway restarted!"
        echo "📍 Entry endpoint  : ${PUBLIC_BASE_URL}/v1"
        echo "   (local)          : ${LOCAL_BASE_URL}/v1"
        echo "⚙️  Dashboard URL  : ${PUBLIC_BASE_URL}/dashboard"
        echo "🔑 Gateway API Key : gateway-pass"
        echo "🔐 LiteLLM Admin UI: ${PUBLIC_BASE_URL}/litellm/ui"
        echo "   Username: admin  |  Password: $LITELLM_MASTER_KEY"

```

If other parts of `start-stack.sh` (or related scripts) also print or depend on specific hostnames/paths, they should be updated to use the same `PUBLIC_BASE_URL` and `LOCAL_BASE_URL` variables for consistency. You may also want to document these env vars in your README or deployment docs so operators know how to override them for different environments.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread start-stack.sh Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces production deployment documentation for the 'boy' host, re-enables the local Qwen-3.6 model, configures root path environment variables and readiness delays for LiteLLM, updates the free models roster, and dynamically resolves external service URLs on the router dashboard. The review feedback highlights three key areas for improvement: a security vulnerability in the dashboard's domain validation logic where loose substring matching could allow open redirects or phishing links, a potential SSH hang in the deployment script due to a missing stdin redirection when running a background daemon with 'nohup', and a missing 'ssh boy' prefix in the verification 'curl' command within the deployment checklist.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread router/main.py
Comment on lines +3075 to +3110
external_host_env = os.getenv("BASEURL") or os.getenv("BASE_URL")
if external_host_env:
if "://" not in external_host_env:
from urllib.parse import urlparse
parsed = urlparse(f"http://{external_host_env}")
else:
from urllib.parse import urlparse
parsed = urlparse(external_host_env)
external_host = parsed.hostname or "localhost"
external_netloc = parsed.netloc or "localhost"
else:
external_host = request.base_url.hostname or "localhost"
external_netloc = request.base_url.netloc or "localhost"

domain = os.getenv("ROUTING_DOMAIN") or "vendeuvre.lan"
import re
if not isinstance(external_host, str) or not re.match(r"^[a-zA-Z0-9.-]+$", external_host):
external_host = "localhost"
if not isinstance(external_netloc, str) or not re.match(r"^[a-zA-Z0-9.-]+(?::\d+)?$", external_netloc):
external_netloc = "localhost"

if external_netloc and domain in external_netloc:
langfuse_url = f"https://{external_netloc}/llm-routing/langfuse"
litellm_url = f"https://{external_netloc}/llm-routing/litellm/ui"
llama_url = f"https://{external_netloc}/llm-routing/llama/"
elif domain in (request.base_url.hostname or ""):
scheme = request.url.scheme if re.match(r"^(?:http|https)$", request.url.scheme) else "https"
netloc = request.url.netloc if re.match(r"^[a-zA-Z0-9.-]+(?::\d+)?$", request.url.netloc) else "localhost"
base = f"{scheme}://{netloc}"
langfuse_url = f"{base}/llm-routing/langfuse"
litellm_url = f"{base}/llm-routing/litellm/ui"
llama_url = f"{base}/llm-routing/llama/"
else:
langfuse_url = f"http://{external_host}:3001"
litellm_url = f"http://{external_host}:4000/ui"
llama_url = f"http://{external_host}:8080"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-high high

The domain validation logic uses loose substring matching (domain in external_netloc and domain in request.base_url.hostname). This can be bypassed if an attacker accesses the dashboard using a domain like attacker-vendeuvre.lan or vendeuvre.lan.attacker.com, potentially leading to open redirect or phishing link generation on the dashboard.

Additionally, the duplicate from urllib.parse import urlparse imports and redundant import re inside the function can be cleaned up.

    external_host_env = os.getenv("BASEURL") or os.getenv("BASE_URL")
    if external_host_env:
        from urllib.parse import urlparse
        if "://" not in external_host_env:
            parsed = urlparse(f"http://{external_host_env}")
        else:
            parsed = urlparse(external_host_env)
        external_host = parsed.hostname or "localhost"
        external_netloc = parsed.netloc or "localhost"
    else:
        external_host = request.base_url.hostname or "localhost"
        external_netloc = request.base_url.netloc or "localhost"

    domain = os.getenv("ROUTING_DOMAIN") or "vendeuvre.lan"
    if not isinstance(external_host, str) or not re.match(r"^[a-zA-Z0-9.-]+$", external_host):
        external_host = "localhost"
    if not isinstance(external_netloc, str) or not re.match(r"^[a-zA-Z0-9.-]+(?::\d+)?$", external_netloc):
        external_netloc = "localhost"

    # Strict domain validation to prevent substring matching vulnerabilities (e.g., attacker-vendeuvre.lan)
    is_valid_external = external_host == domain or external_host.endswith("." + domain)
    is_valid_base = request.base_url.hostname == domain or (request.base_url.hostname or "").endswith("." + domain)

    if is_valid_external:
        langfuse_url = f"https://{external_netloc}/llm-routing/langfuse"
        litellm_url = f"https://{external_netloc}/llm-routing/litellm/ui"
        llama_url = f"https://{external_netloc}/llm-routing/llama/"
    elif is_valid_base:
        scheme = request.url.scheme if re.match(r"^(?:http|https)$", request.url.scheme) else "https"
        netloc = request.url.netloc if re.match(r"^[a-zA-Z0-9.-]+(?::\d+)?$", request.url.netloc) else "localhost"
        base = f"{scheme}://{netloc}"
        langfuse_url = f"{base}/llm-routing/langfuse"
        litellm_url = f"{base}/llm-routing/litellm/ui"
        llama_url = f"{base}/llm-routing/llama/"
    else:
        langfuse_url = f"http://{external_host}:3001"
        litellm_url = f"http://{external_host}:4000/ui"
        llama_url = f"http://{external_host}:8080"

Comment thread .agents/AGENTS.md
docker.io/library/haproxy:alpine"

# 5. Start the host-side agy daemon
ssh boy "pkill -f host_agy_daemon.py || true"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When starting a background process using nohup ... & over SSH, failing to redirect standard input (stdin) can cause the SSH session to hang or terminate the background process when the connection closes. Redirecting stdin from /dev/null ensures the background process is properly decoupled.

Suggested change
ssh boy "pkill -f host_agy_daemon.py || true"
ssh boy "nohup python3 /mnt/DATA/boy/LLM-Routing/scripts/host_agy_daemon.py >/tmp/agy-daemon.log 2>&1 </dev/null &"

Comment thread .agents/AGENTS.md Outdated
Comment on lines +68 to +69
# Replace the cert with a trusted CA-signed cert to remove -k.
curl -k -s --resolve x570.vendeuvre.lan:443:127.0.0.1 \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The verification curl command is missing the ssh boy prefix. Since boy is a remote host, running curl locally with --resolve x570.vendeuvre.lan:443:127.0.0.1 will attempt to connect to the local machine's port 443 instead of the remote boy host where HAProxy is running.

Suggested change
# Replace the cert with a trusted CA-signed cert to remove -k.
curl -k -s --resolve x570.vendeuvre.lan:443:127.0.0.1 \
ssh boy "curl -k -s --resolve x570.vendeuvre.lan:443:127.0.0.1 https://x570.vendeuvre.lan/llm-routing/dashboard" | head -5

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.

1 participant