Howto debianize Java applications

Thursday, 24th December 2009, 1455hrs

Debian | Maven | Java | Ubuntu

Debianization

When you are working on a Java based project, it is often handy to debianize the resulting application. Using a package installer your application can be easily installed together with its dependencies, it will will be accessible automagically by the all the users of the system and can easily be uninstalled,...

Manually

You could do this debianization manually, but there are many drawbacks when doing so:

  • It would work only for your machine type (e.g. Ubuntu Karmic, or Debian Sid), not for other os types.
  • Extra documentation needs to be provided to the other developers in the team.
  • It would be a very repetitive job.
  • I'm sure errors will be made.
The pkg plugin

When the application is built using Maven the pkg plugin can be used to automate the package building process.

The package builder itself is managed at the Evolvis site. It is created by Tarent, an IT company from Bonn, and is released under GPL version 2.

Prepare

Best to start by adding this plugin prepository to your pom or your settings.xml:

<pluginRepositories>
     <pluginRepository>
           <id>evolvis-release-repository</id>
           <name>evolvis.org release repository</name>
           <url>http://maven-repo.evolvis.org/releases</url>
           <snapshots>
               <enabled>false</enabled>
           </snapshots>
     </pluginRepository>
</pluginRepositories>
Configure

Here is an example of a simple Java application configured for Ubuntu Hardy debianization. These tags should be placed in the <build><plugins> section of your pom. Further down the important tags are explained.

