Ir al contenido
↑↓Navigate↵SelectescClose

Installation for contributors

Onboarding guide for Nexus contributors

Esta página aún no está disponible en tu idioma.

This guide covers the nexus gateway and observability platform as a standalone repository. Both the Rust workspace (Cargo.toml) and the TypeScript workspace (pnpm-workspace.yaml) are rooted at the repository root. The shared vortex-* crates ship as the nested vortex-common-crates git submodule, so clone with --recursive (or run git submodule update --init --recursive after cloning). The submodule URL is deliberately relative (../vortex-common-crates.git): the repository is hosted on more than one GitLab instance, and each instance carries the sibling repository at the same group path, so the submodule resolves against whichever remote you cloned from.

After that, a new nexus contributor should be able to:

  1. Run just dev from the repository root.
  2. Optional: just status prints a formatted health summary (published gateway + console, internal Rust services via clickhouse exec, infra probes, and dev-only observability warnings).
  3. Create the first user through the console sign-up flow, which provisions a personal workspace, default organization, default project, and manager memberships.
  4. Or curl the published gateway and console /healthz routes and get HTTP 200.

Cold Docker builds can take several minutes; warm rebuilds are faster.

Nexus work needs Docker with Compose v2, just, Node 20+, pnpm 9.12+, buf, protoc, Rust 1.96.1 (pinned by rust-toolchain.toml), and the initialized vortex-common-crates submodule. Copy .env.example to .env before starting the stack. On Apple Silicon, set DOCKER_PLATFORM=linux/arm64 in .env before the first Docker build. Install pre-commit and its dependencies (gitleaks and prettier) if you plan to commit changes locally. Rust pushes also need the toolchain clippy component (rustup component add clippy); the pre-push hook mirrors the CI rust-lint job.

Quick prerequisite check (from the repository root):

Terminal window
just --version
docker compose version
rustc --version
cargo --version
pnpm --version
buf --version
protoc --version
test -f vortex-common-crates/Cargo.toml && echo "submodule ok"

Install local hooks once:

Terminal window
pre-commit install
pre-commit install --hook-type pre-push

The hook and CI behavior is summarized in dev/ci.md.

From the repository root with .env in place:

Terminal window
# Stand up the full stack (Postgres, ClickHouse, MinIO, NATS, Valkey,
# OTel Collector, the Rust services, and the Next.js console).
just dev

The first invocation compiles every Rust service inside the Docker build steps.

Terminal window
# One-shot summary (published gateway + console, internal Rust + infra).
# Dev-only LGTM readiness is reported as a warning and does not block core stack health.
just status
# Or manually — gateway and console are the primary published app endpoints:
curl -fsS http://127.0.0.1:14450/healthz && echo " (gateway ok)"
curl -fsS http://127.0.0.1:14449/healthz && echo " (console ok)"
# Most other Rust services are also host-published: control-plane :14451,
# auth :14453, ingest :14457, trace-ingest :14458, audit :14459, query :14461/:14462.
# Only policy (:14455) is compose-network-only; probe it with
# `docker compose -f deploy/compose/docker-compose.yml exec -T clickhouse wget -qO- http://policy:14455/healthz`.
# Prometheus counters on the gateway (same published port):
curl -sS http://127.0.0.1:14450/metrics | grep vortex_service_starts_total

All credentials below are set in deploy/compose/docker-compose.yml and are for local development only.

Service URL User / Access Key Password / Secret Key
MinIO Console http://127.0.0.1:9001 vortex vortex-dev-minio
MinIO S3 API http://127.0.0.1:9000 (see service users below)
Postgres postgres:5432 inside Compose vortex vortex
ClickHouse http://clickhouse:8123 inside Compose vortex vortex
Grafana http://127.0.0.1:14470 anonymous admin (no login required)
NATS nats://nats:4222 inside Compose (no auth)
Valkey redis://redis:6379 inside Compose (no auth) Redis-compatible cache/rate-limit backend

