Building LibreOffice Online for Debian

LibreOffice Online is a very cool project by the fine people at Collabora to bring the LibreOffice core functionality into a web browser. In effect, it’s a Free Software version of the Google Docs suite of productivity tools, allowing one or many people to edit and save documents in a browser.

The software builds on the LibreOffice core code, and currently is only distributed as a Docker image, obtainable from the link above. However, my BLSE2 platform does not support Docker now, or at any time in the foreseeable future [aside: maybe I’ll write my reasoning out, one day], and is based entirely around Debian and the idea of “use the package manager as much as possible”. So I set out to build everything myself.

This however was no small task - there’s precious little usable information in any one place on how to do this, especially not from the official Collabora site that just wants you to use the Docker image [re-aside: yea, that’s one reason I hate Docker…]. Luckily, a GitHub user by the name of m-jowlett has written about his processes in a log over at his GitHub page [m-jowett/log.md should this link eventually rot]. His guide is however extremely rough, as well as using Ubuntu and building for integration with another project featuring OwnCloud. It however gave me most of what I needed to get going with this, including help integrating the final package with my OwnCloud instance(s).

As mentioned briefly above, my philosophy in BLSE2 is “use a package” - this is a core feature of Debian, and one of the most solid examples of quality forethought in design in the Free Software world. By separating applications from their libraries, you keep security updates easy and with minimal administrative work. As such, I always choose to build a package if possible, and luckily with LibreOffice Online I can. And it’s right there in the repo! A huge win in my mind, especially considering my initial fear of a program distributed as a Docker Image [re-re-aside: poor dependency life-cycle management and monolithic software bundles - another reason I hate Docker; but I digress]. As this is a brand-new project and I’m a keen dist-upgradeer, I’ve gone with the brand-new Stretch (9.0) release in the amd64 arch - you should probably be running the same, but 32-bit will work too.

$ cat /etc/debian_version
9.0
$ uname -a
Linux libreoffice-online 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u2 (2017-06-26) x86_64 GNU/Linux

So without further ado, here’s how to build LibreOffice Online on Debian Stretch!

Installing the [Easy] Dependencies

The m-jowlett guide lists a couple of dependencies: libpng12-dev, libcap-dev, libtool, m4, and automake, and I’ll add in fakeroot, debhelper, dh-systemd, and the build-essential metapackage to build the Debian packages, as well as unixodbc-dev which is required by the POCO build process below. The only one to cause problems in Stretch is libpng12-dev: we need the libpng-dev package instead, which installs libpng16-dev. The version bump doesn’t seem to affect anything negatively, however. And of course, we need the full libreoffice suite and it’s build-deps installed as well, python-polib, nodejs-legacy and node-jake to grab some modules during the build, as well as libghc-zlib-bindings-dev and libghc-zlib-dev which pulls in ghc.

$ sudo apt install libpng-dev libcap-dev libtool m4 automake fakeroot debhelper dh-systemd build-essential unixodbc-dev libreoffice python-polib nodejs-legacy node-jake libghc-zlib-bindings-dev libghc-zlib-dev
$ sudo apt build-dep libreoffice

Building POCO

The one dependency that is hard is libpoco-dev. LibreOffice Online makes extensive use of JSON processing using libpoco. However, there’s a problem: the author of JSON decided it was a bright idea to troll(?) the Free Software community, and added a problematic line to his otherwise-MIT licensed code. “The Software shall be used for Good, not Evil.” As a result, JSON doesn’t fit the Debian Free Software Guidelines and isn’t present in any Debian packages, and while Debian Stretch contains libpoco of a version we require, the extremely-critical-to-LOOL JSON parsing library is disabled in the code. So, we have to build the libpoco packages ourselves with JSON support, and then install them as dependencies.

First, we start with a bare Stretch machine with the above dependencies installed and several GB of free space, and then create a directory to contain our libpoco build. We then download both the Debian source package and the Git repository fetching the same version branch.

$ mkdir ~/libpoco
$ cd ~/libpoco/
$ apt-get source libpoco-dev
$ git clone --recursive --depth 1 --branch poco-1.7.6-release https://github.com/pocoproject/poco.git
$ ls
poco  poco-1.7.6+dfsg1  poco_1.7.6+dfsg1-5.debian.tar.xz  poco_1.7.6+dfsg1-5.dsc  poco_1.7.6+dfsg1.orig.tar.bz2

Note the +dfsg tag to the source packages - this is indicative that the Debian maintainer modified the package to comply with the DFSG. Luckily though, all they did was set some package rules to avoid building the JSON component, and then removed the relevant source. By cloning the official repo, we can then combine the debian/ folder from the source package, with modifications, and the upstream source in order to build the JSON libraries. Just “don’t use it for evil”!

