Skip to content

Render deploy template + opt-in public demo mode - #1

Merged
Ho1yShif merged 19 commits into
devfrom
render-template
Aug 11, 2026
Merged

Render deploy template + opt-in public demo mode#1
Ho1yShif merged 19 commits into
devfrom
render-template

Conversation

@Ho1yShif

Copy link
Copy Markdown
Collaborator

Adds one-click Render deployment to Odysseus, plus a demo mode that makes a public instance safe to leave running.

Deploy template

  • render.yaml provisions the web service, a SearXNG search service, Postgres, and Key Value under one project.
  • Dockerfile.render and searxng.Dockerfile build the two images; entrypoints in docker/ seed admin auth, generate the SearXNG secret on first boot, and stage settings outside /tmp.
  • docker/seed_openai_endpoint.py registers the OpenAI endpoint at startup so chat works on first load.
  • Boot fails loudly if admin auth isn't seeded, rather than starting an open instance.

Demo mode

src/demo.py gates behavior behind a DEMO flag, off by default:

  • Each visitor gets an isolated session, so chat history never crosses tenants.
  • Per-session and global rate limits in src/rate_limiter.py cap spend on the shared OpenAI key.
  • Model catalog edits are additive, so a demo user can't remove models for everyone else.

Other

  • README gains setup instructions, an architecture diagram, and a Deploy to Render button.
  • .env.example documents the new variables.
  • Test tooling moved to requirements-dev.txt and out of the production image.

Ho1yShif and others added 19 commits August 9, 2026 19:12
Blueprint (render.yaml) provisioning the Odysseus web app plus bundled
private SearXNG and ChromaDB services, with a slim hosted Dockerfile,
generated admin password, secrets as sync:false, and same-origin CORS.

- Dockerfile.render: slim image (drops GPU/local-model/host-Docker tooling)
- searxng.Dockerfile + entrypoint: bake settings.yml (json format) + secret
- entrypoint.render.sh: bind $PORT, run first-time setup
- app.py: default ALLOWED_ORIGINS to RENDER_EXTERNAL_URL when unset
- README: hermes-style Render template README + Deploy button
- favicon: white Render logo as base favicon

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 0bf3cc2af3d51cf90fa62d6e9a78c771f0008405)
…ration details

Clarified the process for obtaining the automatically generated admin password during deployment and emphasized that it should be copied from the Render Dashboard. Added a section on configuring `ALLOWED_ORIGINS` for CORS, specifying when to set it for custom domains or additional origins.

(cherry picked from commit 572f7385a92fe0a6b70c0a836c5586d45c5e7979)
setup.py catches its own admin-creation errors and exits 0, so a silent
seeding failure would boot a healthy-looking service (the "/" health check
still passes) that nobody can log into. Assert the auth invariant in the
Render entrypoint after setup: when auth is enabled, an admin account must
exist, otherwise exit non-zero so the deploy surfaces as failed. Reuses
src.constants.AUTH_FILE so the path tracks ODYSSEUS_DATA_DIR like the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4d3bfb1c2cdffcf1a96ef4a9959d7324ff905b39)
Added a note specifying that a key with only the **Chat completions** permission is sufficient for the `OPENAI_API_KEY`, as embeddings run locally and no other OpenAI endpoints are utilized. This enhances the documentation for better understanding of API key requirements.

(cherry picked from commit 457d6a572495faf1b87c78dcd30d7fd86c172b06)
The base SearXNG image treats /tmp as a volume, so the baked-in
settings.yml.template was wiped at runtime and the entrypoint's sed
failed with "No such file or directory", crashing the deploy. Stage the
template in /usr/local/share instead, which persists in the image layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 9b89e9d772e25c21a550132da5629c8d85b9f6d7)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit dc0c150ce749a5c4834258124acccc9151341f49)
…sseus-dev#2)

* feat(render): add one-click Render deploy template

Blueprint (render.yaml) provisioning the Odysseus web app plus bundled
private SearXNG and ChromaDB services, with a slim hosted Dockerfile,
generated admin password, secrets as sync:false, and same-origin CORS.

- Dockerfile.render: slim image (drops GPU/local-model/host-Docker tooling)
- searxng.Dockerfile + entrypoint: bake settings.yml (json format) + secret
- entrypoint.render.sh: bind $PORT, run first-time setup
- app.py: default ALLOWED_ORIGINS to RENDER_EXTERNAL_URL when unset
- README: hermes-style Render template README + Deploy button
- favicon: white Render logo as base favicon

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(README): update admin password instructions and add CORS configuration details

