CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-loader-tools

Tools for generating executable JAR/WAR files with embedded containers for Spring Boot applications

Pending
Overview
Eval results
Files

launch-scripts.mddocs/

Launch Script Generation

Tools for creating launch scripts that make JAR files directly executable on Unix-like systems. Launch scripts are prepended to JAR files, allowing them to be executed like native executables while maintaining JAR functionality.

Capabilities

Launch Script Interface

Core interface for launch scripts that can be prepended to JAR files.

public interface LaunchScript {
    /**
     * Get the launch script content as a byte array.
     * The script is prepended to the JAR file to make it executable.
     * 
     * @return the script content as bytes
     */
    byte[] toByteArray();
}

Default Launch Script

Standard implementation that provides a configurable Unix shell script for launching JAR files.

public class DefaultLaunchScript implements LaunchScript {
    /**
     * Create a launch script from a template file with property substitution.
     * 
     * @param file the script template file
     * @param properties properties for template variable substitution
     * @throws IOException if the template file cannot be read
     */
    public DefaultLaunchScript(File file, Map<?, ?> properties) throws IOException;
    
    /**
     * Get the launch script content as bytes.
     * 
     * @return the processed script content
     */
    @Override
    public byte[] toByteArray();
}

Usage Examples

Basic Launch Script

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.util.Map;

// Create a basic launch script
File scriptTemplate = new File("src/main/scripts/launch.sh");
Map<String, String> properties = Map.of(
    "initInfoProvides", "myapp",
    "initInfoShortDescription", "My Spring Boot Application"
);

LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, properties);

// Use with JarWriter
File targetJar = new File("myapp.jar");
try (JarWriter writer = new JarWriter(targetJar, launchScript)) {
    // Write JAR contents
    // ... (JAR writing code)
}

// The resulting JAR can be executed directly:
// chmod +x myapp.jar
// ./myapp.jar

Advanced Launch Script Configuration

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

// Configure advanced launch script properties
Map<String, String> properties = new HashMap<>();

// Service information
properties.put("initInfoProvides", "myapp");
properties.put("initInfoShortDescription", "My Spring Boot Application");
properties.put("initInfoDescription", "A comprehensive Spring Boot application for data processing");

// Runtime configuration
properties.put("confFolder", "/etc/myapp");
properties.put("logFolder", "/var/log/myapp");
properties.put("pidFolder", "/var/run/myapp");
properties.put("logFilename", "myapp.log");

// JVM options
properties.put("javaOpts", "-Xmx1024m -Xms512m -Dspring.profiles.active=production");

// User and permissions
properties.put("runAsUser", "myapp");
properties.put("mode", "service"); // or "auto", "force", "run"

// File locations
properties.put("useStartStopDaemon", "true");
properties.put("stopWaitTime", "60");

File scriptTemplate = new File("launch-template.sh");
LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, properties);

// Create executable JAR
File targetJar = new File("myapp.jar");
try (JarWriter writer = new JarWriter(targetJar, launchScript)) {
    // Write JAR contents...
}

// The JAR can now be used as a system service:
// sudo cp myapp.jar /etc/init.d/myapp
// sudo chmod +x /etc/init.d/myapp
// sudo update-rc.d myapp defaults
// sudo service myapp start

Custom Launch Script Template

Create a custom launch script template (launch-template.sh):

#!/bin/bash
#
# {{initInfoProvides}}        {{initInfoShortDescription}}
#
# chkconfig: 35 80 20
# description: {{initInfoDescription}}
#

. /lib/lsb/init-functions

USER="{{runAsUser}}"
DAEMON="{{initInfoProvides}}"
ROOT_DIR="/var/lib/{{initInfoProvides}}"

SERVER="$ROOT_DIR/{{initInfoProvides}}.jar"
LOCK_FILE="/var/lock/subsys/{{initInfoProvides}}"

start() {
    log_daemon_msg "Starting $DAEMON"
    if [ ! -f "$SERVER" ]; then
        log_failure_msg "$SERVER not found"
        exit 1
    fi
    
    if start-stop-daemon --start --quiet --oknodo --pidfile "$PID_FILE" \
        --chuid "$USER" --background --make-pidfile \
        --exec /usr/bin/java -- {{javaOpts}} -jar "$SERVER"
    then
        log_end_msg 0
        touch "$LOCK_FILE"
    else
        log_end_msg 1
    fi
}

