How to install Stirling PDF on Termux

Twjao
7 min read3 days ago

--

In the spirit of good cooking recipe, I’ll start by telling you about my life story. Also in the spirit of a recipe, feel free to skip right ahead to the cooking instructions.

edit: Thanks to Near_Earth / George-Seven, shortly after publishing, I learned that there is actually a Docker-style way of installing Stirling PDF that you can use without needing root called udocker.

Since you’re reading this, you probably know what Stirling PDF and Termux are. Stirling PDF is a web front-end that gives access to a lot of different PDF tools, combining them into a universal Swizz army knife for anything you would want to do with PDFs. And Termux basically gives you a Linux terminal on an Android smartphone. On Termux you can simply install SSH, and turn an old smartphone into a small home server that you can manage remotely. And this is why I wanted to install Stirling PDF on an unused phone.

The reason for this long pre-story is that I actually made several attempts in installing Stirling PDF on Termux in the past and failed, and I wanted to share why I failed and how I in the end found a pretty straightforward way to install it.

Why is installing Stirling PDF so difficult?

Stirling PDF is a Java web application that relies on a lot of tools like LibreOffice and OCRMyPDF to do what it does and for all of its functions to work they need to be in exactly the location where the app expects them to be. Otherwise, only some tools will work. The preferred way to install it is by using Docker. Naturally, unless you’ve rooted your phone and you have a custom kernel, Docker is out of the picture (In fact, you can get Docker also running on a non-rooted phone through emulation, but that comes at a huge performance cost).

edit: Actually, beside rooting your phone and slow emulation, there is a third option: udocker

The alternate way to install it is using the bare metal instructions, and that is exactly what I did at first. I used Ubuntu (because that’s what I daily-drive and what I’m most comfortable with) in proot and followed those instructions. And at first, OCRMyPDF was not recognized properly. By adding some symbolic links, I got that to work. But I just could not figure out how to get Stirling PDF to see my LibreOffice installation.

The solution: Read the Dockerfile

After having tried a lot, the solution hit me. The Dockerfile says exactly what you need to run Stirling PDF where everything is in the right place. So if you do exactly what it says in the Dockerfile, you should end up with a working instance of Stirling PDF — and sure enough it worked like a charm. When you open a Dockerfile, you see that it’s based on Alpine Linux. So, create a proot distro for Alpine, and then follow the Dockerfile step by step.

The recipe: How to install Stirling PDF in Termux

Warning when using Termux for self-hosting
While I do think that Termux is great for self-hosting things on old smartphones, there are some considerations to keep in mind. There is no process isolation and everything you do, runs under the same username and has the same privileges. This means that any process you run in Termux is able to mess with any other process, including SSH. Most importantly, if anything you host gets compromised, it doesn’t require privilege escalation to get access to fundamental services like SSH. Therefore, I strongly advise not to open any ports from Termux to the outside world and only use this within your local network or behind a VPN.
(End of warning message aka “I promise I will be careful.”)

The udocker way

Install udocker

curl https://gist.githubusercontent.com/George-Seven/cbeec25874b34aa824c7ea9b3af3e129/raw/58606d8f8bf69b09b11cd4f194c79a7a11d15c19/install_udocker.sh | bash

Then run:

udocker pull frooodle/s-pdf:latest
udocker create --name=stirling-pdf frooodle/s-pdf:latest
udocker run stirling-pdf&

Each command might sit without output for quite some time. So just try to be patient, and hope for the best :)

If you run into the “Error occurred during initialization of VM” issue, you can use the fix from below as follows:

udocker run stirling-pdf bash -c 'echo nameserver 8.8.8.8 >/etc/resolv.conf; apk del openjdk21-jre; apk add openjdk17-jre'

and then run udocker run stirling-pdf& again.

The proot-distro way

Update repositories, and installed packages:

pkg update && pkg upgrade

Install git, proot-distro, and wget:

pkg install git proot-distro wget

Clone the Stirling-PDF git repository:

git clone https://github.com/Stirling-Tools/Stirling-PDF.git

You may want to look at the Dockerfile inside the just cloned repository. The following instructions are basically just translating the steps in the Dockerfile into regular Linux commands.

Install Alpine:

proot-distro install alpine

