Turning a Raspberry Pi into a UniFi controller appliance (UniFi 4, Raspbian Jessie, Oracle Java 8) en

By ERIKvanPAASSEN on Tuesday 29 December 2015 22:09 - Comments (50)
Category: UniFi, Views: 49.085

In March 2014, I wrote a blog post about running the controller software for Ubiquiti UniFi access points on a Raspberry Pi. This was just after the first hard-float version of the Oracle Java 7 JDK for Raspbian was released and running the UniFi controller on Linux was not officially supported yet. The blog posts was one of the very first available on this topic. I was surprised by the amount of reactions I got, as I considered the setup to be just a proof of concept.

However, after months of service, my Raspberry Pi still runs stable. But over the time, a lot of things have changed. The original tutorial used version 2 of the controller software, while the most recent version available today is 4. With Raspbian Jessie, MongoDB became available from the repositories. The Oracle Java 8 JDK has been released. And so on. After all, I decided it was time to rewrite my tutorial and here it is.

http://static.tweakers.net/ext/f/KON6T2YwLiY7CSKN4sJmjkN4/full.png
Screenshot of the UniFi controller web interface.


I'm not providing ready-to-go images as I think it's good to know how everything is set up when problems might arise. I'll try to explain every step carefully, but just copy-pasting all commands should already be enough to yield a working controller though.


Prerequisites

  • Raspberry Pi with at least 512 MB RAM. For example: RPi 1 model B rev. 2, RPi 1 model B+ or RPi 2 model B.
  • A compatible SD card. I'd recommend using a large card (16 GB) to avoid excessive write wear on it.

Necessary steps

We'll need to take the following steps in order to get your UniFi controller up and running.
  1. Setup and configure the Raspbian OS
  2. Install the dependencies
  3. Install the UniFi controller
  4. Modify the start script in order to use Oracle Java 8 instead of OpenJDK 7
  5. Configure the controller to run as its own user instead of as root
  6. Running the controller


Raspbian setup

The first step in the process will be getting your Pi up and running with Raspbian Jessie. You can download an image on the Raspbian downloads page. I'll be using the Lite image, a minimal image wihout graphical user interface, as that's just what we need for a headless device. If you don't know how to write the image to your SD card, I'd recommend you to take a look at the wiki page on this topic. After you've flashed Raspbian to the card, it's time to boot up your Pi and log in with username pi and password raspberry. If you like, you can also log in through SSH, which is enabled out of the box.

After the image has been flashed, we still need to expand the filesystem to the full size of the SD card to be able to use all the space provided by the card. To do so, run:
sudo raspi-config

Choose Expand Filesystem to adjust the filesystem size. While we're in this neat little configuration tool, you might also want to adjust the amount of RAM assigned to the graphics adapter. You can do so by choosing Advanced Options > Memory split. As we're running headless, 16MB would be more than enough. Reboot the device for the changes to take effect.

You may also want to configure the timezone you're living in, using:
sudo dpkg-reconfigure tzdata

We'll also want to make sure all packages are up to date.
sudo apt-get update && sudo apt-get upgrade




Installing the dependencies

We will be using the unifi package from the official Debian repository of UBNT. It has two main dependencies: MongoDB and a Java Virtual Machine. This time, MongoDB is an easy one: it is available in the Raspbian repositories and will be installed automatically when the unifi package is installed.

The unifi depends on OpenJDK 7 to provide the Java Virtual Machine. That's fine on x86/amd64 platforms, but on a Raspberry Pi this would is not optimal. The performance of OpenJDK JVM is low compared to the Oracle JVM, because it uses soft-floats instead of hard-floats. We'll need to install the Oracle JDK:
sudo apt-get install oracle-java8-jdk

Some additional packages will be installed, but that's fine. Now we're ready to install the UniFi controller itself.



Installing the UniFi controller

To be able to use packages from the UBNT repository for Debian, we need to tell our package manager where the repository is located. We'll need to create the file /etc/apt/sources.list.d/unifi.list with the following content: deb http://www.ubnt.com/downloads/unifi/debian stable ubiquiti. You can do so manually, or let me do it for you by running the following command.
echo 'deb http://www.ubnt.com/downloads/unifi/debian stable ubiquiti' | sudo tee /etc/apt/sources.list.d/unifi.list

