From 65facca7ee99894388c05d2db4901960012eed6c Mon Sep 17 00:00:00 2001 From: Tim Harper Date: Sun, 21 Feb 2016 12:57:54 -0700 Subject: [PATCH] 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 --- .gitignore | 1 + .../archetypes/etc-default-systemd-template | 29 +++++++ .../systemloader/systemd/start-template | 1 + .../sbt/packager/archetypes/JavaAppKeys.scala | 2 + .../archetypes/JavaServerApplication.scala | 77 ++++++++++++++----- .../debian/override-etc-default/build.sbt | 35 +++++++++ .../override-etc-default/project/plugins.sbt | 3 + .../src/templates/etc-default | 1 + src/sbt-test/debian/override-etc-default/test | 6 ++ src/sbt-test/debian/systemd-deb/build.sbt | 7 ++ src/sbt-test/debian/systemd-deb/test | 3 +- src/sphinx/archetypes/cheatsheet.rst | 16 +++- .../archetypes/java_server/customize.rst | 8 +- .../java_server/my-first-project.rst | 2 +- 14 files changed, 166 insertions(+), 25 deletions(-) create mode 100644 src/main/resources/com/typesafe/sbt/packager/archetypes/etc-default-systemd-template create mode 100644 src/sbt-test/debian/override-etc-default/build.sbt create mode 100644 src/sbt-test/debian/override-etc-default/project/plugins.sbt create mode 100644 src/sbt-test/debian/override-etc-default/src/templates/etc-default create mode 100644 src/sbt-test/debian/override-etc-default/test diff --git a/.gitignore b/.gitignore index be5ce9914..ce25a2e4c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ project/project log/ target/ .cache +.ensime* diff --git a/src/main/resources/com/typesafe/sbt/packager/archetypes/etc-default-systemd-template b/src/main/resources/com/typesafe/sbt/packager/archetypes/etc-default-systemd-template new file mode 100644 index 000000000..2236867f6 --- /dev/null +++ b/src/main/resources/com/typesafe/sbt/packager/archetypes/etc-default-systemd-template @@ -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" diff --git a/src/main/resources/com/typesafe/sbt/packager/archetypes/java_server/systemloader/systemd/start-template b/src/main/resources/com/typesafe/sbt/packager/archetypes/java_server/systemloader/systemd/start-template index 935a19b22..0f9a269f7 100644 --- a/src/main/resources/com/typesafe/sbt/packager/archetypes/java_server/systemloader/systemd/start-template +++ b/src/main/resources/com/typesafe/sbt/packager/archetypes/java_server/systemloader/systemd/start-template @@ -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 diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppKeys.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppKeys.scala index 5e5462678..177f786e2 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppKeys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppKeys.scala @@ -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.") diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala index ffb11028e..5d098a61a 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala @@ -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 * @@ -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 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, @@ -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, @@ -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") diff --git a/src/sbt-test/debian/override-etc-default/build.sbt b/src/sbt-test/debian/override-etc-default/build.sbt new file mode 100644 index 000000000..f1f98cc97 --- /dev/null +++ b/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 " + +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") + () +} diff --git a/src/sbt-test/debian/override-etc-default/project/plugins.sbt b/src/sbt-test/debian/override-etc-default/project/plugins.sbt new file mode 100644 index 000000000..3b2cd007e --- /dev/null +++ b/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")) \ No newline at end of file diff --git a/src/sbt-test/debian/override-etc-default/src/templates/etc-default b/src/sbt-test/debian/override-etc-default/src/templates/etc-default new file mode 100644 index 000000000..16fc23fea --- /dev/null +++ b/src/sbt-test/debian/override-etc-default/src/templates/etc-default @@ -0,0 +1 @@ +# right etc-default template \ No newline at end of file diff --git a/src/sbt-test/debian/override-etc-default/test b/src/sbt-test/debian/override-etc-default/test new file mode 100644 index 000000000..8c5ab3e2c --- /dev/null +++ b/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 diff --git a/src/sbt-test/debian/systemd-deb/build.sbt b/src/sbt-test/debian/systemd-deb/build.sbt index 43c05d7be..3fedacb24 100644 --- a/src/sbt-test/debian/systemd-deb/build.sbt +++ b/src/sbt-test/debian/systemd-deb/build.sbt @@ -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) + () +} diff --git a/src/sbt-test/debian/systemd-deb/test b/src/sbt-test/debian/systemd-deb/test index 46faf98c2..0dd84aabf 100644 --- a/src/sbt-test/debian/systemd-deb/test +++ b/src/sbt-test/debian/systemd-deb/test @@ -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 \ No newline at end of file +> check-startup-script +> check-etc-default \ No newline at end of file diff --git a/src/sphinx/archetypes/cheatsheet.rst b/src/sphinx/archetypes/cheatsheet.rst index 8874063fe..a03bd6123 100644 --- a/src/sphinx/archetypes/cheatsheet.rst +++ b/src/sphinx/archetypes/cheatsheet.rst @@ -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/`` template -used when SystemV is the server loader. +for the corresponding loader. +The file `/etc/default/` 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. diff --git a/src/sphinx/archetypes/java_server/customize.rst b/src/sphinx/archetypes/java_server/customize.rst index 0ad34b5ca..22429e6e2 100644 --- a/src/sphinx/archetypes/java_server/customize.rst +++ b/src/sphinx/archetypes/java_server/customize.rst @@ -21,11 +21,13 @@ Linux Configuration There are different ways described in :doc:`Customizing the Application ` 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/`` +Example `/etc/default/` for SystemV: + .. code-block :: bash # Available replacements diff --git a/src/sphinx/archetypes/java_server/my-first-project.rst b/src/sphinx/archetypes/java_server/my-first-project.rst index dd3b70697..e363d35aa 100644 --- a/src/sphinx/archetypes/java_server/my-first-project.rst +++ b/src/sphinx/archetypes/java_server/my-first-project.rst @@ -86,7 +86,7 @@ rights. **** is a placeholder for your actual application name. By defa Folder User Permissions Purpose =============================== ====== =========== ======= /usr/share/**** root 755 / (655) static, non-changeable files -/etc/default/****.conf root 644 default config file +/etc/default/**** root 644 default config file /etc/**** root 644 config folder -> link to /usr/share/****/conf /var/run/**** daemon 644 if the application generates a pid on its own /var/log/**** daemon 644 log folder -> symlinked from /usr/share/****/log