2 Commits

Author SHA1 Message Date
Stephan Berbig
c9e11c3486 cli: add add-backend / remove-backend / list-backends commands
Some checks failed
CI / ruff (push) Has been cancelled
CI / mypy --strict (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / bandit (push) Has been cancelled
CI / pip-audit (push) Has been cancelled
So nobody ever has to hand-write the OLLAMA_BACKENDS JSON again.

  # add a backend, probe it, print the resulting .env line:
  neuronetz-gateway add-backend embedded     http://ollama:11434
  neuronetz-gateway add-backend neuro-ollama http://neuro-ollama:11434 --token ABC

  # update one (e.g. rotate token):
  neuronetz-gateway add-backend neuro-ollama http://neuro-ollama:11434 --token XYZ --replace

  # remove:
  neuronetz-gateway remove-backend neuro-ollama

  # peek (tokens redacted):
  neuronetz-gateway list-backends

  # write directly to a .env file (atomic temp-file + rename):
  neuronetz-gateway add-backend foo http://foo:11434 --token T --write-env /app/.env

  # show what would change without doing it:
  neuronetz-gateway add-backend foo http://foo:11434 --token T --dry-run

What each command does:

- `add-backend NAME URL` (+ optional --token / --header / --scheme / --replace
  / --no-validate / --write-env / --dry-run): builds a new backend list (current
  list parsed from OLLAMA_BACKENDS env, or synthesized from the single-backend
  fallback if unset), validates the new backend by probing /api/tags with the
  same headers the gateway will use at runtime (`build_backend_headers`), then
  prints the resulting OLLAMA_BACKENDS=... line ready to paste — or writes it
  in place if --write-env is given. Refuses to overwrite an existing name
  unless --replace is passed.
- `remove-backend NAME` (+ --write-env / --dry-run): mirror of add-backend for
  removal.
- `list-backends`: shows the configured backends with tokens redacted to
  "***" via `redacted_dump`. Useful sanity check after editing .env.

All the JSON manipulation is in a new pure-helpers module
`cli/backends.py` (parse / serialize / add_or_replace / remove /
update_env_file). The Typer commands in `cli/manage.py` are thin shells
on top — the logic is unit-tested directly without spinning up Typer or
the network. The token is unwrapped from SecretStr exactly once at the
serialization boundary (`to_dict`) and never logged.

New tests (16): full coverage of the helpers — round-trip serialize/parse,
duplicate-name rejection, replace-in-place order preservation, remove
on unknown name, redaction, atomic env-file rewrite (insert / replace /
idempotent re-apply / create-when-missing).

ruff (incl. the per-file ignore add for tests' S105/S106 — placeholder
"tok123"-style strings are inputs, not credentials) + mypy --strict (68
source files) clean. pytest: 76 passed + 39 skipped (the 16 new tests +
no regressions on the existing 60).
2026-05-27 22:59:53 +02:00
Stephan Berbig
d79f17b3bb scaffold: project skeleton, schema, healthz/readyz, CI
Initial project structure for neuronetz-gateway per scope-docs/SPEC.md:

- Python 3.12 / FastAPI / SQLAlchemy 2.0 (async) / Redis / Postgres stack
  managed by uv. Multi-stage non-root Dockerfile, prod + dev compose files
  (ollama service is NEVER published in either), Caddyfile + systemd unit,
  justfile, GitHub Actions CI (ruff, mypy --strict, pytest, bandit, pip-audit).
- Pydantic-Settings config covering every env var from SPEC §7, including the
  MODEL_DISCOVERY_* keys for the dynamic-discovery feature (§4.6).
- Alembic 0001_initial creates the full gateway schema (8 tables, 3 enums,
  notify_key_revoked() trigger), incl. allow_all_models on tenant_limits and
  key_limits for the per-tenant auto-grant toggle.
- Working /healthz, /readyz (fail-closed when deps unreachable), and a
  Prometheus /metrics stub. Sanitizing error handlers that attach X-Request-ID
  to every response and never leak upstream internals.
- SPEC + AGENT_PROMPT included under scope-docs/ (source of truth).
2026-05-26 20:50:35 +02:00