<plugin>
    <groupId>de.tarent.maven.plugins</groupId>
    <artifactId>maven-pkg-plugin</artifactId>
    <version>2.1.4</version>
    <configuration>
         <defaultDistro>ubuntu_hardy</defaultDistro>
         <shortDescription>PPP Monitor</shortDescription>
         <auxPackageMapURL>
               ${project.baseUri}/src/main/package/elevenbits.xml
         </auxPackageMapURL>
         <defaults>
              <distros><string>ubuntu_hardy</string></distros>
              <architecture>all</architecture>
              <section>misc</section>
              <mainClass>com.elevenbits.pppmonitor.PPPMonitor</mainClass>
              <maintainer>
                    ElevenBits &lt;packages@elevenbits.com&gt;
              </maintainer>
              <revision>r6</revision>
              <customCodeUnix>
                    #echo "Updating classpath..."
                    POSTFIX=$${CLASSPATH_ARG#* }
                    PREFIX=/etc/pppmonitor
                    CLASSPATH_ARG="-cp $PREFIX:$POSTFIX"
                    #echo "Classpath: $CLASSPATH_ARG"
               </customCodeUnix>
               <srcSysconfFilesDir>src/main/packager/etc</srcSysconfFilesDir>
               <sysconfFiles>
                    <sysconfFile>
                         <from>log4j.xml</from>
                         <to>pppmonitor</to>
                    </sysconfFile>
                    <sysconfFile>
                         <from>pppmonitor.properties</from>
                         <to>pppmonitor</to>
                    </sysconfFile>
               </sysconfFiles>
               <datadir>
                     /usr/share/pppmonitor/
               </datadir>
               <dataFiles>
                    <dataFile>
                         <from>pppmonitor</from>
                         <to></to>
                    </dataFile>
               </dataFiles>
               <srcAuxFilesDir>src/main/packager/scripts</srcAuxFilesDir>
               <postinstScript>postinst.sh</postinstScript>
               <postrmScript>postrm.sh</postrmScript>
          </defaults>
     </configuration>
</plugin>

Now follows the explanation of the important tags. The pkg web site contains all information on the configuration settings.

<groupId>de.tarent.maven.plugins</groupId>
<artifactId>maven-pkg-plugin</artifactId>
<version>2.1.4</version>

Version 2.1.4 or higher is required, since it allows the installment of pre/post install/remove scripts in the resulting package.

<defaultDistro>ubuntu_hardy</defaultDistro>

The pkg plugin can build packages for distributions like Debian and Ubuntu as well as embedded Linux variants like Maemo and OpenMoko. Additionally it supports the creation of IzPack installers. The defaultDistro tag value can be one of these.

<auxPackageMapURL>
     ${project.baseUri}/src/main/package/elevenbits.xml
</auxPackageMapURL>

The auxPackageMapURL should point to the ElevenBits package map. This package map sets the defaultBinPath to /usr/sbin. That way the generated package will be runnable by root only. You can find more info on the PackageMaps is available at the pkg site. Here is the ElevenBits mapping xml:

<!--
     Package maps document for Ubuntu 8.04 LTS (Hardy Heron) with
     ElevenBits updates.
-->
<package-maps>
     <version>1.0</version>
     <distro>
          <id>ubuntu_hardy</id>
          <label>ElevenBits's Ubuntu Hardy mapping.</label>
          <inherit>ubuntu_gutsy</inherit>
          <defaultBinPath>/usr/sbin</defaultBinPath>
          <map>
               <!-- Add extra mappings here. -->
          </map>
     </distro>
</package-maps>

Lets continue with some other tags:

<architecture>all</architecture>
<section>misc</section>
<mainClass>com.elevenbits.pppmonitor.PPPMonitor</mainClass>

The package will run on all architectures (since it is a Java application), the section in the package list is misc and the main class is com.elevenbits.pppmonitor.PPPMonitor.

<maintainer>
     ElevenBits &lt;packages@elevenbits.com&gt;
</maintainer>

The maintainer will be used when signing the package. This is in detail explained further below.

<customCodeUnix>
      #echo "Updating classpath..."
      POSTFIX=$${CLASSPATH_ARG#* }
      PREFIX=/etc/pppmonitor
      CLASSPATH_ARG="-cp $PREFIX:$POSTFIX"
      #echo "Classpath: $CLASSPATH_ARG"
</customCodeUnix>

When pkg builds a package a script is created using the pom data. At the end of the script the customCodeUnix is added by the plugin. Here is the code generated for this example:

#!/bin/sh
# This file is autogenerated by the maven-pkg-plugin. The source responsible
# for this script can be found in class: de.tarent.maven.plugins.pkg.generator.WrapperScriptGenerator

# You can provide additional VM arguments by setting the VMARGS environment variable.
BOOTCLASSPATH_ARG=""

CLASSPATH_ARG="-cp /usr/share/java/pppmonitor/devices-1.1.0.jar:/usr/share/java/RXTXcomm.jar:/usr/share/java/commons-logging.jar:/usr/share/java/log4j-1.2.jar:/usr/share/java/pppmonitor/mail-1.4.jar:/usr/share/java/activation.jar:/usr/share/java/pppmonitor/jms-1.1.jar:/usr/share/java/pppmonitor/jmxtools-1.2.1.jar:/usr/share/java/pppmonitor/jmxri-1.2.1.jar:/usr/share/java/pppmonitor/rolling-appender-1.0.jar:/usr/share/java/pppmonitor.jar"

MAIN_CLASS=com.elevenbits.pppmonitor.PPPMonitor

# Path of the shared libraries (e.g. for SWT
LIBRARY_PATH_ARG="-Djava.library.path=/usr/lib/jni:/usr/lib"

# Path to BC-ABI compiled classes. Has no effect on runtimes other than GCJ.
DB_PATH_ARG=""

# Additional system properties which are special for this application:
SYSTEM_PROPERTIES=""

# Allows overriding the VM by setting the JAVA environment variable.
if [ x${JAVA} = x ];
then
     JAVA=java
fi

#echo "Updating classpath..."
POSTFIX=${CLASSPATH_ARG#* }
PREFIX=/etc/pppmonitor
CLASSPATH_ARG="-cp $PREFIX:$POSTFIX"
#echo "Classpath: $CLASSPATH_ARG"

exec ${JAVA}  ${VMARGS} ${BOOTCLASSPATH_ARG} ${CLASSPATH_ARG} ${LIBRARY_PATH_ARG} ${DB_PATH_ARG} ${SYSTEM_PROPERTIES} ${MAIN_CLASS} ${@}

To set up the /etc and the related data files you can use these tags:

<srcSysconfFilesDir>src/main/packager/etc</srcSysconfFilesDir>
<sysconfFiles>
     <sysconfFile>
          <from>log4j.xml</from>
          <to>pppmonitor</to>
      </sysconfFile>
      <sysconfFile>
           <from>pppmonitor.properties</from>
           <to>pppmonitor</to>
      </sysconfFile>
</sysconfFiles>
<datadir>
     /usr/share/pppmonitor/
</datadir>
<dataFiles>
     <dataFile>
           <from>pppmonitor</from>
           <to></to>
     </dataFile>
</dataFiles>

The default sysconfDir is /etc. The pppmonitor is a script that will be placed in the /etc/init.d by the postinst.sh. The ppppmonitor script is created with the Linux Standard Base in mind:

#! /bin/sh

### BEGIN INIT INFO
# Provides:             pppmonitor
# Required-Start:       $all
# Required-Stop:
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    The ppp monitor checks the 3G/UMTS state
### END INIT INFO

# Do NOT "set -e"!

PATH=/sbin:/usr/sbin:/bin:/usr/bin
NAME=pppmonitor
DESC="PPP Monitor"
DAEMON=/usr/sbin/pppmonitor
PIDFILE=/var/run/pppmonitor.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x $DAEMON ] || exit 0

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define the LSB start_daemoin, killproc and log_* functions
. /lib/lsb/init-functions

case "$1" in
    start)
         [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
         start_daemon -p $PIDFILE $DAEMON 
         if [ $? -ne 0 ]; then
              [ "$VERBOSE" != no ] && log_end_msg 1 || \
              log_failure_msg "Oops!"
              exit 1
         fi
         [ "$VERBOSE" != no ] && log_end_msg 0
    ;;
    stop)
         [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 
         killproc -p $PIDFILE $DAEMON
         if [ $? -ne 0 ]; then
              [ "$VERBOSE" != no ] && log_end_msg 1
              exit 1
         fi
         [ "$VERBOSE" != no ] && log_end_msg 0
   ;;
   restart)
         log_daemon_msg "Restarting $DESC" "$NAME"
         killproc -p $PIDFILE $DAEMON
         if [ $? -ne 0 ]; then
              log_end_msg 1
              log_failure_msg "Failed to stop $NAME."
              exit 1
         fi
         start_daemon -p $PIDFILE $DAEMON 
         if [ $? -ne 0 ]; then
              log_end_msg 1
              log_failure_msg "Failed to start $NAME."
              exit 1
         fi
         log_end_msg 0
    ;;
    status)
         log_failure_msg "Status is not implemented yet."
    ;;
    *)
         echo "Usage: $SCRIPTNAME {start|stop|restart|status}" >&2
         exit 3
    ;;