Clarified the process for obtaining the automatically generated admin password during deployment and emphasized that it should be copied from the Render Dashboard. Added a section on configuring `ALLOWED_ORIGINS` for CORS, specifying when to set it for custom domains or additional origins.

* fix(render): fail hosted deploy when admin auth isn't seeded

setup.py catches its own admin-creation errors and exits 0, so a silent
seeding failure would boot a healthy-looking service (the "/" health check
still passes) that nobody can log into. Assert the auth invariant in the
Render entrypoint after setup: when auth is enabled, an admin account must
exist, otherwise exit non-zero so the deploy surfaces as failed. Reuses
src.constants.AUTH_FILE so the path tracks ODYSSEUS_DATA_DIR like the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(README): clarify OpenAI API key restrictions for chat completions

Added a note specifying that a key with only the **Chat completions** permission is sufficient for the `OPENAI_API_KEY`, as embeddings run locally and no other OpenAI endpoints are utilized. This enhances the documentation for better understanding of API key requirements.

* fix(render): stage SearXNG settings template outside /tmp

The base SearXNG image treats /tmp as a volume, so the baked-in
settings.yml.template was wiped at runtime and the entrypoint's sed
failed with "No such file or directory", crashing the deploy. Stage the
template in /usr/local/share instead, which persists in the image layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(render): group services under an odysseus project

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(render): add OpenAI model seeding on first boot and update README

Introduced a new script to seed an OpenAI model endpoint during the initial deployment, ensuring chat functionality works out of the box. Updated the README to include the new `OPENAI_DEFAULT_MODEL` variable and clarified the auto-configuration process for the OpenAI endpoint. Enhanced the render.yaml with demo mode settings for public showcase options.

* feat: update OpenAI model references from gpt-4o to gpt-5.6-sol

Replaced all instances of the model identifier `gpt-4o` with `gpt-5.6-sol` across various files, including configuration, documentation, and tests. This change ensures consistency in model usage and reflects the latest updates in the OpenAI model offerings. Updated the README to reflect the new default model and adjusted related code and tests accordingly.

* feat(demo): add opt-in public, locked-down chat demo mode

DEMO=true opens a public, no-signup chat surface on the deployer's OpenAI
key; DEMO=false (default) leaves the full authenticated app unchanged so
forks are unaffected.

Backend choke points (no scattered `if DEMO`):
- src/demo.py: flag, per-visitor cookie/owner, DEMO_PRIVILEGES, rate +
  per-session message caps, pinned model/endpoint/env-key config, output
  token clamp, startup log.
- app.py: demo path in AuthMiddleware (per-visitor synthetic owner on the
  route whitelist only, after the is_configured check); startup mode log;
  boot purge of ephemeral demo sessions.
- core/auth.py: get_privileges returns the locked profile for demo owners.
- core/session_manager.py: skip _persist_message for demo owners
  (ephemeral history); pin model/endpoint/env-key on read.
- routes/chat_routes.py: demo limit check before spend (friendly SSE, no
  500), pre-context lockdown, token clamp, pinned config that bypasses the
  endpoint-row orphan checks.
- src/task_scheduler.py, routes/assistant_routes.py: seeding guards.

Frontend: inject a synchronous window.__ODYSSEUS_DEMO flag, make
/api/auth/status demo-aware, suppress the 401->/login redirect in demo
mode, and hide non-chat sidebar rails.

Tests: tests/test_demo_mode.py (identity, whitelist, cookie minting, limit
trips, pinned config, privileges, ephemeral persist skip).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: improve error logging and update README for demo mode

Updated error handling in the startup event to log exceptions more clearly. Enhanced the README with additional notes on the limitations of demo mode, emphasizing that rate limits are not hard guarantees and should be managed through OpenAI project settings. Adjusted comments in the render.yaml to reflect these changes. Cleaned up code in various files for consistency and clarity.

* feat: update demo model to gpt-5.6-luna and adjust related configurations

Changed the default demo model from `gpt-4.1-nano` to `gpt-5.6-luna` across various files, including environment variables, model discovery, and session handling. Updated comments and documentation to reflect these changes, ensuring consistency in model usage and enhancing the demo experience for users.

* refactor(demo): enhance error handling and logging for demo privileges

