Skip to content

Commit

Permalink
Merge pull request #745 from timcharper/master
Browse files Browse the repository at this point in the history
SystemD services now source /etc/default/{{app_name}} (resolves #737)
  • Loading branch information
muuki88 committed Feb 23, 2016
2 parents 12c1549 + af63778 commit e32a059
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -9,3 +9,4 @@ project/project
log/
target/
.cache
.ensime*
@@ -0,0 +1,29 @@
# #####################################
# ##### Environment Configuration #####
# #####################################

# This file is parsed by systemd. You can modify it to specify environment
# variables for your application.
#
# For a description of the format, see: `man systemd.exec`, section
# `EnvironmentFile`.

# Available replacements
# ------------------------------------------------
# ${{author}} debian author
# ${{descr}} debian package description
# ${{exec}} startup script name
# ${{chdir}} app directory
# ${{retries}} retries for startup
# ${{retryTimeout}} retry timeout
# ${{app_name}} normalized app name
# ${{daemon_user}} daemon user
# -------------------------------------------------

# Setting JAVA_OPTS
# -----------------
# JAVA_OPTS="-Dpidfile.path=/var/run/${{app_name}}/play.pid"

# Setting PIDFILE
# ---------------
# PIDFILE="/var/run/${{app_name}}/play.pid"
Expand Up @@ -5,6 +5,7 @@ Requires=${{start_facilities}}
[Service]
Type=simple
WorkingDirectory=${{chdir}}
EnvironmentFile=${{env_config}}
ExecStart=${{chdir}}/bin/${{exec}}
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
Expand Down
Expand Up @@ -13,6 +13,8 @@ trait JavaAppKeys {
val bashScriptDefines = TaskKey[Seq[String]]("bashScriptDefines", "A list of definitions that should be written to the bash file template.")
val bashScriptExtraDefines = TaskKey[Seq[String]]("bashScriptExtraDefines", "A list of extra definitions that should be written to the bash file template.")
val bashScriptConfigLocation = TaskKey[Option[String]]("bashScriptConfigLocation", "The location where the bash script will load default argument configuration from.")
// TODO - we should change this key name in future versions; it also specified
// the location of the systemd EnvironmentFile
val bashScriptEnvConfigLocation = SettingKey[Option[String]]("bashScriptEnvConfigLocation", "The location of a bash script that will be sourced before running the app.")
val batScriptExtraDefines = TaskKey[Seq[String]]("batScriptExtraDefines", "A list of extra definitions that should be written to the bat file template.")
val scriptClasspathOrdering = TaskKey[Seq[(File, String)]]("scriptClasspathOrdering", "The order of the classpath used at runtime for the bat/bash scripts.")
Expand Down
Expand Up @@ -39,8 +39,6 @@ object JavaServerAppPackaging extends AutoPlugin {
/** These settings will be provided by this archetype*/
def javaServerSettings: Seq[Setting[_]] = linuxSettings ++ debianSettings ++ rpmSettings

protected def etcDefaultTemplateSource: java.net.URL = getClass.getResource(ETC_DEFAULT + "-template")

/**
* general settings which apply to all linux server archetypes
*
Expand All @@ -59,27 +57,30 @@ object JavaServerAppPackaging extends AutoPlugin {
},
// === etc config mapping ===
bashScriptEnvConfigLocation := Some("/etc/default/" + (packageName in Linux).value),
linuxEtcDefaultTemplate <<= sourceDirectory map { dir =>
val overrideScript = dir / "templates" / ETC_DEFAULT
if (overrideScript.exists) overrideScript.toURI.toURL
else etcDefaultTemplateSource
},
linuxStartScriptName := None,
makeEtcDefault <<= (packageName in Linux, target in Universal, linuxEtcDefaultTemplate, linuxScriptReplacements)
map makeEtcDefaultScript,
linuxPackageMappings <++= (makeEtcDefault, bashScriptEnvConfigLocation) map { (conf, envLocation) =>
val mapping = for (
path <- envLocation;
c <- conf
) yield LinuxPackageMapping(Seq(c -> path), LinuxFileMetaData(Users.Root, Users.Root, "644")).withConfig()

mapping.toSeq
}

linuxStartScriptName := None
)

/* etcDefaultConfig is dependent on serverLoading (systemd, systemv, etc.),
* and is therefore distro specific. As such, these settings cannot be defined
* in the global config scope. */
private[this] val etcDefaultConfig: Seq[Setting[_]] = Seq(
linuxEtcDefaultTemplate := getEtcTemplateSource(
sourceDirectory.value,
serverLoading.value),
makeEtcDefault := makeEtcDefaultScript(
(packageName in Linux).value,
(target in Universal).value,
linuxEtcDefaultTemplate.value,
linuxScriptReplacements.value),
linuxPackageMappings ++= etcDefaultMapping(
makeEtcDefault.value,
bashScriptEnvConfigLocation.value)
)

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

def rpmSettings: Seq[Setting[_]] = {
import RpmPlugin.Names.{ Pre, Post, Preun, Postun }
inConfig(Rpm)(etcDefaultConfig) ++
inConfig(Rpm)(Seq(
serverLoading := SystemV,
startRunlevels <<= (serverLoading) apply defaultStartRunlevels,
Expand Down Expand Up @@ -242,6 +244,45 @@ object JavaServerAppPackaging extends AutoPlugin {
}
}

/* Find the template source for the given Server loading scheme, with cascading fallback
* If the serverLoader scheme is SystemD, then searches for files in this order:
*
* (assuming sourceDirectory is `src`)
*
* - src/templates/etc-default-systemd
* - src/templates/etc-default
* - Provided template
*/

private[this] def getEtcTemplateSource(sourceDirectory: File, loader: ServerLoader): java.net.URL = {
val (suffix, default) = loader match {
case Upstart =>
("-upstart", getClass.getResource(ETC_DEFAULT + "-template"))
case SystemV =>
("-systemv", getClass.getResource(ETC_DEFAULT + "-template"))
case Systemd =>
("-systemd", getClass.getResource(ETC_DEFAULT + "-systemd-template"))
}

val overrides = List[File](
sourceDirectory / "templates" / (ETC_DEFAULT + suffix),
sourceDirectory / "templates" / ETC_DEFAULT)
overrides.
find(_.exists).
map(_.toURI.toURL).
getOrElse(default)
}

// Used to tell our packager to install our /etc/default/{{appName}} config file.
protected def etcDefaultMapping(conf: Option[File], envLocation: Option[String]): Seq[LinuxPackageMapping] = {
val mapping = for (
path <- envLocation;
c <- conf
) yield LinuxPackageMapping(Seq(c -> path), LinuxFileMetaData(Users.Root, Users.Root, "644")).withConfig()

mapping.toSeq
}

protected def startScriptMapping(name: String, script: Option[File], loader: ServerLoader, scriptDir: String, scriptName: Option[String]): Seq[LinuxPackageMapping] = {
val (path, permissions, isConf) = loader match {
case Upstart => ("/etc/init/" + scriptName.getOrElse(name + ".conf"), "0644", "true")
Expand Down
35 changes: 35 additions & 0 deletions src/sbt-test/debian/override-etc-default/build.sbt
@@ -0,0 +1,35 @@
import com.typesafe.sbt.packager.archetypes.ServerLoader

enablePlugins(JavaServerAppPackaging, JDebPackaging)

serverLoading in Debian := ServerLoader.Upstart

// TODO change this after #437 is fixed
daemonUser in Linux := "root"

daemonGroup in Linux := "app-group"

mainClass in Compile := Some("empty")

name := "debian-test"

name in Debian := "debian-test"

version := "0.1.0"

maintainer := "Josh Suereth <joshua.suereth@typesafe.com>"

packageSummary := "Test debian package"

packageDescription := """A fun package description of our software,
with multiple lines."""

TaskKey[Unit]("check-etc-default") <<= (target, streams) map { (target, out) =>
val extracted = target / "tmp" / "extracted-package"
extracted.mkdirs()
Seq("dpkg-deb", "-R", (target / "debian-test_0.1.0_all.deb").absolutePath, extracted.absolutePath).!

val script = IO.read(extracted / "etc" / "default" / "debian-test")
assert(script.startsWith("# right etc-default template"), s"etc-default script wasn't picked, contents instead are:\n$script")
()
}
3 changes: 3 additions & 0 deletions src/sbt-test/debian/override-etc-default/project/plugins.sbt
@@ -0,0 +1,3 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))

libraryDependencies += "org.vafer" % "jdeb" % "1.3" artifacts (Artifact("jdeb", "jar", "jar"))
@@ -0,0 +1 @@
# right etc-default template
6 changes: 6 additions & 0 deletions src/sbt-test/debian/override-etc-default/test
@@ -0,0 +1,6 @@
# Run the debian packaging.
> debian:packageBin
$ exists target/debian-test_0.1.0_all.deb

# Check files for defaults
> check-etc-default
7 changes: 7 additions & 0 deletions src/sbt-test/debian/systemd-deb/build.sbt
Expand Up @@ -23,6 +23,13 @@ TaskKey[Unit]("check-startup-script") <<= (target, streams) map { (target, out)
val script = IO.read(target / "debian-test-0.1.0" / "usr" / "lib" / "systemd" / "system" / "debian-test.service")
assert(script.contains("Requires=network.target"), "script doesn't contain Default-Start header\n" + script)
assert(script.contains("User=testuser"), "script doesn't contain `User` header\n" + script)
assert(script.contains("EnvironmentFile=/etc/default/debian-test"), "script doesn't contain EnvironmentFile header\n" + script)
out.log.success("Successfully tested systemd start up script")
()
}

TaskKey[Unit]("check-etc-default") <<= (target, streams) map { (target, out) =>
val script = IO.read(target / "debian-test-0.1.0" / "etc" / "default" / "debian-test")
assert(script.contains("systemd"), s"systemd etc-default template wasn't selected; contents are:\n" + script)
()
}
3 changes: 2 additions & 1 deletion src/sbt-test/debian/systemd-deb/test
Expand Up @@ -4,4 +4,5 @@ $ exists target/debian-test_0.1.0_all.deb

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

> check-startup-script
> check-startup-script
> check-etc-default
16 changes: 14 additions & 2 deletions src/sphinx/archetypes/cheatsheet.rst
Expand Up @@ -228,9 +228,21 @@ You can use ``${{variable_name}}`` to reference variables when writing your scri

.. _server-app-config:

Server App Config - ``src/templates/etc-default``
Server App Config - ``src/templates/etc-default-{systemv,systemd}``
-------------------------------------------------

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

The file `/etc/default/<application>` is used as follows given the loader:

- `systemv`: sourced as a bourne script.
- `systemd`: used as an EnvironmentFile directive parameter (see `man
systemd.exec`, section `EnvironmentFile` for a description of the expected file
format).
- `upstart`: presently ignored.

If you're only overriding `JAVA_OPTS`, your environment file could be compatible
with both systemv and systemd loaders; if such is the case, you can specify a
single file at `src/templates/etc-default` which will serve as an override for
all loaders.
8 changes: 5 additions & 3 deletions src/sphinx/archetypes/java_server/customize.rst
Expand Up @@ -21,11 +21,13 @@ Linux Configuration
There are different ways described in :doc:`Customizing the Application </archetypes/java_app/customize>`
and can be used the same way.


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

Example `/etc/default/<normalizedName>` for SystemV:

.. code-block :: bash
# Available replacements
Expand Down
2 changes: 1 addition & 1 deletion src/sphinx/archetypes/java_server/my-first-project.rst
Expand Up @@ -86,7 +86,7 @@ rights. **<package>** is a placeholder for your actual application name. By defa
Folder User Permissions Purpose
=============================== ====== =========== =======
/usr/share/**<package>** root 755 / (655) static, non-changeable files
/etc/default/**<package>**.conf root 644 default config file
/etc/default/**<package>** root 644 default config file
/etc/**<package>** root 644 config folder -> link to /usr/share/**<package-name>**/conf
/var/run/**<package>** daemon 644 if the application generates a pid on its own
/var/log/**<package>** daemon 644 log folder -> symlinked from /usr/share/**<package>**/log
Expand Down

0 comments on commit e32a059

Please sign in to comment.