refactor: consolidate API routing on ONYX_SERVER_URL (#13375)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
roshanandClaude Fable 5 authored and GitHub committed 2026-07-23 23:13:18 +00:00
1 parent aaaa1efd21
commit 2164093ae3
37 files changed
+210 -144

No files matched your search

@@ -51,7 +51,7 @@ env:
SANDBOX_CONTAINER_IMAGE: "onyxdotapp/sandbox:ci"
# Read by api_server on boot; Sandbox containers call back via this URL on the
# compose bridge.
SANDBOX_API_SERVER_URL: "http://api_server:8080"
ONYX_SERVER_URL: "http://api_server:8080"
jobs:
changes:
+1 -1
View File
@@ -45,7 +45,7 @@ env:
SANDBOX_SERVICE_ACCOUNT_NAME: "sandbox"
SANDBOX_CONTAINER_IMAGE: "localhost:5001/onyx-sandbox:ci"
# Sandbox pod resource requests live in values-ci.yaml (configMap.SANDBOX_POD_*).
SANDBOX_API_SERVER_URL: "http://onyx-api-service.onyx.svc.cluster.local:8080"
ONYX_SERVER_URL: "http://onyx-api-service.onyx.svc.cluster.local:8080"
# Proxy lives in release namespace `onyx`. SANDBOX_PROXY_HOST is set
# later to the proxy Service ClusterIP — the runner has no cluster DNS,
+1 -1
View File
@@ -140,7 +140,7 @@ SANDBOX_CONTAINER_IMAGE=onyxdotapp/sandbox:dev
# How sandboxes reach the api_server: out through the egress proxy to the host
# gateway, not internal cluster DNS. Linux-kind devs sub their own host gateway.
SANDBOX_API_SERVER_URL=http://host.docker.internal:3000
ONYX_SERVER_URL=http://host.docker.internal:3000/api
SANDBOX_NAMESPACE=onyx-sandboxes
+3 -3
View File
@@ -60,7 +60,7 @@ from onyx.sandbox_proxy.logging_utils import (
from onyx.sandbox_proxy.request_evaluator import RequestEvaluator
from onyx.server.features.build.configs import (
MCP_SESSION_TAG_HEADER,
SANDBOX_API_SERVER_URL,
ONYX_SERVER_URL,
SANDBOX_APPROVAL_WAIT_TIMEOUT_SECONDS,
)
from onyx.server.features.build.db import action_approval
@@ -91,9 +91,9 @@ PARSER_MAX_BODY_BYTES = 32 * 1024 * 1024
# in-cluster name resolving to an internal IP, while still denying every other port
# on that host (e.g. a co-located Redis/Postgres reachable at the same hostname).
def _parse_api_server() -> tuple[str | None, int | None]:
if not SANDBOX_API_SERVER_URL:
if not ONYX_SERVER_URL:
return None, None
parsed = urlparse(SANDBOX_API_SERVER_URL)
parsed = urlparse(ONYX_SERVER_URL)
host = (parsed.hostname or "").lower() or None
port = parsed.port or (443 if parsed.scheme == "https" else 80)
return host, port
@@ -1,9 +1,9 @@
"""Onyx-API PAT credential resolver.
Claims requests bound for the Onyx API host (the host of
``SANDBOX_API_SERVER_URL``) and sets both auth headers to the sandbox's real
per-sandbox PAT, read encrypted off ``Sandbox.encrypted_pat``. The tenant is
embedded in the PAT itself, so no separate tenant header is injected.
Claims requests bound for the Onyx API host (the host of ``ONYX_SERVER_URL``)
and sets both auth headers to the sandbox's real per-sandbox PAT, read
encrypted off ``Sandbox.encrypted_pat``. The tenant is embedded in the PAT
itself, so no separate tenant header is injected.
"""
from __future__ import annotations
@@ -24,7 +24,7 @@ from onyx.sandbox_proxy.credential_injection import (
InjectionContext,
)
from onyx.sandbox_proxy.logging_utils import full_log_id, short_log_id
from onyx.server.features.build.configs import SANDBOX_API_SERVER_URL
from onyx.server.features.build.configs import ONYX_SERVER_URL
from onyx.server.features.build.db.sandbox import get_sandbox_by_id
from onyx.utils.logger import setup_logger
@@ -35,7 +35,7 @@ class OnyxPatResolver(CredentialResolver):
"""Injects the sandbox's Onyx API PAT on requests to the configured API host."""
def __init__(self) -> None:
parsed = urlparse(SANDBOX_API_SERVER_URL) if SANDBOX_API_SERVER_URL else None
parsed = urlparse(ONYX_SERVER_URL) if ONYX_SERVER_URL else None
host = parsed.hostname if parsed else None
self._api_host = host.lower() if host else None
if parsed is not None and host:
@@ -97,9 +97,9 @@ ENABLE_OPENCODE_DEBUGGING = (
os.environ.get("ENABLE_OPENCODE_DEBUGGING", "false").lower() == "true"
)
# Must be set when SANDBOX_BACKEND=kubernetes (no default — varies per
# deployment).
SANDBOX_API_SERVER_URL = os.environ.get("SANDBOX_API_SERVER_URL", "")
# Complete Onyx API base URL reachable from the sandbox, including any path
# prefix. Must be set when SANDBOX_BACKEND=kubernetes.
ONYX_SERVER_URL = os.environ.get("ONYX_SERVER_URL", "")
# ==============================================================================
# Sandbox egress proxy
@@ -54,9 +54,13 @@ The Docker backend is intentionally the closest single-VM analogue of the Kubern
#### Docker mode trust boundary
`api_server` and `background` mount the host Docker socket so they can drive sandbox containers. Anything that can talk to that socket is effectively root on the host — only enable Craft on hosts you fully control. Sandbox containers themselves run unprivileged: `--security-opt no-new-privileges`, `--cap-drop ALL`, `user=1000:1000`, no Docker socket, and a fixed env allowlist (`ONYX_PAT` + `ONYX_SERVER_URL`).
`api_server` and `background` mount the host Docker socket so they can drive sandbox containers. Anything that can talk to that socket is effectively root on the host — only enable Craft on hosts you fully control. Sandbox containers themselves run unprivileged: `--security-opt no-new-privileges`, `--cap-drop ALL`, `user=1000:1000`, no Docker socket, and a fixed env allowlist (`ONYX_PAT` + `ONYX_SERVER_URL` + `ONYX_API_PREFIX`).
`SANDBOX_API_SERVER_URL` must be the **public** HTTPS URL that the agent reaches Onyx through (same way any onyx-cli client would). Compose hostnames like `http://api_server:8080` do not resolve from inside the sandbox bridge.
`ONYX_SERVER_URL` is the complete API base URL used by both the host-side
manager and the sandbox client. The Craft Compose overlay defaults it to
`http://onyx-craft-api:8080`, a private alias on the sandbox bridge. Public
reverse-proxy overrides must include their API path prefix, for example
`https://onyx.your-org.example/api`.
On EC2 the Docker bridge by default routes to `169.254.169.254` (IMDS), which can hand out IAM credentials. `install.sh --include-craft` installs a host-level `DOCKER-USER` iptables rule to drop sandbox→IMDS traffic when it has sudo/iptables access, and prints the manual command otherwise. There is no application-level fallback — fix this at the host firewall.
@@ -169,10 +173,10 @@ SANDBOX_SERVICE_ACCOUNT_NAME=sandbox # No storage credentials required
```bash
# Public URL the sandbox agent uses to reach Onyx (HTTPS, externally resolvable —
# compose hostnames like http://api_server:8080 will not resolve from inside the
# sandbox bridge).
SANDBOX_API_SERVER_URL=https://onyx.your-org.example
# Complete API base URL. The Craft Compose overlay defaults to its private
# http://onyx-craft-api:8080 alias; override only to route through a public
# reverse proxy.
ONYX_SERVER_URL=https://onyx.your-org.example/api
# Host path of the Docker socket mounted into api_server/background
SANDBOX_DOCKER_SOCKET=/var/run/docker.sock # Default: /var/run/docker.sock
@@ -222,7 +226,8 @@ uv run pytest backend/tests/integration/tests/craft/k8s/test_kubernetes_sandbox.
- Confirm `api_server` actually has the Docker socket: `docker compose exec api_server ls -l /var/run/docker.sock`
- Confirm the dedicated bridge exists: `docker network inspect onyx_craft_sandbox` (created by `install.sh --include-craft`, or run `docker network create onyx_craft_sandbox` manually)
- Check sandbox logs: `docker logs sandbox-<id8>`
- Confirm `SANDBOX_API_SERVER_URL` is a publicly resolvable HTTPS URL (the agent cannot reach `http://api_server:8080` from inside the sandbox bridge)
- Confirm `ONYX_SERVER_URL` resolves from the sandbox bridge and includes any
API path prefix. The Compose default is `http://onyx-craft-api:8080`.
### Sandbox Stuck in PROVISIONING (Kubernetes)
@@ -252,7 +257,7 @@ uv run pytest backend/tests/integration/tests/craft/k8s/test_kubernetes_sandbox.
- **Sandbox app containers and sidecar init containers** do not receive FileStore, S3, or MinIO credentials
- **Network policies** can restrict sandbox egress traffic
- **Resource limits** prevent resource exhaustion
- **Docker containers** run with `--security-opt no-new-privileges`, `--cap-drop ALL`, `user=1000:1000`, no Docker socket, and a fixed env allowlist (`ONYX_PAT` + `ONYX_SERVER_URL`)
- **Docker containers** run with `--security-opt no-new-privileges`, `--cap-drop ALL`, `user=1000:1000`, no Docker socket, and a fixed env allowlist (`ONYX_PAT` + `ONYX_SERVER_URL` + `ONYX_API_PREFIX`)
- **Docker network isolation** is enforced by joining only the dedicated `onyx_craft_sandbox` bridge — compose's default network (postgres/redis/minio/model servers) is unreachable by DNS from inside a sandbox
- **EC2 IMDS** must be blocked at the host firewall (`install.sh --include-craft` installs a `DOCKER-USER` iptables rule on EC2 when sudo is available) — there is no app-level fallback
@@ -29,6 +29,7 @@ Sandbox containers run with:
- no Docker socket mount
- no S3 / MinIO / Postgres / Redis / FileStore credentials in env
- a fixed env allowlist (``ONYX_PAT``, ``ONYX_SERVER_URL``,
``ONYX_API_PREFIX``,
opencode auth/config only)
- only the dedicated sandbox bridge network — never compose's default
network. As a result api_server / postgres / redis / minio /
@@ -47,8 +48,9 @@ Outbound communication is intentionally limited to:
1. Public internet over HTTPS (the bridge has default internet egress; block at
the host's ``DOCKER-USER`` chain if you need a stricter posture, e.g. for EC2
IMDS).
2. The Onyx API via ``ONYX_SERVER_URL`` — which must be the *public* HTTPS URL
the agent reaches just like any other onyx-cli client.
2. The Onyx API via the complete ``ONYX_SERVER_URL`` API base. The Craft
overlay uses a private alias on the sandbox bridge by default; deployments
may instead provide a public HTTPS API URL.
Most control-plane traffic from api_server → sandbox uses the Docker
Engine API (``docker exec``). Prompt/event transport uses opencode-serve over
@@ -82,10 +84,10 @@ from onyx.db.enums import SandboxStatus
from onyx.file_store.file_store import get_default_file_store
from onyx.server.features.build.configs import (
ATTACHMENTS_DIRECTORY,
ONYX_SERVER_URL,
OPENCODE_DISABLED_TOOLS,
OPENCODE_SERVE_PORT,
OPENCODE_SERVER_PASSWORD,
SANDBOX_API_SERVER_URL,
SANDBOX_CONTAINER_IMAGE,
SANDBOX_DOCKER_CPU_LIMIT,
SANDBOX_DOCKER_MEMORY_LIMIT,
@@ -326,9 +328,9 @@ _COMPOSE_INTERNAL_HOSTNAMES = {
def _looks_like_internal_compose_host(url: str) -> bool:
"""Heuristic: Does ``url`` reference a compose-internal service hostname?
Used to warn deployers that pointed SANDBOX_API_SERVER_URL at the
api_server's compose DNS name. Sandboxes can't resolve that — they only join
the craft bridge network — so the URL must be the public Onyx URL.
Used to warn deployers that pointed ONYX_SERVER_URL at a service available
only on Compose's default network. Sandboxes can resolve the dedicated
``onyx-craft-api`` alias, but not these unrelated service names.
"""
if not url:
return False
@@ -542,10 +544,10 @@ def build_container_create_kwargs(
``firewall-init.sh`` to read ``ca.crt``. That volume also contains
root-only ``ca.key``; the agent runs as UID 1000 after init.
``ONYX_SERVER_URL`` must be the *public* Onyx URL (the one onyx-cli inside
the sandbox will hit over HTTPS) — not an internal compose DNS name. We emit
a warning if it looks like the latter, since reaching it would require the
sandbox to be on the compose default network.
``ONYX_SERVER_URL`` is the complete API base used by onyx-cli inside the
sandbox. The default ``onyx-craft-api`` alias is attached to the sandbox
bridge; we warn about other Compose DNS names because they exist only on the
default network.
``opencode_password`` is generated per-provision by the manager and injected
as the env var named by ``OPENCODE_SERVER_PASSWORD``. The api_server reads
@@ -556,16 +558,19 @@ def build_container_create_kwargs(
"""
if _looks_like_internal_compose_host(api_server_url):
logger.warning(
"SANDBOX_API_SERVER_URL=%s looks like an internal compose hostname. Sandboxes only "
"join the craft bridge network and reach the API server like any other public client, "
"so this URL must resolve publicly (e.g. https://onyx.your-org.com). Internal DNS will "
"fail and the agent will see 'connection refused'.",
"ONYX_SERVER_URL=%s looks like an internal compose hostname. Sandboxes only "
"join the craft bridge network, so default-network DNS will fail. Use the "
"http://onyx-craft-api:8080 bridge alias or a public API base such as "
"https://onyx.your-org.com/api.",
api_server_url,
)
env: dict[str, str] = {
"ONYX_PAT": onyx_pat,
"ONYX_SERVER_URL": api_server_url,
# The deployment URL is already the exact API base. Disable the CLI's
# compatibility prefix for direct services and prefixed ingress URLs.
"ONYX_API_PREFIX": "",
OPENCODE_SERVER_PASSWORD: opencode_password,
"OPENCODE_CONFIG_CONTENT": opencode_config_json,
}
@@ -832,11 +837,11 @@ class DockerSandboxManager(SandboxManager):
) -> SandboxInfo:
if not onyx_pat:
raise ValueError("onyx_pat is required for Docker sandbox provisioning.")
if not SANDBOX_API_SERVER_URL:
if not ONYX_SERVER_URL:
raise ValueError(
"SANDBOX_API_SERVER_URL must be set for Docker sandbox provisioning."
"ONYX_SERVER_URL must be set for Docker sandbox provisioning."
)
validate_sandbox_api_url(SANDBOX_API_SERVER_URL)
validate_sandbox_api_url(ONYX_SERVER_URL)
logger.info(
"Provisioning Docker sandbox %s for user %s, tenant %s.",
@@ -999,7 +1004,7 @@ class DockerSandboxManager(SandboxManager):
tenant_id=tenant_id,
image=self._image,
onyx_pat=onyx_pat,
api_server_url=SANDBOX_API_SERVER_URL,
api_server_url=ONYX_SERVER_URL,
network=self._network_name,
volume_name=volume_name,
memory_limit=self._memory_limit,
@@ -64,10 +64,10 @@ from onyx.cache.interface import CACHE_TRANSIENT_ERRORS
from onyx.db.enums import SandboxStatus
from onyx.file_store.file_store import get_default_file_store
from onyx.server.features.build.configs import (
ONYX_SERVER_URL,
OPENCODE_DISABLED_TOOLS,
OPENCODE_SERVE_PORT,
OPENCODE_SERVER_PASSWORD,
SANDBOX_API_SERVER_URL,
SANDBOX_CONTAINER_IMAGE,
SANDBOX_NAMESPACE,
SANDBOX_NEXTJS_PORT_END,
@@ -1048,11 +1048,11 @@ class KubernetesSandboxManager(SandboxManager):
if not onyx_pat:
raise ValueError("onyx_pat is required for Kubernetes sandbox provisioning")
if not SANDBOX_API_SERVER_URL:
if not ONYX_SERVER_URL:
raise ValueError(
"SANDBOX_API_SERVER_URL must be set for Kubernetes sandbox provisioning"
"ONYX_SERVER_URL must be set for Kubernetes sandbox provisioning"
)
validate_sandbox_api_url(SANDBOX_API_SERVER_URL)
validate_sandbox_api_url(ONYX_SERVER_URL)
if not SANDBOX_PROXY_HOST:
raise ValueError(
"SANDBOX_PROXY_HOST must be set for Kubernetes sandbox provisioning"
@@ -1,10 +1,10 @@
"""Provision-time validation of SANDBOX_API_SERVER_URL.
"""Provision-time validation of ONYX_SERVER_URL.
SANDBOX_API_SERVER_URL is the full base URL a sandbox client uses, any API
path prefix included. The classic misconfiguration is a public URL without
its /api prefix, which fails far from the cause: every LLM-gateway and
onyx-cli call inside the sandbox 404s. Probe once per process at provision
time and fail with the corrected value instead.
ONYX_SERVER_URL is the full base URL a client uses, any API path prefix
included. The classic misconfiguration is a public URL without its /api
prefix, which fails far from the cause: every LLM-gateway and onyx-cli call
inside the sandbox 404s. Probe once per process at provision time and fail
with the corrected value instead.
"""
import threading
@@ -68,7 +68,7 @@ def validate_sandbox_api_url(api_server_url: str) -> None:
response = _probe_health(base)
if response is None:
logger.warning(
"Could not probe %s/health to validate SANDBOX_API_SERVER_URL; "
"Could not probe %s/health to validate ONYX_SERVER_URL; "
"skipping the prefix check",
base,
)
@@ -91,14 +91,14 @@ def validate_sandbox_api_url(api_server_url: str) -> None:
if prefixed_response is not None and _is_health_response(prefixed_response):
raise OnyxError(
OnyxErrorCode.INTERNAL_ERROR,
f"SANDBOX_API_SERVER_URL={api_server_url!r} is missing its API path "
f"ONYX_SERVER_URL={api_server_url!r} is missing its API path "
f"prefix: {base}/health does not serve the API health payload "
f"(HTTP {response.status_code}) while {prefixed}/health does. "
f"Set SANDBOX_API_SERVER_URL to {prefixed!r} — the full base URL a "
f"Set ONYX_SERVER_URL to {prefixed!r} — the full base URL a "
"sandbox client uses.",
)
logger.warning(
"SANDBOX_API_SERVER_URL=%s: %s/health did not serve the API health "
"ONYX_SERVER_URL=%s: %s/health did not serve the API health "
"payload (HTTP %s) and %s/health did not either; sandbox API calls "
"will likely fail. Verify the URL is the full base URL including any "
"path prefix.",
@@ -133,7 +133,7 @@ def lock_env(
preconditions (URL/proxy host) satisfied."""
monkeypatch.setattr(factory, "CACHE_BACKEND", CacheBackendType.REDIS)
monkeypatch.setattr(
kubernetes_sandbox_manager, "SANDBOX_API_SERVER_URL", "http://api-server"
kubernetes_sandbox_manager, "ONYX_SERVER_URL", "http://api-server"
)
monkeypatch.setattr(
kubernetes_sandbox_manager, "SANDBOX_PROXY_HOST", "sandbox-proxy"
@@ -49,7 +49,7 @@ def test_claims_then_resolve_round_trips_minted_pat(
matched_actions=None,
)
monkeypatch.setattr(onyx_pat_mod, "SANDBOX_API_SERVER_URL", f"https://{_API_HOST}")
monkeypatch.setattr(onyx_pat_mod, "ONYX_SERVER_URL", f"https://{_API_HOST}")
resolver = OnyxPatResolver()
assert resolver.claims(MagicMock(host=_API_HOST, port=443), ctx) is True
assert resolver.claims(MagicMock(host="slack.com", port=443), ctx) is False
@@ -101,7 +101,7 @@ def test_reuse_existing_pod_clears_stale_tombstone() -> None:
mgr._terminated_sandboxes.add(sandbox_id)
with (
mock.patch.object(k8s_mod, "SANDBOX_API_SERVER_URL", "http://api"),
mock.patch.object(k8s_mod, "ONYX_SERVER_URL", "http://api"),
mock.patch.object(k8s_mod, "SANDBOX_PROXY_HOST", "proxy.local"),
mock.patch.object(mgr, "_pod_exists_and_healthy", return_value=True),
mock.patch.object(mgr, "_ensure_service_exists"),
@@ -250,7 +250,7 @@ def proxy_kwargs() -> ContainerCreateKwargs:
tenant_id=TENANT_ID,
image="onyxdotapp/sandbox:test",
onyx_pat=SANDBOX_PROXY_INJECTED_PLACEHOLDER,
api_server_url="https://onyx.example.com",
api_server_url="https://onyx.example.com/api",
network="onyx_craft_sandbox",
volume_name="onyx-craft-sandbox-12345678",
memory_limit="2g",
@@ -359,6 +359,7 @@ def test_container_kwargs_env_allowlist_excludes_storage_credentials(
# Required env
assert env["ONYX_PAT"] == "pat-redacted"
assert env["ONYX_SERVER_URL"] == "http://api_server:8080"
assert env["ONYX_API_PREFIX"] == ""
# opencode-serve transport wiring
assert env["OPENCODE_SERVER_PASSWORD"] == _OPENCODE_PASSWORD
assert env["OPENCODE_CONFIG_CONTENT"] == _OPENCODE_CONFIG_JSON
@@ -493,6 +494,7 @@ def test_container_kwargs_env_is_a_minimal_allowlist(
assert set(env.keys()) == {
"ONYX_PAT",
"ONYX_SERVER_URL",
"ONYX_API_PREFIX",
"OPENCODE_SERVER_PASSWORD",
"OPENCODE_CONFIG_CONTENT",
}
@@ -526,7 +528,7 @@ def test_container_kwargs_mounts_tmp_as_tmpfs(
def test_container_kwargs_warns_on_internal_compose_host(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Deployers that point SANDBOX_API_SERVER_URL at compose DNS get warned."""
"""Deployers that point ONYX_SERVER_URL at compose DNS get warned."""
import logging
with caplog.at_level(logging.WARNING):
@@ -563,7 +565,7 @@ def test_container_kwargs_no_warning_for_public_url(
tenant_id=TENANT_ID,
image="onyxdotapp/sandbox:test",
onyx_pat="pat",
api_server_url="https://onyx.example.com",
api_server_url="https://onyx.example.com/api",
network="onyx_craft_sandbox",
volume_name="vol",
memory_limit="2g",
@@ -656,10 +658,11 @@ def test_proxy_kwargs_env_contains_proxy_and_ca_keys(
contract vars.
"""
env = proxy_kwargs["environment"]
# The legacy 4-key core is preserved; ONYX_PAT is the proxy placeholder in
# this posture (real value lives in Postgres, proxy injects on wire).
# The compatibility core is preserved; ONYX_PAT is the proxy placeholder
# in this posture (real value lives in Postgres, proxy injects on wire).
assert env["ONYX_PAT"] == SANDBOX_PROXY_INJECTED_PLACEHOLDER
assert env["ONYX_SERVER_URL"] == "https://onyx.example.com"
assert env["ONYX_SERVER_URL"] == "https://onyx.example.com/api"
assert env["ONYX_API_PREFIX"] == ""
assert env["OPENCODE_SERVER_PASSWORD"] == _OPENCODE_PASSWORD
assert env["OPENCODE_CONFIG_CONTENT"] == _OPENCODE_CONFIG_JSON
# firewall-init.sh contract.
@@ -729,6 +732,7 @@ def test_proxy_kwargs_env_is_a_locked_allowlist(
# Legacy core
"ONYX_PAT",
"ONYX_SERVER_URL",
"ONYX_API_PREFIX",
"OPENCODE_SERVER_PASSWORD",
"OPENCODE_CONFIG_CONTENT",
# firewall-init.sh contract
@@ -777,7 +781,7 @@ def test_proxy_kwargs_requires_ca_volume() -> None:
tenant_id=TENANT_ID,
image="onyxdotapp/sandbox:test",
onyx_pat="pat",
api_server_url="https://onyx.example.com",
api_server_url="https://onyx.example.com/api",
network="onyx_craft_sandbox",
volume_name="vol",
memory_limit="2g",
@@ -46,6 +46,8 @@ def _bare_manager() -> DockerSandboxManager:
mgr._agent_instructions_template_path = ( # type: ignore[attr-defined]
dsm.Path(dsm.__file__).parent.parent.parent / "AGENTS.template.md"
)
mgr._image_checked = False # type: ignore[attr-defined]
mgr._image_check_lock = dsm.threading.Lock() # type: ignore[attr-defined]
mgr._init_serve_state()
return mgr
@@ -316,7 +318,7 @@ def test_provision_generates_fresh_password_and_injects_into_container_env(
every later request 401s.
"""
monkeypatch.setattr(dsm, "DEV_MODE", True)
monkeypatch.setattr(dsm, "SANDBOX_API_SERVER_URL", "https://onyx.example.com")
monkeypatch.setattr(dsm, "ONYX_SERVER_URL", "https://onyx.example.com/api")
monkeypatch.setattr(dsm, "validate_sandbox_api_url", lambda *_: None)
# Skip the actual readiness HTTP probe — that needs a real container.
monkeypatch.setattr(
@@ -376,6 +378,7 @@ def test_provision_generates_fresh_password_and_injects_into_container_env(
assert set(env.keys()) == {
"ONYX_PAT",
"ONYX_SERVER_URL",
"ONYX_API_PREFIX",
OPENCODE_SERVER_PASSWORD,
"OPENCODE_CONFIG_CONTENT",
}
@@ -475,7 +478,7 @@ def test_provision_removes_container_when_history_restore_fails(
home.
"""
monkeypatch.setattr(dsm, "DEV_MODE", True)
monkeypatch.setattr(dsm, "SANDBOX_API_SERVER_URL", "https://onyx.example.com")
monkeypatch.setattr(dsm, "ONYX_SERVER_URL", "https://onyx.example.com/api")
monkeypatch.setattr(dsm, "validate_sandbox_api_url", lambda *_: None)
mgr = _bare_manager()
@@ -41,6 +41,7 @@ from onyx.server.features.build.sandbox.image.sandbox_daemon.contract import (
)
from onyx.server.features.build.sandbox.kubernetes import sidecar_client
from onyx.server.features.build.sandbox.kubernetes.kubernetes_sandbox_manager import (
OPENCODE_HISTORY_RESTORE_TIMEOUT_SECONDS,
KubernetesSandboxManager,
_build_targz,
)
@@ -703,7 +704,7 @@ def test_restore_opencode_history_posts_archive_to_sidecar(
assert archive_file.read() == archive_body
assert sha256_hex == hashlib.sha256(archive_body).hexdigest()
assert operation_label == "opencode history restore"
assert timeout_seconds == 300.0
assert timeout_seconds == OPENCODE_HISTORY_RESTORE_TIMEOUT_SECONDS
calls.append("restore")
sidecar_client = MagicMock()
@@ -737,7 +738,7 @@ def test_restore_opencode_history_marks_sidecar_ready_when_no_snapshot(
assert sandbox_id == expected_sandbox_id
assert endpoint_path == SIDECAR_OPENCODE_HISTORY_MARK_RESTORED_PATH
assert operation_label == "opencode history restore marker"
assert timeout_seconds == 300.0
assert timeout_seconds == OPENCODE_HISTORY_RESTORE_TIMEOUT_SECONDS
sidecar_client = MagicMock()
sidecar_client.post_empty.side_effect = fake_mark_restored
@@ -753,7 +754,7 @@ def test_provision_cleans_up_pod_when_opencode_history_restore_fails(
) -> None:
import onyx.server.features.build.sandbox.kubernetes.kubernetes_sandbox_manager as ksm
monkeypatch.setattr(ksm, "SANDBOX_API_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "ONYX_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "validate_sandbox_api_url", lambda *_: None)
monkeypatch.setattr(ksm, "SANDBOX_PROXY_HOST", "proxy.local")
@@ -798,7 +799,7 @@ def test_provision_existing_healthy_pod_does_not_restore_opencode_history(
) -> None:
import onyx.server.features.build.sandbox.kubernetes.kubernetes_sandbox_manager as ksm
monkeypatch.setattr(ksm, "SANDBOX_API_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "ONYX_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "validate_sandbox_api_url", lambda *_: None)
monkeypatch.setattr(ksm, "SANDBOX_PROXY_HOST", "proxy.local")
@@ -836,7 +837,7 @@ def test_provision_conflicting_healthy_pod_skips_startup_restore(
) -> None:
import onyx.server.features.build.sandbox.kubernetes.kubernetes_sandbox_manager as ksm
monkeypatch.setattr(ksm, "SANDBOX_API_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "ONYX_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "validate_sandbox_api_url", lambda *_: None)
monkeypatch.setattr(ksm, "SANDBOX_PROXY_HOST", "proxy.local")
@@ -886,7 +887,7 @@ def test_provision_conflicting_not_ready_pod_runs_startup_restore(
) -> None:
import onyx.server.features.build.sandbox.kubernetes.kubernetes_sandbox_manager as ksm
monkeypatch.setattr(ksm, "SANDBOX_API_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "ONYX_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "validate_sandbox_api_url", lambda *_: None)
monkeypatch.setattr(ksm, "SANDBOX_PROXY_HOST", "proxy.local")
@@ -948,7 +949,7 @@ def test_provision_conflicting_not_ready_pod_restore_failure_does_not_cleanup(
) -> None:
import onyx.server.features.build.sandbox.kubernetes.kubernetes_sandbox_manager as ksm
monkeypatch.setattr(ksm, "SANDBOX_API_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "ONYX_SERVER_URL", "http://api-server")
monkeypatch.setattr(ksm, "validate_sandbox_api_url", lambda *_: None)
monkeypatch.setattr(ksm, "SANDBOX_PROXY_HOST", "proxy.local")
@@ -1,6 +1,6 @@
"""Unit tests for `OnyxPatResolver`.
Coverage: the host claim rule (incl. inert when `SANDBOX_API_SERVER_URL` is
Coverage: the host claim rule (incl. inert when `ONYX_SERVER_URL` is
unset), header rendering on both auth headers, and the fail-closed cases the
dispatcher maps to a 403. The encrypt/store/read round-trip against a real DB
is pinned in `tests/external_dependency_unit/sandbox_proxy/test_onyx_pat_resolver.py`.
@@ -70,7 +70,7 @@ def _ctx() -> InjectionContext:
@pytest.fixture
def resolver(monkeypatch: pytest.MonkeyPatch) -> OnyxPatResolver:
# __init__ captures the API host, so the patch must precede construction.
monkeypatch.setattr(onyx_pat, "SANDBOX_API_SERVER_URL", _API_URL)
monkeypatch.setattr(onyx_pat, "ONYX_SERVER_URL", _API_URL)
return OnyxPatResolver()
@@ -91,7 +91,7 @@ def test_claims_requires_matching_host_and_port(resolver: OnyxPatResolver) -> No
def test_claims_matches_explicit_port(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(onyx_pat, "SANDBOX_API_SERVER_URL", "http://internal:8080")
monkeypatch.setattr(onyx_pat, "ONYX_SERVER_URL", "http://internal:8080")
resolver = OnyxPatResolver()
assert (
resolver.claims(make_flow(host="internal", port=8080).request, _ctx()) is True
@@ -102,7 +102,7 @@ def test_claims_matches_explicit_port(monkeypatch: pytest.MonkeyPatch) -> None:
def test_inert_when_api_url_unset(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(onyx_pat, "SANDBOX_API_SERVER_URL", "")
monkeypatch.setattr(onyx_pat, "ONYX_SERVER_URL", "")
assert (
OnyxPatResolver().claims(make_flow(host=_API_HOST, port=443).request, _ctx())
is False
+2 -1
View File
@@ -31,7 +31,8 @@ Environment variables override config file values:
| Variable | Required | Description |
|----------|----------|-------------|
| `ONYX_SERVER_URL` | No | Server URL (default: `https://cloud.onyx.app`) |
| `ONYX_SERVER_URL` | No | Server origin or already-prefixed API base (default: `https://cloud.onyx.app`) |
| `ONYX_API_PREFIX` | No | API path prefix (default: `/api`); set to empty for direct backend access |
| `ONYX_PAT` | No | Personal access token for authentication (required if no config file) |
| `ONYX_PERSONA_ID` | No | Default agent/persona ID |
| `ONYX_STREAM_MARKDOWN` | No | Enable/disable progressive markdown rendering (true/false) |
+2 -2
View File
@@ -36,8 +36,8 @@ type Client struct {
}
// NewClient creates a new API client from config.
// ServerURL is the server origin (e.g. "https://cloud.onyx.app").
// APIURL appends the /api prefix to form the API base URL.
// ServerURL may be a server origin or an API base that already includes the
// configured API prefix.
func NewClient(cfg config.OnyxCliConfig) *Client {
var transport *http.Transport
if t, ok := http.DefaultTransport.(*http.Transport); ok {
+1 -1
View File
@@ -29,7 +29,7 @@ func TestSearch_Success(t *testing.T) {
if r.Method != "POST" {
t.Errorf("method = %s, want POST", r.Method)
}
if !strings.HasSuffix(r.URL.Path, "/search") {
if r.URL.Path != "/api/search" {
t.Errorf("path = %s, want /api/search", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
+29 -8
View File
@@ -11,6 +11,7 @@ import (
const (
EnvServerURL = "ONYX_SERVER_URL"
EnvAPIPrefix = "ONYX_API_PREFIX"
EnvAPIKey = "ONYX_PAT"
EnvAgentID = "ONYX_PERSONA_ID"
EnvSSHHostKey = "ONYX_SSH_HOST_KEY"
@@ -56,18 +57,38 @@ func (c OnyxCliConfig) IsConfigured() bool {
return c.APIKey != ""
}
// APIURL appends the API prefix (default "/api") to the server origin.
// Set ONYX_API_PREFIX="" for direct backend access without the proxy prefix.
func APIURL(serverURL string) string {
func apiPrefix() string {
prefix := "/api"
if v, ok := os.LookupEnv("ONYX_API_PREFIX"); ok {
prefix = v
if value, ok := os.LookupEnv(EnvAPIPrefix); ok {
prefix = value
}
u := strings.TrimRight(serverURL, "/")
return strings.Trim(prefix, "/")
}
// APIURL returns the API base for a server origin or an already-prefixed URL.
// Set ONYX_API_PREFIX="" for direct backend access without a proxy path.
func APIURL(serverURL string) string {
baseURL := strings.TrimRight(serverURL, "/")
prefix := apiPrefix()
if prefix == "" {
return u
return baseURL
}
return u + "/" + strings.Trim(prefix, "/")
suffix := "/" + prefix
if strings.HasSuffix(baseURL, suffix) {
return baseURL
}
return baseURL + suffix
}
// OnyxWebURL removes an API prefix when the configured URL already includes it.
func OnyxWebURL(serverURL string) string {
baseURL := strings.TrimRight(serverURL, "/")
prefix := apiPrefix()
if prefix == "" {
return baseURL
}
return strings.TrimSuffix(baseURL, "/"+prefix)
}
// ConfigDir returns ~/.config/onyx-cli
+30 -6
View File
@@ -254,14 +254,16 @@ func TestSaveCreatesParentDirs(t *testing.T) {
}
func TestAPIURL(t *testing.T) {
t.Setenv(EnvAPIPrefix, "/api")
cases := []struct {
input string
want string
}{
{"https://cloud.onyx.app", "https://cloud.onyx.app/api"},
{"https://cloud.onyx.app/", "https://cloud.onyx.app/api"},
{"https://cloud.onyx.app/api", "https://cloud.onyx.app/api"},
{"https://cloud.onyx.app/api/", "https://cloud.onyx.app/api"},
{"http://localhost:8080", "http://localhost:8080/api"},
{"http://localhost:3000", "http://localhost:3000/api"},
}
for _, tc := range cases {
got := APIURL(tc.input)
@@ -271,20 +273,42 @@ func TestAPIURL(t *testing.T) {
}
}
func TestAPIURLCustomPrefix(t *testing.T) {
t.Setenv(EnvAPIPrefix, "/onyx/api")
for _, input := range []string{
"https://onyx.example",
"https://onyx.example/onyx/api",
} {
got := APIURL(input)
if got != "https://onyx.example/onyx/api" {
t.Errorf("APIURL(%q) = %q", input, got)
}
}
}
func TestAPIURLEmptyPrefix(t *testing.T) {
t.Setenv("ONYX_API_PREFIX", "")
t.Setenv(EnvAPIPrefix, "")
if got := APIURL("http://localhost:8080/"); got != "http://localhost:8080" {
t.Errorf("APIURL() = %q", got)
}
}
func TestOnyxWebURL(t *testing.T) {
t.Setenv(EnvAPIPrefix, "/api")
cases := []struct {
input string
want string
}{
{"http://localhost:8080", "http://localhost:8080"},
{"http://localhost:8080/", "http://localhost:8080"},
{"https://cloud.onyx.app/api", "https://cloud.onyx.app"},
{"https://cloud.onyx.app/api/", "https://cloud.onyx.app"},
{"https://onyx.example/base/api", "https://onyx.example/base"},
{"https://cloud.onyx.app", "https://cloud.onyx.app"},
{"http://localhost:8080", "http://localhost:8080"},
}
for _, tc := range cases {
got := APIURL(tc.input)
got := OnyxWebURL(tc.input)
if got != tc.want {
t.Errorf("APIURL(%q) = %q, want %q", tc.input, got, tc.want)
t.Errorf("OnyxWebURL(%q) = %q, want %q", tc.input, got, tc.want)
}
}
}
+2 -1
View File
@@ -34,7 +34,8 @@ export ONYX_PAT="your-pat"
| Variable | Required | Description |
| ----------------- | -------- | -------------------------------------------------------- |
| `ONYX_SERVER_URL` | No | Onyx server URL (default: `https://cloud.onyx.app`) |
| `ONYX_SERVER_URL` | No | Server origin or already-prefixed API base (default: `https://cloud.onyx.app`) |
| `ONYX_API_PREFIX` | No | API path prefix (default: `/api`); empty for direct backend access |
| `ONYX_PAT` | Yes | Personal access token for authentication (unless config file exists) |
| `ONYX_PERSONA_ID` | No | Default agent/persona ID |
| `ONYX_STREAM_MARKDOWN` | No | Enable/disable progressive markdown rendering (true/false) |
+5 -3
View File
@@ -13,9 +13,11 @@ import (
"github.com/onyx-dot-app/onyx/cli/internal/iostreams"
)
// NewClient creates a test API client pointed at the given URL.
func NewClient(url string) *api.Client {
return api.NewClient(config.OnyxCliConfig{ServerURL: url, APIKey: "test-key"})
// NewClient creates a test API client pointed at the given server URL.
func NewClient(serverURL string) *api.Client {
return api.NewClient(
config.OnyxCliConfig{ServerURL: serverURL, APIKey: "test-key"},
)
}
// StatusServer returns an httptest.Server that always responds with the given status code.
+2 -2
View File
@@ -49,7 +49,7 @@ func handleSlashCommand(m Model, text string) (Model, tea.Cmd) {
return cmdNew(m)
case "/connectors":
url := strings.TrimRight(m.config.ServerURL, "/") + "/admin/indexing/status"
url := config.OnyxWebURL(m.config.ServerURL) + "/admin/indexing/status"
if browser.OpenBrowser(url) {
m.viewport.addInfo("Opened " + url + " in browser")
} else {
@@ -58,7 +58,7 @@ func handleSlashCommand(m Model, text string) (Model, tea.Cmd) {
return m, nil
case "/settings":
url := strings.TrimRight(m.config.ServerURL, "/") + "/app/settings/general"
url := config.OnyxWebURL(m.config.ServerURL) + "/app/settings/general"
if browser.OpenBrowser(url) {
m.viewport.addInfo("Opened " + url + " in browser")
} else {
@@ -8,9 +8,7 @@ services:
api_server:
environment:
- SANDBOX_BACKEND=${SANDBOX_BACKEND:-docker}
# Public Onyx URL; Internal compose hostnames don't resolve from the
# sandbox bridge.
- SANDBOX_API_SERVER_URL=${SANDBOX_API_SERVER_URL:-}
- ONYX_SERVER_URL=${ONYX_SERVER_URL:-http://onyx-craft-api:8080}
- SANDBOX_CONTAINER_IMAGE=${SANDBOX_CONTAINER_IMAGE:-onyxdotapp/sandbox:${IMAGE_TAG:-latest}}
# Pinned: Must match the compose `networks:` block + install.sh literal.
# An env_file leak would point Python at a non-existent network.
@@ -30,9 +28,10 @@ services:
volumes:
- ${SANDBOX_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
networks:
# Bridge join lets api_server proxy the Next.js preview iframe.
- default
- onyx_craft_sandbox
default:
onyx_craft_sandbox:
aliases:
- onyx-craft-api
# service_healthy, not service_started: The proxy binds its listener only
# after initial sync, so service_started would race startup provisions.
depends_on:
@@ -44,7 +43,7 @@ services:
# Must match api_server so the idle-cleanup celery task uses the same
# backend to snapshot + terminate sandboxes.
- SANDBOX_BACKEND=${SANDBOX_BACKEND:-docker}
- SANDBOX_API_SERVER_URL=${SANDBOX_API_SERVER_URL:-}
- ONYX_SERVER_URL=${ONYX_SERVER_URL:-http://onyx-craft-api:8080}
- SANDBOX_CONTAINER_IMAGE=${SANDBOX_CONTAINER_IMAGE:-onyxdotapp/sandbox:${IMAGE_TAG:-latest}}
# See api_server above re: Pinned env_file-override vars.
- SANDBOX_DOCKER_NETWORK=onyx_craft_sandbox
@@ -85,7 +84,7 @@ services:
# OnyxPatResolver reads this at boot to know which host to claim for PAT
# injection. Without it, /me requests pass through bare with the
# placeholder bearer and api_server 403s.
- SANDBOX_API_SERVER_URL=${SANDBOX_API_SERVER_URL:-}
- ONYX_SERVER_URL=${ONYX_SERVER_URL:-http://onyx-craft-api:8080}
volumes:
- sandbox_proxy_ca:/var/lib/sandbox-proxy/ca
# RO: the proxy only inspects (events watcher + initial sync).
+8 -6
View File
@@ -21,15 +21,17 @@ IMAGE_TAG=latest
## kubernetes by image tag.
# SANDBOX_BACKEND=docker
#
## SANDBOX_API_SERVER_URL must be a *public* HTTPS URL the sandbox can reach
## the same way any other Onyx client does — internal compose hostnames like
## "http://api_server:8080" do not resolve from inside the sandbox bridge.
# SANDBOX_API_SERVER_URL=https://onyx.your-org.example
## ONYX_SERVER_URL is the complete API base URL used by Onyx clients. The Craft
## compose overlay defaults it to its private onyx-craft-api network alias,
## which works on Docker Desktop and Linux without exposing another host port.
## Override only when routing sandbox API traffic through a public reverse
## proxy, including the /api path prefix served by that proxy.
# ONYX_SERVER_URL=https://onyx.your-org.example/api
#
## Sandbox bridge network. Created by install.sh (or manually:
## docker network create onyx_craft_sandbox
## Sandboxes join only this network — they cannot reach postgres, redis,
## minio, or any other compose service by name.
## Sandboxes join only this network. The firewall forces API and public egress
## through sandbox-proxy; postgres, redis, and minio stay off the bridge.
# SANDBOX_DOCKER_NETWORK=onyx_craft_sandbox
#
## Per-sandbox container resource limits. Defaults match K8s pod *requests*
+4 -7
View File
@@ -5,7 +5,7 @@ home: https://www.onyx.app/
sources:
- "https://github.com/onyx-dot-app/onyx"
type: application
version: 0.7.2
version: 0.7.3
appVersion: latest
annotations:
category: Productivity
@@ -17,12 +17,9 @@ annotations:
image: docker.io/onyxdotapp/onyx-backend:latest
artifacthub.io/changes: |
- kind: changed
description: The inference and indexing model servers now run on a distroless Docker
Hardened Image (no shell or package manager) as nonroot (the onyx user, UID 1001). Custom CA
certificates for the model servers are merged with certifi's public roots in Python
at startup instead of via update-ca-certificates. This chart version requires the
distroless model-server image — upgrade the chart and the model-server image
together (see values.yaml).
description: Replace configMap.SANDBOX_API_SERVER_URL with
configMap.ONYX_SERVER_URL for Craft sandbox API routing. Set it to the
exact API base, including /api when routing through the standard ingress.
dependencies:
# Helm uses the first condition path that exists: `postgresqlOperator.enabled`
# is unset by default, so this falls through to `postgresql.enabled`. Set it
@@ -5,8 +5,8 @@
{{- if and $sandboxBackend (ne $sandboxBackend "kubernetes") }}
{{- fail "configMap.SANDBOX_BACKEND must be \"kubernetes\" when configMap.ENABLE_CRAFT is enabled in the Helm chart" }}
{{- end }}
{{- if not (index .Values.configMap "SANDBOX_API_SERVER_URL") }}
{{- fail "configMap.SANDBOX_API_SERVER_URL must be set when configMap.ENABLE_CRAFT is enabled" }}
{{- if not (index .Values.configMap "ONYX_SERVER_URL") }}
{{- fail "configMap.ONYX_SERVER_URL must be set when configMap.ENABLE_CRAFT is enabled" }}
{{- end }}
{{- $auth := .Values.auth | default dict }}
{{- $sandboxPushSecret := (index $auth "sandboxPushSecret") | default dict }}
@@ -25,7 +25,7 @@ kubelet evicts sandbox pods first under node disk pressure. */}}
{{- $proxyPort := include "onyx.sandboxProxyPort" . }}
{{- $proxyHost := include "onyx.sandboxProxyHost" . }}
{{- $caConfigMap := (index $cm "SANDBOX_PROXY_CA_CONFIGMAP") | default "sandbox-proxy-ca-bundle" }}
{{- $serverUrl := (index $cm "SANDBOX_API_SERVER_URL") }}
{{- $serverUrl := (index $cm "ONYX_SERVER_URL") }}
{{- $sandboxPod := .Values.sandboxPod | default dict }}
{{- $caBundleFile := "/etc/ssl/sandbox/ca-bundle.crt" }}
apiVersion: v1
@@ -140,6 +140,8 @@ template:
env:
- { name: ONYX_PAT, value: "replaced_by_egress_proxy" }
- { name: ONYX_SERVER_URL, value: "{{ $serverUrl }}" }
{{/* The deployment value is the exact API base; do not append the CLI default. */}}
- { name: ONYX_API_PREFIX, value: "" }
- { name: GH_TOKEN, value: "replaced_by_egress_proxy" }
- { name: GH_NO_UPDATE_NOTIFIER, value: "1" }
- { name: OPENCODE_DATA_HOME, value: "/workspace/opencode-data" }
+1 -1
View File
@@ -43,7 +43,7 @@ configMap:
# No model server in this lane; skips the GPU-status probe that would hang API startup.
DISABLE_MODEL_SERVER: "true"
# Sandbox pods call the chart-managed API service, matching production.
SANDBOX_API_SERVER_URL: "http://onyx-api-service.onyx.svc.cluster.local:8080"
ONYX_SERVER_URL: "http://onyx-api-service.onyx.svc.cluster.local:8080"
# Sandbox image the kind cluster pulls (built+pushed by the CI workflow).
SANDBOX_CONTAINER_IMAGE: "localhost:5001/onyx-sandbox:ci"
# Tiny requests so the sandbox pod schedules alongside the full Onyx stack on
@@ -79,7 +79,8 @@ codeInterpreter:
configMap:
ENABLE_CRAFT: "true"
# Host gateway (Docker Desktop), not internal cluster DNS; Linux-kind devs sub their own.
SANDBOX_API_SERVER_URL: "http://host.docker.internal:3000"
# Full base URL, path prefix included — the Next dev server proxies the API under /api.
ONYX_SERVER_URL: "http://host.docker.internal:3000/api"
ENABLE_PAID_ENTERPRISE_EDITION_FEATURES: "true"
sandboxProxy:
+4 -1
View File
@@ -1648,9 +1648,12 @@ configMap:
SANDBOX_BACKEND: "kubernetes"
SANDBOX_NAMESPACE: "onyx-sandboxes"
SANDBOX_SERVICE_ACCOUNT_NAME: "sandbox"
# ONYX_SERVER_URL must be the full API base URL, path prefix included — e.g.
# "https://onyx.example/api" through the public proxy, or
# "http://<release>-api-service.<ns>.svc.cluster.local:8080" directly at the
# API service (no prefix). It is required when ENABLE_CRAFT=true.
SANDBOX_PROXY_CA_SECRET: "sandbox-proxy-ca"
SANDBOX_PROXY_CA_CONFIGMAP: "sandbox-proxy-ca-bundle"
# -- Craft sandbox RBAC (rendered when configMap.ENABLE_CRAFT=true).
craft:
# Extra SAs (release ns) to also grant sandbox RBAC, e.g. a separate worker SA.
+1 -1
View File
@@ -371,7 +371,7 @@ For Craft development, the required vars (already in the template) are:
ENABLE_CRAFT=true
SANDBOX_BACKEND=kubernetes
SANDBOX_CONTAINER_IMAGE=onyxdotapp/sandbox:dev
SANDBOX_API_SERVER_URL=http://onyx-api-service.onyx.svc.cluster.local:8080
ONYX_SERVER_URL=http://onyx-api-service.onyx.svc.cluster.local:8080
ONYX_SANDBOX_PUSH_PRIVATE_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
```
+5 -11
View File
@@ -30,7 +30,6 @@ bash "$WT"/deployment/docker_compose/install.sh --local --include-craft
cat >> ~/onyx_data/deployment/.env <<'ENV'
ENABLE_CRAFT=true
SANDBOX_BACKEND=docker
SANDBOX_API_SERVER_URL=http://host.docker.internal:3001
HOST_PORT=3001
ENV
@@ -49,12 +48,8 @@ ENV
## Prerequisites
- macOS with **Docker Desktop** (or OrbStack) — these provide `host.docker.internal`
resolution from inside the `onyx_craft_sandbox` bridge network, which the
sandbox container needs to reach api_server.
- On Linux, replace `http://host.docker.internal:3001` with your machine's
reachable address (or use `--add-host` workarounds). Native Linux Docker
does *not* resolve `host.docker.internal` by default.
- Docker Desktop, OrbStack, or Docker Engine on Linux. The Craft overlay uses
the private `onyx-craft-api` bridge alias on every platform.
- ~80 GB free Docker disk. Onyx's full stack pulls ~30 GB; local image
builds add another 10–15 GB; build cache balloons to 40+ GB if you let
it. See [OpenSearch read-only block](#opensearch-flipped-into-read-only-mode-disk-full) below.
@@ -70,7 +65,7 @@ These must end up in `~/onyx_data/deployment/.env` after install:
|---|---|---|
| `ENABLE_CRAFT=true` | yes | `--include-craft` sets this (fresh installs and existing `.env`). |
| `SANDBOX_BACKEND=docker` | yes | `--include-craft` sets this alongside `ENABLE_CRAFT`. |
| `SANDBOX_API_SERVER_URL=http://host.docker.internal:3001` | yes | Provision raises `ValueError("SANDBOX_API_SERVER_URL must be set")` without it. Must be a URL the sandbox container can reach **from the `onyx_craft_sandbox` bridge** — compose-internal hostnames (`api_server`, `nginx`) won't resolve there. Match the port to `HOST_PORT`. |
| `ONYX_SERVER_URL` | optional | Complete API base URL. The Craft overlay defaults to `http://onyx-craft-api:8080` on the private sandbox bridge. Override with a public URL only when desired, including its `/api` path prefix. |
| `HOST_PORT=3001` | only if 3000 conflicts | Default is 3000; nginx binds this on the host. Free up 3000 or change here. |
| `IMAGE_TAG` | optional | Uses the normal compose default (`latest`) unless set. Craft uses this same tag for the sandbox image, so do not set a separate sandbox image for normal deployments. There are **no** Craft-specific app/backend images — Craft is enabled at runtime via `ENABLE_CRAFT=true` (above). See [image architecture](../infra/image-architecture.md). |
| `ONYX_BACKEND_IMAGE` | only when running unreleased PRs | Lets you override just the backend image without forcing model-server / web-server to the same tag. |
@@ -122,12 +117,11 @@ adapt the prompts (Standard mode = `2`, keep existing env = blank).
### 3. Fix the .env
On an existing `.env`, `--include-craft` writes `ENABLE_CRAFT=true` and
`SANDBOX_BACKEND=docker` for you (on both the update and restart paths). It
does **not** set the host-specific values, so append those yourself:
`SANDBOX_BACKEND=docker` for you (on both the update and restart paths).
Set `HOST_PORT` only when the default port is unavailable:
```bash
cat >> ~/onyx_data/deployment/.env <<'ENV'
SANDBOX_API_SERVER_URL=http://host.docker.internal:3001
HOST_PORT=3001
ENV
```
@@ -491,7 +491,7 @@ The proxy uses first-claim-wins credential dispatch. Resolvers must keep
### Onyx PAT Resolver
`OnyxPatResolver` claims requests whose host and port match
`SANDBOX_API_SERVER_URL`.
`ONYX_SERVER_URL`.
It loads the sandbox row and decrypts `Sandbox.encrypted_pat`, then injects both
Onyx API key header names as bearer tokens. The tenant is embedded in the PAT,
@@ -747,7 +747,8 @@ Relevant config:
- `ENABLE_CRAFT=true` enables Craft chart resources.
- `SANDBOX_BACKEND` is `kubernetes` or `docker`.
- `SANDBOX_API_SERVER_URL` must be set for sandbox API calls and PAT injection.
- `ONYX_SERVER_URL` must be the complete API base for sandbox API calls and PAT
injection.
- `SANDBOX_PROXY_HOST` and `SANDBOX_PROXY_PORT` tell sandboxes where to proxy.
- `SANDBOX_PROXY_LISTEN_PORT` and `SANDBOX_PROXY_HEALTHZ_PORT` configure the
proxy process.
+1 -1
View File
@@ -210,7 +210,7 @@ sandboxProxy:
- ElastiCache: `REDIS_HOST`, `REDIS_SSL=true`, `REDIS_SSL_CERT_REQS=none`, `auth.redis.enabled=false` (no auth token).
- OpenSearch (v4.0 search backend): `ONYX_DISABLE_VESPA=true`, `ENABLE_OPENSEARCH_INDEXING/RETRIEVAL_FOR_ONYX=true`, `USING_AWS_MANAGED_OPENSEARCH=true`, `OPENSEARCH_REST_API_PORT=443`, `OPENSEARCH_USE_SSL=true`, `OPENSEARCH_ADMIN_USERNAME=admin`.
- S3: `S3_FILE_STORE_BUCKET_NAME`, `S3_ENDPOINT_URL=""`, `AWS_REGION_NAME`.
- Craft: `ENABLE_CRAFT=true`, `SANDBOX_API_SERVER_URL=http://onyx-api-service.onyx.svc.cluster.local:8080`, `auth.sandboxPushSecret.enabled=true`. (`SANDBOX_SERVICE_ACCOUNT_NAME`/`SANDBOX_CONTAINER_IMAGE` default correctly.)
- Craft: `ENABLE_CRAFT=true`, `ONYX_SERVER_URL=http://onyx-api-service.onyx.svc.cluster.local:8080`, `auth.sandboxPushSecret.enabled=true`. (`SANDBOX_SERVICE_ACCOUNT_NAME`/`SANDBOX_CONTAINER_IMAGE` default correctly.)
### Images
Use an immutable release tag for Kubernetes customer Craft deployments, e.g.