petrhurtak.com site logopetrhurtak.com

Debian packages and front-end apps at Seznam.cz

At Seznam.cz we use Debian packages for packaging and distribution of a decent amount of our applications. While we are in the process of transitioning to solutions using Docker, Debian packages still play a significant role.

In this article, I will tell you the basics about how to make a simple Debian package with a static website. I wrote this article from the frontend developer point of view, so not all parts of Debian packaging are covered.

Basics

  • Create a directory named debian in the root of your project.
  • We will use debhelper which is a collection of small tools that are used to automate various aspects of building a Debian package.
  • Add DEBEMAIL and DEBFULLNAME environment variables to your.bashrc to have your name and email automatically filled when you will manipulate the changelog with the dch.
export DEBFULLNAME="Forename Surname"
export DEBEMAIL="name@email.com"
  • You will need debhelper and dch. These tools are in build-essential and devscripts packages.
  • apt-get install build-essential devscripts

Folder structure

Inside the debian folder you will need to create these five files:

FilenameDescription
changelogChanges and release dates of each version of your package.
compatDefines the debhelper compatibility level.
controlMetadata about the package, like its name, maintainers or dependencies.
rulesSpecifies how to build the package.
package-name.installWhat files to include in the package.

Changelog file

Create debian/changelog file, either by running dch --create or manually.

package-name (0.0.1) UNRELEASED; urgency=medium

  * Initial release.

-- Forename Surname <name@email.com>  Mon, 15 May 2017 20:00:00 +0200

Working with the changelog

CommandDescription
dch --createCreates new changelog file with Initial release entry.
dchAdds new version if the latest one is not UNRELEASED. Otherwise, it adds a new item to the current version and updates the date.
dch -rChanges from UNRELEASED to released state and updates the date.
Just use your editorWorks fine most of the time.

At Seznam.cz when we release a package we do not use the official distributions, instead we do something like dch -r --force-distribution --distribution Seznam.

Compat file

The compat file defines the debhelper compatibility level. Currently, you should set it to the debhelper v9.

echo 9 > debian/compat

Control file

  • In this file, we specify all the metadata about our packages, like their names, dependencies, and maintainers.
  • There are two types of packages:
    • Source releases: contain a human readable version of the application, meaning they have to be compiled before they can be used.
    • Binary releases: include computer readable version of the app, meaning they are already compiled.
  • For our purposes, we will only build the binary package, but fields for the source release also need to be specified.

Minimal control file

Source: package-name
Maintainer: Forename Surname <name@email.com>
Section: fulltext/Seznam
Priority: extra
Build-Depends: debhelper (>= 9.0.0)
Standards-Version: 3.9.8

Package: package-name
Architecture: all
Section: fulltext/Seznam
Priority: extra
Depends: ${misc:Depends}
Description: Package description

Source package fields

FieldTypeDescription
SourcemandatoryThe source package name. Must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.).
package-name
MantainermandatoryThe package maintainer’s name and email address.
Forename Surname <name@email.com>.
SectionrecommendedAn application area into which the package has been classified.
At Seznam.cz we use something like fulltext/Seznam.
PriorityrecommendedHow important it is that the user have the package installed. The priorities are (in descending order): required, important, standard, optional, extra.
Build-DependsoptionalWhat dependencies need to be installed before we can start building the package.
Standards-VersionrecommendedThe most recent version of the Debian Policy Manual with which the package complies. You can find the lates version at the bottom of every Debian policy page.

Binary package fields

FieldTypeDescription
PackagemandatoryName of the binary package. Binary package names must follow the same syntax and restrictions as source package names.
ArchitecturemandatoryUsually one of the following:
all - Architecture independent, usually consisting of text, images, or scripts in an interpreted language.
any - Architecture dependent, usually in a compiled language.
SectionrecommendedSame as Section of source package.
PriorityrecommendedSame as Priority of source package.
DependsoptionalDependencies of the package. Some debhelper commands may cause the generated package to depend on some additional packages. All such commands produce a list of required packages for each binary package. This list will then substitute the ${misc:Depends} string.
DescriptionmandatoryDescription.

Rules file

  • Create debian/rules. This file is Makefile so make sure to:
    • Mark it as executable chmod u+x debian/rules.
    • Use only tabs for indentation.
  • This file contains rules about how to build your package. We will be using the dh command from the debhelper package to help us with the build. Here is minimal rules file that only calls the dh without any other configuration.
#!/usr/bin/make -f

%:
  dh $@
  • The dh command has many build stages, and you will see them mentioned in the console once you start building your package. If you need to override or extend parts of the dh build steps, here is how you do it:
#!/usr/bin/make -f

%:
  dh $@

override_dh_auto_build:
  dh_auto_build # We can omit this call and only use our scripts
  ./my-script

Debian install script

  • Create file named debian/package-name.install.
  • In this file, we specify what files or folders will be copied into the package. Then once the package is installed, these files will be copied from the package onto the filesystem.
  • Directories are relative to the parent of debian directory, for example, if you have project/debian/package-name.install, paths will be relative to the project dir.
dist/* /www/package-name/

Build the package

  • In the root of your project directory run dpkg-buildpackage -b -uc.
    • -b only make a binary package and do not create a package with source files.
    • -uc skip the signing stage of the .changes file with your PGP key.
  • If everything went ok, you should see file named package-name_1.0.0_all.deb one level up from the root directory of your project.

Build artefacts

After the build, you will see some build artifacts inside the debian directory. They are useful for your build debugging, but if everything goes ok, you can safely remove them with something like:

git clean -df debian

Inspecting the package

  • To get basic metadata about package (mostly stuff from debian/control)
    • dpkg --info <file>.deb
  • Midnight Commander program is pretty helpful if we want to take a look inside of the package
    • sudo apt-get install mc
    • Press F3 or the View command while having the package selected to see basic info.
    • Or press enter to open the package and inspect its content.
  • Trying to install the package is also useful to see if there are not any conflicts or dependency problems
    • sudo dpkg -i <file>.deb

Static files build dependencies

So far we have only covered how to take already existing dist directory and package it up, but how do we express dependencies needed for building the dist directory? I think there are two reasonable approaches.

Express everything in the debian/control and debian/rules

In case we would want to express the static files build dependencies and the build itself in the debian directory, here is how it could look like.

Control file:

  • From Build-Depends: debhelper (>= 9.0.0).
  • To Build-Depends: debhelper (>= 9.0.0), nodejs (>= 6.0.0) (or whatever dependencis you need and can install with apt).

Rules file:

  • Add an override to the build step where we will do the installation of additional dependencies from other package managers and also the building itself.
#!/usr/bin/make -f

%:
  dh $@

override_dh_auto_build:
  cd src && npm install && ./build-script

CI Build pipeline

If we have some CI build pipeline, it makes more sense to have one CI task for the build of the static files, and another one for the creation of the Debian package. Then we just pass the build artifacts from the build task to the package task. This will allow use to use the right Docker image for given task (e.g. node:7 for the static files, and debian:8 for the packaging) and worry less about installing additional dependencies.

Example of GitLab CI file

stages:
  - build
  - package

build:
  stage: build
  image: node:7.8.0
  script:
    - cd app
    - npm install
    - ./scripts/build
  artifacts:
    expire_in: "1 week"
    paths:
      - ./app/build

package:
  stage: package
  image: debian:8.8
  script:
    - apt-get update
    - apt-get install --yes build-essential devscripts
    - dpkg-buildpackage -b -uc
    - mv ../*.deb .
  artifacts:
    expire_in: "1 week"
    paths:
      - ./*.deb

Links