Local corporate APT repositories

Vincent Bernat

Dis­tribut­ing soft­ware ef­fi­ciently across your plat­form can be dif­fi­cult. Ev­ery dis­tri­bu­tion comes with a pack­age man­ager which is usu­ally suited for this task. APT can be re­lied upon when us­ing De­bian or a de­rivative.

Un­for­tu­nately, the of­fi­cial repos­i­to­ries may not con­tain ev­ery­thing you need. When you re­quire un­pack­aged soft­ware or more re­cent ver­sions, it is pos­si­ble to set up your own lo­cal repository.

Most of what is pre­sented here was set up for Dai­ly­mo­tion and was greatly in­spired by the work done by Raphaël Pin­son at Orange.

Setting up your repositories#

There are three kinds of repos­i­to­ries you may want to set up:

  1. A dis­tri­bu­tion mir­ror. Such a mir­ror will save band­width, pro­vide faster down­loads and per­ma­nent ac­cess, even when some­one types Google into Google.

  2. A lo­cal repos­i­tory for your own pack­ages with the abil­ity to have a stag­ing zone to test pack­ages on some servers be­fore putting them in pro­duction.

  3. Mir­rors for un­of­fi­cial repos­i­to­ries, like Ubuntu PPA. To avoid un­ex­pected changes, such a repos­i­tory will also get a stag­ing and a pro­duc­tion zone.

Be­fore go­ing fur­ther, it is im­por­tant to un­der­stand what a repos­i­tory is. Let’s il­lus­trate with the fol­low­ing line from my /etc/apt/sources.list:

deb http://ftp.debian.org/debian/ unstable main contrib non-free

In this ex­am­ple, http://ftp.debian.org/debian/ is the repos­i­tory and unstable is the dis­tri­bu­tion. A dis­tri­bu­tion is sub­di­vided into com­po­nents. We have three com­po­nents: main, contrib, and non-free.

To set up repos­i­to­ries, we will use repre­pro. This is not the only so­lu­tion but it has a good bal­ance be­tween ver­sa­til­ity and sim­plic­ity. repre­pro can only han­dle one repos­i­tory. The first choice is about how you will split your pack­ages into repos­i­to­ries, dis­tri­bu­tions, and com­ponents.

Up­date (2019-06)

aptly is an al­ter­na­tive to repre­pro. It han­dles mul­ti­ple repos­i­to­ries. It fea­tures an HTTP API. It can keep older ver­sions of a pack­age. To mir­ror com­plete dis­tri­bu­tions, repre­pro is more per­for­mant but for ev­ery­thing else, I ad­vise you to use aptly.

Here is what matters:

  • A repos­i­tory can­not con­tain two iden­ti­cal pack­ages (same name, ver­sion, and ar­chi­tecture).
  • In­side a com­po­nent, you can only have one ver­sion of a package.
  • Usu­ally, a dis­tri­bu­tion is a sub­set of the ver­sions while a com­po­nent is a sub­set of the pack­ages. For ex­am­ple, in De­bian, with the dis­tri­bu­tion unstable, you choose to get the most re­cent ver­sions while with the com­po­nent main, you choose to get DFSG-free soft­ware only.

If you go for sev­eral repos­i­to­ries, you will have to han­dle sev­eral repre­pro in­stances and won’t be able to eas­ily copy pack­ages from one place to an­other. At Dai­ly­mo­tion, we put ev­ery­thing in the same repos­i­tory but it would also be per­fectly valid to have three repos­itories:

  • one to mir­ror the dis­tribution;
  • one for your lo­cal pack­ages; and
  • one to mir­ror un­of­fi­cial repos­itories.

Here is our tar­get setup:

Local APT repository
A repository with three distributions, including a staging area.

Initial setup#

First, cre­ate a sys­tem user to work with the repos­itories:

$ adduser --system --disabled-password --disabled-login \
>         --home /srv/packages \
>         --group reprepro