Improved error handling in the AuthManager and SessionManager classes to log exceptions when demo privilege checks fail. Updated the demo session configuration to conditionally apply rate limiting based on the DEMO_MODE flag. Adjusted comments for clarity and consistency across the codebase, ensuring better maintainability and understanding of demo-related functionality.

* feat(demo): implement trusted client IP handling for rate limiting

Enhanced the demo mode by implementing a trusted client IP mechanism for rate limiting, ensuring that rate limits are keyed on the real client IP rather than the potentially spoofable leftmost entry in the X-Forwarded-For header. This change improves security and prevents users from bypassing rate limits by rotating IPs. Updated related documentation in the README and configuration files to reflect these changes, including new environment variables for daily message ceilings per IP. Added tests to verify the correct behavior of the new rate limiting logic.

* refactor(demo): streamline demo privilege handling and request validation

Refactored the demo privilege management by deriving fallback privileges from DEFAULT_PRIVILEGES, ensuring new keys default to off. Introduced a centralized function, is_demo_request, to validate demo requests consistently across routes. This change enhances code maintainability and clarity while improving the handling of demo session configurations.

* fix(render): update health check path in configuration

Changed the health check path in render.yaml from '/' to '/api/health' to ensure proper monitoring of the service's health status. This update aligns with the new API structure and enhances the reliability of health checks.

* fix(demo): honor TRUSTED_PROXY_HOPS=0 for directly-exposed deploys

_trusted_proxy_hops() forced hops >= 1, so a proxy-less fork (no edge
appending X-Forwarded-For) still trusted the wholly attacker-supplied XFF
as the rate-limit / IP-daily-cap key — a full bypass. Follow the
conventional trusted-hop semantics (Werkzeug ProxyFix, uvicorn
--forwarded-allow-ips): 0 = no trusted proxy, ignore XFF and key on the
real TCP peer. Unset/invalid/negative still fall back to 1 (Render default
unchanged). Docstrings + README updated; regression tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(demo): polish logging, typing, and entrypoint robustness

- Log (not silently pass) on demo status resolution and demo session-config
  pin failures, matching sibling except-branches.
- Rate-limit the src.demo import-failure warning in get_privileges (hot path)
  to one-shot so a broken import can't spam the log.
- DEMO_PRIVILEGES: Dict[str, object] -> Dict[str, Any] for consistency.
- entrypoint.render.sh: set -e -> set -eu (all expansions already defaulted).
- searxng-render-entrypoint.sh: substitute the secret via python/env instead
  of sed string interpolation so a sed metachar can't corrupt settings.yml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): enhance demo privilege import logging and session handling

- Implemented a locking mechanism to prevent spamming of demo privilege import failure logs in concurrent scenarios.
- Updated the auth_status route to ensure a stable demo owner cookie is set on the response, improving the user experience for demo users.
- Refined comments in the demo limits function to clarify the order of rate limit checks and their implications on user experience.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit ebac06cd57b92749398eb35d586dd2e5b43a01c8)
Browsers auto-request the domain-root /favicon.ico for tabs and bookmarks,
independent of the page's <link rel="icon">. That path was hitting the auth
gate and 302-ing to /login, so browsers received HTML instead of an icon and
fell back to a stale cached favicon. Add a root /favicon.ico route serving the
static icon and exempt it from AuthMiddleware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 79e3a96fdfc1be8f3dfbb60cd463274f697b0928)
Clarified the process for setting the `OPENAI_API_KEY` in the environment variables before the first boot to ensure chat functionality works immediately. This change enhances the user experience by providing clearer guidance on the necessary steps for deploying the application successfully.

(cherry picked from commit 98bd0257069ce67de5db5bc4870aa21456b9cd22)
…criptions

Added new environment variables for OpenAI model seeding, cloud web-search providers, trusted proxy hops, and demo mode configurations in the .env.example file. This update enhances clarity and provides guidance for users on configuring their environment for optimal application performance.

(cherry picked from commit 90c4391f320a9f63150e0a26844cd44de7e7cff7)
Changed the value of the `generation` key under `previews` in render.yaml from off to "off" to ensure proper parsing and compatibility with the configuration format. This minor adjustment enhances clarity and consistency in the configuration file.

(cherry picked from commit 42539b29a936c336861528e4fdddfa05f40585d2)
(cherry picked from commit 1f6b4f21ce2af6fcd3c2ebe4ffddf2c3964d64cf)
(cherry picked from commit db5432b6e390abc48b1108437e79635945728880)
Removed image tag from README and added a link.