Host-published service ports are listed in reference/ports.md. Postgres, ClickHouse, NATS, and Valkey are intentionally Compose-network-only in the stock dev stack.

MinIO service users (created by deploy/compose/minio/bucket-init.sh on first boot):

User Secret Policy Used by
ingest ingest-dev-secret ingest-write ingest service (sole MinIO writer)
gateway gateway-dev-secret gateway-readonly gateway (read-only; PutObject denied)

Beyond compose: real BYOK chat through the gateway

Section titled “Beyond compose: real BYOK chat through the gateway”

The Compose stack above proves every Rust service answers /healthz. It does not by itself seed users, workspaces, orgs, API keys, or provider keys inside the gateway container. Create the first user at http://127.0.0.1:14449/sign-up; sign-up provisions a personal workspace, default organization, default project, and manager memberships.

For an annotated flow that uses the running compose stack, mints a Nexus API key, registers a BYOK provider key, sends one curl to POST /v1/chat/completions, and inspects audit.log, follow End-to-end walkthrough: real BYOK chat request.

Use nexus context create / nexus context use to pin DB URLs, tenant id, and state dir for repeated admin work. Shell completion is provided by nexus completions <shell> and includes dynamic value completion for positional refs (org/workspace/project slugs and ids) when the active context can reach Postgres.

Tenant BYOK provider keys are managed with nexus credentials provider-keys flows. Production PTB uses separate operator-managed platform keys through nexus credentials platform-keys:

Terminal window
nexus credentials platform-keys set \
--provider openai \
--secret-env OPENAI_PLATFORM_API_KEY \
--workspace ws_... \
--org org_... \
--label prod-openai-us
nexus credentials platform-keys list --provider openai --workspace ws_...
nexus credentials platform-keys rm ppk_...
nexus credentials platform-keys rotate ppk_... --secret-env OPENAI_PLATFORM_API_KEY_NEXT

Scope flags are optional. Project-scoped keys win over org-scoped keys, org over workspace, workspace over tenant/operator-global, and broader keys are fallback material when a more-specific key is absent, revoked, unhealthy, or disallowed.

To seed all supported platform keys from .env values that are present:

Terminal window
just load-provider-keys
# optionally scope loaded keys to a workspace id:
just load-provider-keys ws_...

The loader checks common provider env vars such as OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY, COHERE_API_KEY, XAI_API_KEY, TOGETHER_API_KEY, FIREWORKS_API_KEY, DEEPSEEK_API_KEY, and PERPLEXITY_API_KEY.

This is optional and separate from BYOK. Use it when you want to verify operator-managed platform credentials and wallet-funded pass-through billing. It requires NEXUS_GATEWAY_PTB=1 in .env; after changing that value, recreate the gateway with just compose up -d gateway so the container receives the new environment. Platform provider keys and Wallet eligibility are control-plane data. The gateway reads them at request time with a short provider-key cache, so wait up to 60 seconds after changing those rows or recreate the gateway if you need immediate verification. Provider keys and model registry artifacts are separate inputs. The gateway reads provider-key changes through its short credential cache and converges to committed registry changes through its periodic Postgres poll.