All op­er­a­tions should be done with this user only. If you want to set up sev­eral repos­i­to­ries, cre­ate a di­rec­tory for each of them. Each repos­i­tory has these sub­di­rectories:

  • conf/ con­tains the con­fig­u­ra­tion files
  • gpg/ con­tains the GPG stuff to sign the repository1
  • logs/ con­tains the logs
  • www/ con­tains the repos­i­tory that should be ex­ported by the web server

Here is the con­tent of conf/options:

outdir +b/www
logdir +b/logs
gnupghome +b/gpg

Then, you need to cre­ate the GPG key to sign the repository:

$ GNUPGHOME=gpg gpg --gen-key
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 10y
Key expires at mer. 08 nov. 2023 22:30:58 CET
Is this correct? (y/N) y

Real name: Dailymotion Archive Automatic Signing Key
Email address: the-it-operations@dailymotion.com
Comment:
[…]

By set­ting an empty pass­word, you al­low repre­pro to run unat­tended. You will have to dis­trib­ute the pub­lic key of your new repos­i­tory to let APT check the ar­chive sig­na­ture. An easy way is to ship it in some package.

Local mirror of an official distribution#

Let’s start by mir­ror­ing a dis­tri­bu­tion. We want a lo­cal mir­ror of Ubuntu Pre­cise. For this, we need to do two things:

  1. Set up a new dis­tri­bu­tion in conf/distributions.
  2. Con­fig­ure the up­date sources in conf/updates.

Let’s add this block to conf/distributions:

# Ubuntu Precise
Origin: Ubuntu
Label: Ubuntu
Suite: precise
Version: 12.04
Codename: precise
Architectures: i386 amd64
Components: main restricted universe multiverse
UDebComponents: main restricted universe multiverse
Description: Ubuntu Precise 12.04 (with updates and security)
Contents: .gz .bz2
UDebIndices: Packages Release . .gz
Tracking: minimal
Update: - ubuntu-precise ubuntu-precise-updates ubuntu-precise-security
SignWith: yes

This de­fines the precise dis­tri­bu­tion in our repos­i­tory. It con­tains four com­po­nents: main, restricted, universe, and multiverse (like the reg­u­lar dis­tri­bu­tion in of­fi­cial repos­i­tories).

The Update line starts with a dash. This means repre­pro will mark ev­ery­thing as deleted be­fore up­dat­ing with the pro­vided sources. Old pack­ages will not be kept when they are re­moved from Ubuntu. In conf/updates, we de­fine the sources:

# Ubuntu Precise
Name: ubuntu-precise
Method: http://fr.archive.ubuntu.com/ubuntu
Fallback: http://de.archive.ubuntu.com/ubuntu
Suite: precise
Components: main multiverse restricted universe
UDebComponents: main restricted universe multiverse
Architectures: amd64 i386
VerifyRelease: 437D05B5
GetInRelease: no

# Ubuntu Precise Updates
Name: ubuntu-precise-updates
Method: http://fr.archive.ubuntu.com/ubuntu
Fallback: http://de.archive.ubuntu.com/ubuntu
Suite: precise-updates
Components: main restricted universe multiverse
UDebComponents: main restricted universe multiverse
Architectures: amd64 i386
VerifyRelease: 437D05B5
GetInRelease: no

# Ubuntu Precise Security
Name: ubuntu-precise-security
Method: http://fr.archive.ubuntu.com/ubuntu
Fallback: http://de.archive.ubuntu.com/ubuntu
Suite: precise-security
Components: main restricted universe multiverse
UDebComponents: main restricted universe multiverse
Architectures: amd64 i386
VerifyRelease: 437D05B5
GetInRelease: no

The VerifyRelease lines are GPG key fin­ger­print to use to check the re­mote repos­i­tory. The key needs to be im­ported in the lo­cal keyring:

$ gpg --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg \
>     --export 437D05B5 | GNUPGHOME=gpg gpg --import

