Saturday, October 20, 2012

Holiday planning by Debian packages

Today I am facing a rather difficult decision. Should I travel to Lauterbrunnen for a week of BASE jumping or should I stay with my friends in Sweden for the last weekend of skydiving this season (with the obligatory party), and try out my brand new wingsuit? Facing this decision, I did what any rational human being would do: I decided to ask the aptitude package manager for help in making this decision.

Setting up a local Debian repository

If you already have a local debian repository, you can skip this, otherwise it's useful to set one up. Install a web server of your choice (I happened to be running lighttpd already) and then install, for example, reprepro. I am not going to elaborate on this, here:s a guide that may be useful: Local Debian Repository.

Creating a debian project

A very simple debian project consists of a debian debian directory with the 3 files "control", "rules" and "changelog", so let's create this.

bjorn@terra:~/src/$ mkdir -p decision-example/debian
bjorn@terra:~/src/$ cd decision-example
bjorn@terra:~/src/decision-example$ touch debian/control
bjorn@terra:~/src/decision-example$ cat debian/rules # create this
#!/usr/bin/make -f

%:
 dh $@
bjorn@terra:~/src/decision-example$ chmod +x debian/rules
bjorn@terra:~/src/decision-example$ cat debian/changelog # create this
decision-example (0.0.0) unstable; urgency=low

  * Initial release. (Closes: #XXXXXX)

 -- Björn Edström   Sat, 20 Oct 2012 11:37:03 +0200

While optional, I am also going to create the "compat" file because it will supress a bunch of warnings when building the package.

bjorn@terra:~/src/decision-example$ cat debian/compat # create this
7

The interesting file is the control file, which specifies one or more debian packages to be built. Just to get this out of the way, we are going to add a source package on top, and a single package representing the decision to go skydiving (this package is called gryttjom becayse my home dropzone is a place called Gryttjom which is north of Stockholm in Sweden):

Source: decision-example
Section: devel
Priority: optional
Maintainer: Root Key
Build-Depends: debhelper (>= 7)
Standards-Version: 3.8.4

Package: decision-gryttjom
Architecture: amd64
Description: Go to gryttjom

Having set this up, we can now build our package(s)

bjorn@terra:~/src/decision-example$ debuild -d -us -uc

Essentially that means to build binary (non-source), ignore dependencies when building, and not signing the package. If successful this will create a bunch of files in the parent directory.

Let's publish this package to our debian repository:

bjorn@terra:~/src$ dput -f -u decision decision-example_0.0.0_amd64.changes
Uploading to decision (via local to localhost):
Successfully uploaded packages.
bjorn@terra:~/src$ sudo /usr/bin/reprepro -b /var/www/debian processincoming decision
Exporting indices...

Given that you have configured your /etc/apt/sources correctly that package will now be available after an aptitude update:

bjorn@terra:~$ aptitude search decision
p   decision-gryttjom            - Go to gryttjom

Because we are going to create a bunch of packages from now on, let us create a build script:

bjorn@terra:~/src$ cat ./decision-example/publish.sh #/bin/bash
sudo rm -r /var/www/debian/{pool,db,dists} # lazy way to delete previous, only do this if you don't have other packages
dput -f -u decision decision-example_0.0.0_amd64.changes
sudo /usr/bin/reprepro -b /var/www/debian processincoming decision
sudo aptitude update

Decisions and dependencies

Aptitude, which is a package manager for debian, has a rather advanced dependency resolver. Specifically, you can use the keywords "Depends", "Conflicts", "Suggests", "Enhances", "Recommends" and a bunch of other to specify relations between packages. This is what we are going to use to make our decision.

On one level, we can specify our problem as "go to gryttjom or go to the alps". So let's create two packages for each option.

Package: decision-gryttjom
Provides: decision-root
Priority: standard
Conflicts: decision-alps
Architecture: amd64
Description: Go to gryttjom

Package: decision-alps
Provides: decision-root
Priority: standard
Conflicts: decision-gryttjom
Architecture: amd64
Description: Stay in the alps

Let us build these packages (debuild step) and publish (using the build script). Now we have:

bjorn@terra:~$ aptitude search decision
p   decision-alps                - Stay in the alps
p   decision-gryttjom            - Go to gryttjom
v   decision-root                -

We have now codified a decision problem as shown:

bjorn@terra:~$ sudo aptitude install decision-root
...
"decision-root" is a virtual package provided by:
  decision-gryttjom decision-alps
You must choose one to install.

bjorn@terra:~$ sudo aptitude install --schedule-only decision-gryttjom decision-alps
bjorn@terra:~$ sudo aptitude install # we scheduled above and execute here to avoid installing the default
...
The following packages are BROKEN:
  decision-alps decision-gryttjom
0 packages upgraded, 2 newly installed, 0 to remove and 1 not upgraded.
Need to get 1,966B of archives. After unpacking 57.3kB will be used.
The following packages have unmet dependencies:
  decision-alps: Conflicts: decision-gryttjom but 0.0.0 is to be installed.
  decision-gryttjom: Conflicts: decision-alps but 0.0.0 is to be installed.
The following actions will resolve these dependencies:

Keep the following packages at their current version:
decision-alps [Not Installed]

Score is 117

Accept this solution? [Y/n/q/?] n

The following actions will resolve these dependencies:

Keep the following packages at their current version:
decision-gryttjom [Not Installed]

Score is 117

Accept this solution? [Y/n/q/?]

What we have done is we have created two packages and a root "virtual" package and specified a relationship between them. Using the excellent debtree utility we can visualize this as follows:


bjorn@terra:~/src$ debtree -R -S decision-alps | dot -T png > decision-example/img/debtree1.png

Above is the dependency graph centered on the decision to go to the Alps. The root decision shows the two options: alps or gryttjom, while the red arrow shows a conflict.

Adding data points, additional decisions

The decision I am making is heavily influenced by weather forecasts. Furthermore, if I go to the Alps I a have the option to either stay in Lauterbrunnen or travel to Mt Brento in Italy to meet up with a friend. So lets create some data points and sub decisions:

Package: decision-gryttjom
Provides: decision-root
Priority: standard
Conflicts: decision-alps
Architecture: amd64
Description: Go to gryttjom

Package: decision-alps
Provides: decision-root
Priority: standard
Conflicts: decision-gryttjom
Suggests: decision-alps-brento, data-good-weather-alps
Architecture: amd64
Description: Stay in the alps

Package: decision-alps-brento
Depends: decision-alps
Architecture: amd64
Description: Go to brento

Package: data-good-weather-alps
Architecture: amd64
Enhances: decision-alps
Description: Good weather in the alps

Package: data-bad-weather-gryttjom
Architecture: amd64
Enhances: decision-alps
Recommends: decision-alps
Description: Bad weather at home

Here I have basically set up a bunch of hints or indications that if the weather forecast for Sweden looks bad then that leans towards going to the Alps. Building and publishing gives us:

bjorn@terra:~$ aptitude search decision
p   decision-alps                   - Stay in the alps
p   decision-alps-brento            - Go to brento
p   decision-gryttjom               - Go to gryttjom
v   decision-root                   -
bjorn@terra:~$ aptitude search ^data- | egrep 'alps|gryttjom'
p   data-bad-weather-gryttjom       - Bad weather at home
p   data-good-weather-alps          - Good weather in the alps


Now, let's say the forecast says it seems to be bad weather in Sweden. Let's see what aptitude says:

bjorn@terra:~$ sudo aptitude install --schedule-only decision-gryttjom decision-alps data-bad-weather-gryttjom

bjorn@terra:~$ sudo aptitude install
...
The following packages are BROKEN:
  decision-alps decision-gryttjom
The following NEW packages will be installed:
  data-bad-weather-gryttjom
0 packages upgraded, 3 newly installed, 0 to remove and 1 not upgraded.
Need to get 3,010B of archives. After unpacking 86.0kB will be used.
The following packages have unmet dependencies:
  decision-alps: Conflicts: decision-gryttjom but 0.0.0 is to be installed.
  decision-gryttjom: Conflicts: decision-alps but 0.0.0 is to be installed.
The following actions will resolve these dependencies:

Keep the following packages at their current version:
decision-gryttjom [Not Installed]

Score is 117

Accept this solution? [Y/n/q/?] n
The following actions will resolve these dependencies:

Keep the following packages at their current version:
decision-alps [Not Installed]

Leave the following dependencies unresolved:
data-bad-weather-gryttjom recommends decision-alps
Score is -83

As can be seen, given the simple dependencies we have set up shows that aptitude prefers BASE jumping if the weather is bad in Sweden.

Conclusion

To not make this article too long and tedious, I will stop here and not add more data points. However, by using Debian packaging "Suggest", "Ehances" and "Recommends" on some other data points (cost/travel overhead of getting to Brento, if my cold develops further, how much money I have etc) you can actually get something useful. As the number of data points increase it is not immediately obvious (as it is in the example above) what to do, and aptitude actually gives some suggestions.

Of course, I do not recommend anyone to base their life on what a Linux package manager says, and for that reason this article is a little bit tounge-in-cheek, and I realize this article is also the result of some procrastination on my part. However, actually formalizing the data points and relations between decisions helped me out, and when I have the data points I have formalized (travel costs, weather forecasts etc) will give aptitude a spin and hear it out.

Cheers,

Björn