Goals
  1. Package a Spring Boot Service as RPM Package

  2. Configure systemd integration

  3. Add install/uninstall hooks to create users, directories etc.

Maven Setup

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.2</version>
    <relativePath/>
  </parent>
  <groupId>com.hascode</groupId>
  <artifactId>sample-app</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <name>sample-app</name>

  <properties>
    <java.version>11</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin> (1)
        <groupId>de.dentrassi.maven</groupId>
        <artifactId>rpm</artifactId>
        <version>1.5.0</version>
        <executions>
          <execution>
            <goals>
              <goal>rpm</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <packageName>sample-app</packageName>
          <skipSigning>true</skipSigning>
          <group>Application/Misc</group>
          <requires>
            <require>java-11-openjdk-headless</require> (2)
          </requires>
          <entries>
            <entry> (3)
              <name>/opt/sample-app</name>
              <directory>true</directory>
              <user>root</user>
              <group>root</group>
              <mode>0755</mode>
            </entry>
            <entry> (4)
              <name>/opt/sample-app/log</name>
              <directory>true</directory>
              <user>sample-app</user>
              <group>sample-app</group>
              <mode>0750</mode>
            </entry>
            <entry> (5)
              <name>/opt/sample-app/sample-app.jar</name>
              <file>${project.build.directory}/${project.build.finalName}.jar</file>
              <user>root</user>
              <group>root</group>
              <mode>0644</mode>
            </entry>
            <entry> (6)
              <name>/usr/lib/systemd/system/sample-app.service</name>
              <file>${project.basedir}/src/main/dist/sample-app.service</file>
              <mode>0644</mode>
            </entry>
          </entries>
          <beforeInstallation> (7)
            <file>${project.basedir}/src/main/dist/preinstall.sh</file>
          </beforeInstallation>
          <afterInstallation>
            <file>${project.basedir}/src/main/dist/postinstall.sh</file>
          </afterInstallation>
          <beforeRemoval>
            <file>${project.basedir}/src/main/dist/preuninstall.sh</file>
          </beforeRemoval>
          <license>All rights reserved</license>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>
1 We’re using the RPM Builder Plugin here
2 Adding OpenJDK 11 as a required dependency on the installation system
3 The directory our app will be installed to and its permissions and owner/group set
4 The directory for our log files
5 Our Spring Boot runnable jar file will be installed there
6 Adding a systemd service entry
7 Different hooks that must be run before install, after install and before removal

Systemd Service and Lifecycle Hooks

We’re adding the following three bash-scripts and the service declaration in a directory src/main/dist in our project directory:

├── src
│   ├── main
│   │   ├── dist
│   │   │   ├── sample-app.service
│   │   │   ├── postinstall.sh
│   │   │   ├── preinstall.sh
│   │   │   └── preuninstall.sh
sample-app.service
[Unit]
Description=hasCode.com sample-app
After=network-online.target
Wants=network-online.target

[Service]
User=sample-app
ExecStart=/usr/lib/jvm/jre-11/bin/java $JAVA_OPTS -jar /opt/sample-app/sample-app.jar
SyslogIdentifier=sample-app
StandardOutput=journal
StandardError=journal

# workaround to recognize status code 143 as successful exit code
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target
postinstall.sh
#!/bin/bash

# reload for changed systemd unit files
systemctl daemon-reload

exit 0
preinstall.sh
#!/bin/bash

USER_NAME=sample-app
GROUP_NAME=sample-app
HOME_DIR=/opt/sample-app

# Create group if not exists
getent group ${GROUP_NAME} >/dev/null || groupadd -r ${GROUP_NAME}

# Create user if not exists
getent passwd ${USER_NAME} >/dev/null || \
  useradd -r -g ${GROUP_NAME} -d ${HOME_DIR} -s /sbin/nologin \
    -c "hasCode.com Sample App Account" ${USER_NAME}

# Create user home if not exists
[ -d ${HOME_DIR} ] || mkdir ${HOME_DIR}
chown ${USER_NAME}:${GROUP_NAME} ${HOME_DIR}
chmod 755 ${HOME_DIR}

exit 0
preuninstall.sh
#!/bin/bash

# stop ignoring errors on uninstall
systemctl stop sample-app

exit 0

Build RPM File

Just run mvn package …​

./mvnw clean package
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.hascode:sample-app >--------------------
[INFO] Building sample-app 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[[..]
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[..]
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ sample-app ---
[INFO] Building jar: /home/soma/project/sample-app/target/sample-app-1.0.0-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.4.2:repackage (repackage) @ sample-app ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- rpm:1.5.0:rpm (default) @ sample-app ---
[INFO] Building with SNAPSHOT version
[INFO] Writing to target to: /home/soma/project/sample-app/target/sample-app-1.0.0-0.202104071937.noarch.rpm
[INFO] Building with SNAPSHOT version
[INFO] RPM base information - name: sample-app, version: 1.0.0-0.202104071937, arch: noarch
[INFO] Writing target file: /home/soma/project/sample-app/target/sample-app-1.0.0-0.202104071937.noarch.rpm
[INFO] Building with SNAPSHOT version
[INFO] [script prein]: Using script interpreter: /bin/bash
[INFO] [script postin]: Using script interpreter: /bin/bash
[INFO] [script prerm]: Using script interpreter: /bin/bash
[INFO] Adding dependency [require]: name = java-11-openjdk-headless, version = null, flags = []
[INFO] Required RPM version: 4.11
[INFO] attaching rpm
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.103 s
[INFO] Finished at: 2021-04-07T21:37:04+02:00
[INFO] ------------------------------------------------------------------------

Install RPM

yum localinstall sample-app-1.0.0-0.202104071937.noarch.rpm