esac
exit 0

The last plugin tags define the install and remove scripts:

<srcAuxFilesDir>src/main/packager/scripts</srcAuxFilesDir>
<postinstScript>postinst.sh</postinstScript>
<postrmScript>postrm.sh</postrmScript>

They are placed in the directory set in the srcAuxFilesDir. Here uis the postinst.sh:

echo "Installing $name..."
/bin/cp $datadir/$name /etc/init.d
/bin/chmod +x /etc/init.d/$name
cd /etc/init.d 
/usr/sbin/update-rc.d $name start 95 2 3 4 5 . stop 95 0 1 6 .
echo "Installed $name successfully!"

The postrm.sh:

echo "Removing $name..."
/bin/rm -f /etc/init.d/$name
/usr/sbin/update-rc.d -f $name remove
echo "Removed $name successfully!"

When following this example, do use this code layout for your maven package:

alt code

Execute

The pkg plugin has four goals: config, pkg, sign and deploy. To create a package, use this command:

user@box:~/java/workspace/pppmonitor$ mvn pkg:pkg
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building PPP Monitor
[INFO]    task-segment: [pkg:pkg]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing pkg:pkg
[INFO] [resources:resources {execution: default-resources}]
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources {execution: default-testResources}]
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] Nothing to compile - all classes are up to date
[INFO] [surefire:test {execution: default-test}]
[INFO] Surefire report directory: /home/jw/java/workspace/pppmonitor/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.elevenbits.pppmonitor.command.CurrentOperatorCommandTest
...
[device] [DEBUG] [2009.12.25 13:52:11] com.elevenbits.pppmonitor.command.ATCommand.handleLine(146) | Received empty line, ignore
[device] [DEBUG] [2009.12.25 13:52:11] com.elevenbits.pppmonitor.command.ATCommand.handleLine(148) | Received OK, output complete
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec

Results :

Tests run: 10, Failures: 0, Errors: 0, Skipped: 0