An­other im­por­tant point is that we merge three dis­tri­bu­tions (precise, precise-updates and precise-security) into a sin­gle dis­tri­bu­tion (precise) in our lo­cal repos­i­tory. This may cause some dif­fi­cul­ties with tools ex­pect­ing the three dis­tri­bu­tions to be avail­able (like the De­bian In­staller2).

Next, you can run repre­pro and ask it to up­date your lo­cal mirror:

$ reprepro update

This will take some time on the first run. You can ex­e­cute this com­mand ev­ery night. repre­pro is not the fastest mir­ror so­lu­tion but it is easy to set up, flex­i­ble, and reliable.

Repository for local packages#

Let’s con­fig­ure the repos­i­tory to ac­cept lo­cal pack­ages. For each of­fi­cial dis­tri­bu­tion (like precise), we will con­fig­ure two dis­tri­butions:

  • precise-staging con­tains pack­ages that have not been fully tested and are not ready to go to pro­duction.
  • precise-prod con­tains pro­duc­tion pack­ages copied from precise-staging.

In our work­flow, pack­ages are in­tro­duced in precise-staging where they can be tested and will be copied to precise-prod when we want them to be avail­able for pro­duc­tion. You can adopt a more com­plex work­flow if you need it. The repre­pro part is quite easy. We add the fol­low­ing blocks into conf/distributions:

# Dailymotion Precise packages (staging)
Origin: Dailymotion # ❸
Label: dm-staging   # ❸
Suite: precise-staging
Codename: precise-staging
Architectures: i386 amd64 source
Components: main role/dns role/database role/web # ❶
Description: Dailymotion Precise staging repository
Contents: .gz .bz2
Tracking: keep
SignWith: yes
NotAutomatic: yes # ❷
Log: packages.dm-precise-staging.log
 --type=dsc email-changes

# Dailymotion Precise packages (prod)
Origin: Dailymotion # ❸
Label: dm-prod      # ❸
Suite: precise-prod
Codename: precise-prod
Architectures: i386 amd64 source
Components: main role/dns role/database role/web # ❶
Description: Dailymotion Precise prod repository
Contents: .gz .bz2
Tracking: keep
SignWith: yes
Log: packages.dm-precise-prod.log

