The objective is to run a local LT server as a systemd service that starts automatically when booting and runs in the background. In part this works, but breaks when running the service as a non-root user without home directory.
Here’s a recipe:
-
Download LanguageTool to a directory of your choice:
$ wget https://languagetool.org/download/LanguageTool-stable.zip
-
Install LT into directory
/opt/
and create a soft link/opt/LanguageTool
so we don’t have to deal with the version number in paths:$ sudo unzip -d /opt/ LanguageTool-stable.zip $ sudo ln -s /opt/LanguageTool-<version>/ /opt/LanguageTool $ ls -l /opt/ lrwxrwxrwx 1 root root 22 Mär 8 11:05 LanguageTool -> /opt/LanguageTool-4.8/ drwxr-xr-x 6 root root 4096 Mär 8 11:06 LanguageTool-4.8
-
Create a service unit file
languagetool.service
in a user writable directory (we need to modify this file later):[Unit] Description=LanguageTool server After=network.target [Service] Type=simple ExecStart=/usr/bin/java -cp /opt/LanguageTool/languagetool-server.jar org.languagetool.server.HTTPServer --port 8081 --allow-origin "*" [Install] WantedBy=multi-user.target
-
Copy the unit file to a directory where systemd searches system units:
$ sudo cp languagetool.service /etc/systemd/system/
-
Open a new terminal to watch service activity:
$ journalctl -f -u languagetool.service
-
Start the service:
$ sudo systemctl enable languagetool.service Created symlink /etc/systemd/system/multi-user.target.wants/languagetool.service → /etc/systemd/system/languagetool.service. $ sudo systemctl start languagetool.service
-
Test the server:
$ curl --data "language=en-US&text=a simple test" http://localhost:8081/v2/check {"software":{"name":"LanguageTool","version":"4.8","buildDate":"2019-12-27 13:18","apiVersion":1,"premium":false,"premiumHint":"You might be missing errors only the Premium version can find. Contact us at support<at>languagetoolplus.com.","status":""},"warnings":{"incompleteResults":false},"language":{"name":"English (US)","code":"en-US","detectedLanguage":{"name":"French","code":"fr","confidence":0.815771}},"matches":[{"message":"This sentence does not start with an uppercase letter","shortMessage":"","replacements":[{"value":"A"}],"offset":0,"length":1,"context":{"text":"a simple test","offset":0,"length":1},"sentence":"a simple test","type":{"typeName":"Other"},"rule":{"id":"UPPERCASE_SENTENCE_START","description":"Checks that a sentence starts with an uppercase letter","issueType":"typographical","category":{"id":"CASING","name":"Capitalization"}},"ignoreForIncompleteSentence":true,"contextForSureMatch":-1}]}
This works just fine. Except that the log in the other terminal contains a big warning about the server running as root.
systemd[1]: Started LanguageTool server.
java[28357]: 2020-03-07 16:59:25 +0100 INFO org.languagetool.server.DatabaseAccess Not setting up database access, dbDriver is not configured
java[28357]: 2020-03-07 15:59:25 +0000 ****************************************************************************************************
java[28357]: 2020-03-07 15:59:25 +0000 *** WARNING: this process is running as root - please do not run it as root for security reasons ***
java[28357]: 2020-03-07 15:59:25 +0000 ****************************************************************************************************
java[28357]: 2020-03-07 15:59:25 +0000 WARNING: running in HTTP mode, consider using org.languagetool.server.HTTPSServer for encrypted connections
java[28357]: 2020-03-07 15:59:27 +0000 Setting up thread pool with 10 threads
java[28357]: 2020-03-07 15:59:27 +0000 Starting LanguageTool 4.8 (build date: 2019-12-27 13:18, 72d565f) server on http://localhost:8081...
java[28357]: 2020-03-07 15:59:27 +0000 Server started
That warning makes certainly sense. A hacker exploiting a bug in LT or Java could do harm on our machine.
To run as non-root, we create a new system user (without home directory). As far as we need configuration files later, we can store them either in /opt/LanguageTool/
or somewhere below /etc/
. The following command creates a system user that cannot log in. (A system user has no privileges, it just has no home directory and UID is below 1000):
$ sudo useradd --system --shell /usr/sbin/nologin langtool
Now we need to direct systemd to run the service unit as user langtool
.
Disable the running service:
$ sudo systemctl disable languagetool.service
Removed /etc/systemd/system/multi-user.target.wants/languagetool.service.
$ sudo systemctl stop languagetool.service
Add the line
User=langtool
to the service unit file inserting it above the line containing the ExecStart=
directive.
Now go to item 4. of the recipe.
After testing the server, this time the log contains these error messages:
systemd[1]: Started LanguageTool server.
java[21759]: 2020-03-08 12:30:31 +0100 INFO org.languagetool.server.DatabaseAccess Not setting up database access, dbDriver is not configured
java[21759]: 2020-03-08 11:30:31 +0000 WARNING: running in HTTP mode, consider using org.languagetool.server.HTTPSServer for encrypted connections
java[21759]: 2020-03-08 11:30:33 +0000 Setting up thread pool with 10 threads
java[21759]: 2020-03-08 11:30:33 +0000 Starting LanguageTool 4.8 (build date: 2019-12-27 13:18, 72d565f) server on http://localhost:8081...
java[21759]: 2020-03-08 11:30:33 +0000 Server started
java[21759]: 2020-03-08 12:30:40 +0100 ERROR org.languagetool.server.LanguageToolHttpHandler An error has occurred: 'java.lang.IllegalArgumentException: Cannot open file .languagetool.cfg in directory /home/langtool, detected: fr', sending HTTP code 400. Access from 127.0.0.1, HTTP user agent: curl/7.65.3, User agent param: null, Referrer: null, language: en-US, h: 1, r: 1, time: 2376text length: 13, m: ALL, Stacktrace follows:java.lang.RuntimeException: java.lang.IllegalArgumentException: Cannot open file .languagetool.cfg in directory /home/langtool, detected: fr
java[21759]: at org.languagetool.server.TextChecker.checkText(TextChecker.java:345)
java[21759]: at org.languagetool.server.ApiV2.handleCheckRequest(ApiV2.java:141)
java[21759]: at org.languagetool.server.ApiV2.handleRequest(ApiV2.java:71)
java[21759]: at org.languagetool.server.LanguageToolHttpHandler.handle(LanguageToolHttpHandler.java:170)
java[21759]: at jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:77)
java[21759]: at jdk.httpserver/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)
java[21759]: at jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:80)
java[21759]: at jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:692)
java[21759]: at jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:77)
java[21759]: at jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:664)
java[21759]: at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
java[21759]: at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
java[21759]: at java.base/java.lang.Thread.run(Thread.java:834)
java[21759]: Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Cannot open file .languagetool.cfg in directory /home/langtool
java[21759]: at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
java[21759]: at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
java[21759]: at org.languagetool.server.TextChecker.checkText(TextChecker.java:326)
java[21759]: ... 12 more
java[21759]: Caused by: java.lang.IllegalArgumentException: Cannot open file .languagetool.cfg in directory /home/langtool
java[21759]: at org.languagetool.gui.Configuration.<init>(Configuration.java:188)
java[21759]: at org.languagetool.gui.Configuration.<init>(Configuration.java:183)
java[21759]: at org.languagetool.gui.Configuration.<init>(Configuration.java:179)
java[21759]: at org.languagetool.gui.Configuration.<init>(Configuration.java:171)
java[21759]: at org.languagetool.server.PipelinePool.configureFromGUI(PipelinePool.java:234)
java[21759]: at org.languagetool.server.PipelinePool.createPipeline(PipelinePool.java:207)
java[21759]: at org.languagetool.server.PipelinePool.getPipeline(PipelinePool.java:175)
java[21759]: at org.languagetool.server.TextChecker.getRuleMatches(TextChecker.java:509)
java[21759]: at org.languagetool.server.TextChecker.access$000(TextChecker.java:54)
java[21759]: at org.languagetool.server.TextChecker$1.call(TextChecker.java:319)
java[21759]: at org.languagetool.server.TextChecker$1.call(TextChecker.java:312)
java[21759]: at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
java[21759]: ... 3 more
The error seems to happen when reading a file .languagetool.cfg
in the user’s home directory. Since that file doesn’t exist for root as well and no error happened in the first attempt, I think the error occurs because the home directory doesn’t even exist. LT not catching that case looks like a bug to me.
Am I missing something?