Terminal window
# 1. Enable PTB in .env, then recreate the gateway.
# NEXUS_GATEWAY_PTB=1
just compose up -d gateway
# 2. Load operator-managed platform provider keys from .env.
just load-provider-keys
just validate-provider-keys --live
# 3. Enable Wallet/PTB eligibility. From the CLI:
just cli ops wallet set --workspace <workspace_slug_or_id> --state on
just cli ops wallet status --workspace <workspace_slug_or_id> --org <org_slug>
# Or from the console.
# Workspace scope: Workspace -> Billing -> check
# "Wallet enabled for this workspace". This is the master switch;
# organizations and projects can only turn Wallet off below it.
# Organization scope: Workspace -> Limits, edit the target organization row
# and set Wallet to "Inherit from workspace" or "On".
# Project scope: open the project -> Limits and set Wallet to
# "Inherit from organization" or "On".
#
# 4. Fund the workspace wallet with a local operator grant.
just cli workspaces list
just cli ops grants create \
--workspace <workspace_slug_or_id> \
--amount-usd 10.00 \
--reason "local PTB smoke" \
--idempotency-key local-ptb-smoke-001
# 5. Create a project with no BYOK key and mint a Nexus API key for it.
just cli projects create --org <org_slug> --slug ptb-smoke --name PTB-smoke
PTB_TOKEN=$(just cli credentials api-keys create \
--org <org_slug> \
--project ptb-smoke \
--name ptb-smoke \
--issued-by "$(whoami)")
# If the console is already open, refresh the browser so its org/project list reloads.
# 6. Send a request through the PTB project.
curl http://localhost:14450/v1/chat/completions \
-H "Authorization: Bearer $PTB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-5.4-mini","messages":[{"role":"user","content":"ptb hello"}],"max_tokens":8}'

The PTB project must not have a BYOK provider key for the requested provider, otherwise routing prefers the tenant BYOK key.

Three surfaces write the same rows: nexus ops wallet, the console, and the limits API (PATCH /api/workspaces/:id/limits, /api/orgs/:id/limits, or /api/projects/:id/limits with ptb_enabled). On those routes an omitted field leaves the stored value alone, and ptb_enabled: null returns an organization or project to inheriting its parent. The workspace route rejects null because the workspace switch is what the others inherit. The GET responses carry both ptb_enabled, the effective value across all three scopes, and ptb_override, the value stored at the requested scope.

nexus ops wallet status prints the same two facts per scope, so a request the gateway refuses because Wallet is unavailable can be traced to the scope that turned it off:

Terminal window
# Workspace, organization, and project rows for one project.
nexus ops wallet status --workspace acme --org engineering --project api
# scope scope_id setting effective
# workspace ws_... on on
# org org_... inherit on
# project proj_... off off

setting is the value stored at that scope — on, off, or inherit (no stored value) — and effective is what the scope resolves to after its ancestors are applied. The workspace is the master switch and defaults to off; an organization or project can only turn Wallet off below it, never on. Add --format json or --format tsv for machine-readable output; the plain-language summary naming the blocking scope goes to stderr.

The setter takes the same scope flags plus a state:

Terminal window
nexus ops wallet set --workspace acme --state on
nexus ops wallet set --org engineering --state off
nexus ops wallet set --org engineering --project api --state inherit

--state inherit deletes the organization’s or project’s stored value so it follows its parent again; the workspace rejects it. --org identifies its workspace on its own, so --workspace is only required when the workspace itself is the target. Every write records a workspace.limit.updated, org.limit.updated, or project.limit.updated audit event, and the command prints the resulting scope chain so the outcome of the write is visible.

PTB credits are funded by auditable operator grants. Auto-refill settings are alerting configuration only.

Terminal window
nexus ops grants create \
--workspace ws_... \
--amount-usd 100.00 \
--reason "pilot credit" \
--idempotency-key pilot-credit-001
nexus ops grants list ws_...
Terminal window
just down # stops containers, preserves volumes
just dev-clean # stops containers and destroys volumes

Run from the repository root unless noted.

Command Purpose
just up / just dev / just down Start or stop the nexus dev stack
just status Pretty health summary for the dev stack
just proto buf generate into packages/proto/src/gen
just proto-lint Lint protobuf sources
just migrate Apply dev database migrations via compose
just registry-generate … Generate a canonical offline provider-catalog artifact
just registry-import … Import an artifact: print the plan, confirm, apply
just load-provider-keys Load PTB platform provider keys from supported .env API key variables
just node-install pnpm install across the nexus TS workspace
just node-build pnpm build across every pnpm workspace package
just node-lint Lint nexus TS packages
just node-test Test nexus TS packages
just compose-it Smoke test: stack up, /healthz probe, teardown
just cli … Run nexus inside dev compose; e.g. just cli orgs list
just cli-local … Host cargo run -p nexus-cli (needs DB at NEXUS_DATABASE_URL)
just test … Vitest contract suite against a running gateway