We start by copying the necessary files from the Git repository into the Alpine “file system” (File system is in quotes because proot distros actually just live in sub folders and without rooting your phone termux cannot manage file systems).

cp Stirling-PDF/scripts $PREFIX/var/lib/proot-distro/installed-rootfs/alpine/scripts -r
cp Stirling-PDF/pipeline $PREFIX/var/lib/proot-distro/installed-rootfs/alpine/pipeline -r
mkdir -p $PREFIX/var/lib/proot-distro/installed-rootfs/alpine/usr/share/fonts/opentype/noto/
cp Stirling-PDF/src/main/resources/static/fonts/*.ttf $PREFIX/var/lib/proot-distro/installed-rootfs/alpine/usr/share/fonts/opentype/noto/

The Java .jar file is not in the repository, but you can download it from the releases

wget https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v0.26.1/Stirling-PDF.jar -O $PREFIX/var/lib/proot-distro/installed-rootfs/alpine/app.jar

Now, we are ready to go into the Alpine environment and install everything we need:

proot-distro login --isolated --no-kill-on-exit alpine

I use the “isolated” flag to forbid the Alpine environment access to the files inside of Termux. The second option “no-kill-on-exit” will allow us to keep Stirling PDF running when we can exit out of the Alpine environment in the end.

Next, we skip ahead in the Dockerfile where it says RUN and execute every listed command inside the alpine proot environment. If you want to use a Stirling PDF with more than just English OCR, go ahead and add additional packages alongside tesseract-ocr-data-eng. Note that tesseract-ocr-data-eng is required even if you do not require OCR in English.

echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
apk upgrade --no-cache -a && \
apk add --no-cache \
ca-certificates \
tzdata \
tini \
bash \
curl \
shadow \
su-exec \
openssl \
openssl-dev \
openjdk21-jre \
libreoffice \
poppler-utils \
ocrmypdf \
tesseract-ocr-data-eng \
py3-opencv \
python3 && \
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \
chmod +x /scripts/* && \
chmod +x /scripts/init.sh && \
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
tesseract --list-langs

You probably don’t need everything in there, but since it’s in the Dockerfile we might as well ...

At this point Stirling PDF should be installed and the last two lines in the Dockerfile tell us how to start it:

tini -- /scripts/init.sh
java -Dfile.encoding=UTF-8 -jar /app.jar

Now, Stirling PDF should be starting and you can test it in your browser by visiting http://[local ip of your phone]:8080 on any device on the same network or http://localhost:8080 on your phone. Unless you see an error message right after launch, don’t be alarmed if the website does not load immediately. It might take a minute or two for the Java app to launch.

Starting screen of Stirling PDF right after installation.

To exit out of Alpine without closing Stirling PDF do the following:

  • Press Ctrl+Z
  • Execute the command “bg”
  • Execute the command “exit”
  • Press Ctrl+Z
  • Execute the command “bg”
  • Execute the command “exit”

What this does is the following. You stop (On Linux “stop” means pause and is very different from “kill”) the java process, which you then continue in the background and exit out of the Alpine environment. Because we’ve used the “no-kill-on-exit” flag the proot-distro process will block the Termux session until Stirling PDF terminates. To get out of it we can do CTRL-Z again, and then continue the proot-distro process in the background.

Possible issues

Error occurred during initialization of VM

You get an error like this when you run Stirling PDF:

Error occurred during initialization of VM
Failed to mark memory page as executable - check if grsecurity/PaX is enabled

I get this error on my newer every-day phone running Android 13, too. On my old phone with Android 7, I do not have this issue, and I don’t know why but downgrading the JRE on my newer device made it work. Within the Alpine environment, execute:

apk del openjdk21-jre
apk add openjdk17-jre

This replaces the installed Java runtime environment with an older version, which for me somehow did not have the same issue.

Everything is super slow (especially when the device is locked)

This can happen due to battery optimizations. There is not really a one-fits-all solution, but you can start by using termux-wake-lock and disabling battery optimizations for Termux. On the device I am using for this, none of this worked initially. Instead, there is an option called “Sports Mode” in the Android settings which basically allows unrestricted power usage and which fixed the issue, but I haven’t seen this on any other device. On all devices I have used, I was able to get around the problem, but it seemed to slightly different each time.

--

--