Skip to content

Commit 65facca

Browse files
committed
SystemD services now source /etc/default/{{app_name}} #737
This change modifies the SystemD template to include the packaged /etc/default environment settings file. Since a sourced SystemD environment file is not the same thing as a bourne shell source script, a different template is used. Those deploying their package to multiple platforms can also specify different /etc/default templates by suffixing `-systemd` to their name, ie: src/templates/etc-default-systemd
1 parent 0ff5b77 commit 65facca

File tree

14 files changed

+166
-25
lines changed

14 files changed

+166
-25
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ project/project
99
log/
1010
target/
1111
.cache
12+
.ensime*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# #####################################
2+
# ##### Environment Configuration #####
3+
# #####################################
4+
5+
# This file is parsed by systemd. You can modify it to specify environment
6+
# variables for your application.
7+
#
8+
# For a description of the format, see: `man systemd.exec`, section
9+
# `EnvironmentFile`.
10+
11+
# Available replacements
12+
# ------------------------------------------------
13+
# ${{author}} debian author
14+
# ${{descr}} debian package description
15+
# ${{exec}} startup script name
16+
# ${{chdir}} app directory
17+
# ${{retries}} retries for startup
18+
# ${{retryTimeout}} retry timeout
19+
# ${{app_name}} normalized app name
20+
# ${{daemon_user}} daemon user
21+
# -------------------------------------------------
22+
23+
# Setting JAVA_OPTS
24+
# -----------------
25+
# JAVA_OPTS="-Dpidfile.path=/var/run/${{app_name}}/play.pid"
26+
27+
# Setting PIDFILE
28+
# ---------------
29+
# PIDFILE="/var/run/${{app_name}}/play.pid"

src/main/resources/com/typesafe/sbt/packager/archetypes/java_server/systemloader/systemd/start-template

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Requires=${{start_facilities}}
55
[Service]
66
Type=simple
77
WorkingDirectory=${{chdir}}
8+
EnvironmentFile=${{env_config}}
89
ExecStart=${{chdir}}/bin/${{exec}}
910
ExecReload=/bin/kill -HUP $MAINPID
1011
Restart=always