(cherry picked from commit 1459eb1d21a9cc534cc3e72cf4fb1afb16664dd0)
Expanded the README to include a new architecture section detailing the service layout and interactions. Added a diagram illustrating the public and private services, enhancing clarity on how the application components communicate over Render's private network.

(cherry picked from commit 7d8fbd4f84bdd785b14712ad5643254b01576def)
(cherry picked from commit 9eec59ff68b102b323e6525ddf565df1809d20ee)
…dev#4)

* chore(deps): move test tooling out of the production image

pytest, pytest-asyncio, and httpx2 were in requirements.txt, so the hosted
Render image (Dockerfile.render installs requirements.txt) shipped test-only
tooling even though tests/ is excluded from the build context — pure bloat.

Move them to a new requirements-dev.txt and install it where tests actually
run (CI + the CONTRIBUTING manual-dev path). Production requirements.txt and
both Dockerfiles now install runtime deps only; pins are unchanged and the
full suite still collects (4655 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: run tests on Python 3.14 to match the production runtime

CI pinned Python 3.11, but the pinned deps target 3.14 (both Dockerfiles use
python:3.14-slim) — numpy==2.5.1 requires >=3.12, so `pip install -r
requirements.txt` failed at install time on 3.11 and the pytest job never ran.
Bump both the syntax and test jobs to 3.14 so CI exercises the versions that
actually ship.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(README): reference the orphaned workspace screenshot

docs/odysseus-browser.jpg was committed as a README screenshot ("Refresh
README screenshot") but never wired into any doc, so test_no_orphan_images_in_docs
flagged it as an orphan. Add it to the README as a product screenshot below the
demo clip — fulfilling its original intent instead of deleting it — which greens
the docs-hygiene test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6696373557296370fb5bce91c1118ef9313d7adb)
The Render template port replaced gpt-4o entries across the model catalog
instead of adding the newer tier alongside them, which silently regressed
three things for anyone still on gpt-4o: it stopped being detected as
vision-capable (src/chat_helpers.py), lost its context window so prompts
truncated on the wrong boundary (src/model_context.py), and lost its
pricing so the cost readout went blank (static/js/chatRenderer.js). The
curated picker list, discovery list, VL auto-detect candidates, and the
gpt-4o-mini-tts option had shrunk the same way. Every one of those edits
is now additive — the new IDs are added, the existing entries stay.

src.demo was imported three inconsistent ways: unconditionally at module
top in app.py and chat_routes, lazily-with-fallback in core/auth.py and
core/session_manager.py, and lazily-unguarded in assistant_routes and
task_scheduler. The module-top imports meant the fallbacks could never
fire in a running app — src.demo failing to import takes the boot down
first — so they were dead code carrying real complexity, including two
double-checked one-shot log guards. src.demo is a leaf module (stdlib
plus src.rate_limiter), so there was never a cycle to dodge. Every caller
now imports it at module top, and the fallbacks are gone.

Callers reference it as a module rather than importing names, so values
like DEMO_MODE, DEMO_MODEL, and DEMO_PRIVILEGES are read through it: a
`from src.demo import DEMO_MODE` binds the value and goes stale whenever
the module is reloaded, which the test suite does to toggle the flag.

Also: serve_html_with_nonce matched a literal "<head>", so a template
with attributes on the tag would fall through to prepending the demo flag
ahead of <html> — after the fetch wrapper it exists to gate. It now
matches the tag with a regex and 500s if there is no <head> at all,
rather than serving a demo page that bounces visitors to /login. And the
README deploy button still pointed at render-examples/odysseus-render,
the mis-forked repo this one replaces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

⚠️ PR description — action needed

The following required sections are missing or incomplete. Please update the PR description to address them:

  • Summary is empty or too short — describe what changed and why.
  • Linked Issue — add a reference like Fixes #NNN, a bare #NNN, or a link to the issue.
  • Type of Change — check at least one box.
  • Checklist — check the duplicate-search box to confirm you searched existing issues and PRs.
  • How to Test — explain how a reviewer can verify this change. Numbered steps, the commands you ran, or a short code block all work — give a sentence or two of real detail (not just "tested locally").

This comment is deleted automatically once all sections are complete.

@Ho1yShif
Ho1yShif merged commit 9fe237c into dev Aug 11, 2026
14 of 19 checks passed
@Ho1yShif
Ho1yShif deleted the render-template branch August 11, 2026 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant