Building Kura - #2

TL;DR: OLinuXino branch

Last time, we took a tour of the Eclipse Kura build system, building Kura for the raspberry pi 2, and also the user workspace archive (dev-env).

Now its time to look into the guts of the installer and figure out what needs to change to build the 'port' for the OLinuXino. Let's start in the kura/kura/distrib/target directory where we have the built distribution for the pi 2.

First of all, let's look at the .deb for some insight.
A deb archive has two parts; a data part for programs and resources, and a control part for (un)installation scripts and package information. We'll extract both of these parts:

$ mkdir -p extracted/control
$ mkdir -p extracted/data
$ dpkg -e kura_2.0.0-SNAPSHOT_raspberry-pi-2_installer.deb extracted/control
$ dpkg -x kura_2.0.0-SNAPSHOT_raspberry-pi-2_installer.deb extracted/data
$ cd extracted
$ tree
.
├── control
│   ├── control
│   ├── md5sums
│   ├── postinst
│   ├── preinst
│   └── prerm
└── data
    └── tmp
        └── kura_2.0.0-SNAPSHOT_raspberry-pi-2.zip

3 directories, 6 files  

The preinst and postinst files in the control directory should give us an idea about how this is supposed to work.
First, preinst:

$ cat control/preinst 
#!/bin/bash

INSTALL_DIR=/opt/eclipse

##############################################
# PRE-INSTALL SCRIPT
##############################################
PIDS=`pgrep java`

echo ""  
echo "Installing Kura..."  
echo "Installing Kura..." > /tmp/kura_install.log 2>&1

#Kill JVM and monit for installation
if [ -f "/var/run/kura.pid" ] ; then  
  KURA_PID=`cat /var/run/kura.pid`

  for pid in "${PIDS[@]}"
  do
    if [ "$KURA_PID" == "$pid" ] ; then
      `kill $pid` >> /tmp/kura_install.log 2>&1
    fi
  done
fi

#clean up old installation if present
rm -fr /opt/eclipse/kura* >> /tmp/kura_install.log 2>&1  
rm -fr ${INSTALL_DIR}/kura* >> /tmp/kura_install.log 2>&1  
rm -fr /tmp/.kura/ >> /tmp/kura_install.log 2>&1  
rm /etc/init.d/firewall >> /tmp/kura_install.log 2>&1  
rm /etc/dhcpd-*.conf >> /tmp/kura_install.log 2>&1  
rm /etc/named.conf >> /tmp/kura_install.log 2>&1  
rm /etc/wpa_supplicant.conf >> /tmp/kura_install.log 2>&1  
rm /etc/hostapd.conf >> /tmp/kura_install.log 2>&1  
rm /tmp/coninfo-* >> /tmp/kura_install.log 2>&1  
rm /var/log/kura.log >> /tmp/kura_install.log 2>&1  
rm -fr /etc/ppp/chat >> /tmp/kura_install.log 2>&1  
rm -fr /etc/ppp/peers >> /tmp/kura_install.log 2>&1  
rm -fr /etc/ppp/scripts >> /tmp/kura_install.log 2>&1  
rm /etc/ppp/*ap-secrets >> /tmp/kura_install.log 2>&1  
rm -f kura-*.zip >> /tmp/kura_install.log 2>&1  
rm -f kura_*.zip >> /tmp/kura_install.log 2>&1

echo ""  
##############################################
# END PRE-INSTALL SCRIPT
##############################################

This all looks fairly straightforward, and we see references to some programs which kura uses here like named, hostapd and wpa_supplicant. It's worth noting that kura takes control of these things, and in doing so, it wipes out whatever was already there.

Remembering the deb's file structure, we can see that a zip file was present in the data area, under a directory 'tmp'. dpkg maps this onto the filesystem and, after running the preinst script above, will copy this file into tmp.

It will then proceed to run the postinst script from the control area:

$ cat control/postinst
#!/bin/bash

INSTALL_DIR=/opt/eclipse  
TIMESTAMP=`date +%Y%m%d%H%M%S`  
LOG=/tmp/kura_install_${TIMESTAMP}.log

##############################################
# POST INSTALL SCRIPT
##############################################
mkdir -p ${INSTALL_DIR} >> ${LOG} 2>&1  
unzip /tmp/kura_*.zip -d ${INSTALL_DIR} >> ${LOG} 2>&1

#install KURA files
sh ${INSTALL_DIR}/kura_*/install/kura_install.sh >> ${LOG} 2>&1

#clean up
rm -rf ${INSTALL_DIR}/kura/install >> ${LOG} 2>&1  
rm /tmp/kura_*.zip >> ${LOG} 2>&1

#move the log file
mkdir -p ${INSTALL_DIR}/kura/log  
mv ${LOG} ${INSTALL_DIR}/kura/log/

#flush all cached filesystem to disk
sync

echo ""  
echo "Finished. KURA has been installed to ${INSTALL_DIR}/kura and will start automatically after a reboot"  
exit 0  
#############################################
# END POST INSTALL SCRIPT
##############################################

Again, fairly straightforward. It just takes the zip that the install process copied into /tmp, and extracts it to /opt/eclipse/kura_SOMEVERSION/ . It then runs an inner kura_install.sh script, copies the install log to somewhere in there and finishes.

So, the deb doesn't do anything fancy or platform-dependent. It just basically wraps the kura_install.sh script, which is presumably where any real magic happens. There's probably nothing here which we'll ever have to change.

Anyway, let's get a look inside that zip file and at that install script:

$ unzip kura_2.0.0-SNAPSHOT_raspberry-pi-2.zip -d unzipped
Archive:  kura_2.0.0-SNAPSHOT_raspberry-pi-2.zip  
   creating: unzipped/kura_2.0.0-SNAPSHOT_raspberry-pi-2/
   creating: unzipped/kura_2.0.0-SNAPSHOT_raspberry-pi-2/kura/
...
$ cat kura_install.sh 
#!/bin/sh
#
...

This is definitely where the important stuff is happening. First up, a symlink is created, /opt/eclipse/kura, which points to the kura install directory, /opt/eclipse/kura_SOMEVERSION/.
The script then copies an init script to /etc/init.d/kura and makes it executable. The presence of 'raspbian' in a few places indicates that this file is probably pulled from some directory specific to the raspberry pi, the beaglebone and other targets having their own directory and scripts.

The rest of the file does the following, roughly:

  • Install a new /etc/network/interfaces file
  • Install some new ifup and ifdown hook scripts
  • Overwrite the standard firewall init script with a kura one
  • Build a new hostapd conf, generating an SSID based on the MAC of eth0 (the eth0 MAC is a common device-identifier in kura)
  • Configure the dhcp server (dhcpd) to run on eth0 and wlan0
  • Install a DNS server (bind) configuration
  • Install a preliminary network configuration for kura (kuranet.conf)
  • Add startup links for Kura and the firewall
  • Install a new logrotate configuration for Kura's logs.

The files referenced (and where they end up) are:

kura.init.raspbian -> /etc/init.d/kura  
network.interfaces.raspbian -> /etc/network/interfaces  
ifup-local.raspbian -> /etc/network/if-up.d/ifup-local  
ifdown-local -> /etc/network/if-up.d/ifdown-local  
recover_default_config.init -> /opt/eclipse/kura/.data/.recoverDefaultConfig  
firewall.init -> /etc/init.d/firewall  
iptables.init -> /etc/sysconfig/iptables,/opt/eclipse/kura/.data/iptables  
hostapd.conf -> /etc/hostapd-wlan0.conf,/opt/eclipse/kura/.data/hostapd-wlan0.conf  
dhcpd-eth0.conf -> /etc/dhcpd-eth0.conf,/opt/eclipse/kura/.data/dhcpd-eth0.conf  
dhcpd-wlan0.conf -> /etc/dhcpd-wlan0.conf,/opt/eclipse/kura/.data/dhcpd-wlan0.conf  
named.conf -> /etc/bind/named.conf  
named.ca -> /var/named/named.ca  
named.rfc1912.zones -> /etc/named.rfc1912.zones  
usr.sbin.named -> /etc/apparmor.d  
logrotate.conf -> /etc/logrotate.conf  
kura.logrotate -> /etc/logrotate.d/kura  
monitrc.raspbian -> /etc/monit/conf.d/monitrc.raspbian  

The kura install process is certainly pretty invasive, and should probably be considered a 1-way operation.

Now is a good time to find where that install script was pulled from. In the distrib directory, a 'find' gives us the answer:

$ find -name kura_install.sh
...
...
./src/main/resources/raspberry-pi-nn/kura_install.sh
./src/main/resources/raspberry-pi-2-nn/kura_install.sh
./src/main/resources/intel-edison/kura_install.sh
./src/main/resources/beaglebone/kura_install.sh
./src/main/resources/raspberry-pi-bplus-nn/kura_install.sh
...

To src/main/resources we go. In there, we find a directory for each platform:

$ cd src/main/resources/
$ ls
beaglebone     common        intel-edison-nn  raspberry-pi-2     raspberry-pi-bplus     raspberry-pi-nn  
beaglebone-nn  intel-edison  raspberry-pi     raspberry-pi-2-nn  raspberry-pi-bplus-nn  

Let's copy raspberry-pi-2 to make a new directory for our target:

$ cp -r raspberry-pi-2 olinuxino
$ cd olinuxino
$ ls
firewall.init  jdk.dio.properties  kuranet.conf     kura_upgrade.sh   recover_default_config.init  
iptables.init  kura_install.sh     kura.properties  log4j.properties  snapshot_0.xml  

