# Serving a Deb / RPM repo from holger — guide
> **Class:** `guide` · **Function:** `serve`. Architecture:
> [`deb-rpm-native-metadata.md`](deb-rpm-native-metadata.md). Dead-simple version:
> [`deb-rpm-native-metadata-for-idiots`](deb-rpm-native-metadata-for-idiots.md).
> This is the how-to: stand up an APT or dnf repo backed by a `.znippy` archive and
> see the real control/header fields that now land in the index.
## What holger does for you
Point holger at an archive that contains bare `.deb` / `.rpm` files and it
**synthesizes the native index** the client expects, straight from the packages
present — no upstream index required:
- **Deb:** `dists/{dist}/Release` + `dists/{dist}/{component}/binary-{arch}/Packages`,
each stanza carrying real `Package`/`Version`/`Architecture` **plus the authoritative
`Depends` / `Maintainer` / `Description` parsed out of the package's own
`control.tar.*`**, with a real SHA256 over the served bytes so `apt [trusted=yes]`
verifies.
- **RPM:** `repodata/repomd.xml` + `repodata/primary.xml`, each `<package>` carrying
the filename NEVRA **enriched with the real `epoch` and the `<format>` block —
`<summary>`, `<url>`, `<rpm:license>`, `<rpm:provides>`, `<rpm:requires>` — parsed
out of the binary RPM header**, again with a real SHA256.
If the archive *already* carries a real index (a byte-exact upstream mirror), holger
serves that verbatim instead — passthrough always wins over synthesis.
## 1 · Put the packages in an archive
Use the agent to pack a directory of packages into a `.znippy` (see
[`agent-guide`](agent-guide.md)). A plain mirror is fine — you do **not** need the
znippy `deb_native` / `rpm_native` plugin; the native parse works on the raw package
bytes. (If you *do* build with the plugin, its typed sub-index is used in preference
and you get identical fields.)
## 2 · Wire a repository in `holger.ron`
Add a repository backed by the archive and expose it, exactly like any other native
backend (`serve-guide` shows the endpoint/storage wiring). The Deb backend registers
as `repository-znippy-deb`, the RPM backend as `repository-znippy-rpm`.
## 3 · Fetch the index and confirm the real fields
**Debian / APT** — the client fetches `Release`, then the per-arch `Packages`:
```bash
# the dist Release referencing each Packages by SHA256+size
curl -s http://localhost:8081/debrepo/dists/stable/Release
# the synthesized Packages — note the parsed control fields
curl -s http://localhost:8081/debrepo/dists/stable/main/binary-amd64/Packages
```
A stanza now looks like this (the `Depends` / `Maintainer` / `Description` came from
the package's `control`, not the filename):
```
Package: hello
Version: 2.10-2
Architecture: amd64
Maintainer: Santiago <santiago@debian.org>
Depends: libc6 (>= 2.14)
Description: friendly GNU greeter
Filename: pool/main/h/hello/hello_2.10-2_amd64.deb
Size: 1234
SHA256: <64-hex>
```
Point `apt` at it (self-signed / airgap: `[trusted=yes]`):
```
deb [trusted=yes] http://holger.example:8081/debrepo stable main
```
then `apt update && apt install hello`.
**RHEL / Fedora / dnf** — the client fetches `repomd.xml`, then `primary.xml`:
```bash
curl -s http://localhost:8081/rpmrepo/repodata/repomd.xml
curl -s http://localhost:8081/rpmrepo/repodata/primary.xml
```
A `<package>` now carries the real epoch and `<format>`:
```xml
<package type="rpm">
<name>zlib</name>
<arch>x86_64</arch>
<version epoch="7" ver="1.3.1" rel="2.fc40"/>
<checksum type="sha256" pkgid="YES">…</checksum>
<summary>zlib compression library</summary>
<url>https://zlib.net</url>
<size package="1234"/>
<location href="Packages/zlib-1.3.1-2.fc40.x86_64.rpm"/>
<format>
<rpm:license>Zlib</rpm:license>
<rpm:provides><rpm:entry name="libz.so.1"/></rpm:provides>
<rpm:requires><rpm:entry name="libc.so.6"/></rpm:requires>
</format>
</package>
```
Add a `.repo` file (airgap: `gpgcheck=0`) and `dnf install zlib` resolves against it.
## Which fields appear now (vs. before)
| Client field | Deb source | RPM source | Before this change |
|---|---|---|---|
| name / version / arch | control stanza → filename | header/filename NEVRA | filename only |
| Depends / Requires | `control.tar.*` `Depends` | header `REQUIRENAME` | absent |
| Maintainer / Vendor | control `Maintainer` | header `VENDOR` | absent |
| Description / Summary | control `Description` | header `SUMMARY` | absent |
| epoch | n/a | header `EPOCH` | `0` |
| license / url / provides / sourcerpm | n/a | header tags | absent |
| Filename / Size / SHA256 | holger, over served bytes | `location` / `size` / `checksum` | already real |
## Supported codecs & limits
- **Deb** `control.tar` members: uncompressed, `.gz`, `.xz`, `.zst`. An unknown codec
(e.g. `.lz4`) simply falls back to filename-derived fields — the index still serves.
- The control tarball is capped at 16 MiB decompressed and the RPM header at
64 MiB / 65 536 tags; a decompression bomb or a lying header degrades to the
filename fallback rather than exhausting memory. Hostile bytes never crash the
server — worst case you get the filename-only stanza you had before.