Skip to content

Commit

Permalink
Merge pull request #256 from kardapoltsev/wip/systemd
Browse files Browse the repository at this point in the history
FIX #249. Systemd support for Debian and Rpm
  • Loading branch information
muuki88 committed May 26, 2014
2 parents ed929e8 + 613525a commit dab0f55
Show file tree
Hide file tree
Showing 21 changed files with 219 additions and 22 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ For the native packager keys add this to your `build.sbt`
import com.typesafe.sbt.SbtNativePackager._
import NativePackagerKeys._

## Experimental systemd bootsystem support ##

Native packager now provides experimental `systemd` startup scripts.
Currently it works on Fedora `Fedora release 20 (Heisenbug)` and doesn't work on Ubuntu because of partial `systemd` support in `Ubuntu 14.04 LTS`.
To enable this feature follow [My First Packaged Server Project guide](http://www.scala-sbt.org/sbt-native-packager/GettingStartedServers/MyFirstProject.html) and use `Systemd` as server loader:


import com.typesafe.sbt.packager.archetypes.ServerLoader.Systemd
serverLoading in Rpm := Systemd

Any help on testing and improving this feature is appreciated so feel free to report bugs or making PR.

## Documentation ##

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
systemctl enable ${{app_name}}.service
systemctl start ${{app_name}}.service || echo "${{app_name}} could not be started. Try manually with service ${{app_name}} start"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
systemctl enable ${{app_name}}.service
systemctl start ${{app_name}}.service || echo "${{app_name}} could not be started. Try manually with service ${{app_name}} start"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
systemctl disable ${{app_name}}.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Removing system user/group : ${{daemon_user}} and ${{daemon_group}}
echo "Try deleting system user and group [${{daemon_user}}:${{daemon_group}}]"
if getent passwd | grep -q "^${{daemon_user}}:";
then
echo "Deleting system user: ${{daemon_user}}"
userdel ${{daemon_user}}
fi
if getent group | grep -q "^${{daemon_group}}:" ;
then
echo "Deleting system group: ${{daemon_group}}"
groupdel ${{daemon_group}}
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Adding system user/group : ${{daemon_user}} and ${{daemon_group}}
if ! getent group | grep -q "^${{daemon_group}}:" ;
then
echo "Creating system group: ${{daemon_group}}"
groupadd --system ${{daemon_group}}
fi
if ! getent passwd | grep -q "^${{daemon_user}}:";
then
echo "Creating system user: ${{daemon_user}}"
useradd --gid ${{daemon_group}} --no-create-home --system -c '${{descr}}' ${{daemon_user}}
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
systemctl ${{app_name}} stop || echo "${{app_name}} wasn't even running!"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Halting ${{app_name}}
echo "Shutdown ${{app_name}}"
systemctl stop ${{app_name}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Unit]
Description=${{descr}}
Requires=${{start_facilities}}

[Service]
Type=simple
WorkingDirectory=${{chdir}}
ExecStart=${{chdir}}/bin/${{app_name}}
Restart=always
RestartSec=${{retryTimeout}}

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Unit]
Description=${{descr}}
Requires=${{start_facilities}}

[Service]
Type=simple
WorkingDirectory=${{chdir}}
ExecStart=${{chdir}}/bin/${{app_name}}
Restart=always
RestartSec=${{retryTimeout}}