src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppKeys.scala

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ trait JavaAppKeys {
1313
val bashScriptDefines = TaskKey[Seq[String]]("bashScriptDefines", "A list of definitions that should be written to the bash file template.")
1414
val bashScriptExtraDefines = TaskKey[Seq[String]]("bashScriptExtraDefines", "A list of extra definitions that should be written to the bash file template.")
1515
val bashScriptConfigLocation = TaskKey[Option[String]]("bashScriptConfigLocation", "The location where the bash script will load default argument configuration from.")
16+
// TODO - we should change this key name in future versions; it also specified
17+
// the location of the systemd EnvironmentFile
1618
val bashScriptEnvConfigLocation = SettingKey[Option[String]]("bashScriptEnvConfigLocation", "The location of a bash script that will be sourced before running the app.")
1719
val batScriptExtraDefines = TaskKey[Seq[String]]("batScriptExtraDefines", "A list of extra definitions that should be written to the bat file template.")
1820
val scriptClasspathOrdering = TaskKey[Seq[(File, String)]]("scriptClasspathOrdering", "The order of the classpath used at runtime for the bat/bash scripts.")

src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala

+59-18
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ object JavaServerAppPackaging extends AutoPlugin {
3939
/** These settings will be provided by this archetype*/
4040
def javaServerSettings: Seq[Setting[_]] = linuxSettings ++ debianSettings ++ rpmSettings
4141

42-
protected def etcDefaultTemplateSource: java.net.URL = getClass.getResource(ETC_DEFAULT + "-template")
43-
4442
/**
4543
* general settings which apply to all linux server archetypes
4644
*
@@ -59,27 +57,30 @@ object JavaServerAppPackaging extends AutoPlugin {
5957
},
6058
// === etc config mapping ===
6159
bashScriptEnvConfigLocation := Some("/etc/default/" + (packageName in Linux).value),
62-
linuxEtcDefaultTemplate <<= sourceDirectory map { dir =>
63-
val overrideScript = dir / "templates" / ETC_DEFAULT
64-
if (overrideScript.exists) overrideScript.toURI.toURL
65-
else etcDefaultTemplateSource
66-
},
67-
linuxStartScriptName := None,
68-
makeEtcDefault <<= (packageName in Linux, target in Universal, linuxEtcDefaultTemplate, linuxScriptReplacements)
69-
map makeEtcDefaultScript,
70-
linuxPackageMappings <++= (makeEtcDefault, bashScriptEnvConfigLocation) map { (conf, envLocation) =>
71-
val mapping = for (
72-
path <- envLocation;
73-
c <- conf
74-
) yield LinuxPackageMapping(Seq(c -> path), LinuxFileMetaData(Users.Root, Users.Root, "644")).withConfig()
75-
76-
mapping.toSeq
77-
}
7860

61+
linuxStartScriptName := None
62+
)
63+
64+
/* etcDefaultConfig is dependent on serverLoading (systemd, systemv, etc.),
65+
* and is therefore distro specific. As such, these settings cannot be defined
66+
* in the global config scope. */
67+
private val etcDefaultConfig: Seq[Setting[_]] = Seq(
68+
linuxEtcDefaultTemplate := getEtcTemplateSource(
69+
sourceDirectory.value,
70+
serverLoading.value),
71+
makeEtcDefault := makeEtcDefaultScript(
72+
(packageName in Linux).value,
73+
(target in Universal).value,
74+
linuxEtcDefaultTemplate.value,
75+
linuxScriptReplacements.value),
76+
linuxPackageMappings ++= etcDefaultMapping(
77+
makeEtcDefault.value,
78+
bashScriptEnvConfigLocation.value)
7979
)
8080

8181
def debianSettings: Seq[Setting[_]] = {
8282
import DebianPlugin.Names.{ Preinst, Postinst, Prerm, Postrm }
83+
inConfig(Debian)(etcDefaultConfig) ++
8384
inConfig(Debian)(Seq(
8485
serverLoading := Upstart,
8586
startRunlevels <<= (serverLoading) apply defaultStartRunlevels,
@@ -131,6 +132,7 @@ object JavaServerAppPackaging extends AutoPlugin {
131132

132133
def rpmSettings: Seq[Setting[_]] = {
133134
import RpmPlugin.Names.{ Pre, Post, Preun, Postun }
135+
inConfig(Rpm)(etcDefaultConfig) ++
134136
inConfig(Rpm)(Seq(
135137
serverLoading := SystemV,
136138
startRunlevels <<= (serverLoading) apply defaultStartRunlevels,
@@ -242,6 +244,45 @@ object JavaServerAppPackaging extends AutoPlugin {
242244
}
243245
}
244246

247+
/* Find the template source for the given Server loading scheme, with cascading fallback
248+
* If the serverLoader scheme is SystemD, then searches for files in this order:
249+
*
250+
* (assuming sourceDirectory is `src`)
251+
*
252+
* - src/templates/etc-default-systemd
253+
* - src/templates/etc-default
254+
* - Provided template
255+
*/
256+
257+
private[this] def getEtcTemplateSource(sourceDirectory: File, loader: ServerLoader): java.net.URL = {
258+
val (suffix, default) = loader match {
259+
case Upstart =>
260+
("-upstart", getClass.getResource(ETC_DEFAULT + "-template"))
261+
case SystemV =>
262+
("-systemv", getClass.getResource(ETC_DEFAULT + "-template"))
263+
case Systemd =>
264+
("-systemd", getClass.getResource(ETC_DEFAULT + "-systemd-template"))
265+
}
266+
267+
val overrides = List[File](
268+
sourceDirectory / "templates" / (ETC_DEFAULT + suffix),
269+
sourceDirectory / "templates" / ETC_DEFAULT)
270+
overrides.
271+
find(_.exists).
272+
map(_.toURI.toURL).
273+
getOrElse(default)
274+
}
275+
276+
// Used to tell our packager to install our /etc/default/{{appName}} config file.
277+
protected def etcDefaultMapping(conf: Option[File], envLocation: Option[String]): Seq[LinuxPackageMapping] = {
278+
val mapping = for (
279+
path <- envLocation;
280+
c <- conf
281+
) yield LinuxPackageMapping(Seq(c -> path), LinuxFileMetaData(Users.Root, Users.Root, "644")).withConfig()
282+
283+
mapping.toSeq
284+
}
285+
245286
protected def startScriptMapping(name: String, script: Option[File], loader: ServerLoader, scriptDir: String, scriptName: Option[String]): Seq[LinuxPackageMapping] = {
246287
val (path, permissions, isConf) = loader match {
247288
case Upstart => ("/etc/init/" + scriptName.getOrElse(name + ".conf"), "0644", "true")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import com.typesafe.sbt.packager.archetypes.ServerLoader
2+
3+
enablePlugins(JavaServerAppPackaging, JDebPackaging)
4+
5+
serverLoading in Debian := ServerLoader.Upstart
6+
7+
// TODO change this after #437 is fixed
8+
daemonUser in Linux := "root"
9+
10+
daemonGroup in Linux := "app-group"
11+
12+
mainClass in Compile := Some("empty")
13+
14+
name := "debian-test"
15+
16+
name in Debian := "debian-test"
17+
18+
version := "0.1.0"
19+
20+
maintainer := "Josh Suereth <[email protected]>"
21+
22+
packageSummary := "Test debian package"
23+
24+
packageDescription := """A fun package description of our software,
25+
with multiple lines."""
26+
27+
TaskKey[Unit]("check-etc-default") <<= (target, streams) map { (target, out) =>
28+
val extracted = target / "tmp" / "extracted-package"
29+
extracted.mkdirs()
30+
Seq("dpkg-deb", "-R", (target / "debian-test_0.1.0_all.deb").absolutePath, extracted.absolutePath).!
31+
32+
val script = IO.read(extracted / "etc" / "default" / "debian-test")
33+
assert(script.startsWith("# right etc-default template"), s"etc-default script wasn't picked, contents instead are:\n$script")
34+
()
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))
2+
3+
libraryDependencies += "org.vafer" % "jdeb" % "1.3" artifacts (Artifact("jdeb", "jar", "jar"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# right etc-default template
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Run the debian packaging.
2+
> debian:packageBin
3+
$ exists target/debian-test_0.1.0_all.deb
4+
5+
# Check files for defaults
6+
> check-etc-default

src/sbt-test/debian/systemd-deb/build.sbt

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ TaskKey[Unit]("check-startup-script") <<= (target, streams) map { (target, out)
2323
val script = IO.read(target / "debian-test-0.1.0" / "usr" / "lib" / "systemd" / "system" / "debian-test.service")
2424
assert(script.contains("Requires=network.target"), "script doesn't contain Default-Start header\n" + script)
2525
assert(script.contains("User=testuser"), "script doesn't contain `User` header\n" + script)
26+
assert(script.contains("EnvironmentFile=/etc/default/debian-test"), "script doesn't contain EnvironmentFile header\n" + script)
2627
out.log.success("Successfully tested systemd start up script")
2728
()
2829
}
30+
31+
TaskKey[Unit]("check-etc-default") <<= (target, streams) map { (target, out) =>
32+
val script = IO.read(target / "debian-test-0.1.0" / "etc" / "default" / "debian-test")
33+
assert(script.contains("systemd"), s"systemd etc-default template wasn't selected; contents are:\n" + script)
34+
()
35+
}

src/sbt-test/debian/systemd-deb/test

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ $ exists target/debian-test_0.1.0_all.deb
44

55
$ exists target/debian-test-0.1.0/usr/lib/systemd/system/debian-test.service
66

7-
> check-startup-script
7+
> check-startup-script
8+
> check-etc-default

src/sphinx/archetypes/cheatsheet.rst

+14-2
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,21 @@ You can use ``${{variable_name}}`` to reference variables when writing your scri
228228

229229
.. _server-app-config:
230230

231-
Server App Config - ``src/templates/etc-default``
231+
Server App Config - ``src/templates/etc-default-{systemv,systemd}``
232232
-------------------------------------------------
233233

234234
Creating a file here will override the ``/etc/default/<application>`` template
235-
used when SystemV is the server loader.
235+
for the corresponding loader.
236236

237+
The file `/etc/default/<application>` is used as follows given the loader:
238+
239+
- `systemv`: sourced as a bourne script.
240+
- `systemd`: used as an EnvironmentFile directive parameter (see `man
241+
systemd.exec`, section `EnvironmentFile` for a description of the expected file
242+
format).
243+
- `upstart`: presently ignored.
244+
245+
If you're only overriding `JAVA_OPTS`, your environment file could be compatible
246+
with both systemv and systemd loaders; if such is the case, you can specify a
247+
single file at `src/templates/etc-default` which will serve as an override for
248+
all loaders.

src/sphinx/archetypes/java_server/customize.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ Linux Configuration
2121
There are different ways described in :doc:`Customizing the Application </archetypes/java_app/customize>`
2222
and can be used the same way.
2323

24-
25-
The server archetype adds an additional way with an ``etc-default`` file placed in ``src/templates``, which currently
26-
only works for **SystemV**. The file gets sourced before the actual startscript is executed.
24+
The server archetype adds an additional way with an ``etc-default`` file placed
25+
in ``src/templates``, which currently only works for **SystemV** and
26+
**systemd**. The file gets sourced before the actual startscript is executed.
2727
The file will be installed to ``/etc/default/<normalizedName>``
2828

29+
Example `/etc/default/<normalizedName>` for SystemV:
30+
2931
.. code-block :: bash
3032
3133
# Available replacements

src/sphinx/archetypes/java_server/my-first-project.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ rights. **<package>** is a placeholder for your actual application name. By defa
8686
Folder User Permissions Purpose
8787
=============================== ====== =========== =======
8888
/usr/share/**<package>** root 755 / (655) static, non-changeable files
89-
/etc/default/**<package>**.conf root 644 default config file
89+
/etc/default/**<package>** root 644 default config file
9090
/etc/**<package>** root 644 config folder -> link to /usr/share/**<package-name>**/conf
9191
/var/run/**<package>** daemon 644 if the application generates a pid on its own
9292
/var/log/**<package>** daemon 644 log folder -> symlinked from /usr/share/**<package>**/log

0 commit comments

Comments
 (0)