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