sbom-nexus/docs/operator-guide.md
tegwick 879012c776
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 38s
feat: add controlled source ingestion and replay
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b22-9638-76d2-bbff-b7ea1770b118
2026-08-22 23:57:37 +02:00

140 lines
4.6 KiB
Markdown

# Operator guide
## Run locally
```bash
make install
make run
```
The API defaults to `127.0.0.1:8010` and `./sbom-nexus.db`. SQLite schema is
created automatically for local development.
For PostgreSQL, migrate before starting the API:
```bash
export SBOM_NEXUS_DATABASE_URL='postgresql+psycopg://user:password@host/sbom_nexus'
make migrate
make run
```
When migrations use short-lived login roles, set
`SBOM_NEXUS_MIGRATION_ROLE` to the reviewed durable owner role. Alembic issues
`SET ROLE` before creating or changing any object, so ownership does not become
tied to an expiring lease. The production package uses `sbom_nexus_owner`.
Production deployments should mount a Secret and set
`SBOM_NEXUS_DATABASE_URL_FILE` to its `url` file rather than exposing the DSN
in a manifest or command argument. The same file setting is consumed by both
Alembic and the API process.
PostgreSQL never auto-creates tables unless `SBOM_NEXUS_AUTO_CREATE=1` is set
explicitly. Normal production operation must use Alembic.
## Register and ingest a repository
```bash
curl -X PUT http://127.0.0.1:8010/repositories/example \
-H 'Content-Type: application/json' \
-d '{"checkout_path":"/srv/repos/example","active":true}'
curl -X POST http://127.0.0.1:8010/sbom/example/ingest
```
The ingest response is terminal: `ingested`, or `skipped` with one of
`no-checkout`, `no-manifest`, `ingest-error`, `source-unavailable`, or
`source-rejected`. A skip advances queue fairness but does not advance
`last_success_at`.
## Controlled Forgejo source
Production automation uses the contract selected by `CUST-WP-0064`: Repo
Manager projects a canonical Coulomb repository and full commit SHA, Activity
Core freezes that `source_ref` with its bounded target set, and Nexus alone
fetches and scans the source. Example projection:
```json
{
"source_ref": {
"kind": "forgejo-archive-v1",
"repository": "coulomb/example",
"revision": "0123456789abcdef0123456789abcdef01234567",
"observed_ref": "refs/heads/main",
"observed_at": "2026-08-22T20:00:00Z"
}
}
```
Enable only after the package has migration `0002`, bounded ephemeral storage,
and Forgejo/DNS egress:
```sh
export SBOM_NEXUS_CONTROLLED_SOURCE_ENABLED=true
export SBOM_NEXUS_FORGEJO_BASE_URL=http://forgejo-gitea-http.forgejo.svc.cluster.local:3000
export SBOM_NEXUS_SOURCE_TMP=/var/run/sbom-sources
```
The runtime defaults are one concurrent scan, 120 seconds each for fetch and
scan, 100 MiB compressed, 512 MiB expanded, and 100,000 archive members. The
source endpoint accepts no arbitrary URL and requires no credential for public
Coulomb repositories. Never substitute a Forgejo administrator token.
Automated ingest and skip requests send the same value in `Idempotency-Key`
and `X-Activity-Core-Operation-ID`. Nexus persists that operation identity in
the snapshot transaction. A retry with the same request replays the original
terminal outcome; reuse against a different route, repository, reason, or
source reference returns HTTP 409.
## Inspect catch-up
```bash
curl -s 'http://127.0.0.1:8010/sbom/catch-up?limit=3' | python3 -m json.tool
```
The default stale threshold is 30 days. Override it globally with
`SBOM_NEXUS_STALE_DAYS` or per query with `stale_days` during controlled
operation.
## Import State Hub history
Synchronize the minimum repository identity/path projection first. This command
does not call an ingest endpoint or create an SBOM snapshot:
```bash
uv run python scripts/sync_repository_projections.py \
--source-url http://127.0.0.1:8000 \
--target-url http://127.0.0.1:8010 \
--host-id bnt-lap001 \
--dry-run
```
Remove `--dry-run` only after the target is deployed. The apply path upserts all
active and inactive records, then reads them back and fails on a missing or
mismatched active/path projection. Extra target rows are reported but retained.
Run a read-only preview first:
```bash
uv run python scripts/import_state_hub.py --dry-run
```
Then run against a backed-up Nexus database:
```bash
uv run python scripts/import_state_hub.py \
--source-url http://127.0.0.1:8000 \
--target-url http://127.0.0.1:8010
```
Imports are idempotent on the State Hub snapshot UUID. Before cutover, compare
the source/target repository, snapshot, and entry counts described in the
extraction review. The command fails unless every legacy snapshot id, repository,
timestamp, entry count, licence group, and direct-production copyleft count
reconciles.
## Current production limitations
- Authentication and authorization are not yet integrated.
- Structured operational metrics, backup/restore proof, and retention policy are
required before authority cutover.
- State Hub and Repo Manager callers have not yet been retargeted.