First no­tice we use sev­eral com­po­nents (in ❶):

  • main will con­tain pack­ages that are not spe­cific to a sub­set of the plat­form. If you put a pack­age in main, it should work cor­rectly on any host.
  • role/* are com­po­nents ded­i­cated to a sub­set of the plat­form. For ex­am­ple, in role/dns, we ship a cus­tom ver­sion of BIND.

The stag­ing dis­tri­bu­tion has the NotAutomatic flag (in ❷) which dis­al­lows the pack­age man­ager to in­stall these pack­ages ex­cept if the user ex­plic­itly re­quests it. Just be­low, when a new dsc file is up­loaded, the hook email-changes will be ex­e­cuted. It should be in the conf/ di­rectory.

The Origin and Label lines (in ❸) are quite im­por­tant to be able to de­fine an ex­plicit pol­icy of which pack­ages should be in­stalled. Let’s say we use the fol­low­ing /etc/apt/sources.list file:

# Ubuntu packages
deb http://packages.dm.gg/dailymotion precise main restricted universe multiverse

# Dailymotion packages
deb http://packages.dm.gg/dailymotion precise-prod    main role/dns
deb http://packages.dm.gg/dailymotion precise-staging main role/dns

All servers have the precise-staging dis­tri­bu­tion. We must en­sure we won’t in­stall these pack­ages by mis­take. The NotAutomatic flag is one pos­si­ble safe­guard. We also use a tai­lored /etc/apt/preferences:

Explanation: Dailymotion packages of a specific component should be more preferred
Package: *
Pin: release o=Dailymotion, l=dm-prod, c=role/*
Pin-Priority: 950

Explanation: Dailymotion packages should be preferred
Package: *
Pin: release o=Dailymotion, l=dm-prod
Pin-Priority: 900

Explanation: staging should never be preferred
Package: *
Pin: release o=Dailymotion, l=dm-staging
Pin-Priority: -100

By de­fault, pack­ages will have a pri­or­ity of 500. By set­ting a pri­or­ity of -100 to the stag­ing dis­tri­bu­tion, we en­sure the pack­ages can­not be in­stalled at all. This is stronger than NotAutomatic which sets the pri­or­ity to 1. When a pack­age ex­ists in Ubuntu and our lo­cal repos­i­tory, we en­sure that, if this is a pro­duc­tion pack­age, we will use ours by us­ing a pri­or­ity of 900 (or 950 if we match a spe­cific role com­ponent).

Have a look at the “How APT In­ter­prets Pri­or­i­ties” sec­tion of apt_preferences(5) man­ual page for ad­di­tional in­for­ma­tion. Keep in mind that ver­sion mat­ters only when the pri­or­ity is the same. To check if ev­ery­thing works as you ex­pect, use apt-cache policy:

$ apt-cache policy php5-memcache
  Installed: 3.0.8-1~precise2~dm1
  Candidate: 3.0.8-1~precise2~dm1
  Version table:
 *** 3.0.8-1~precise2~dm1 0
        950 http://packages.dm.gg/dailymotion/ precise-prod/role/web amd64 Packages
        100 /var/lib/dpkg/status
     3.0.8-1~precise1~dm4 0
        900 http://packages.dm.gg/dailymotion/ precise-prod/main amd64 Packages
       -100 http://packages.dm.gg/dailymotion/ precise-staging/main amd64 Packages
     3.0.6-1 0
        500 http://packages.dm.gg/dailymotion/ precise/universe amd64 Packages

If we want to in­stall a pack­age from the stag­ing dis­tri­bu­tion, we can use apt-get with the -t precise-staging op­tion to raise the pri­or­ity of this dis­tri­bu­tion to 990.

Once you have tested your pack­age, you can copy it from the stag­ing dis­tri­bu­tion to the pro­duc­tion dis­tribution:

$ reprepro -C main copysrc precise-prod precise-staging wackadoodle

Local mirror of third-party repositories#

Some­times, you want a piece of soft­ware pub­lished on some third-party repos­i­tory with­out repack­ag­ing it your­self. A com­mon ex­am­ple is the repos­i­to­ries edited by hard­ware ven­dors. As for an Ubuntu mir­ror, there are two steps: defin­ing the dis­tri­bu­tion and defin­ing the source.

We chose to put such mir­rors into the same dis­tri­bu­tions as our lo­cal pack­ages but with a ded­i­cated com­po­nent for each mir­ror. This way, these third-party pack­ages will share the same work­flow as our lo­cal pack­ages: they will ap­pear in the stag­ing dis­tri­bu­tion, we val­i­date them and copy them to the pro­duc­tion dis­tribution.

The first step is to add the com­po­nents and an ap­pro­pri­ate Update line to conf/distributions:

Origin: Dailymotion
Label: dm-staging
Suite: precise-staging
Components: main role/dns role/database role/web vendor/hp
Update: hp
# […]

Origin: Dailymotion
Label: dm-prod
Suite: precise-prod
Components: main role/dns role/database role/web vendor/hp
# […]

We added the vendor/hp com­po­nent to both the stag­ing and the pro­duc­tion dis­tri­bu­tions. How­ever, only the stag­ing dis­tri­bu­tion gets an Update line (re­mem­ber, pack­ages will be copied man­u­ally into the pro­duc­tion dis­tri­bution).

We de­clare the source in conf/updates:

# HP repository
Name: hp
Method: http://downloads.linux.hp.com/SDR/downloads/ManagementComponentPack/
Suite: precise/current
Components: non-free>vendor/hp
Architectures: i386 amd64
VerifyRelease: 2689B887
GetInRelease: no

Don’t for­get to add the GPG key to your lo­cal keyring. No­tice an in­ter­est­ing fea­ture of repre­pro: we copy the re­mote non-free com­po­nent to our lo­cal vendor/hp component.

Then, you can syn­chro­nize the mir­ror with reprepro update. Once the pack­ages have been tested, you will have to copy them in the pro­duc­tion dis­tribution.

Building Debian packages#

Our repre­pro setup seems com­plete, but how do we put pack­ages into the stag­ing dis­tribution?

You have sev­eral op­tions to build De­bian pack­ages for your lo­cal repos­i­tory. It de­pends on how much time you want to in­vest in this activity:

  1. Build pack­ages from source by adding a debian/ di­rec­tory. This is the clas­sic way of build­ing De­bian pack­ages. You can start from scratch or use an ex­ist­ing pack­age as a base. In the lat­ter case, the pack­age can be from the of­fi­cial ar­chive but for a more re­cent dis­tri­bu­tion or a back­port or from an un­of­fi­cial repository.

  2. Use a tool that will cre­ate a bi­nary pack­age from a di­rec­tory, like fpm. Such a tool will try to guess a lot of things to min­i­mize your work. It can even down­load ev­ery­thing for you.

There is no uni­ver­sal so­lu­tion. If you don’t have the time bud­get for build­ing pack­ages from source, have a look at fpm. I would ad­vise you to use the first ap­proach when pos­si­ble be­cause you will get these perks for free:

  • You keep the sources in your repos­i­tory. When­ever you need to re­build some­thing to fix an emer­gency bug, you won’t have to hunt the sources which may be un­avail­able when you need them the most. This only works if you build pack­ages that don’t down­load stuff di­rectly from the Internet.

  • You also keep the recipe3 to build the pack­age in your repos­i­tory. If some­one en­ables some op­tion and re­builds the pack­age, you won’t ac­ci­den­tally drop this op­tion on the next build. These changes can be doc­u­mented in debian/changelog. More­over, you can use a ver­sion con­trol soft­ware for the whole debian/ di­rectory.

  • You can pro­pose your pack­age for in­clu­sion into De­bian. This will help many peo­ple once the pack­age hits the archive.

Up­date (2017-10)

I present an al­ter­nate ap­proach to cre­ate De­bian pack­ages with­out much ef­fort in “Prag­matic De­bian packaging.”

Builders#

We chose pbuilder as a builder.4 Its setup is quite straight­for­ward. Here is our /etc/pbuilderrc:

DISTRIBUTION=$DIST
NAME="$DIST-$ARCH"
MIRRORSITE=http://packages.dm.gg/dailymotion
COMPONENTS=("main" "restricted" "universe" "multiverse")
OTHERMIRROR="deb http://packages.dm.gg/dailymotion ${DIST}-staging main"
HOOKDIR=/etc/pbuilder/hooks.d
BASE=/var/cache/pbuilder/dailymotion
BASETGZ=$BASE/$NAME/base.tgz
BUILDRESULT=$BASE/$NAME/results/
APTCACHE=$BASE/$NAME/aptcache/
DEBBUILDOPTS="-sa"
KEYRING="/usr/share/keyrings/dailymotion-archive.keyring.gpg"
DEBOOTSTRAPOPTS=("--arch" "$ARCH" "--variant=buildd" "${DEBOOTSTRAPOPTS[@]}" "--keyring=$KEYRING")
APTKEYRINGS=("$KEYRING")
EXTRAPACKAGES=("dailymotion-archive-keyring")

pbuilder is ex­pected to be in­voked with DIST, ARCH and op­tion­ally ROLE en­vi­ron­ment vari­ables. Build­ing the ini­tial bases can be done like this:

for ARCH in i386 amd64; do
  for DIST in precise; do
    export ARCH
    export DIST
    pbuilder --create
  done
done

We don’t cre­ate a base for each role. In­stead, we use a D hook to add the ap­pro­pri­ate source:

#!/bin/bash
[ -z "$ROLE" ] || {
  cat >> /etc/apt/sources.list <<EOF
deb http://packages.dm.gg/dailymotion ${DIST}-staging role/${ROLE}
EOF
}

apt-get update

We en­sure pack­ages from our stag­ing dis­tri­bu­tion are pre­ferred over other pack­ages by adding an /etc/apt/preferences file in an E hook:

#!/bin/bash

cat > /etc/apt/preferences <<EOF
Explanation: Dailymotion packages are of higher priority
Package: *
Pin: release o=Dailymotion
Pin-Priority: 900
EOF

We also use a C hook to get a shell in case there is an er­ror. This is con­ve­nient to de­bug a problem:

#!/bin/bash
apt-get install -y --force-yes vim less
cd /tmp/buildd/*/debian/..
/bin/bash < /dev/tty > /dev/tty 2> /dev/tty