First we create our “source” tar archive for the package build process (the name is explained below), then copy over, clean, and commit the stock debian/ folder - I recommend doing this for all your custom packaging as it makes retracing your changes far easier!

$ tar -cvJf poco_1.7.6-lool.orig.tar.xz poco/
$ cp -a poco-1.7.6+dfsg1/debian poco/
$ cd poco/
$ git checkout -b debian
$ dh_clean
$ git add debian/
$ git commit -m "Initial debian folder from Stretch source package"

Now we can begin modifying the Debian package rules. This is fairly straightforward even without Debian packaging experience. I’ll indicate the file edits with vim <filename>; you can replace the vim with the editor of you choice. The output that follows is a basic git-style diff of the changes, as generated as the changes are committed to the custom branch.

The first target is the changelog file, to tell it we have a new version. Note that the version string (lool-1) is chosen specifically because it is “higher” than the official package’s +dfsg1 string. You can validate this yourself using dpkg --compare-versions. This ensures that our custom packages will supersede the official ones, should you commit them to a custom repo and upgrade, though in this guide we install them locally with dpkg. Note that the formatting of this file must match exactly, including every space and the full date, but feel free to edit the note and name/email as you desire - this is irrelevant unless you intend to distribute the packages.

$ vim debian/changelog
@@ -1,3 +1,9 @@
+poco (1.7.6-lool-1) stable; urgency=medium
+
+  * A custom build of 1.7.6 including non-DFSG JSON libraries for LibreOffice Online
+
+ -- Your Name <you@example.com>  Tue, 06 Jul 2017 23:47:21 -0400
+
 poco (1.7.6+dfsg1-5) unstable; urgency=medium

   * Add missing dependencies (Closes: #861682)

The next file to edit is the control file. In here, we add an entry for the libpocojson46 package that will also be built with libpoco-dev. This edit should not require any changes from what is presented here.

$ vim debian/control
@@ -228,3 +228,19 @@ Description: C++ Portable Components (POCO) Zip library
  consistent and easy to maintain.
  .
  This package provides the POCO Zip library.
+
+Package: libpocojson46
+Architecture: any
+Depends: libpocofoundation46 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
+Description: C++ Portable Components (POCO) JSON library
+ The POCO C++ Libraries are a collection of open source C++ class libraries
+ that simplify and accelerate the development of network-centric, portable
+ applications in C++. The libraries integrate perfectly with the C++ Standard
+ Library and fill many of the functional gaps left open by it.
+ .
+ POCO is built strictly using standard ANSI/ISO C++, including the standard
+ library. The contributors attempt to find a good balance between using advanced
+ C++ features and keeping the classes comprehensible and the code clean,
+ consistent and easy to maintain.
+ .
+ This package provides the POCO JSON library.

Next we edit the rules file to remove the exclusion of the JSON component.

$ vim debian/rules
@@ -4,7 +4,7 @@ DPKG_EXPORT_BUILDFLAGS = 1
 export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
 include /usr/share/dpkg/buildflags.mk

-CONFFLAGS = --prefix=/usr --no-samples --no-tests --unbundled --everything --omit=JSON --cflags="-DPOCO_UTIL_NO_JSONCONFIGURATION" --odbc-lib=/usr/lib/$(DEB_HOST_MULTIARCH)/
+CONFFLAGS = --prefix=/usr --no-samples --no-tests --unbundled --everything --odbc-lib=/usr/lib/$(DEB_HOST_MULTIARCH)/

 # Disable parallel build on armel and mipsel
 ifneq (,$(filter $(DEB_BUILD_ARCH),armel mipsel))

Now we remove the patches that disabled JSON support in the package. First we remove the patch, then we remove the series entry for it.

$ rm debian/patches/0006-Disable-JSON-targets-in-Makefiles.patch
$ vim debian/patches/series
@@ -6,7 +6,6 @@ no-link-dl-rt.patch  #could be removed now
 # patches for build/rules/*
 no-debug-build.patch
 no-strip-release-build.patch
-0006-Disable-JSON-targets-in-Makefiles.patch

 # upstream patches
 0007-Add-patch-for-OpenSSL-1.1.0.patch

Finally we remove the old lintian overrides (no longer needed) and create the install file for our new libpocojson46 package.

$ rm debian/source.lintian-overrides
$ vim debian/libpocojson46.install
@@ -0,0 +1 @@
+usr/lib/libPocoJSON.so.*

We can now build the package; I use -j4 on my quad-vCPU build machine but you should adjust this based on your core count for best performance. Note also that we’re using sudo here; for whatever reason, trying to compile libpoco without root causes a spew of error messages like object 'libfakeroot-sysv.so' from LD_PRELOAD cannot be preloaded, on both my testing machines.

$ sudo dpkg-buildpackage -us -uc -j4
[lots of output]
dpkg-buildpackage: info: full upload (original source is included)

That last line indicates that the build succeeded; above it, we see a long list of generated packages in the parent directory. Move up a directory, remove the unneeded dbgsym packages, and install all the rest. Note the sudo commands again (due to the permissions of dpkg-buildpackage).

$ cd ..
$ sudo rm *-dbgsym_*.deb
$ sudo dpkg -i *.deb

We now have a working set of POCO libraries and can now begin building LibreOffice Online itself!

Building LibreOffice Online

Once the dependencies are in place building the LibreOffice Online package itself is actually fairly straightforward - the repo contains a working debian folder, though it too requires some tweaking to build properly.

Begin by making a new directory, and cloning the git repo; I’m using version 2.1.2 as it’s the latest stable one at the time of writing. Note that because the LibreOffice Online developers use tags, we have to actually cd into and git checkout the right version before we proceed. Then, as we did for POCO, make a tar archive for the package build to use containing the source before we start editing anything.

$ mkdir ~/loolwsd
$ cd ~/loolwsd/
$ git clone https://github.com/LibreOffice/online.git
$ cd online/
$ git checkout -b debian tags/2.1.2
$ cd ..
$ tar -cvJf loolwsd_2.1.2.orig.tar.xz online/
$ cd online

We now need to do a some editing of the Debian control files, similar to POCO. First we add a changelog entry (as is customary).

$ vim debian/changelog
@@ -1,3 +1,9 @@
+loolwsd (2.1.2-7) stable; urgency=medium
+
+  * Custom build of 2.1.2 for Debian Stretch
+
+ -- Your Name <you@example.com>  Tue, 06 Jul 2017 23:47:21 -0400
+
 loolwsd (2.1.2-6) unstable; urgency=medium

   * see the git log: http://col.la/cool21

Next we edit the control file. By default, LibreOffice online depends on the collaboraofficebasis5.3 suite, however we can override that and allow it to run against the stock Stretch libreoffice package. While the diff is long, simply search for the first instance of collabora on that line and delete everything after it, as well as the entry for libssl1.0.0 which is obsolete in Stretch and should be present by default anyways.

$ vim debian/control
@@ -8,7 +8,7 @@ Standards-Version: 3.9.7
 Package: loolwsd
 Section: web
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, fontconfig, libsm6, libssl1.0.0, libodbc1, libxinerama1, libcairo2, libgl1-mesa-glx, libcups2, libdbus-glib-1-2, cpio, collaboraofficebasis5.3-calc (>= 5.3.10.15), collaboraofficebasis5.3-core (>= 5.3.10.15), collaboraofficebasis5.3-graphicfilter (>= 5.3.10.15), collaboraofficebasis5.3-images (>= 5.3.10.15), collaboraofficebasis5.3-impress (>= 5.3.10.15), collaboraofficebasis5.3-ooofonts (>= 5.3.10.15), collaboraofficebasis5.3-writer (>= 5.3.10.15), collaboraoffice5.3 (>= 5.3.10.15), collaboraoffice5.3-ure (>= 5.3.10.15), collaboraofficebasis5.3-en-us (>= 5.3.10.15), collaboraofficebasis5.3-en-us-calc (>= 5.3.10.15), collaboraofficebasis5.3-en-us-res (>= 5.3.10.15), collaboraofficebasis5.3-noto-fonts (>= 5.3.10.15), collaboraofficebasis5.3-draw (>= 5.3.10.15), collaboraofficebasis5.3-extension-pdf-import (>= 5.3.10.15)
+Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, fontconfig, libsm6, libodbc1, libxinerama1, libcairo2, libgl1-mesa-glx, libcups2, libdbus-glib-1-2, cpio, libreoffice
 Description: LibreOffice Online WebSocket Daemon
  LOOLWSD is a daemon that talks to web browser clients and provides LibreOffice
  services.

Next we edit the rules to add a few missing things. First, we add some additional configuration flags for disabling SSL (instead use a forward proxy; this also prevents build errors) and specifying the library directory; this is needed for the build process to find the libreoffice libraries. We also add a call to autogen.sh in the configuration step (missing inexplicably in this version), as well as overrides to the auto-build (to allow -jX flags to work).

@@ -5,7 +5,7 @@ DPKG_EXPORT_BUILDFLAGS = 1
 
 include /usr/share/dpkg/default.mk
 
-CONFFLAGS = --enable-silent-rules --prefix=/usr --localstatedir=/var --sysconfdir=/etc --with-lokit-path=`pwd`/bundled/include $(CONFIG_OPTIONS)
+CONFFLAGS = --enable-silent-rules --disable-ssl --prefix=/usr --localstatedir=/var --sysconfdir=/etc --with-lokit-path=`pwd`/bundled/include --libdir=/usr/lib/x86_64-linux-gnu $(CONFIG_OPTIONS)
 
 # Avoid setcap when doing "make", when building for packaging
 # the setcap is done at installation time
@@ -16,10 +16,14 @@ export BUILDING_FROM_RPMBUILD=yes
        dh $@ --with=systemd
 
 override_dh_auto_configure:
+       ./autogen.sh
        ./configure $(CONFFLAGS)
 
 override_dh_auto_test:
        # do not test
 
+override_dh_auto_build:
+       dh_auto_build --parallel $(MAKEARGS)
+
 override_dh_installinit:
        # no init.d scripts here, assume systemd

Now we have to write some patches. quilt is a phenomenal tool for this, and I recommend reading up on it, but for simplicity I’ll provide the patch files as-is. First create a debian/patches folder and create a series file in it.

$ mkdir debian/patches
$ vim debian/patches/series
@@ -0,0 +1,2 @@
+enable-ssl-always
+fix-libpath

Next we create the two patch files. The first is to enable SSL support always - this seems to contradict the above change to disable SSL, but otherwise the build process fails. The second fixes up the location of the libreoffice libraries so the amd64-only build can find where they exist in Stretch amd64.

Note: to avoid showing a diff of a diff, the following two entries are the verbatim contents of the file

$ vim debian/patches/enable-ssl-always
Description: Enable SSL always
 Ensure that SSL is always enabled during build
 .
 loolwsd (2.1.2-7) stable; urgency=medium
 .
   * Custom build of 2.1.2 for Debian Stretch
Author: Your Name <you@example.com>

---
The information above should follow the Patch Tagging Guidelines, please
checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here
are templates for supplementary fields that you might want to add:

Origin: <vendor|upstream|other>, <url of original patch>
Bug: <url in upstream bugtracker>
Bug-Debian: https://bugs.debian.org/<bugnumber>
Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
Forwarded: <no|not-needed|url proving that it has been forwarded>
Reviewed-By: <name and email of someone who approved the patch>
Last-Update: 2017-06-23

--- loolwsd-2.1.2.orig/Makefile.am
+++ loolwsd-2.1.2/Makefile.am
@@ -38,9 +38,9 @@ endif

 AM_LDFLAGS = -pthread -Wl,-E,-rpath,/snap/loolwsd/current/usr/lib $(ZLIB_LIBS)

-if ENABLE_SSL
+#if ENABLE_SSL
 AM_LDFLAGS += -lssl -lcrypto
-endif
+#endif

 loolwsd_fuzzer_CPPFLAGS = -DKIT_IN_PROCESS=1 -DFUZZER=1 -DTDOC=\"$(abs_top_srcdir)/test/data\" $(AM_CPPFLAGS)

$ vim debian/patches/fix-libpath
Description: Fix the LibreOffice library path
 Ensure that the build can find the LibreOffice files
 .
 loolwsd (2.1.2-7) stable; urgency=medium
 .
   * Custom build of 2.1.2 for Debian Stretch
Author: Your Name <you@example.com>

---
The information above should follow the Patch Tagging Guidelines, please
checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here
are templates for supplementary fields that you might want to add:

Origin: <vendor|upstream|other>, <url of original patch>
Bug: <url in upstream bugtracker>
Bug-Debian: https://bugs.debian.org/<bugnumber>
Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
Forwarded: <no|not-needed|url proving that it has been forwarded>
Reviewed-By: <name and email of someone who approved the patch>
Last-Update: 2017-06-23

--- loolwsd-2.1.2.orig/configure.ac
+++ loolwsd-2.1.2/configure.ac
@@ -166,7 +166,7 @@ AS_IF([test -n "$with_lokit_path"],
       [CPPFLAGS="$CPPFLAGS -I${with_lokit_path}"])
 lokit_msg="$with_lokit_path"

-LO_PATH="/usr/lib64/libreoffice"
+LO_PATH="/usr/lib/libreoffice"
 JAIL_PATH=not-set
 SYSTEMPLATE_PATH=not-set
 have_lo_path=false

One final tweak to perform is to edit the systemd unit file to point to the libreoffice templates directory rather than the Collabora one, and change the Red Hat-specific EnvironmentFile directive to the Debian one (which isn’t installed by default but can be used if needed).

$ vim debian/loolwsd.service
@@ -3,8 +3,8 @@ Description=LibreOffice Online WebSocket Daemon
 After=network.target

 [Service]
-EnvironmentFile=-/etc/sysconfig/loolwsd
-ExecStart=/usr/bin/loolwsd --version --o:sys_template_path=/opt/lool/systemplate --o:lo_template_path=/opt/collaboraoffice5.3 --o:child_root_path=/opt/lool/child-roots --o:file_server_root_path=/usr/share/loolwsd
+EnvironmentFile=-/etc/default/loolwsd
+ExecStart=/usr/bin/loolwsd --version --o:sys_template_path=/opt/lool/systemplate --o:lo_template_path=/usr/lib/libreoffice --o:child_root_path=/opt/lool/child-roots --o:file_server_root_path=/usr/share/loolwsd
 User=lool
 KillMode=control-group
 Restart=always

Now we install some required npm dependencies; the nodejs-legacy package will provide our npm; don’t install the npm package itself as this will cause dependency hell. I do this in the main homedir to avoid putting cruft into the source directories.

$ cd ~
$ npm install uglify-js exorcist d3 evol-colorpicker bootstrap eslint browserify-css d3

Finally, we can build the LibreOffice Online package.

$ cd ~/loolwsd/online/
$ sudo dpkg-buildpackage -us -uc -j4
[lots of output]
dpkg-buildpackage: info: full upload (original source is included)
$ cd ..

Install the resulting deb file and we’re set - LibreOffice Online, in a Debian package. To install it on another machine, all we need are the packages generated by this guide (libpoco-dev and friends, and loolwsd).

Bonus - Changing the LibreOffice Online directory

By default, the LibreOffice Online package installs the main components of the loolwsd service under /opt/lool. I’m not a fan of putting anything under /opt however, and in BLSE2 everything that is per-server and not automated via configuration management goes under /srv. If you also desire this, it’s very straightforward to edit the Debian configuration to support installing to an arbitrary target directory before building the package.

$ cd ~/loolwsd/online/
$ vim debian/loolwsd.postinst.in
@@ -7,24 +7,24 @@ case "$1" in
        setcap cap_fowner,cap_mknod,cap_sys_chroot=ep /usr/bin/loolforkit || true
        setcap cap_sys_admin=ep /usr/bin/loolmount || true

-       adduser --quiet --system --group --home /opt/lool lool
+       adduser --quiet --system --group --home /srv/lool lool
        mkdir -p /var/cache/loolwsd && chown lool: /var/cache/loolwsd
        rm -rf /var/cache/loolwsd/*
        chown lool: /etc/loolwsd/loolwsd.xml
        chmod 640 /etc/loolwsd/loolwsd.xml

        # We assume that the LibreOffice to be used is built TDF-style
-       # and installs in @LO_PATH@, and that /opt/lool is
+       # and installs in @LO_PATH@, and that /srv/lool is
        # on the same file system

-       rm -rf /opt/lool
-       mkdir -p /opt/lool/child-roots
-       chown lool: /opt/lool
-       chown lool: /opt/lool/child-roots
+       rm -rf /srv/lool
+       mkdir -p /srv/lool/child-roots
+       chown lool: /srv/lool
+       chown lool: /srv/lool/child-roots

        fc-cache @LO_PATH@/share/fonts/truetype

-       su lool --shell=/bin/sh -c "loolwsd-systemplate-setup /opt/lool/systemplate @LO_PATH@ >/dev/null 2>&1"
+       su lool --shell=/bin/sh -c "loolwsd-systemplate-setup /srv/lool/systemplate @LO_PATH@ >/dev/null 2>&1"
        ;;

 esac
$ vim debian/loolwsd.service
@@ -4,7 +4,7 @@ After=network.target

 [Service]
 EnvironmentFile=-/etc/default/loolwsd
-ExecStart=/usr/bin/loolwsd --version --o:sys_template_path=/opt/lool/systemplate --o:lo_template_path=/usr/lib/libreoffice --o:child_root_path=/opt/lool/child-roots --o:file_server_root_path=/usr/share/loolwsd
+ExecStart=/usr/bin/loolwsd --version --o:sys_template_path=/srv/lool/systemplate --o:lo_template_path=/usr/lib/libreoffice --o:child_root_path=/srv/lool/child-roots --o:file_server_root_path=/usr/share/loolwsd
 User=lool
 KillMode=control-group
 Restart=always

I hope this helps you avoid many hours of headache! I’ll document the configuration and integration of LibreOffice Online in another post. Happy building!