0
# Launch Script Generation
1
2
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.
3
4
## Capabilities
5
6
### Launch Script Interface
7
8
Core interface for launch scripts that can be prepended to JAR files.
9
10
```java { .api }
11
public interface LaunchScript {
12
/**
13
* Get the launch script content as a byte array.
14
* The script is prepended to the JAR file to make it executable.
15
*
16
* @return the script content as bytes
17
*/
18
byte[] toByteArray();
19
}
20
```
21
22
### Default Launch Script
23
24
Standard implementation that provides a configurable Unix shell script for launching JAR files.
25
26
```java { .api }
27
public class DefaultLaunchScript implements LaunchScript {
28
/**
29
* Create a launch script from a template file with property substitution.
30
*
31
* @param file the script template file
32
* @param properties properties for template variable substitution
33
* @throws IOException if the template file cannot be read
34
*/
35
public DefaultLaunchScript(File file, Map<?, ?> properties) throws IOException;
36
37
/**
38
* Get the launch script content as bytes.
39
*
40
* @return the processed script content
41
*/
42
@Override
43
public byte[] toByteArray();
44
}
45
```
46
47
## Usage Examples
48
49
### Basic Launch Script
50
51
```java
52
import org.springframework.boot.loader.tools.*;
53
import java.io.File;
54
import java.util.Map;
55
56
// Create a basic launch script
57
File scriptTemplate = new File("src/main/scripts/launch.sh");
58
Map<String, String> properties = Map.of(
59
"initInfoProvides", "myapp",
60
"initInfoShortDescription", "My Spring Boot Application"
61
);
62
63
LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, properties);
64
65
// Use with JarWriter
66
File targetJar = new File("myapp.jar");
67
try (JarWriter writer = new JarWriter(targetJar, launchScript)) {
68
// Write JAR contents
69
// ... (JAR writing code)
70
}
71
72
// The resulting JAR can be executed directly:
73
// chmod +x myapp.jar
74
// ./myapp.jar
75
```
76
77
### Advanced Launch Script Configuration
78
79
```java
80
import org.springframework.boot.loader.tools.*;
81
import java.io.File;
82
import java.util.HashMap;
83
import java.util.Map;
84
85
// Configure advanced launch script properties
86
Map<String, String> properties = new HashMap<>();
87
88
// Service information
89
properties.put("initInfoProvides", "myapp");
90
properties.put("initInfoShortDescription", "My Spring Boot Application");
91
properties.put("initInfoDescription", "A comprehensive Spring Boot application for data processing");
92
93
// Runtime configuration
94
properties.put("confFolder", "/etc/myapp");
95
properties.put("logFolder", "/var/log/myapp");
96
properties.put("pidFolder", "/var/run/myapp");
97
properties.put("logFilename", "myapp.log");
98
99
// JVM options
100
properties.put("javaOpts", "-Xmx1024m -Xms512m -Dspring.profiles.active=production");
101
102
// User and permissions
103
properties.put("runAsUser", "myapp");
104
properties.put("mode", "service"); // or "auto", "force", "run"
105
106
// File locations
107
properties.put("useStartStopDaemon", "true");
108
properties.put("stopWaitTime", "60");
109
110
File scriptTemplate = new File("launch-template.sh");
111
LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, properties);
112
113
// Create executable JAR
114
File targetJar = new File("myapp.jar");
115
try (JarWriter writer = new JarWriter(targetJar, launchScript)) {
116
// Write JAR contents...
117
}
118
119
// The JAR can now be used as a system service:
120
// sudo cp myapp.jar /etc/init.d/myapp
121
// sudo chmod +x /etc/init.d/myapp
122
// sudo update-rc.d myapp defaults
123
// sudo service myapp start
124
```
125
126
### Custom Launch Script Template
127
128
Create a custom launch script template (`launch-template.sh`):
129
130
```bash
131
#!/bin/bash
132
#
133
# {{initInfoProvides}} {{initInfoShortDescription}}
134
#
135
# chkconfig: 35 80 20
136
# description: {{initInfoDescription}}
137
#
138
139
. /lib/lsb/init-functions
140
141
USER="{{runAsUser}}"
142
DAEMON="{{initInfoProvides}}"
143
ROOT_DIR="/var/lib/{{initInfoProvides}}"
144
145
SERVER="$ROOT_DIR/{{initInfoProvides}}.jar"
146
LOCK_FILE="/var/lock/subsys/{{initInfoProvides}}"
147
148
start() {
149
log_daemon_msg "Starting $DAEMON"
150
if [ ! -f "$SERVER" ]; then
151
log_failure_msg "$SERVER not found"
152
exit 1
153
fi
154
155
if start-stop-daemon --start --quiet --oknodo --pidfile "$PID_FILE" \
156
--chuid "$USER" --background --make-pidfile \
157
--exec /usr/bin/java -- {{javaOpts}} -jar "$SERVER"
158
then
159
log_end_msg 0
160
touch "$LOCK_FILE"
161
else
162
log_end_msg 1
163
fi
164
}
165
166
stop() {
167
log_daemon_msg "Stopping $DAEMON"
168
if start-stop-daemon --stop --quiet --oknodo --pidfile "$PID_FILE"
169
then
170
log_end_msg 0
171
rm -f "$LOCK_FILE"
172
else
173
log_end_msg 1
174
fi
175
}
176
177
case "$1" in
178
start)
179
start
180
;;
181
stop)
182
stop
183
;;
184
restart)
185
stop
186
start
187
;;
188
status)
189
status_of_proc -p $PID_FILE "$DAEMON" "$DAEMON" && exit 0 || exit $?
190
;;
191
*)
192
echo "Usage: $0 {start|stop|restart|status}"
193
exit 1
194
;;
195
esac
196
197
exit 0
198
199
# The actual JAR content follows after this script
200
```
201
202
### Integration with Repackager
203
204
```java
205
import org.springframework.boot.loader.tools.*;
206
import java.io.File;
207
import java.util.Map;
208
209
// Create executable JAR with launch script using Repackager
210
File sourceJar = new File("target/myapp.jar");
211
File destination = new File("dist/myapp");
212
213
Repackager repackager = new Repackager(sourceJar);
214
215
// Configure launch script
216
File scriptTemplate = getClass().getResource("/launch-template.sh").getFile();
217
Map<String, String> scriptProperties = Map.of(
218
"initInfoProvides", "myapp",
219
"initInfoShortDescription", "My Application",
220
"runAsUser", "appuser",
221
"javaOpts", "-Xmx512m -Dspring.profiles.active=prod"
222
);
223
224
LaunchScript launchScript = new DefaultLaunchScript(scriptTemplate, scriptProperties);
225
226
// Repackage with launch script
227
repackager.repackage(destination, Libraries.NONE, launchScript);
228
229
// Set executable permissions
230
destination.setExecutable(true, false);
231
232
System.out.println("Created executable JAR: " + destination.getAbsolutePath());
233
System.out.println("Run with: " + destination.getAbsolutePath());
234
```
235
236
### Conditional Launch Script
237
238
```java
239
import org.springframework.boot.loader.tools.*;
240
import java.io.File;
241
import java.util.Map;
242
243
// Only add launch script for Unix-like systems
244
public class ConditionalLaunchScript {
245
public static LaunchScript createLaunchScript() {
246
String osName = System.getProperty("os.name").toLowerCase();
247
if (osName.contains("windows")) {
248
// No launch script for Windows
249
return null;
250
}
251
252
// Create launch script for Unix-like systems
253
try {
254
File template = new File("unix-launch-template.sh");
255
Map<String, String> props = Map.of(
256
"initInfoProvides", "myapp",
257
"initInfoShortDescription", "My App"
258
);
259
return new DefaultLaunchScript(template, props);
260
} catch (Exception e) {
261
System.err.println("Failed to create launch script: " + e.getMessage());
262
return null;
263
}
264
}
265
}
266
267
// Usage
268
File sourceJar = new File("myapp.jar");
269
Repackager repackager = new Repackager(sourceJar);
270
271
LaunchScript launchScript = ConditionalLaunchScript.createLaunchScript();
272
if (launchScript != null) {
273
repackager.repackage(Libraries.NONE, launchScript);
274
System.out.println("Created executable JAR with launch script");
275
} else {
276
repackager.repackage(Libraries.NONE);
277
System.out.println("Created JAR without launch script");
278
}
279
```
280
281
### Custom Launch Script Implementation
282
283
```java
284
import org.springframework.boot.loader.tools.LaunchScript;
285
import java.io.ByteArrayOutputStream;
286
import java.io.IOException;
287
import java.io.InputStream;
288
import java.nio.charset.StandardCharsets;
289
290
// Custom launch script implementation
291
public class CustomLaunchScript implements LaunchScript {
292
private final String applicationName;
293
private final String javaOpts;
294
private final String workingDir;
295
296
public CustomLaunchScript(String applicationName, String javaOpts, String workingDir) {
297
this.applicationName = applicationName;
298
this.javaOpts = javaOpts;
299
this.workingDir = workingDir;
300
}
301
302
@Override
303
public byte[] toByteArray() {
304
String script = generateScript();
305
return script.getBytes(StandardCharsets.UTF_8);
306
}
307
308
private String generateScript() {
309
return String.format("""
310
#!/bin/bash
311
#
312
# %s startup script
313
#
314
315
JAVA_OPTS="%s"
316
WORKING_DIR="%s"
317
318
# Change to working directory
319
cd "$WORKING_DIR" || exit 1
320
321
# Find Java executable
322
if [ -n "$JAVA_HOME" ]; then
323
JAVA_EXE="$JAVA_HOME/bin/java"
324
else
325
JAVA_EXE="java"
326
fi
327
328
# Execute the JAR
329
exec "$JAVA_EXE" $JAVA_OPTS -jar "$0" "$@"
330
331
# JAR content follows
332
""", applicationName, javaOpts, workingDir);
333
}
334
}
335
336
// Usage
337
LaunchScript customScript = new CustomLaunchScript(
338
"MyApp",
339
"-Xmx1g -Dspring.profiles.active=prod",
340
"/opt/myapp"
341
);
342
343
try (JarWriter writer = new JarWriter(new File("myapp.jar"), customScript)) {
344
// Write JAR contents...
345
}
346
```
347
348
### Launch Script with Environment Detection
349
350
```java
351
import org.springframework.boot.loader.tools.*;
352
import java.io.File;
353
import java.util.Map;
354
355
// Launch script that detects and adapts to the runtime environment
356
public class EnvironmentAwareLaunchScript implements LaunchScript {
357
private final Map<String, String> properties;
358
359
public EnvironmentAwareLaunchScript(Map<String, String> properties) {
360
this.properties = properties;
361
}
362
363
@Override
364
public byte[] toByteArray() {
365
String script = """
366
#!/bin/bash
367
#
368
# Environment-aware launch script
369
#
370
371
# Detect environment
372
if [ -f "/etc/debian_version" ]; then
373
INIT_SYSTEM="systemd"
374
USER_HOME="/home"
375
elif [ -f "/etc/redhat-release" ]; then
376
INIT_SYSTEM="systemd"
377
USER_HOME="/home"
378
elif [ "$(uname)" = "Darwin" ]; then
379
INIT_SYSTEM="launchd"
380
USER_HOME="/Users"
381
else
382
INIT_SYSTEM="generic"
383
USER_HOME="/home"
384
fi
385
386
# Set environment-specific defaults
387
case "$INIT_SYSTEM" in
388
systemd)
389
LOG_DIR="/var/log/%s"
390
PID_DIR="/var/run"
391
CONFIG_DIR="/etc/%s"
392
;;
393
launchd)
394
LOG_DIR="$USER_HOME/Library/Logs/%s"
395
PID_DIR="/tmp"
396
CONFIG_DIR="$USER_HOME/Library/Application Support/%s"
397
;;
398
*)
399
LOG_DIR="/tmp/logs/%s"
400
PID_DIR="/tmp"
401
CONFIG_DIR="/tmp/config/%s"
402
;;
403
esac
404
405
# Create directories if they don't exist
406
mkdir -p "$LOG_DIR" "$PID_DIR" "$CONFIG_DIR"
407
408
# Set JVM options based on available memory
409
TOTAL_MEM=$(free -m 2>/dev/null | awk 'NR==2{printf "%%d", $2}' || echo "1024")
410
if [ "$TOTAL_MEM" -gt 4096 ]; then
411
JAVA_OPTS="-Xmx2g -Xms1g"
412
elif [ "$TOTAL_MEM" -gt 2048 ]; then
413
JAVA_OPTS="-Xmx1g -Xms512m"
414
else
415
JAVA_OPTS="-Xmx512m -Xms256m"
416
fi
417
418
# Add application-specific properties
419
JAVA_OPTS="$JAVA_OPTS -Dlogging.file.path=$LOG_DIR"
420
JAVA_OPTS="$JAVA_OPTS -Dspring.config.additional-location=$CONFIG_DIR/"
421
422
# Execute the application
423
exec java $JAVA_OPTS -jar "$0" "$@"
424
425
""".formatted(
426
properties.getOrDefault("appName", "app"),
427
properties.getOrDefault("appName", "app"),
428
properties.getOrDefault("appName", "app"),
429
properties.getOrDefault("appName", "app"),
430
properties.getOrDefault("appName", "app")
431
);
432
433
return script.getBytes(StandardCharsets.UTF_8);
434
}
435
}
436
```
437
438
## Launch Script Properties
439
440
Common template variables supported by `DefaultLaunchScript`:
441
442
| Property | Description | Default |
443
|----------|-------------|---------|
444
| `initInfoProvides` | Service name | Application name |
445
| `initInfoShortDescription` | Brief description | Application description |
446
| `initInfoDescription` | Full description | Application description |
447
| `confFolder` | Configuration directory | Same as JAR location |
448
| `logFolder` | Log file directory | `/var/log` |
449
| `pidFolder` | PID file directory | `/var/run` |
450
| `logFilename` | Log file name | Application name + `.log` |
451
| `javaOpts` | JVM options | Empty |
452
| `runAsUser` | User to run as | Current user |
453
| `mode` | Execution mode | `auto` |
454
| `useStartStopDaemon` | Use system daemon | `true` |
455
| `stopWaitTime` | Stop timeout (seconds) | `60` |
456
457
## System Integration
458
459
Launch scripts enable Spring Boot JARs to integrate seamlessly with system service managers:
460
461
- **systemd** (Modern Linux distributions)
462
- **init.d** (Traditional Unix systems)
463
- **launchd** (macOS)
464
- **Windows Services** (with additional tooling)
465
466
The generated executable JARs can be installed as system services, providing process management, automatic restart, and system integration capabilities.