Next, we'll need to pass it the UBNT public key (you can verify it here) for this repo:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv C0A52C50

Then we'll tell apt to update its index and install the unifi package.
sudo apt-get update
sudo apt-get install unifi

Let's have a look at the output of the last command.
pi@raspberrypi:~ $ sudo apt-get install unifi
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
  ca-certificates-java icedtea-7-jre-jamvm java-common jsvc libasyncns0 libboost-atomic1.55.0 libboost-filesystem1.55.0 libboost-program-options1.55.0 libboost-system1.55.0 libboost-thread1.55.0
  libcommons-daemon-java libflac8 libice6 liblcms2-2 libnspr4 libnss3 libogg0 libpcap0.8 libpcrecpp0 libpulse0 libsctp1 libsm6 libsnappy1 libsndfile1 libv8-3.14.5 libvorbis0a libvorbisenc2 libx11-xcb1
  lksctp-tools mongodb-clients mongodb-server openjdk-7-jre-headless tzdata-java
Suggested packages:
  default-jre equivs liblcms2-utils pulseaudio sun-java6-fonts fonts-dejavu-extra fonts-ipafont-gothic fonts-ipafont-mincho ttf-wqy-microhei ttf-wqy-zenhei fonts-indic
The following NEW packages will be installed:
  ca-certificates-java icedtea-7-jre-jamvm java-common jsvc libasyncns0 libboost-atomic1.55.0 libboost-filesystem1.55.0 libboost-program-options1.55.0 libboost-system1.55.0 libboost-thread1.55.0
  libcommons-daemon-java libflac8 libice6 liblcms2-2 libnspr4 libnss3 libogg0 libpcap0.8 libpcrecpp0 libpulse0 libsctp1 libsm6 libsnappy1 libsndfile1 libv8-3.14.5 libvorbis0a libvorbisenc2 libx11-xcb1
  lksctp-tools mongodb-clients mongodb-server openjdk-7-jre-headless tzdata-java unifi
0 upgraded, 34 newly installed, 0 to remove and 0 not upgraded.
Need to get 189 MB of archives.
After this operation, 314 MB of additional disk space will be used.
Do you want to continue? [Y/n]

You can see OpenJDK 7 (openjdk-7-jre-headless) is being installed. We won't need it, but it's not easy to exclude and it won't really harm us if there's plenty of disk space around.

After the installation is complete, stop the unifi service, so we can start configuring it.
sudo systemctl stop unifi




The important part: modifying the start script

By default, the UniFi controller will run in the OpenJDK 7 JVM and that's not what we want. To tell it to use the Oracle 8 JVM, we'll need to modify the start script located at /usr/li/unifi/bin/unifi.init. It contains a function called set_java_home detecting all sorts of OpenJDK 6 and 7 JVMs, but it can't detect Oracle JVMs. The solution is to replace this detection mechanism:
sudo sed -i 's@^set_java_home$@#set_java_home\n\n# Use Oracle Java 8 JVM instead.\nJAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt@' /usr/lib/unifi/bin/unifi.init

It replaces the line set_java_home by JAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt to skip JVM detection entirely and use the Oracle 8 JVM right away.

Note that with UniFi version 4.8.9 (which is currently in beta) or higher, the command above doesn't work due to differences in the start script. This new start script allows to set the JAVA_HOME environment variable instead. This can be done in the systemd unit file.

Copy the default systemd unit file to /etc/systemd/system/unifi.service so we can override some settings.
sudo cp /lib/systemd/system/unifi.service /etc/systemd/system/
sudo sed -i '/^\[Service\]$/a Environment=JAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt' /etc/systemd/system/unifi.service

It adds the line Environment=JAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt under the [Service] section in the newly created unit file.

Make systemd aware of the change in its unit files.
sudo systemctl daemon-reload

When we're at it, we might also want to adjust the memory limit available to the UniFi controller which is 1 GB by default. Using a Raspberry Pi with 512 MB of RAM, in many cases, 384 MB would do. You can configure this using:
sudo sed -i 's@-Xmx1024M@-Xmx384M@' /usr/lib/unifi/bin/unifi.init

