Spring Boot executable jar as Linux service

In this blog post I will describe required steps in order to build with Spring boot a fully executable application as a jar file, deploy over ssh, run it as a System V service using a custom user for this.

I have generated a Spring Boot application using Initializr and configured the build.gradle script to build the project as an executable jar. This configuration will generate so called „fatJar” (a jar archive with all dependencies) and in manifest file will add a line specifying the Main Class:

jar {
	from {
		configurations.compile.collect { zipTree(it) }
	}

manifest {
		attributes 'Main-Class': 'com.iftodi.dan.foundation.FoundationApplication'
	}
}

We don’t want to execute our jar file as jar -jar myApplication.jar, in order to achive this we will add to the build script (see details on Spring Boot docs read more):

bootJar {
	launchScript()
}

In order to deploy over SSH we will use org.hidetake.ssh plugin, will add a remote host with key based authentication (to generate ssh keys use ssh-keygen, I have generated them in ./deploy folder).

Add a task named deploy that will depend on task build, task will be responsible for remote stopping the app, copying new version and start remote service with service foundation start – service will be started as foundation user (build.gradle):

plugins {
	id "org.hidetake.ssh" version "2.9.0"
}
remotes {
	raspberry {
		host = '192.168.0.103'
		user = 'foundation'
		identity = file('deploy/id_rsa')
	}
}
task deploy {
	group 'deploy'
	description 'Deploys jar files over SSH'
	dependsOn build
	ext.appHome = '/var/webapps/foundation'

	doFirst {
		ssh.run {
			session(remotes.raspberry) {
				execute "sudo /bin/systemctl stop foundation"
			}
		}
	}
	doLast {
		ssh.run {
			session(remotes.raspberry) {
				put from: jar.archivePath, into: appHome
				execute "sudo /bin/systemctl start foundation"
			}
		}
	}
}

Now if we will try to execute gradle deploy it should build our application, save the ‘fatJar’ into ./build/libs folder and then will try to deploy. Most probably this will fail as we have not created yet the remote user.

In order to create foundation user we should login to our machine and write:

//add user
useradd foundation -d /var/webapps/foundation
//switch to foundation user
sudo su foundation
//create .ssh folder
cd ~ & mkdir .ssh
//add key from project to the authorized list
vim ~/.ssh/authorized_keys
//paste the content of ./deploy/id_pub.rsa
//wq

Our gradle deploy command may still fail, a foundation.service file should be created and foundation user should have the correct permissions in order to start this service.

Create /etc/systemd/system/foundation.service with content:

Unit]
Description=Foundation App
After=syslog.target
 
[Service]
User=foundation
ExecStart=/var/webapps/foundation/foundation-0.0.1-SNAPSHOT.jar SuccessExitStatus=143 
 
[Install] 
WantedBy=multi-user.target

Now you can try to run service foundation start, if it doesn’t ask you for password, then you are ready to go. If it asked you for password as it was in my case, then follow next steps.

In case it is asking for password, we will add foundation user to the sudoers list and will allow execution for several commands without sudo password, lets add a new sudoers file with sudo visudo -f /etc/sudoers.d/foundation:

Now execute gradle deploy on your dev machine, and your application should be deployed.

Cmnd_Alias FOUNDATION_CMDS = /bin/systemctl start foundation, /bin/systemctl stop foundation, /bin/systemctl status foundation, /bin/systemctl restart foundation
foundation ALL=(ALL) NOPASSWD: FOUNDATION_CMDS

Some distributions are using PolicyKit for managing unprivileged users permissions. In order to allow to your user foundation to use service foundation start\stop\restart\status command we will create file /etc/polkit-1/localauthority.conf.d/51-foundation.conf with content (don’t forget to replace the service name and username):

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units") {
        var verb = action.lookup("verb");
        var unit = action.lookup("unit");
        if (subject.user == "foundation"
            && unit == "foundation.service"
            && (verb == "start" || verb == "stop" || verb == "status" || verb == "restart"))
        {
            return polkit.Result.YES;
        }
    }
});

At the end don’t forget to restart polkitd with service polkit restart

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *

Acest site folosește Akismet pentru a reduce spamul. Află cum sunt procesate datele comentariilor tale.