[INFO] [jar:jar {execution: default-jar}]
[INFO] [pkg:pkg {execution: default-cli}]
[INFO] distribution             : ubuntu_hardy
[INFO] package system           : deb
[INFO] default package map      : built-in
[INFO] auxiliary package map    : file:/home/jw/java/workspace/pppmonitor//src/main/packager/elevenbits.xml
[INFO] type of project          : application
[INFO] section                  : misc
[INFO] bundle all dependencies  : no
[INFO] ahead of time compilation: no
[INFO] custom jar libraries     : <none>
[INFO] JNI libraries            : <none>
[INFO] auxiliary file source dir: src/main/packager/scripts
[INFO] auxiliary files          : <none>
[INFO] prefix                   : / (default)
[INFO] sysconf files source dir : src/main/packager/etc
[INFO] sysconfdir               : (default)
[INFO] dataroot files source dir: src/main/auxfiles (default)
[INFO] dataroot                 : (default)
[INFO] data files source dir    : src/main/auxfiles (default)
[INFO] datadir                  : /usr/share/pppmonitor/
[INFO] bindir                   : (default)
[INFO] creating temporary directory: /home/jw/java/workspace/pppmonitor/target/deb-tmp
[INFO] cleaning the temporary directory
[INFO] creating package directory: /home/jw/java/workspace/pppmonitor/target/deb-tmp/pppmonitor-0.0.3-0ubuntu~hardy-r6
[INFO] copying artifact: /home/jw/java/workspace/pppmonitor/target/pppmonitor-0.0.3.jar
[INFO] destination: /home/jw/java/workspace/pppmonitor/target/deb-tmp/pppmonitor-0.0.3-0ubuntu~hardy-r6/usr/share/java/pppmonitor.jar
[INFO] copying sysconf file: /home/jw/java/workspace/pppmonitor/src/main/packager/etc/log4j.xml
[INFO] destination: /home/jw/java/workspace/pppmonitor/target/deb-tmp/pppmonitor-0.0.3-0ubuntu~hardy-r6/etc/pppmonitor
[INFO] copying sysconf file: /home/jw/java/workspace/pppmonitor/src/main/packager/etc/pppmonitor.properties
[INFO] destination: /home/jw/java/workspace/pppmonitor/target/deb-tmp/pppmonitor-0.0.3-0ubuntu~hardy-r6/etc/pppmonitor
[INFO] copying data file: /home/jw/java/workspace/pppmonitor/src/main/packager/scripts/pppmonitor
[INFO] destination: /home/jw/java/workspace/pppmonitor/target/deb-tmp/pppmonitor-.0.3-0ubuntu~hardy-r6/usr/share/pppmonitor
[INFO] resolving dependency artifacts
[WARNING] ubuntu_hardy has no entry for: com.elevenbits:devices:jar:1.1.0:compile
[WARNING] ubuntu_hardy has no entry for: javax.mail:mail:jar:1.4:compile
[WARNING] ubuntu_hardy has no entry for: javax.jms:jms:jar:1.1:compile
[WARNING] ubuntu_hardy has no entry for: com.sun.jdmk:jmxtools:jar:1.2.1:compile
[WARNING] ubuntu_hardy has no entry for: com.sun.jmx:jmxri:jar:1.2.1:compile
[WARNING] ubuntu_hardy has no entry for: org.apache.log4j:rolling-appender:jar:1.0:compile
[INFO] using traditional starter
[INFO] copying 6 dependency artifacts.
[INFO] destination: /home/jw/java/workspace/pppmonitor/target/deb-tmp/pppmonitor-0.0.3-0ubuntu~hardy-r6/usr/share/java/pppmonitor
[INFO] copying artifact: org.apache.log4j:rolling-appender:jar:1.0:compile
[INFO] copying artifact: com.elevenbits:devices:jar:1.1.0:compile
[INFO] copying artifact: com.sun.jmx:jmxri:jar:1.2.1:compile
[INFO] copying artifact: javax.jms:jms:jar:1.1:compile
[INFO] copying artifact: com.sun.jdmk:jmxtools:jar:1.2.1:compile
[INFO] copying artifact: javax.mail:mail:jar:1.4:compile
[WARNING] ubuntu_hardy has no entry for: com.elevenbits:devices:jar:1.1.0:compile
[WARNING] ubuntu_hardy has no entry for: javax.mail:mail:jar:1.4:compile
[WARNING] ubuntu_hardy has no entry for: javax.jms:jms:jar:1.1:compile
[WARNING] ubuntu_hardy has no entry for: com.sun.jdmk:jmxtools:jar:1.2.1:compile
[WARNING] ubuntu_hardy has no entry for: com.sun.jmx:jmxri:jar:1.2.1:compile
[WARNING] ubuntu_hardy has no entry for: org.apache.log4j:rolling-appender:jar:1.0:compile
[INFO] creating control file: /home/jw/java/workspace/pppmonitor/target/deb-tmp/pppmonitor-0.0.3-0ubuntu~hardy-r6/DEBIAN/control
[INFO] calling dpkg-deb to create binary package
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5 seconds
[INFO] Finished at: Fri Dec 25 13:52:13 CET 2009
[INFO] Final Memory: 13M/78M
[INFO] ------------------------------------------------------------------------

You can find the .deb file in the target folder.

To sign and deploy a site to your (private) package list, you can use this command:

user@box:~/java/workspace/pppmonitor$ mvn pkg sign deploy -Dmaven.test.skip -Drepo=<dupload config name>

where the dupload config name is the name linking to your packaging system as specified in the /etc/dupload.conf file. The dupload tool is a utility to upload Debian packages. See here for an example and information regarding this command.