diff --git a/src/main/resources/com/typesafe/sbt/packager/archetypes/sysvinit-template b/src/main/resources/com/typesafe/sbt/packager/archetypes/sysvinit-template new file mode 100644 index 000000000..544a7e85f --- /dev/null +++ b/src/main/resources/com/typesafe/sbt/packager/archetypes/sysvinit-template @@ -0,0 +1,49 @@ +#! /bin/sh + +### BEGIN INIT INFO +# Provides: ${{app_name}} +# Required-Start: $syslog +# Required-Stop: $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: +# Short-Description: ${{descr}} +### END INIT INFO + +PIDFILE=/var/run/${{app_name}}.pid +DAEMON_USER=${{daemon_user}} + +. /lib/init/vars.sh +. /lib/lsb/init-functions + +get_java_cmd() { + if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then + echo "$JAVA_HOME/bin/java" + else + echo "java" + fi +} + +JAVA_CMD=$(get_java_cmd) + +RUN_CMD=$JAVA_CMD -cp ${{app_classpath}} ${{app_main_class}} + +case "$1" in + +start) log_daemon_msg "Starting ${{app_name}}" + + start-stop-daemon --background --start --chuid $DAEMON_USER --make-pidfile --pidfile $PIDFILE --exec $RUN_CMD + + ;; +stop) log_daemon_msg "Stopping ${{app_name}}" + + start-stop-daemon --stop --pidfile $PIDFILE --chuid $DAEMON_USER + + RETVAL=$? + [ $RETVAL -eq 0 ] && [ -e "$PIDFILE" ] && rm -f $PIDFILE + exit 2 + ;; +*) log_daemon_msg "Usage: /etc/init.d/${{app_name}} {start|stop}" + exit 2 + ;; +esac +exit 0 \ No newline at end of file diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala index 071405dbc..bfbdff0fc 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala @@ -1,17 +1,37 @@ package com.typesafe.sbt.packager.archetypes /** - * Constructs an upstart script for running a java application. - * - * Makes use of the associated upstart-template, with a few hooks + * Constructs an start script for running a java application. * */ -object JavaAppUpstartScript { +object JavaAppStartScript { + + import ServerLoader._ + + protected def upstartTemplateSource: java.net.URL = getClass.getResource("upstart-template") + protected def sysvinitTemplateSource: java.net.URL = getClass.getResource("sysvinit-template") + + protected def postinstTemplateSource: java.net.URL = getClass.getResource("postinst-template") + protected def preremTemplateSource: java.net.URL = getClass.getResource("prerem-template") + + + def generateScript(replacements: Seq[(String, String)], loader: ServerLoader): String = + loader match { + case Upstart => + TemplateWriter.generateScript(upstartTemplateSource, replacements) + case SystemV => + TemplateWriter.generateScript(sysvinitTemplateSource, replacements) + } + + + def generatePrerm(appName: String): String = + TemplateWriter.generateScript(preremTemplateSource, Seq("app_name" -> appName)) + + + def generatePostinst(appName: String): String = + TemplateWriter.generateScript(postinstTemplateSource, Seq("app_name" -> appName)) - private[this] def upstartTemplateSource: java.net.URL = getClass.getResource("upstart-template") - private[this] def postinstTemplateSource: java.net.URL = getClass.getResource("postinst-template") - private[this] def preremTemplateSource: java.net.URL = getClass.getResource("prerem-template") /** * * @param author - @@ -24,23 +44,31 @@ object JavaAppUpstartScript { */ def makeReplacements( author: String, - descr: String, + description: String, execScript: String, chdir: String, + appName: String, + appMainClass: String, + appClasspath: String, + daemonUser: String, retries: Int = 0, - retryTimeout: Int = 60): Seq[(String, String)] = Seq( - "exec" -> execScript, - "author" -> author, - "descr" -> descr, - "chdir" -> chdir, - "retries" -> retries.toString, - "retryTimeout" -> retryTimeout.toString) + retryTimeout: Int = 60): Seq[(String, String)] = + Seq( + "author" -> author, + "descr" -> description, + "exec" -> execScript, + "chdir" -> chdir, + "retries" -> retries.toString, + "retryTimeout" -> retryTimeout.toString, + "app_name" -> appName, + "app_main_class" -> appMainClass, + "app_classpath" -> appClasspath, + "daemon_user" -> daemonUser + ) +} - def generateScript(replacements: Seq[(String, String)]): String = - TemplateWriter.generateScript(upstartTemplateSource, replacements) - def generatePrerm(appName: String): String = - TemplateWriter.generateScript(preremTemplateSource, Seq("app_name" -> appName)) - def generatePostinst(appName: String): String = - TemplateWriter.generateScript(postinstTemplateSource, Seq("app_name" -> appName)) +object ServerLoader extends Enumeration { + type ServerLoader = Value + val Upstart, SystemV = Value } 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 30b0934cc..5ef9f5b42 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala @@ -20,48 +20,70 @@ import com.typesafe.sbt.packager.linux.LinuxPackageMapping * **NOTE: EXPERIMENTAL** This currently only supports debian upstart scripts. */ object JavaServerAppPackaging { + import ServerLoader._ - def settings: Seq[Setting[_]] = - JavaAppPackaging.settings ++ - debianUpstartSettings + def settings: Seq[Setting[_]] = JavaAppPackaging.settings ++ debianSettings - def debianUpstartSettings: Seq[Setting[_]] = + def debianSettings: Seq[Setting[_]] = Seq( - debianUpstartScriptReplacements <<= (maintainer in Debian, packageSummary in Debian, normalizedName, sbt.Keys.version, defaultLinuxInstallLocation) map { (author, descr, name, version, installLocation) => + debianStartScriptReplacements <<= ( + maintainer in Debian, packageSummary in Debian, serverLoading in Debian, daemonUser in Debian, normalizedName, sbt.Keys.version, defaultLinuxInstallLocation, sbt.Keys.mainClass in Compile, scriptClasspath) + map { (author, descr, loader, daemonUser, name, version, installLocation, mainClass, cp) => // TODO name-version is copied from UniversalPlugin. This should be consolidated into a setting (install location...) - val chdir = installLocation + "/" + name + "/bin" - JavaAppUpstartScript.makeReplacements(author = author, descr = descr, execScript = name, chdir = chdir) + val appDir = installLocation + "/" + name + val chdir = appDir + "/bin" + val appClasspath = cp.map(appDir + "/lib/" + _).mkString(":") + + JavaAppStartScript.makeReplacements( + author = author, + description = descr, + execScript = name, + chdir = chdir, + appName = name, + appClasspath = appClasspath, + appMainClass = mainClass.get, + daemonUser = daemonUser + ) }, - debianMakeUpstartScript <<= (debianUpstartScriptReplacements, normalizedName, target in Universal) map makeDebianUpstartScript, - linuxPackageMappings in Debian <++= (debianMakeUpstartScript, normalizedName) map { (script, name) => + debianMakeStartScript <<= (debianStartScriptReplacements, normalizedName, target in Universal, serverLoading in Debian) map makeDebianStartScript, + linuxPackageMappings in Debian <++= (debianMakeStartScript, normalizedName, serverLoading in Debian) map { (script, name, loader) => + val (path, permissions) = loader match { + case Upstart => ("/etc/init/" + name + ".conf", "0644") + case SystemV => ("/etc/init.d/" + name, "0755") + } + for { s <- script.toSeq - } yield LinuxPackageMapping(Seq(s -> ("/etc/init/" + name + ".conf"))).withPerms("0644") + } yield LinuxPackageMapping(Seq(s -> path)).withPerms(permissions) }, // TODO - only make these if the upstart config exists... debianMakePrermScript <<= (normalizedName, target in Universal) map makeDebianPrermScript, debianMakePostinstScript <<= (normalizedName, target in Universal) map makeDebianPostinstScript) - private[this] final def makeDebianPrermScript(name: String, tmpDir: File): Option[File] = { - val scriptBits = JavaAppUpstartScript.generatePrerm(name) + + private def makeDebianStartScript( + replacements: Seq[(String, String)], name: String, tmpDir: File, loader: ServerLoader): Option[File] = + if (replacements.isEmpty) None + else { + val scriptBits = JavaAppStartScript.generateScript(replacements, loader) + val script = tmpDir / "tmp" / "bin" / name + IO.write(script, scriptBits) + Some(script) + } + + + protected def makeDebianPrermScript(name: String, tmpDir: File): Option[File] = { + val scriptBits = JavaAppStartScript.generatePrerm(name) val script = tmpDir / "tmp" / "bin" / "debian-prerm" IO.write(script, scriptBits) Some(script) } - private[this] final def makeDebianPostinstScript(name: String, tmpDir: File): Option[File] = { - val scriptBits = JavaAppUpstartScript.generatePostinst(name) + + protected def makeDebianPostinstScript(name: String, tmpDir: File): Option[File] = { + val scriptBits = JavaAppStartScript.generatePostinst(name) val script = tmpDir / "tmp" / "bin" / "debian-postinst" IO.write(script, scriptBits) Some(script) } - - private[this] final def makeDebianUpstartScript(replacements: Seq[(String, String)], name: String, tmpDir: File): Option[File] = - if (replacements.isEmpty) None - else { - val scriptBits = JavaAppUpstartScript.generateScript(replacements) - val script = tmpDir / "tmp" / "bin" / (name + ".conf") - IO.write(script, scriptBits) - Some(script) - } -} \ No newline at end of file +} diff --git a/src/main/scala/com/typesafe/sbt/packager/debian/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/debian/Keys.scala index 66da9378b..166e4d540 100644 --- a/src/main/scala/com/typesafe/sbt/packager/debian/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/debian/Keys.scala @@ -46,8 +46,8 @@ trait DebianKeys { | version - app version """.stripMargin) - val debianMakeUpstartScript = TaskKey[Option[File]]("makeUpstartScript", "Creates or discovers the upstart script used by this project") - val debianUpstartScriptReplacements = TaskKey[Seq[(String, String)]]("upstartScriptReplacements", + val debianMakeStartScript = TaskKey[Option[File]]("makeStartScript", "Creates or discovers the start script used by this project") + val debianStartScriptReplacements = TaskKey[Seq[(String, String)]]("upstartScriptReplacements", """|Replacements of template parameters used in the upstart script. | Default supported templates: | execScript - name of the script in /usr/bin @@ -56,9 +56,14 @@ trait DebianKeys { | chdir - execution path of the script | retries - on fail, how often should a restart be tried | retryTimeout - pause between retries + | appName - name of application + | appClasspath - application classpath + | appMainClass - main class to start + | daemonUser - daemon user """.stripMargin) } + /** Keys used for Debian specific settings. */ object Keys extends DebianKeys { // Metadata keys @@ -77,5 +82,9 @@ object Keys extends DebianKeys { def target = sbt.Keys.target def streams = sbt.Keys.streams + //init script parameters + def daemonUser = linux.Keys.daemonUser + def serverLoading = linux.Keys.serverLoading + val debianPackageInstallSize = TaskKey[Long]("debian-installed-size") } diff --git a/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala index fc6d6fce7..a91868cbe 100644 --- a/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala @@ -3,6 +3,7 @@ package packager package linux import sbt._ +import com.typesafe.sbt.packager.archetypes.ServerLoader.ServerLoader /** Linux packaging generic build targets. */ trait Keys { @@ -10,6 +11,8 @@ trait Keys { val packageSummary = SettingKey[String]("package-summary", "Summary of the contents of a linux package.") val packageDescription = SettingKey[String]("package-description", "The description of the package. Used when searching.") val maintainer = SettingKey[String]("maintainer", "The name/email address of a maintainer for the native package.") + val daemonUser = SettingKey[String]("daemon-user", "User to start application daemon") + val serverLoading = SettingKey[ServerLoader]("server-loader", "Loading system to be used for application start script") val linuxPackageMappings = TaskKey[Seq[LinuxPackageMapping]]("linux-package-mappings", "File to install location mappings including owner and privileges.") val linuxPackageSymlinks = TaskKey[Seq[LinuxSymlink]]("linux-package-symlinks", "Symlinks we should produce in the underlying package.") val generateManPages = TaskKey[Unit]("generate-man-pages", "Shows all the man files in the current project") diff --git a/src/sbt-test/debian/sysvinit-deb/build.sbt b/src/sbt-test/debian/sysvinit-deb/build.sbt new file mode 100644 index 000000000..aae500ea3 --- /dev/null +++ b/src/sbt-test/debian/sysvinit-deb/build.sbt @@ -0,0 +1,22 @@ +import NativePackagerKeys._ +import com.typesafe.sbt.packager.archetypes.ServerLoader + +packageArchetype.java_server + +serverLoading in Debian := ServerLoader.SystemV + +daemonUser in Debian := "root" + +mainClass in Compile := Some("empty") + +name := "debian-test" + +version := "0.1.0" + +maintainer := "Josh Suereth " + +packageSummary := "Test debian package" + +packageDescription := """A fun package description of our software, + with multiple lines.""" + diff --git a/src/sbt-test/debian/sysvinit-deb/project/plugins.sbt b/src/sbt-test/debian/sysvinit-deb/project/plugins.sbt new file mode 100644 index 000000000..b53de154c --- /dev/null +++ b/src/sbt-test/debian/sysvinit-deb/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version")) diff --git a/src/sbt-test/debian/sysvinit-deb/test b/src/sbt-test/debian/sysvinit-deb/test new file mode 100644 index 000000000..b7ae637c9 --- /dev/null +++ b/src/sbt-test/debian/sysvinit-deb/test @@ -0,0 +1,6 @@ +# Run the debian packaging. +> debian:package-bin +$ exists target/debian-test-0.1.0.deb + +$ exists target/debian-test-0.1.0/etc +$ exists target/debian-test-0.1.0/etc/init.d/debian-test