stop() {
    log_daemon_msg "Stopping $DAEMON"
    if start-stop-daemon --stop --quiet --oknodo --pidfile "$PID_FILE"
    then
        log_end_msg 0
        rm -f "$LOCK_FILE"
    else
        log_end_msg 1
    fi
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        start
        ;;
    status)
        status_of_proc -p $PID_FILE "$DAEMON" "$DAEMON" && exit 0 || exit $?
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

exit 0

# The actual JAR content follows after this script

Integration with Repackager

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.util.Map;

// Create executable JAR with launch script using Repackager
File sourceJar = new File("target/myapp.jar");
File destination = new File("dist/myapp");

Repackager repackager = new Repackager(sourceJar);

// Configure launch script
File scriptTemplate = getClass().getResource("/launch-template.sh").getFile();
Map<String, String> scriptProperties = Map.of(
    "initInfoProvides", "myapp",
    "initInfoShortDescription", "My Application",
    "runAsUser", "appuser",
    "javaOpts", "-Xmx512m -Dspring.profiles.active=prod"
);

LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, scriptProperties);

// Repackage with launch script
repackager.repackage(destination, Libraries.NONE, launchScript);

// Set executable permissions
destination.setExecutable(true, false);

System.out.println("Created executable JAR: " + destination.getAbsolutePath());
System.out.println("Run with: " + destination.getAbsolutePath());

Conditional Launch Script

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.util.Map;

// Only add launch script for Unix-like systems
public class ConditionalLaunchScript {
    public static LaunchScript createLaunchScript() {
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("windows")) {
            // No launch script for Windows
            return null;
        }
        
        // Create launch script for Unix-like systems
        try {
            File template = new File("unix-launch-template.sh");
            Map<String, String> props = Map.of(
                "initInfoProvides", "myapp",
                "initInfoShortDescription", "My App"
            );
            return new DefaultLaunchScript(template, props);
        } catch (Exception e) {
            System.err.println("Failed to create launch script: " + e.getMessage());
            return null;
        }
    }
}

// Usage
File sourceJar = new File("myapp.jar");
Repackager repackager = new Repackager(sourceJar);

LaunchScript launchScript = ConditionalLaunchScript.createLaunchScript();
if (launchScript != null) {
    repackager.repackage(Libraries.NONE, launchScript);
    System.out.println("Created executable JAR with launch script");
} else {
    repackager.repackage(Libraries.NONE);
    System.out.println("Created JAR without launch script");
}

Custom Launch Script Implementation

import org.springframework.boot.loader.tools.LaunchScript;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

// Custom launch script implementation
public class CustomLaunchScript implements LaunchScript {
    private final String applicationName;
    private final String javaOpts;
    private final String workingDir;
    
    public CustomLaunchScript(String applicationName, String javaOpts, String workingDir) {
        this.applicationName = applicationName;
        this.javaOpts = javaOpts;
        this.workingDir = workingDir;
    }
    
    @Override
    public byte[] toByteArray() {
        String script = generateScript();
        return script.getBytes(StandardCharsets.UTF_8);
    }
    
    private String generateScript() {
        return String.format("""
            #!/bin/bash
            #
            # %s startup script
            #
            
            JAVA_OPTS="%s"
            WORKING_DIR="%s"
            
            # Change to working directory
            cd "$WORKING_DIR" || exit 1
            
            # Find Java executable
            if [ -n "$JAVA_HOME" ]; then
                JAVA_EXE="$JAVA_HOME/bin/java"
            else
                JAVA_EXE="java"
            fi
            
            # Execute the JAR
            exec "$JAVA_EXE" $JAVA_OPTS -jar "$0" "$@"
            
            # JAR content follows
            """, applicationName, javaOpts, workingDir);
    }
}

// Usage
LaunchScript customScript = new CustomLaunchScript(
    "MyApp", 
    "-Xmx1g -Dspring.profiles.active=prod",
    "/opt/myapp"
);

try (JarWriter writer = new JarWriter(new File("myapp.jar"), customScript)) {
    // Write JAR contents...
}

Launch Script with Environment Detection

