Gunnar

nordisk/holger

.nornir/deb-rpm-native-metadata-guide.md

# 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.