Skip to content

chore: subdirectory routing, configurable domain, and deployment guidelines (PR #241 fixes)#242

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

chore: subdirectory routing, configurable domain, and deployment guidelines (PR #241 fixes)#242
sheepdestroyer wants to merge 18 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 #241, 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.

Addressed PR #241 Review Fixes:

  • start-stack.sh: Avoided hardcoded SUCCESS endpoints by deriving them from PUBLIC_BASE_URL and LOCAL_BASE_URL env/config.
  • router/main.py: Refactored the dashboard handler to use strict domain validation to prevent loose substring match bypasses (open redirect / phishing vulnerability), and moved urlparse import to module scope.
  • pod.yaml: Made PROXY_BASE_URL dynamically configurable via PROXY_BASE_URL_PLACEHOLDER replacing the hardcoded URL.
  • .agents/AGENTS.md: Added stdin redirection to /dev/null for SSH nohup calls, and added missing ssh boy prefix to the verification curl command.

Summary by Sourcery

Introduce subdirectory-aware routing and configurable external URLs for the router dashboard and admin UIs, align pod configuration with the production host layout, and document the end-to-end deployment flow for the boy environment.

New Features:

  • Support domain-validated, subdirectory-based routing for Langfuse, LiteLLM, and Llama dashboards from the router UI.
  • Expose the local-qwen-3.6 model again via LiteLLM using the llama.cpp backend on port 8081.

Enhancements:

  • Derive public and proxy base URLs from environment/config in start-stack.sh and pod.yaml instead of hardcoding localhost endpoints.
  • Tighten domain validation and URL construction in the router dashboard handler to avoid unsafe host/header usage.
  • Adjust LiteLLM health probe configuration and readiness timing to better match startup behavior.
  • Update pod volume mounts, DBus/xdg paths, and environment variables to match the boy production filesystem and routing scheme.

Documentation:

  • Expand AGENTS.md with a detailed production deployment checklist, notes, and GitHub operations policy for the boy host, and refresh internal NotebookLM references.

@sourcery-ai

sourcery-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements secure, domain-aware subdirectory routing for the dashboard and proxy, makes URLs/envs configurable for production behind HAProxy, syncs production pod/deployment wiring (paths, probes, qwen model, roster), and adds a concrete deployment checklist for the boy host user.

Sequence diagram for secure domain-aware dashboard routing

sequenceDiagram
    actor User
    participant RouterApp
    participant get_dashboard
    participant LangfuseUI
    participant LiteLLMAdmin
    participant LlamaServer

    User->>RouterApp: GET /llm-routing/dashboard
    RouterApp->>get_dashboard: get_dashboard(request)

    alt [external_host in ROUTING_DOMAIN]
        get_dashboard-->>User: HTML with https://external_netloc/llm-routing/langfuse
        get_dashboard-->>User: HTML with https://external_netloc/llm-routing/litellm/ui
        get_dashboard-->>User: HTML with https://external_netloc/llm-routing/llama/
    else [base_url in ROUTING_DOMAIN]
        get_dashboard-->>User: HTML with {scheme}://{netloc}/llm-routing/langfuse
        get_dashboard-->>User: HTML with {scheme}://{netloc}/llm-routing/litellm/ui
        get_dashboard-->>User: HTML with {scheme}://{netloc}/llm-routing/llama/
    else [untrusted domain]
        get_dashboard-->>User: HTML with http://external_host:3001
        get_dashboard-->>User: HTML with http://external_host:4000/ui
        get_dashboard-->>User: HTML with http://external_host:8080
    end

    User->>LangfuseUI: Open langfuse_url link
    User->>LiteLLMAdmin: Open litellm_url link
    User->>LlamaServer: Open llama_url link
Loading

File-Level Changes

Change Details Files
Make the dashboard generate Langfuse/LiteLLM/Llama URLs based on validated external/base domains and /llm-routing/... subpaths instead of hardcoded localhost links.
  • Inject Request into the /dashboard handler and derive external_host/external_netloc from BASEURL/BASE_URL or request.base_url.
  • Introduce ROUTING_DOMAIN env with a vendeuvre.lan default and validate host/netloc with strict regexes to avoid HTML injection.
  • Enforce strict domain checks (exact match or subdomain of ROUTING_DOMAIN) to decide whether to emit subdirectory-based HTTPS URLs, fall back to request URL, or final localhost ports.
  • Thread computed langfuse_url, litellm_url, and llama_url into the rendered HTML template anchors instead of http://localhost:* constants.
  • Move urlparse import to module scope for reuse and clarity.
router/main.py
Wire LiteLLM to run under a /llm-routing/litellm subpath behind HAProxy and configure its base URL dynamically from templated env.
  • Set SERVER_ROOT_PATH=/llm-routing/litellm and PROXY_BASE_URL=PROXY_BASE_URL_PLACEHOLDER in the LiteLLM container env.
  • Add PROXY_BASE_URL_PLACEHOLDER to the pod template placeholder list and fail if it is missing during rendering.
  • Derive PUBLIC_BASE_URL in start-stack.sh from env or a https://x570.vendeuvre.lan/llm-routing default and export it to the YAML renderer.
  • Compute PROXY_BASE_URL as ${PUBLIC_BASE_URL}/litellm in the Python templating script and substitute it into the pod YAML.
  • Update success output messages in start-stack.sh to use PUBLIC_BASE_URL/LOCAL_BASE_URL-derived endpoints (including LiteLLM admin UI) instead of hardcoded localhost URLs.
pod.yaml
start-stack.sh
Adjust health probes and pod hostPath mappings for the production boy environment and LiteLLM behaviour.
  • Change LiteLLM readiness probe to call the in-pod http://localhost:4000/health/readiness endpoint and extend initialDelaySeconds from 10 to 45 to avoid false negatives.
  • Keep the liveness probe using an in-pod /ping check rather than reverse proxy paths.
  • Normalize all pod hostPath mounts from the developer home under /home/gpav/... to production paths under /mnt/DATA/boy/... and update DBus/XDG runtime uid-based paths to use uid 1002.
  • Ensure all application data directories (valkey, router, postgres, clickhouse, redis-lf, minio, datasets, secrets, keyrings, agy binary) are mapped to boy's production layout.
pod.yaml
Re-enable the local Qwen model via llama.cpp on port 8081 and refresh the free models roster for OpenRouter.
  • Uncomment and adapt the local-qwen-3.6 entry to point to http://127.0.0.1:8081/v1 with model: openai/local-qwen-3.6 and request_timeout: 300.
  • Update router/free_models_roster.json to sync the free model lists with current production defaults (exact model list changes in the diff).
litellm/config.yaml
router/free_models_roster.json
Document and script the production deployment flow for the boy user, including network, HAProxy, and agent daemon setup.
  • Add a production deployment checklist to .agents/AGENTS.md describing host prerequisites (sysctl, firewall, HAProxy cert/config, mount directories) and the full deploy sequence using ssh boy and start-stack.sh --full-rebuild.
  • Document manual HAProxy container management via podman, and starting the host-side agy daemon with nohup and stdin redirected to /dev/null.
  • Add guidance for verifying the deployment via curl with --resolve and -k for the self-signed HAProxy cert, and notes about GitHub CLI auth, sudo password handling, and agy systemd limitations.
  • Fix smaller doc issues: updated Notebook name/URL, ensure ssh boy prefixes are present, and clarify GitHub API policy (prefer gh / gh api).
.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: 23 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: 1657cb0f-3fc7-4092-a9c8-437f9f6aa2d6

📥 Commits

Reviewing files that changed from the base of the PR and between 454593c and 60ea4af.

📒 Files selected for processing (8)
  • .agents/AGENTS.md
  • README.md
  • litellm/config.yaml
  • pod.yaml
  • router/agy_proxy.py
  • 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:

  • The logic for deriving base/public URLs and subdirectory paths is now spread across start-stack.sh, router/main.py and pod.yaml (e.g. /llm-routing, /llm-routing/litellm); consider centralizing this into a single config/env convention so that changing the base path or domain only requires updating it in one place.
  • The updated pod.yaml hardcodes host paths and user-specific IDs for the boy environment (e.g. /mnt/DATA/boy/..., 1002); if this pod needs to be reused in other environments, consider parameterizing these values (similar to the existing placeholders) rather than baking them directly into the manifest.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The logic for deriving base/public URLs and subdirectory paths is now spread across `start-stack.sh`, `router/main.py` and `pod.yaml` (e.g. `/llm-routing`, `/llm-routing/litellm`); consider centralizing this into a single config/env convention so that changing the base path or domain only requires updating it in one place.
- The updated `pod.yaml` hardcodes host paths and user-specific IDs for the `boy` environment (e.g. `/mnt/DATA/boy/...`, `1002`); if this pod needs to be reused in other environments, consider parameterizing these values (similar to the existing placeholders) rather than baking them directly into the manifest.

## Individual Comments

### Comment 1
<location path="start-stack.sh" line_range="612-616" />
<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  : ${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: http://localhost:4000/ui"
+        echo "🔐 LiteLLM Admin UI: ${PUBLIC_BASE_URL}/litellm/ui"
         echo "   Username: admin  |  Password: $LITELLM_MASTER_KEY"
         echo "========================================================================="
</code_context>
<issue_to_address>
**nitpick:** Appending paths directly to PUBLIC_BASE_URL can produce double slashes if the base ends with `/`.

This assumes `PUBLIC_BASE_URL` has no trailing slash. If someone sets `PUBLIC_BASE_URL=https://example.com/llm-routing/`, these lines will output `//v1` and `/llm-routing//litellm/ui`. While most browsers tolerate this, you can avoid it by normalizing the value once (e.g. `PUBLIC_BASE_URL="${PUBLIC_BASE_URL%/}"`) before concatenation, or by trimming any trailing slash where the variable is initialized.
</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

@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 a production deployment checklist, re-enables the local Qwen model, updates the free models roster, and implements dynamic base URL resolution for the router dashboard. Feedback on these changes highlights critical issues: hardcoding paths in pod.yaml breaks the placeholder validation and dynamic deployment logic in start-stack.sh; PUBLIC_BASE_URL and LOCAL_BASE_URL are defined redundantly across different scopes in start-stack.sh; and the re-enabled local-qwen-3.6 model remains commented out in the fallback chains within litellm/config.yaml.

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 pod.yaml Outdated
volumes:
- hostPath:
path: /home/gpav/Vrac/LAB/AI/LLM-Routing/valkey-data
path: /mnt/DATA/boy/LLM-Routing/valkey-data

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.

critical

Hardcoded paths break start-stack.sh placeholder validation and dynamic deployment

By replacing the placeholders /home/gpav/Vrac/LAB/AI/LLM-Routing, /home/gpav/, and /run/user/1000 with hardcoded paths (/mnt/DATA/boy/LLM-Routing, /mnt/DATA/boy/, and /run/user/1002), the placeholder validation in start-stack.sh (lines 545-548) will fail because it strictly checks for the presence of the original placeholders in pod.yaml:

placeholders = [
    "/home/gpav/Vrac/LAB/AI/LLM-Routing",
    "/home/gpav/",
    "/run/user/1000",
    ...
]
for ph in placeholders:
    if ph not in text:
        sys.stderr.write(f"Error: Required placeholder '{ph}' not found in pod.yaml...")
        sys.exit(1)

Furthermore, start-stack.sh already dynamically replaces these placeholders with the current WORKDIR, HOME, and user uid at runtime. Hardcoding these paths is completely unnecessary and breaks multi-user/dynamic deployment.

Please revert all these paths in pod.yaml back to their original placeholder forms (e.g., /home/gpav/Vrac/LAB/AI/LLM-Routing/..., /home/gpav/..., and /run/user/1000/...).

      path: /home/gpav/Vrac/LAB/AI/LLM-Routing/valkey-data

Comment thread start-stack.sh Outdated

render_pod_yaml() {
export WORKDIR HOME LITELLM_MASTER_KEY POSTGRES_PASSWORD NEXTAUTH_SECRET SALT ENCRYPTION_KEY LANGFUSE_INIT_USER_PASSWORD MINIO_ROOT_USER MINIO_ROOT_PASSWORD OLLAMA_API_KEY LANGFUSE_PUBLIC_KEY LANGFUSE_SECRET_KEY CLASSIFIER_INPUT_MAX_CHARS REDIS_AUTH CLICKHOUSE_PASSWORD
PUBLIC_BASE_URL="${PUBLIC_BASE_URL:-${BASE_URL:-${BASEURL:-https://x570.vendeuvre.lan/llm-routing}}}"

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

Duplicate definition of PUBLIC_BASE_URL and LOCAL_BASE_URL

PUBLIC_BASE_URL is defined with a fallback value inside render_pod_yaml (line 514), and then redefined again on lines 606 and 634. Similarly, LOCAL_BASE_URL is defined twice on lines 607 and 635.

Since render_pod_yaml is executed in a pipeline (render_pod_yaml | podman play kube -), it runs in a subshell in many environments, meaning variables set inside it do not persist to the parent shell. To avoid duplication and ensure consistency, define these variables once at the top of the script (e.g., right after loading the .env file) and export them so they are available globally.

Comment thread litellm/config.yaml
Comment on lines +93 to +99
# Re-enabled 2026-07-08 — llama.cpp on port 8081, alias local-qwen-3.6
- litellm_params:
api_base: http://127.0.0.1:8081/v1
api_key: local-token
model: openai/local-qwen-3.6
request_timeout: 300
model_name: local-qwen-3.6

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

local-qwen-3.6 is still commented out in fallback chains

Although the local-qwen-3.6 model has been re-enabled in the model_list (lines 94-99), it remains commented out in the fallbacks configuration (e.g., lines 41, 48, 54, 63). If this model is intended to be used as a fallback when other backends fail, please uncomment it in the fallback chains.

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