[Install]
WantedBy=multi-user.target
1 change: 1 addition & 0 deletions src/main/scala/com/typesafe/sbt/packager/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ object Keys extends linux.Keys
val defaultLinuxInstallLocation = SettingKey[String]("defaultLinuxInstallLocation", "The location where we will install generic linux packages.")
val defaultLinuxLogsLocation = SettingKey[String]("defaultLinuxLogsLocation", "The location where application logs will be stored.")
val defaultLinuxConfigLocation = SettingKey[String]("defaultLinuxConfigLocation", "The location where application config files will be stored")
val defaultLinuxStartScriptLocation = SettingKey[String]("defaultLinuxStartScriptLocation", "The location where start script for server application will be stored")
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ trait JavaAppStartScriptBuilder {
/** Scripts to include for upstart. By default only startScript */
val upstartScripts: Seq[String]

/** Scripts to include for ssystemV. By default only startScript */
/** Scripts to include for systemV. By default only startScript */
val systemvScripts: Seq[String]

/** Scripts to include for systemd. By default only startScript */
val systemdScripts: Seq[String]

/**
* Generating the URL to the startScript template.
* 1. Looking in defaultLocation
Expand Down Expand Up @@ -61,10 +64,12 @@ trait JavaAppStartScriptBuilder {
*/
def templateUrl(templateName: String, loader: ServerLoader, template: Option[URL] = None): Option[URL] = template orElse {
Option(loader match {
case Upstart if (upstartScripts contains templateName) =>
case Upstart if upstartScripts contains templateName =>
getClass getResource ("upstart/" + templateName + "-template")
case SystemV if (systemvScripts contains templateName) =>
case SystemV if systemvScripts contains templateName =>
getClass getResource ("systemv/" + templateName + "-template")
case Systemd if systemdScripts contains templateName =>
getClass getResource ("systemd/" + templateName + "-template")
case _ => null
})
}
Expand Down Expand Up @@ -117,6 +122,7 @@ object JavaAppStartScript {
val startScript = "start-rpm"
val upstartScripts = Seq(startScript)
val systemvScripts = Seq(startScript, Pre, Post, Preun, Postun)
val systemdScripts = Seq(startScript, Pre, Post, Preun, Postun)
}

object Debian extends JavaAppStartScriptBuilder {
Expand All @@ -126,11 +132,12 @@ object JavaAppStartScript {
val startScript = "start-debian"
val upstartScripts = Seq(startScript, Postinst, Prerm)
val systemvScripts = Seq(startScript, Postinst, Prerm, Postrm)
val systemdScripts = Seq(startScript, Postinst, Prerm, Postrm)
}

}

object ServerLoader extends Enumeration {
type ServerLoader = Value
val Upstart, SystemV = Value
val Upstart, SystemV, Systemd = Value
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,39 +31,52 @@ object JavaServerAppPackaging {
requiredStopFacilities: String,
startRunlevels: String,
stopRunlevels: String,
loader: ServerLoader.ServerLoader): Seq[(String, String)] = {
loader: ServerLoader): Seq[(String, String)] = {
loader match {
case ServerLoader.SystemV =>
case SystemV =>
Seq("start_runlevels" -> startRunlevels,
"stop_runlevels" -> stopRunlevels,
"start_facilities" -> requiredStartFacilities,
"stop_facilities" -> requiredStopFacilities)
case ServerLoader.Upstart =>
case Upstart =>
Seq("start_runlevels" -> startRunlevels,
"stop_runlevels" -> stopRunlevels,
"start_facilities" -> requiredStartFacilities,
"stop_facilities" -> requiredStopFacilities)
case Systemd =>
Seq("start_facilities" -> requiredStartFacilities)
}
}

private[this] def defaultFacilities(loader: ServerLoader.ServerLoader): String = {
private[this] def defaultFacilities(loader: ServerLoader): String = {
loader match {
case ServerLoader.SystemV => "$remote_fs $syslog"
case ServerLoader.Upstart => "[networking]"
case SystemV => "$remote_fs $syslog"
case Upstart => "[networking]"
case Systemd => "network.target"
}
}

private[this] def defaultStartRunlevels(loader: ServerLoader.ServerLoader): String = {
private[this] def defaultStartRunlevels(loader: ServerLoader): String = {
loader match {
case ServerLoader.SystemV => "2 3 4 5"
case ServerLoader.Upstart => "[2345]"
case SystemV => "2 3 4 5"
case Upstart => "[2345]"
case Systemd => ""
}
}

private[this] def defaultStopRunlevels(loader: ServerLoader.ServerLoader): String = {
private[this] def defaultStopRunlevels(loader: ServerLoader): String = {
loader match {
case ServerLoader.SystemV => "0 1 6"
case ServerLoader.Upstart => "[016]"
case SystemV => "0 1 6"
case Upstart => "[016]"
case Systemd => ""
}
}

private[this] def getStartScriptLocation(loader: ServerLoader): String = {
loader match {
case Upstart => "/etc/init/"
case SystemV => "/etc/init.d/"
case Systemd => "/usr/lib/systemd/system/"
}
}

Expand Down Expand Up @@ -116,11 +129,12 @@ object JavaServerAppPackaging {
linuxStartScriptTemplate in Debian <<= (serverLoading in Debian, sourceDirectory, linuxJavaAppStartScriptBuilder in Debian) map {
(loader, dir, builder) => builder.defaultStartScriptTemplate(loader, dir / "templates" / "start")
},
defaultLinuxStartScriptLocation in Debian <<= (serverLoading in Debian) apply getStartScriptLocation,
linuxMakeStartScript in Debian <<= (target in Universal, serverLoading in Debian, linuxScriptReplacements in Debian, linuxStartScriptTemplate in Debian, linuxJavaAppStartScriptBuilder in Debian)
map { (tmpDir, loader, replacements, template, builder) =>
makeMaintainerScript(builder.startScript, Some(template))(tmpDir, loader, replacements, builder)
},
linuxPackageMappings in Debian <++= (normalizedName, linuxMakeStartScript in Debian, serverLoading in Debian) map startScriptMapping,
linuxPackageMappings in Debian <++= (normalizedName, linuxMakeStartScript in Debian, serverLoading in Debian, defaultLinuxStartScriptLocation in Debian) map startScriptMapping,

// === Maintainer scripts ===
debianMakePreinstScript <<= (target in Universal, serverLoading in Debian, linuxScriptReplacements, linuxJavaAppStartScriptBuilder in Debian) map makeMaintainerScript(Preinst),
Expand All @@ -133,8 +147,8 @@ object JavaServerAppPackaging {
import RpmPlugin.Names.{ Pre, Post, Preun, Postun }
Seq(
serverLoading in Rpm := SystemV,
startRunlevels in Rpm <<= (serverLoading in Debian) apply defaultStartRunlevels,
stopRunlevels in Rpm <<= (serverLoading in Debian) apply defaultStopRunlevels,
startRunlevels in Rpm <<= (serverLoading in Rpm) apply defaultStartRunlevels,
stopRunlevels in Rpm <<= (serverLoading in Rpm) apply defaultStopRunlevels,
requiredStartFacilities in Rpm <<= (serverLoading in Rpm) apply defaultFacilities,
requiredStopFacilities in Rpm <<= (serverLoading in Rpm) apply defaultFacilities,
linuxJavaAppStartScriptBuilder in Rpm := JavaAppStartScript.Rpm,
Expand All @@ -145,11 +159,12 @@ object JavaServerAppPackaging {
(loader, dir, builder) =>
builder.defaultStartScriptTemplate(loader, dir / "templates" / "start")
},
linuxMakeStartScript in Rpm <<= (target in Universal, serverLoading in Rpm, linuxScriptReplacements, linuxStartScriptTemplate in Rpm, linuxJavaAppStartScriptBuilder in Rpm)
linuxMakeStartScript in Rpm <<= (target in Universal, serverLoading in Rpm, linuxScriptReplacements in Rpm, linuxStartScriptTemplate in Rpm, linuxJavaAppStartScriptBuilder in Rpm)
map { (tmpDir, loader, replacements, template, builder) =>
makeMaintainerScript(builder.startScript, Some(template))(tmpDir, loader, replacements, builder)
},
linuxPackageMappings in Rpm <++= (normalizedName, linuxMakeStartScript in Rpm, serverLoading in Rpm) map startScriptMapping,
defaultLinuxStartScriptLocation in Rpm <<= (serverLoading in Rpm) apply getStartScriptLocation,
linuxPackageMappings in Rpm <++= (normalizedName, linuxMakeStartScript in Rpm, serverLoading in Rpm, defaultLinuxStartScriptLocation in Rpm) map startScriptMapping,

// == Maintainer scripts ===
// TODO this is very basic - align debian and rpm plugin
Expand All @@ -176,10 +191,11 @@ object JavaServerAppPackaging {
/* ============ Helper Methods ============== */
/* ========================================== */

protected def startScriptMapping(name: String, script: Option[File], loader: ServerLoader): Seq[LinuxPackageMapping] = {
protected def startScriptMapping(name: String, script: Option[File], loader: ServerLoader, scriptDir: String): Seq[LinuxPackageMapping] = {
val (path, permissions) = loader match {
case Upstart => ("/etc/init/" + name + ".conf", "0644")
case SystemV => ("/etc/init.d/" + name, "0755")
case Systemd => ("/usr/lib/systemd/system/" + name + ".service", "0644")
}
for {
s <- script.toSeq
Expand Down
26 changes: 26 additions & 0 deletions src/sbt-test/debian/systemd-deb/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import NativePackagerKeys._
import com.typesafe.sbt.packager.archetypes.ServerLoader

packageArchetype.java_server

serverLoading in Debian := ServerLoader.Systemd

name := "debian-test"

version := "0.1.0"

maintainer := "Alexey Kardapoltsev <alexey.kardapoltsev@frumatic.com>"

packageSummary := "Test debian package"

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

requiredStartFacilities in Debian := "network.target"

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)
out.log.success("Successfully tested systemd start up script")
()
}
1 change: 1 addition & 0 deletions src/sbt-test/debian/systemd-deb/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))
10 changes: 10 additions & 0 deletions src/sbt-test/debian/systemd-deb/src/main/scala/test/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package test

object Main {
def main (args: Array[String]) {
//server app imitation
while (true){
Thread.sleep(1000L)
}
}
}
7 changes: 7 additions & 0 deletions src/sbt-test/debian/systemd-deb/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Run the debian packaging.
> debian:package-bin
$ exists target/debian-test-0.1.0.deb

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

> check-startup-script
41 changes: 41 additions & 0 deletions src/sbt-test/rpm/systemd-rpm/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import NativePackagerKeys._
import com.typesafe.sbt.packager.archetypes.ServerLoader

packageArchetype.java_server

serverLoading in Rpm := ServerLoader.Systemd

name := "rpm-test"

version := "0.1.0"

maintainer := "Alexey Kardapoltsev <alexey.kardapoltsev@frumatic.com>"

packageSummary := "Test rpm package"

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

rpmRelease := "1"

rpmVendor := "typesafe"

rpmUrl := Some("http://github.com/sbt/sbt-native-packager")

rpmLicense := Some("BSD")

requiredStartFacilities in Rpm := "serviceA.service"

TaskKey[Unit]("unzip") <<= (packageBin in Rpm, streams) map { (rpmFile, streams) =>
val rpmPath = Seq(rpmFile.getAbsolutePath)
Process("rpm2cpio" , rpmPath) #| Process("cpio -i --make-directories") ! streams.log
()
}

TaskKey[Unit]("checkStartupScript") <<= (target, streams) map { (target, out) =>
val script = IO.read(file("usr/lib/systemd/system/rpm-test.service"))
val runScript = file("usr/share/rpm-test/bin/rpm-test")
assert(script.contains("Requires=serviceA.service"), "script doesn't contain Default-Start header\n" + script)
out.log.success("Successfully tested systemd start up script")
()
}
1 change: 1 addition & 0 deletions src/sbt-test/rpm/systemd-rpm/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))
10 changes: 10 additions & 0 deletions src/sbt-test/rpm/systemd-rpm/src/main/scala/test/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package test

object Main {
def main (args: Array[String]) {
//server app imitation
while (true){
Thread.sleep(1000L)
}
}
}
8 changes: 8 additions & 0 deletions src/sbt-test/rpm/systemd-rpm/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Run the debian packaging.
> rpm:packageBin
$ exists target/rpm/RPMS/noarch/rpm-test-0.1.0-1.noarch.rpm

> unzip
$ exists usr/lib/systemd/system/rpm-test.service

> checkStartupScript

0 comments on commit dab0f55

Please sign in to comment.