A man­ual build can be run with:

$ ARCH=amd64 DIST=precise ROLE=web pbuilder \
>         --build somepackage.dsc

Version numbering#

To avoid ap­ply­ing com­plex rules to choose a ver­sion num­ber for a pack­age, we chose to treat ev­ery­thing as a back­port, even in-house soft­ware. We use the fol­low­ing scheme: X-Y~preciseZ+dmW.

  • X is the up­stream version.5

  • Y is the De­bian ver­sion. If there is no De­bian ver­sion, use 0.

  • Z is the Ubuntu back­port ver­sion. Again, if such a ver­sion doesn’t ex­ist, use 0.

  • W is our ver­sion of the pack­age. We in­cre­ment it when we make a change to the pack­ag­ing. This is the only num­ber we are al­lowed to con­trol. All the oth­ers are set by an up­stream en­tity un­less it doesn’t ex­ist and, in this case, you use 0.

Let’s sup­pose you need to back­port wackadoodle. It is avail­able in a more re­cent ver­sion of Ubuntu as 1.4-3. Your first back­port will be 1.4-3~precise0+dm1. Af­ter a change to the pack­ag­ing, the ver­sion will be 1.4-3~precise0+dm2. A new up­stream ver­sion 1.5 is avail­able and you need it. You will use 1.5-0~precise0+dm1.