It replaces the -Xmx1024M JVM option with -Xmx384M.

If you're using a Raspberry Pi 2 model B with 1 GB of RAM, you might want to give it 768M instead:
sudo sed -i 's@-Xmx1024M@-Xmx768M@' /usr/lib/unifi/bin/unifi.init

At this point, you should be able to run the controller already, but I advise you also to work through the next section to let the controller run as its own user. This would prevent immediate root access when the controller would be exploited. If you however decide you want the controller to run as root, you can skip this part.



Configuring the controller to run as its own user

UPDATE December 31, 2015: there seems to be some issues when configuring the service to run as a different user. Please skip this section for now while I'm working on a solution.

We'll create the new user first. The -r flag means it will be a system user, which has no password and cannot log in.
sudo useradd -r unifi

Make the new user owner of the various directories the UniFi controller needs to be able to write to.
sudo chown -R unifi:unifi /var/lib/unifi /var/log/unifi /var/run/unifi /usr/lib/unifi/work

This tells which user to run the service as:
sudo sed -i '/^\[Service\]$/a User=unifi' /etc/systemd/system/unifi.service

It adds the line User=unifi under the [Service] section in the systemd unit file which was created earlier on.

Make systemd aware of the change in its unit files.
sudo systemctl daemon-reload




Running the controller

You can now start the UniFi controller with the following command. It should also run automatically after the Raspberry Pi is rebooted.
sudo systemctl start unifi

It will take some time (about 2 minutes on my old RPi model B rev 2) for the controller to start, but eventually you should be able to connect to https://<ip.of.your.rpi>:8443.

http://static.tweakers.net/ext/f/4eMK01YGNpI9gE4neECh6PXO/full.png
???? PROFIT!!!!


If things are not working for you, you should view the server's log file.
sudo tail /var/log/unifi/server.log

You can (and should) also verify the controller is running as the unifi user. The first column shows the name of the user.
pi@raspberrypi:~ $ ps aux | grep unifi
unifi     9851  0.0  0.2   2072  1060 ?        Ss   17:30   0:00 unifi -home /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt -cp /usr/share/java/commons-daemon.jar:/usr/lib/unifi/lib/ace.jar -pidfile /var/run/unifi/unifi.pid -procname unifi -outfile SYSLOG -errfile SYSLOG -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xmx384M com.ubnt.ace.Launcher start
unifi     9853  0.0  0.2   2072  1264 ?        S    17:30   0:00 unifi -home /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt -cp /usr/share/java/commons-daemon.jar:/usr/lib/unifi/lib/ace.jar -pidfile /var/run/unifi/unifi.pid -procname unifi -outfile SYSLOG -errfile SYSLOG -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xmx384M com.ubnt.ace.Launcher start
unifi     9854  0.3  5.2 461592 25896 ?        Sl   17:30   0:46 unifi -home /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt -cp /usr/share/java/commons-daemon.jar:/usr/lib/unifi/lib/ace.jar -pidfile /var/run/unifi/unifi.pid -procname unifi -outfile SYSLOG -errfile SYSLOG -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xmx384M com.ubnt.ace.Launcher start
unifi     9872  1.3 15.6 1151876 77180 ?       Sl   17:30   3:27 /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt/jre/bin/java -Xmx1024M -Dapple.awt.UIElement=true -jar /usr/lib/unifi/lib/ace.jar start
unifi     9888  0.8 18.2 220780 89892 ?        Sl   17:31   2:08 bin/mongod --dbpath /usr/lib/unifi/data/db --port 27117 --logappend --logpath logs/mongod.log --nohttpinterface --bind_ip 127.0.0.1
pi       10027  0.0  0.3   4252  1844 pts/0    S+   21:38   0:00 grep --color=auto unifi

Note that it also shows you it is using the Oracle 8 JVM and the lowered memory setting.



A note on log rotation

The UniFi controller comes with built-in log rotation which is fine in most situations. Especially when you're using a large SD card (16 GB or more), which should leave you with enough disk space to avoid excessive wear on it. It would be wise to create a backup image now, however. If your SD card would eventually fail due to write wear, you could just get another one and flash your image on it, upload your backup config (don't forget) in the UniFi interface and you're all set again.