# rustingface **A sovereign, HuggingFace-compatible model registry.** Single Rust binary. One config file. S3-compatible storage. Anything a client fetches through it lives in your bucket until you explicitly remove it. Set `HF_ENDPOINT` and nothing else changes: ```sh export HF_ENDPOINT=https://rf.internal python -c "from transformers import AutoModel; AutoModel.from_pretrained('Qwen/Qwen3-0.6B')" ``` The design is in [`doc/spec.md`](doc/spec.md). Architectural conventions this project inherits are in [`~/git/architecture`](https://git.lair.cafe/grenade/architecture) — workspace layout, deployment, systemd hardening, firewalld, SELinux, port allocation. --- ## What it guarantees 1. **Fetch-once retention.** Any blob served through rustingface is durably stored before the response completes. 2. **No eviction.** No TTL, no LRU, no background cleaner. Storage growth is the operator's problem, by design. 3. **Offline sufficiency.** Once a `(repo, revision, file)` triple has been served, it is servable forever with no network path to any upstream. 4. **Reference stability.** `main` resolves to the same commit on every later request until an operator repins it. 5. **State portability.** The bucket is the entire system state. It does not reimplement the Hub's semantics. It records the exact HTTP metadata upstream returned at first fetch — `ETag`, `X-Repo-Commit`, `X-Linked-Etag`, `X-Linked-Size` — and replays it byte-identically, so a `~/.cache/huggingface` populated through rustingface is interchangeable with one populated directly from the Hub. --- ## Layout ``` crates/ rustingface-entities/ manifest, ref and repo schemas; bucket key layout; configuration; the X-Error-Code taxonomy. No I/O. rustingface-core/ resolver, freeze pinning, single-flight, the streaming tee, policy, gc/verify/refresh. Defines the Store and Upstream ports. rustingface-data/ adapters: object_store (S3 and local) and reqwest. rustingface-api/ the axum surface and the header contract. rustingface/ the binary: serve, plus the admin subcommands. web/ Vite + React + SWC + TS; the operator-facing UI asset/ systemd, firewalld, nginx, config template script/infra-setup.sh one-time host provisioning test/conformance/ the suite that drives a real huggingface_hub ``` Two departures from `architecture/generic.md`, both deliberate: - **One binary, not `-api` plus `-cli`.** `doc/spec.md` §9 specifies a single binary with subcommands so the CLI and the service cannot drift. The library crate split is unchanged. - **`rustingface-api` is a library**, consumed by the binary, rather than being the binary itself. Same reason. --- ## The web UI `web/` is a static single-page app served from the nginx host. Three routes: `/` renders this readme, `/models` is a paged, filterable listing of what the bucket holds, and `/{namespace}/{name}` shows one repository — its pinned refs, the files actually stored, and its model card. It reads a **`/v1/` API** that is deliberately separate from `/api/`. That surface belongs to the Hub: it is recorded and replayed verbatim, and adding routes of our own to it risks a client mistaking one for the real thing. `/v1/` answers a question the Hub has no equivalent for — *what is in this bucket* — which is inventory, not the model search the spec rules out. ``` GET /v1/status what this instance is; whether it is sealed GET /v1/repos?page=&per_page=&type=&q= paged inventory GET /v1/repos/{type}/{repo_id} one repository, with its stored files ``` Two things the UI is careful about, because model cards are third-party content: - Card HTML is parsed and then **sanitised** against GitHub's allowlist. Verified against a card crafted with `