These certainly aren't all of the files referenced from the install script, but if we look back, we see a 'common' directory. This appears to be something of a 'misc' directory for stuff which is common to multiple platforms. For example, there's an ifup-local.raspbian, which is used for all of the raspberry pi targets.

We now begin the process of olinuxino-ifying the target directory we made. A recursive grep can probably help with that:

 # (in src/main/resources/olinuxino)
$ rgrep raspbian .
kura_upgrade.sh:cp ${INSTALL_DIR}/kura/install/ifup-local.raspbian /etc/network/if-up.d/ifup-local  
kura_install.sh:cp ${INSTALL_DIR}/kura/install/kura.init.raspbian /etc/init.d/kura  
kura_install.sh:cp ${INSTALL_DIR}/kura/install/network.interfaces.raspbian /etc/network/interfaces  
kura_install.sh:cp ${INSTALL_DIR}/kura/install/network.interfaces.raspbian ${INSTALL_DIR}/kura/.data/interfaces  
kura_install.sh:cp ${INSTALL_DIR}/kura/install/ifup-local.raspbian /etc/network/if-up.d/ifup-local  
kura_install.sh:    cp ${INSTALL_DIR}/kura/install/monitrc.raspbian /etc/monit/conf.d/  

A sed can fix those:

$ find . -exec sed -i 's/raspbian/olinuxino/g' '{}' ';'
sed: couldn't edit .: not a regular file  

Now we go through all of the files we saw in the grep output (they are all in the 'common' directory), copying them to make a '.olinuxino' variant, and checking inside to ensure that there's nothing that won't play nicely. It's useful here to diff the file in question with other variants, to get some tips about what might need to change.

That's done, and it doesn't look like there will be any issues with the 'common' files. In fact, it looks like there are only trivial differences between the variants already there. The process would definitely benefit from some refactoring. One problem at a time; let's move on.

Back in the olinuxino platform directory, there are a couple of files which need editing:

$ rgrep -i rasp
kura.properties:kura.project=Raspberry-Pi  
kura.properties:kura.platform=Raspberry-Pi  
kura.properties:kura.device.name=Raspberry-Pi  
kura.properties:kura.model.id=Raspberry-Pi  
kura.properties:kura.model.name=Raspberry-Pi  
kura.properties:kura.partNumber=Raspberry-Pi  
kura.properties:kura.serialNumber=Raspberry-Pi  
snapshot_0.xml:                <esf:value>Raspberry Pi 2</esf:value>  

The kura.properties lines mostly just affect what the user sees.
The snapshot_0.xml file is the initial configuration for all components in kura.
On first-run, kura expects to find this file and will assume whatever configuration is in it. It will then write back an encrypted version of the snapshot, with the current UNIX time in place of '0'. Kura will also parse configuration from other places at this point, including the firewall, hostapd (wifi AP), ppp (cellular) and dhcp server.

After those files are edited, it doesn't look like there's anything left to do for now. Back to the distrib directory, to figure out how those files get into the folder (which goes into the zip (which goes into the deb (which goes into the pi (now hear the word of the lord?)))).

 # (in distrib)
$ cd src
$ rgrep -i rasp
main/ant/build_equinox_distrib.xml:        <copy file="src/main/resources/common/kura.init.raspbian"  
main/ant/build_equinox_distrib.xml:              tofile="${project.build.directory}/${build.output.name}/kura.init.raspbian"  
main/ant/build_equinox_distrib.xml:        <replaceregexp file="${project.build.directory}/${build.output.name}/kura.init.raspbian" match="INSTALL_DIR=.*" replace="INSTALL_DIR=${kura.install.dir}" />  
main/ant/build_equinox_distrib.xml:            <zipfileset file="${project.build.directory}/${build.output.name}/kura.init.raspbian"  
main/ant/build_equinox_distrib.xml:            <zipfileset file="src/main/resources/common/monit.init.raspbian"  
main/ant/build_equinox_distrib.xml:            <zipfileset file="src/main/resources/common/monitrc.raspbian"  
main/ant/build_equinox_distrib.xml:            <zipfileset file="src/main/resources/common/network.interfaces.raspbian"  
main/ant/build_equinox_distrib.xml:            <zipfileset file="src/main/resources/common/ifup-local.raspbian"  
main/resources/raspberry-pi-nn/kura.properties:kura.project=raspberry-pi-nn  
...
...
main/deb/control/control:Depends: [[project.raspbian.dependencies]]  
main/deb/control_nn/control:Depends: [[project.raspbian.dependencies.nn]]  

From here, that ant script, build_equinox_distrib.xml, looks like it might be what pulls all the files together. It also looks like we found where the .deb control scripts come from, at the end there.
They aren't platform-specific; I'm guessing the 'raspbian' name is because the pi was (before edison/beaglebone) the only device kura would build a deb for.

