Debian / APT
NORA serves APT repositories for apt. Each {repo} under /deb/ is an independent repository of one of two kinds:
- Hosted — you publish packages.
PUTa.deband NORA parses its control paragraph server-side (nodpkg-scanpackagesneeded) and regenerates every affected index, signed with the instance key. A hosted repo can use the flat layout, the structureddists/layout, or both — chosen per package at upload. - Pull-through proxy — a repo name mapped to one upstream APT repository in
[deb.proxies]. Read-only; see Upstream proxy.
Client Configuration
Section titled “Client Configuration”Fetch the signing key, then add the repository. The key is served armored, which signed-by accepts as an .asc file:
sudo mkdir -p /etc/apt/keyringscurl -fsSL http://nora.example.com:4000/deb/myrepo/pubkey.gpg \ | sudo tee /etc/apt/keyrings/nora.asc >/dev/nullFlat repository — note the trailing ./:
echo "deb [signed-by=/etc/apt/keyrings/nora.asc] http://nora.example.com:4000/deb/myrepo ./" \ | sudo tee /etc/apt/sources.list.d/nora.listStructured repository — distribution and component, as with a regular mirror:
echo "deb [signed-by=/etc/apt/keyrings/nora.asc] http://nora.example.com:4000/deb/myrepo trixie main" \ | sudo tee /etc/apt/sources.list.d/nora.listThen:
sudo apt update && sudo apt install mypackageA dearmored keyring (gpg --dearmor -o /etc/apt/keyrings/nora.gpg) works as well. With signing disabled (NORA_SIGNING_ENABLED=false), use [trusted=yes] instead of [signed-by=…].
Publishing
Section titled “Publishing”Upload a package to a hosted repo with PUT; the indexes regenerate automatically.
Flat (the default): Packages, Packages.gz, Release, InRelease and Release.gpg at the repo root.
curl -u user:pass -T mypackage_1.0_amd64.deb \ http://nora.example.com:4000/deb/myrepo/mypackage_1.0_amd64.debStructured: add ?distribution= (and optionally &component=, default main). Indexes go under dists/{distribution}/{component}/binary-{arch}/, with one signed Release per distribution. The upload path is free-form; pool/ is a convention, not a requirement.
curl -u user:pass -T mypackage_1.0_amd64.deb \ "http://nora.example.com:4000/deb/myrepo/pool/mypackage_1.0_amd64.deb?distribution=trixie&component=main"Packages with Architecture: all are listed in every architecture index of their distribution (in binary-all when it has no other architecture). DELETE the package path removes it and regenerates the indexes; a distribution left empty is removed. Invalid .deb files are rejected.
If packages were added to or removed from storage out of band, POST /deb/{repo}/-/reindex reconciles the repo: it drops orphan metadata, adopts added packages (into the flat layout), and rebuilds and re-signs the indexes.
Upstream Proxy
Section titled “Upstream Proxy”Map a local repo name to an upstream APT repository (the directory that contains dists/):
[deb]enabled = true
[deb.proxies]debian = "https://deb.debian.org/debian"internal = { url = "https://apt.internal.example.com/debian", auth = "user:pass" }or NORA_DEB_PROXIES="debian=https://deb.debian.org/debian,internal=https://…|user:pass". Clients use deb [signed-by=…] http://nora.example.com:4000/deb/debian trixie main.
- A proxied repo is read-only:
PUT,DELETEand reindex answer409. - Upstream indexes —
dists/, including the upstream’sInRelease/Release.gpg— are served verbatim and refreshed aftermetadata_ttlseconds (default300). Verify them with the upstream’s key insigned-by(e.g. the distribution’s archive keyring), not NORA’s. - Packages are cached forever on first download.
- When the upstream is down, a stale cached copy is served with
x-nora-stale: true.
Offline mirror
Section titled “Offline mirror”nora mirror deb fetches every package of a proxied repo through NORA, so air-gapped clients can install without the upstream:
nora mirror --registry http://nora.example.com:4000 deb --repo debian --dist trixie --component main --arch amd64Omit --dist for a flat upstream; --component and --arch default to everything listed in the Release file.
Configuration
Section titled “Configuration”config.toml:
[deb]enabled = truemax_file_size = 1073741824 # 1 GiBproxy_timeout = 30metadata_ttl = 300Environment variables:
| Variable | Description | Default |
|---|---|---|
NORA_DEB_ENABLED | Enable the Debian/APT registry | false |
NORA_DEB_MAX_FILE_SIZE | Max .deb upload size in bytes | 1073741824 (1 GiB) |
NORA_DEB_PROXIES | Pull-through repos: name=url[|user:pass],… | — |
NORA_DEB_PROXY_TIMEOUT | Upstream timeout in seconds | 30 |
NORA_DEB_METADATA_TTL | Seconds upstream metadata stays fresh (0 or less = revalidate every pull) | 300 |
Signing
Section titled “Signing”Hosted indexes are signed on every regeneration (InRelease clearsigned, Release.gpg detached; per distribution in the structured layout). The armored public key is served at pubkey.gpg in each hosted repo. The key and the NORA_SIGNING_* settings are shared with RPM.
Features
Section titled “Features”| Feature | Status | Notes |
|---|---|---|
Package upload (PUT) | Full | Control parsed and validated; indexes regenerated |
Package download (GET) | Full | Byte-identical |
Delete (DELETE) | Full | Indexes regenerated; empty distributions removed |
Flat layout (deb … ./) | Full | Packages, Packages.gz, Release at the repo root |
Structured layout (dists/) | Full | ?distribution=…&component=… at upload |
Signed indexes (InRelease / Release.gpg) | Full | Verify with signed-by |
Public key (pubkey.gpg) | Full | Armored |
Reconcile (POST {repo}/-/reindex) | Full | Heals out-of-band storage changes |
| Upstream proxy | Full | [deb.proxies], read-only, stale-on-error |
| Offline mirror | Full | nora mirror deb |
| by-hash | — | Not generated; index files are served Cache-Control: no-cache |
| Translations / Contents indexes | — | Not generated for hosted repos (proxied repos pass them through) |
Known Limitations
Section titled “Known Limitations”- Disabled by default. Set
NORA_DEB_ENABLED=trueto activate. - A proxied repo maps to exactly one upstream repository; it cannot be published to.
- Decompression of a package’s
control.tar.{gz,xz,zst}is capped; an oversized control archive is rejected.