The one-shot just cli ... passthrough is best for arguments without spaces. Use just cli to open the interactive shell when a command needs quoted values.

Rust workspace commands (just lint, just test-workspace, just fmt, just deny, just build) run cargo against the workspace at the repository root. Use dev/testing.md to choose the right test suite and dev/ci.md to map local commands to GitLab jobs.

Generate a canonical OpenAI and Anthropic catalog into ignored local output:

Terminal window
just registry-generate \
--generated-at 2026-07-23T12:00:00Z \
--artifact-id local-provider-catalog \
--output dist/provider-catalog
just registry-import dist/provider-catalog/openai.json --dry-run
just registry-import dist/provider-catalog/openai.json

The generator CLI does not write Postgres. Import prints the plan and asks to confirm before writing; --dry-run stops at the plan, --yes skips the prompt, and --check validates the file offline without loading the control plane. Unsigned artifacts are accepted as-is; for signed artifacts pass both --expected-key-id <id> and --verifying-key-path <public-key-file>. The apply half uses the artifact captured with the plan and does not contact providers. The running gateway polls Postgres every 60 seconds by default, so an applied change set does not require a gateway restart. Poll just cli registry models get <model> until the expected endpoint appears.

Registration invites (invite-only sign-up)

Section titled “Registration invites (invite-only sign-up)”

To gate self-service sign-up without attaching new users to a shared org or workspace, set AUTH_ALLOW_REGISTRATION=invite in .env and issue registration credentials with the operator CLI.

Creates a single-use invite for one email. The user receives only a token link (no email in the URL) and must register with the invited address.

Terminal window
nexus ops registration-invites create \
--email newuser@example.com \
--base-url http://127.0.0.1:14449

The command prints:

  • email= on stderr for you to communicate out-of-band
  • the raw invite token on the first stdout line
  • the sign-up URL, such as http://127.0.0.1:14449/sign-up?invite=nxi_..., on the second stdout line

List or revoke pending invites:

Terminal window
nexus ops registration-invites list
nexus ops registration-invites revoke inv_...

Allows any email to register while uses remain. The command prints the raw code on the first stdout line and the sign-up URL (/sign-up?code=nxr_...) on the second stdout line.

Terminal window
nexus ops registration-codes create --max-uses 10 --base-url http://127.0.0.1:14449
nexus ops registration-codes list
nexus ops registration-codes revoke rc_...

New accounts still receive only their auto-provisioned personal workspace. Org and workspace membership invites remain separate and additive via /accept-invite.

Under invite-only registration, a membership invite link (/accept-invite?token=nxi_...) cannot by itself create an account, so inviting someone who has no account yet leaves them unable to join. Set AUTH_INVITE_CODE_SIGNUP=true to let membership invitation tokens also satisfy the sign-up gate.

With the flag enabled, the accept-invite page offers a Create account action that carries the invite token into /sign-up?invite=nxi_.... The recipient must register with the email the invitation was sent to. The token is not consumed at sign-up; membership is granted (and the invite marked accepted) when the new account lands back on /accept-invite and accepts.

  • Not a guarantee that every container participates in the BYOK chat path without extra setup (see the end-to-end doc).
  • Not production-shaped telemetry dashboards for every histogram described in ../vortex-common-crates/contracts/telemetry.md.
  • Not a substitute for release-artifact verification; use ops/releases.md for signed image, SBOM, and provenance checks.

If any of the above fail on your machine, include just status output and the failing service logs in the issue.