Skip to content

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. PUT a .deb and NORA parses its control paragraph server-side (no dpkg-scanpackages needed) and regenerates every affected index, signed with the instance key. A hosted repo can use the flat layout, the structured dists/ 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.

Fetch the signing key, then add the repository. The key is served armored, which signed-by accepts as an .asc file:

Terminal window
sudo mkdir -p /etc/apt/keyrings
curl -fsSL http://nora.example.com:4000/deb/myrepo/pubkey.gpg \
| sudo tee /etc/apt/keyrings/nora.asc >/dev/null

Flat repository — note the trailing ./:

Terminal window
echo "deb [signed-by=/etc/apt/keyrings/nora.asc] http://nora.example.com:4000/deb/myrepo ./" \
| sudo tee /etc/apt/sources.list.d/nora.list

Structured repository — distribution and component, as with a regular mirror:

Terminal window
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.list

Then:

Terminal window
sudo apt update && sudo apt install mypackage

A 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=…].

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.

Terminal window
curl -u user:pass -T mypackage_1.0_amd64.deb \
http://nora.example.com:4000/deb/myrepo/mypackage_1.0_amd64.deb

Structured: 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.

Terminal window
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.

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, DELETE and reindex answer 409.
  • Upstream indexes — dists/, including the upstream’s InRelease / Release.gpg — are served verbatim and refreshed after metadata_ttl seconds (default 300). Verify them with the upstream’s key in signed-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.

nora mirror deb fetches every package of a proxied repo through NORA, so air-gapped clients can install without the upstream:

Terminal window
nora mirror --registry http://nora.example.com:4000 deb --repo debian --dist trixie --component main --arch amd64

Omit --dist for a flat upstream; --component and --arch default to everything listed in the Release file.

config.toml:

[deb]
enabled = true
max_file_size = 1073741824 # 1 GiB
proxy_timeout = 30
metadata_ttl = 300

Environment variables:

VariableDescriptionDefault
NORA_DEB_ENABLEDEnable the Debian/APT registryfalse
NORA_DEB_MAX_FILE_SIZEMax .deb upload size in bytes1073741824 (1 GiB)
NORA_DEB_PROXIESPull-through repos: name=url[|user:pass],…—
NORA_DEB_PROXY_TIMEOUTUpstream timeout in seconds30
NORA_DEB_METADATA_TTLSeconds upstream metadata stays fresh (0 or less = revalidate every pull)300

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.

FeatureStatusNotes
Package upload (PUT)FullControl parsed and validated; indexes regenerated
Package download (GET)FullByte-identical
Delete (DELETE)FullIndexes regenerated; empty distributions removed
Flat layout (deb … ./)FullPackages, Packages.gz, Release at the repo root
Structured layout (dists/)Full?distribution=…&component=… at upload
Signed indexes (InRelease / Release.gpg)FullVerify with signed-by
Public key (pubkey.gpg)FullArmored
Reconcile (POST {repo}/-/reindex)FullHeals out-of-band storage changes
Upstream proxyFull[deb.proxies], read-only, stale-on-error
Offline mirrorFullnora 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)
  • Disabled by default. Set NORA_DEB_ENABLED=true to 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.