import org.springframework.boot.loader.tools.*;
import java.io.File;
import java.util.Map;

// Launch script that detects and adapts to the runtime environment
public class EnvironmentAwareLaunchScript implements LaunchScript {
    private final Map<String, String> properties;
    
    public EnvironmentAwareLaunchScript(Map<String, String> properties) {
        this.properties = properties;
    }
    
    @Override
    public byte[] toByteArray() {
        String script = """
            #!/bin/bash
            #
            # Environment-aware launch script
            #
            
            # Detect environment
            if [ -f "/etc/debian_version" ]; then
                INIT_SYSTEM="systemd"
                USER_HOME="/home"
            elif [ -f "/etc/redhat-release" ]; then
                INIT_SYSTEM="systemd"
                USER_HOME="/home"
            elif [ "$(uname)" = "Darwin" ]; then
                INIT_SYSTEM="launchd"
                USER_HOME="/Users"
            else
                INIT_SYSTEM="generic"
                USER_HOME="/home"
            fi
            
            # Set environment-specific defaults
            case "$INIT_SYSTEM" in
                systemd)
                    LOG_DIR="/var/log/%s"
                    PID_DIR="/var/run"
                    CONFIG_DIR="/etc/%s"
                    ;;
                launchd)
                    LOG_DIR="$USER_HOME/Library/Logs/%s"
                    PID_DIR="/tmp"
                    CONFIG_DIR="$USER_HOME/Library/Application Support/%s"
                    ;;
                *)
                    LOG_DIR="/tmp/logs/%s"
                    PID_DIR="/tmp"
                    CONFIG_DIR="/tmp/config/%s"
                    ;;
            esac
            
            # Create directories if they don't exist
            mkdir -p "$LOG_DIR" "$PID_DIR" "$CONFIG_DIR"
            
            # Set JVM options based on available memory
            TOTAL_MEM=$(free -m 2>/dev/null | awk 'NR==2{printf "%%d", $2}' || echo "1024")
            if [ "$TOTAL_MEM" -gt 4096 ]; then
                JAVA_OPTS="-Xmx2g -Xms1g"
            elif [ "$TOTAL_MEM" -gt 2048 ]; then
                JAVA_OPTS="-Xmx1g -Xms512m"
            else
                JAVA_OPTS="-Xmx512m -Xms256m"
            fi
            
            # Add application-specific properties
            JAVA_OPTS="$JAVA_OPTS -Dlogging.file.path=$LOG_DIR"
            JAVA_OPTS="$JAVA_OPTS -Dspring.config.additional-location=$CONFIG_DIR/"
            
            # Execute the application
            exec java $JAVA_OPTS -jar "$0" "$@"
            
            """.formatted(
                properties.getOrDefault("appName", "app"),
                properties.getOrDefault("appName", "app"),
                properties.getOrDefault("appName", "app"),
                properties.getOrDefault("appName", "app"),
                properties.getOrDefault("appName", "app")
            );
        
        return script.getBytes(StandardCharsets.UTF_8);
    }
}

Launch Script Properties

Common template variables supported by DefaultLaunchScript:

PropertyDescriptionDefault
initInfoProvidesService nameApplication name
initInfoShortDescriptionBrief descriptionApplication description
initInfoDescriptionFull descriptionApplication description
confFolderConfiguration directorySame as JAR location
logFolderLog file directory/var/log
pidFolderPID file directory/var/run
logFilenameLog file nameApplication name + .log
javaOptsJVM optionsEmpty
runAsUserUser to run asCurrent user
modeExecution modeauto
useStartStopDaemonUse system daemontrue
stopWaitTimeStop timeout (seconds)60

System Integration

Launch scripts enable Spring Boot JARs to integrate seamlessly with system service managers:

  • systemd (Modern Linux distributions)
  • init.d (Traditional Unix systems)
  • launchd (macOS)
  • Windows Services (with additional tooling)

The generated executable JARs can be installed as system services, providing process management, automatic restart, and system integration capabilities.

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-boot--spring-boot-loader-tools

docs

build-integration.md

image-packaging.md

index.md

jar-writing.md

launch-scripts.md

layer-support.md

layout-management.md

library-management.md

main-class-detection.md

repackaging.md

tile.json