To the ant script:

$ cat main/ant/build_equinox_distrib.xml
...
        <target name="dist-linux">

                <property name="build.output.name" value="kura_${project.version}_${build.name}"/>
...
                <!-- Populate parameters -->
                <copy file="src/main/resources/${build.name}/kura_install.sh"
              tofile="${project.build.directory}/${build.output.name}/kura_install.sh" />
                <copy file="src/main/resources/common/kura.init.wrl"
              tofile="${project.build.directory}/${build.output.name}/kura.init.wrl"
              failonerror="false" />
                <copy file="src/main/resources/common/kura.init.yocto"
              tofile="${project.build.directory}/${build.output.name}/kura.init.yocto"
              failonerror="false" />
                <copy file="src/main/resources/common/kura.init.raspbian"
              tofile="${project.build.directory}/${build.output.name}/kura.init.raspbian"
                  failonerror="false" />
                <copy file="src/main/resources/common/kura.service.edison"
              tofile="${project.build.directory}/${build.output.name}/kura.service.edison"
                <replaceregexp file="${project.build.directory}/${build.output.name}/kura_install.sh" match="INSTALL_DIR=.*" replace="INSTALL_DIR=${kura.install.dir}" />
                <replaceregexp file="${project.build.directory}/${build.output.name}/kura.init.wrl" match="INSTALL_DIR=.*" replace="INSTALL_DIR=${kura.install.dir}" />
                <zip destfile="${project.build.directory}/${build.output.name}.zip">
                        <zipfileset file="${project.build.directory}/${build.output.name}/snapshot_0.xml"
                        prefix="${build.output.name}/data/snapshots/" />

                        <zipfileset file="${project.build.directory}/${build.output.name}/config.ini"
...
...

It looks like we found what generates the zip!
The relevant stuff that seems to be happening here is:

  • Copy some of the 'common' scripts to a build directory, and use a regex to set the INSTALL_DIR=XX line to say whatever kura.install.dir says.
  • Build a zip file with these, and other files from both the common directory and the 'build.output.name' directory, which is going to be our target name like 'raspberry-pi-2'.

As with most real/production software, you can definitely see the somewhat organic growth which has occurred. It really feels like it could use a tidy, but for now let's go full steam ahead and work with what's we've got. All that needed to be done here was to follow the raspbian/edison ant directives and add in an olinuxino variant to do the same things with the new files.

I feel we're nearing the end now. Our final port of call will probably be the pom.xml in distrib, which must call this ant script. Let's just confirm that:

 # (in distrib)
$ grep build_equinox_distrib.xml pom.xml
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml" target="dist-linux" />
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml" target="dist-linux" />
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"
                                        <ant antfile="${basedir}/src/main/ant/build_equinox_distrib.xml"

Taking a look inside, this seems to be correct, and it's a simple case of adding a new 'profile' block for olinuxino based on the raspberry-pi-2 one.

Now it's time to build the new profile!
We'll go back to the kura directory and build with pom_pom.xml as detailed in the previous post. This time, we activate the new 'olinuxino' profile:

 # (in kura)
$ mvn -Dmaven.test.skip=true -f pom_pom.xml -Pweb,olinuxino install  

Lots of maven noise and 2.5 minutes later, we have some files waiting for us:

$ ls -l distrib/target | grep olinuxino
...
drwxr-xr-x  3 lee lee     4096 May 18 00:23 kura_2.0.0-SNAPSHOT_olinuxino  
...
-rw-r--r--  1 lee lee 48921460 May 18 00:23 kura_2.0.0-SNAPSHOT_olinuxino_installer.deb
...
-rwxr-xr-x  1 lee lee 48914806 May 18 00:23 kura_2.0.0-SNAPSHOT_olinuxino_installer.sh
...
-rw-r--r--  1 lee lee 48935593 May 18 00:23 kura_2.0.0-SNAPSHOT_olinuxino_upgrader.dp
...
-rwxr-xr-x  1 lee lee 48917302 May 18 00:23 kura_2.0.0-SNAPSHOT_olinuxino_upgrader.sh
...
-rw-r--r--  1 lee lee 48930055 May 18 00:23 kura_2.0.0-SNAPSHOT_olinuxino_upgrade.zip
-rw-r--r--  1 lee lee 48934358 May 18 00:23 kura_2.0.0-SNAPSHOT_olinuxino.zip
...

That looks like progress! Next, to test it on the real thing.
After backing up my OS, that is ;) .

Changes on github

Note: reviewing changesets by hand is crucial in any case. In doing this here, we see that some additional work is actually required for the jdk.dio.properties file. This file contains a declaration for each GPIO pin which is exposed to through the OpenJDK DIO library