feat: rename CF_ORCH_URL to GPU_SERVER_URL for self-hoster clarity #14
6 changed files with 122 additions and 9 deletions
|
|
@ -23,10 +23,13 @@ HEIMDALL_ADMIN_TOKEN=
|
||||||
# Must match COORDINATOR_PRODUCT_KEYS["pagepiper"] in cf-orch.env on the coordinator
|
# Must match COORDINATOR_PRODUCT_KEYS["pagepiper"] in cf-orch.env on the coordinator
|
||||||
COORDINATOR_PAGEPIPER_KEY=
|
COORDINATOR_PAGEPIPER_KEY=
|
||||||
|
|
||||||
# cf-orch coordinator URL — routes chat/embed calls through managed GPU allocation
|
# GPU server / cf-orch coordinator URL — routes chat/embed calls through managed
|
||||||
# CF_LICENSE_KEY is the auth token sent to the coordinator (same value as COORDINATOR_PAGEPIPER_KEY)
|
# GPU allocation. CF_LICENSE_KEY is the auth token sent to the coordinator (same
|
||||||
# Leave CF_ORCH_URL blank to skip allocation and hit PAGEPIPER_OLLAMA_URL directly
|
# value as COORDINATOR_PAGEPIPER_KEY). Leave blank to skip allocation and hit
|
||||||
CF_ORCH_URL=
|
# PAGEPIPER_OLLAMA_URL directly. CF_ORCH_URL is still honoured as a legacy alias
|
||||||
|
# if GPU_SERVER_URL is unset — Paid+ tiers auto-default to orch.circuitforge.tech
|
||||||
|
# when CF_LICENSE_KEY is present.
|
||||||
|
GPU_SERVER_URL=
|
||||||
CF_LICENSE_KEY=
|
CF_LICENSE_KEY=
|
||||||
CF_APP_NAME=pagepiper
|
CF_APP_NAME=pagepiper
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,12 @@ PAGEPIPER_DATA_DIR=data
|
||||||
# PAGEPIPER_CHAT_MODEL=mistral:7b
|
# PAGEPIPER_CHAT_MODEL=mistral:7b
|
||||||
# PAGEPIPER_EMBED_MODEL=nomic-embed-text
|
# PAGEPIPER_EMBED_MODEL=nomic-embed-text
|
||||||
|
|
||||||
|
# Self-hosted GPU rig URL — set this instead of PAGEPIPER_OLLAMA_URL if you're
|
||||||
|
# running a cf-orch coordinator (e.g. a home GPU rack). The coordinator resolves
|
||||||
|
# the actual service URL at allocation time. (CF_ORCH_URL is still honoured as
|
||||||
|
# a legacy alias if you already have it set.)
|
||||||
|
# GPU_SERVER_URL=http://localhost:7700
|
||||||
|
|
||||||
# Forgejo API token — enables the in-app feedback button (files Forgejo issues)
|
# Forgejo API token — enables the in-app feedback button (files Forgejo issues)
|
||||||
# Create a token at https://git.opensourcesolarpunk.com/user/settings/applications
|
# Create a token at https://git.opensourcesolarpunk.com/user/settings/applications
|
||||||
# FORGEJO_API_TOKEN=
|
# FORGEJO_API_TOKEN=
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,21 @@ VEC_DIMENSIONS = int(os.environ.get("PAGEPIPER_EMBED_DIMS", "1024"))
|
||||||
|
|
||||||
LOCAL_USER_ID = "__local__"
|
LOCAL_USER_ID = "__local__"
|
||||||
|
|
||||||
|
CF_LICENSE_KEY = os.environ.get("CF_LICENSE_KEY")
|
||||||
|
|
||||||
|
# Priority: GPU_SERVER_URL env var -> CF_ORCH_URL env var (backward compat)
|
||||||
|
# -> https://orch.circuitforge.tech when CF_LICENSE_KEY is present (Paid+)
|
||||||
|
# Resolved value is written back to os.environ["CF_ORCH_URL"] so every existing
|
||||||
|
# caller that reads os.environ.get("CF_ORCH_URL") directly (get_llm_config below,
|
||||||
|
# app/api/chat.py) sees the right URL without any further changes.
|
||||||
|
GPU_SERVER_URL = (
|
||||||
|
os.environ.get("GPU_SERVER_URL")
|
||||||
|
or os.environ.get("CF_ORCH_URL")
|
||||||
|
or ("https://orch.circuitforge.tech" if CF_LICENSE_KEY else None)
|
||||||
|
)
|
||||||
|
if GPU_SERVER_URL:
|
||||||
|
os.environ["CF_ORCH_URL"] = GPU_SERVER_URL
|
||||||
|
|
||||||
|
|
||||||
def user_data_dir(user_id: str) -> Path:
|
def user_data_dir(user_id: str) -> Path:
|
||||||
"""Return (and create) the per-user data directory under DATA_DIR/users/."""
|
"""Return (and create) the per-user data directory under DATA_DIR/users/."""
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ services:
|
||||||
PAGEPIPER_BOOKS_DIR: /devl/pagepiper-cloud-data/books
|
PAGEPIPER_BOOKS_DIR: /devl/pagepiper-cloud-data/books
|
||||||
# PAGEPIPER_OLLAMA_URL — set in .env (BYOK gate for hybrid search + RAG)
|
# PAGEPIPER_OLLAMA_URL — set in .env (BYOK gate for hybrid search + RAG)
|
||||||
# HEIMDALL_URL, HEIMDALL_ADMIN_TOKEN — set in .env for license validation
|
# HEIMDALL_URL, HEIMDALL_ADMIN_TOKEN — set in .env for license validation
|
||||||
# cf-orch: route LLM inference through coordinator for managed GPU access
|
# GPU server: route LLM inference through the cf-orch coordinator for
|
||||||
CF_ORCH_URL: http://host.docker.internal:7700
|
# managed GPU access (app/config.py normalizes this into CF_ORCH_URL
|
||||||
|
# internally, so no other service code needs to change)
|
||||||
|
GPU_SERVER_URL: http://host.docker.internal:7700
|
||||||
CF_APP_NAME: pagepiper
|
CF_APP_NAME: pagepiper
|
||||||
# CF_LICENSE_KEY is the auth token CFOrchClient sends to the coordinator
|
# CF_LICENSE_KEY is the auth token CFOrchClient sends to the coordinator
|
||||||
CF_LICENSE_KEY: ${COORDINATOR_PAGEPIPER_KEY:-}
|
CF_LICENSE_KEY: ${COORDINATOR_PAGEPIPER_KEY:-}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,13 @@ Copy `.env.example` to `.env` and configure as needed.
|
||||||
| `PAGEPIPER_EMBED_DIMS` | `1024` | Embedding dimensions (must match the model) |
|
| `PAGEPIPER_EMBED_DIMS` | `1024` | Embedding dimensions (must match the model) |
|
||||||
| `PAGEPIPER_CHAT_MODEL` | `mistral:7b` | Ollama chat/completion model |
|
| `PAGEPIPER_CHAT_MODEL` | `mistral:7b` | Ollama chat/completion model |
|
||||||
|
|
||||||
## cf-orch (managed deployments)
|
## GPU server / cf-orch (managed deployments)
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `CF_ORCH_URL` | _(unset)_ | cf-orch coordinator URL for GPU allocation |
|
| `GPU_SERVER_URL` | _(unset)_ | Self-hosted GPU rig / cf-orch coordinator URL for GPU allocation. Preferred over `CF_ORCH_URL`. |
|
||||||
| `CF_LICENSE_KEY` | _(unset)_ | License key for cf-orch authentication |
|
| `CF_ORCH_URL` | _(unset)_ | Legacy alias for `GPU_SERVER_URL` — still honoured if `GPU_SERVER_URL` is unset. |
|
||||||
|
| `CF_LICENSE_KEY` | _(unset)_ | License key for cf-orch authentication. When set and neither `GPU_SERVER_URL` nor `CF_ORCH_URL` is configured, defaults `GPU_SERVER_URL` to `https://orch.circuitforge.tech` (Paid+ tiers). |
|
||||||
| `CF_APP_NAME` | `pagepiper` | Application identifier sent to cf-orch |
|
| `CF_APP_NAME` | `pagepiper` | Application identifier sent to cf-orch |
|
||||||
|
|
||||||
## License (cloud tier)
|
## License (cloud tier)
|
||||||
|
|
|
||||||
86
tests/test_config.py
Normal file
86
tests/test_config.py
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# tests/test_config.py
|
||||||
|
"""Unit tests for the GPU_SERVER_URL / CF_ORCH_URL resolution in app/config.py.
|
||||||
|
|
||||||
|
app/config.py resolves GPU_SERVER_URL at *module import time* (module-level
|
||||||
|
code, not a function), so each test reloads the module after adjusting env
|
||||||
|
vars via monkeypatch, then reloads it again afterward with the real
|
||||||
|
environment restored so later tests see normal module state.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import app.config as config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _restore_config_module():
|
||||||
|
yield
|
||||||
|
importlib.reload(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_orch_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.delenv("GPU_SERVER_URL", raising=False)
|
||||||
|
monkeypatch.delenv("CF_ORCH_URL", raising=False)
|
||||||
|
monkeypatch.delenv("CF_LICENSE_KEY", raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_server_url_prefers_explicit_env_var(monkeypatch):
|
||||||
|
_clear_orch_env(monkeypatch)
|
||||||
|
monkeypatch.setenv("GPU_SERVER_URL", "http://my-rig:7700")
|
||||||
|
monkeypatch.setenv("CF_ORCH_URL", "http://should-be-ignored:7700")
|
||||||
|
|
||||||
|
importlib.reload(config)
|
||||||
|
|
||||||
|
assert config.GPU_SERVER_URL == "http://my-rig:7700"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_server_url_falls_back_to_cf_orch_url_legacy_alias(monkeypatch):
|
||||||
|
_clear_orch_env(monkeypatch)
|
||||||
|
monkeypatch.setenv("CF_ORCH_URL", "http://legacy-coordinator:7700")
|
||||||
|
|
||||||
|
importlib.reload(config)
|
||||||
|
|
||||||
|
assert config.GPU_SERVER_URL == "http://legacy-coordinator:7700"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_server_url_defaults_when_license_key_present(monkeypatch):
|
||||||
|
_clear_orch_env(monkeypatch)
|
||||||
|
monkeypatch.setenv("CF_LICENSE_KEY", "test-key-123")
|
||||||
|
|
||||||
|
importlib.reload(config)
|
||||||
|
|
||||||
|
assert config.GPU_SERVER_URL == "https://orch.circuitforge.tech"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_server_url_none_when_nothing_set(monkeypatch):
|
||||||
|
_clear_orch_env(monkeypatch)
|
||||||
|
|
||||||
|
importlib.reload(config)
|
||||||
|
|
||||||
|
assert config.GPU_SERVER_URL is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_server_url_writes_back_to_cf_orch_url_for_legacy_callers(monkeypatch):
|
||||||
|
"""get_llm_config() and app/api/chat.py read os.environ["CF_ORCH_URL"] directly —
|
||||||
|
the write-back must happen so they keep working without code changes."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
_clear_orch_env(monkeypatch)
|
||||||
|
monkeypatch.setenv("GPU_SERVER_URL", "http://my-rig:7700")
|
||||||
|
|
||||||
|
importlib.reload(config)
|
||||||
|
|
||||||
|
assert os.environ.get("CF_ORCH_URL") == "http://my-rig:7700"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpu_server_url_unset_does_not_write_back(monkeypatch):
|
||||||
|
import os
|
||||||
|
|
||||||
|
_clear_orch_env(monkeypatch)
|
||||||
|
|
||||||
|
importlib.reload(config)
|
||||||
|
|
||||||
|
assert "CF_ORCH_URL" not in os.environ
|
||||||
Loading…
Reference in a new issue