Later, this new up­stream ver­sion will be avail­able in some ver­sion of Ubuntu as 1.5-3ubuntu1. You will re­base your changes on this ver­sion and get 1.5-3ubuntu1~precise0+dm1.

When us­ing De­bian in­stead of Ubuntu, a com­pat­i­ble con­ven­tion could be: X-Y~bpo70+Z~dm+W.

Uploading#

To up­load a pack­age, a com­mon setup is the fol­low­ing workflow:

  1. Up­load the source pack­age to an in­com­ing di­rectory.
  2. repre­pro will no­tice the source pack­age, check its cor­rect­ness (sig­na­ture, dis­tri­bu­tion), and put it in the archive.
  3. The builder will no­tice a new pack­age needs to be built and build it.
  4. Once the pack­age is built, the builder will up­load the re­sult to the in­com­ing di­rectory.
  5. repre­pro will no­tice again the new bi­nary pack­age and in­te­grate it into the archive.

This work­flow has the dis­ad­van­tage to have many mov­ing pieces and to leave the user in the dark while the com­pi­la­tion is in progress. In­stead, a build script is used to ex­e­cute each step syn­chronously. The user can fol­low on their ter­mi­nal that ev­ery­thing works as ex­pected. The build script also han­dles up­load­ing the fi­nal pack­ages to the ar­chive with the ap­pro­pri­ate command:

$ reprepro -C main include precise-staging \
>      wackadoodle_1.4-3~precise0+dm4_amd64.changes

Happy hacking!