Mark Underwood Mark Underwood - 3 months ago 23
Java Question

Spring boot executable jar with init.d launching as root instead of user

HELP!

I'm using the built-in launch script with SpringBoot 1.3.6 and Gradle. Oh, and the distZip task to zip things up.

At one point not long ago, this was all working quite well... then I did -- I know not what -- to screw it up.

I've installed the package (unzipped the Zip, basically) on my raspberry Pi, and checked the ownership and permissions. Everything is owned by the user I want to run the app as (user "appservice" group "pi") and confirmed that the permissions for the files are -- if anything, too permissive (755 for the myapp/bin/myapp script and pretty much everything else).

I've put a symlink in /etc/init.d pointing to ~appservice/myapp/bin/myapp and I've run update-rc.d myapp defaults to get it into the system. Note the symlink itself is owned by root/root, but I believe it's supposed to be, isn't it?

I'm seeing two problems, that I think are interrelated.

First, no matter how I launch the script (on boot with init.d or manually with "sudo service myapp start"), it appears to run as root (specifically, paths that the app is trying to use to access files come out as /root/myapp/files instead of /home/appservice/myapp/files).

Second, the app will crash... specifically I get the syslog message "myapp.service start operation timed out. Terminating." followed by what looks like an orderly shutdown of the app. Oh, and related... if I launch with "sudo service myapp start" the command never returns. Which is new...

Third, the application log output is going to /var/log/syslog instead of to /var/log/myapp.log which seems counter to what the Spring Boot documentation says.

I'm in final regression testing of deployment for this, and (famous last words) I haven't changed anything recently... :) No, really, the most recent change relevant to distribution was adding the src/main/dist directory, and it was working after that (launching at boot as the correct user).

I'd post the launch script but AFAIK it's the default script provided by Spring Boot when you use the springBoot { executable = true } task.

Here's my full build.gradle file... I don't see anything amiss, but maybe you will.

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.6.RELEASE")
}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'application'

apply from: 'gradle/gradle/helpers.gradle'

mainClassName = 'app.Application'

if (!hasProperty('mainClass')) {
ext.mainClass = 'app.Application'
}

springBoot {
executable = true
}

repositories {
mavenCentral()
}

sourceSets {
main {
java { srcDir 'src/main/java' }
resources { srcDir '/src/main/resources' }
}
test {
java { srcDir 'src/test/java' }
resources { srcDir 'src/test/resources' }
}
}

project.ext {
applicationVersion = "0.1.5-alpha"

applicationRelease = isApplicationRelease()
applicationDate = new Date()
applicationRevision = getRevision()

applicationVersionSnapshot = (!applicationRelease) ? "+SNAPSHOT.${asUTC(applicationDate, 'yyMMddHHmm')}.git${applicationRevision}" : ""
applicationVersionFull = "${applicationVersion}${applicationVersionSnapshot}"
}

jar {
baseName = 'myapp'
version = '0.9.0'
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {

compile group: 'com.sun.mail', name: 'javax.mail', version: '1.5.2'
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.6'
compile group: 'commons-cli', name:'commons-cli', version: '1.3.1'
compile group: 'org.json', name:'json', version: '20140107'

compile "commons-codec:commons-codec:1.10"

compile("org.springframework.boot:spring-boot-starter-hateoas")
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-mail")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework:spring-web")
compile("org.springframework:spring-messaging")
compile("joda-time:joda-time:2.2")
compile("com.fasterxml.jackson.core:jackson-databind")
compile("com.googlecode.json-simple:json-simple")
compile("org.jdom:jdom:2.0.0")
compile("org.hibernate:hibernate-validator")
compile("org.apache.tomcat.embed:tomcat-embed-el")
compile("org.apache.commons:commons-io:1.3.2")
compile("org.kamranzafar:jtar:2.3")
compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")
compile("com.jcraft:jsch:0.1.53")
compile("javax.jmdns:jmdns:3.4.1")
testCompile("org.springframework.boot:spring-boot-starter-test")


}

task wrapper(type: Wrapper) {
gradleVersion = '2.14'
}

Answer

I would recommend to run and manage your application with systemd.

This makes it very easy to run the application under a specific user.

To do so, go on as follows:

First, create a service definition file /etc/systemd/system/myapp.service with this content:

[Unit]
Description=My App

[Service]
User=nobody
# The configuration file application.properties should be here:
WorkingDirectory=/home/appservice/myapp/files 
ExecStart=/usr/bin/java -Xmx256m -jar myapp.jar
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

Then, notify systemd of the new service file:

systemctl daemon-reload

and enable it, so it runs on boot:

systemctl enable myapp.service

At the end you can use the following commands to start/stop your new service:

systemctl start myapp
systemctl stop myapp
Comments