├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.markdown ├── Rakefile ├── TODO.txt ├── bin ├── jruby-jsvc-initd ├── jsvc-wrapper.sh └── make-debian-initd-files.sh ├── debian ├── README ├── changelog ├── compat ├── control ├── copyright ├── jruby-jsvc-example.install ├── jruby-jsvc-initd.install ├── libjruby-jsvc-java.install ├── libjruby-jsvc-java.links ├── libjruby-jsvc-java.substvars ├── libjruby-jsvc-ruby.install ├── libjruby-jsvc-ruby.substvars └── rules ├── example ├── bin │ ├── bad_daemon.rb │ └── good_daemon.rb └── lib │ └── crazy_daemon.rb ├── lib ├── jsvc.rb └── jsvc │ ├── initd.rb │ ├── initd │ ├── param_dsl.rb │ ├── parameters.rb │ └── template_binding.rb │ ├── initd_cli.rb │ └── initd_cli │ └── params.rb ├── pom.xml ├── spec ├── daemon_spec.rb ├── helpers.rb ├── initd_spec.rb └── param_dsl_spec.rb ├── src └── main │ ├── java │ └── com │ │ └── msp │ │ ├── jsvc │ │ └── JRubyDaemon.java │ │ └── procrun │ │ └── JRubyService.java │ └── resources │ └── ruby │ └── lib │ └── jsvc │ └── errors.rb └── templates └── linux ├── 010-head.sh.erb ├── 050-vars.sh.erb ├── 080-form-args.sh.erb └── 100-start-stop.sh.erb /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *~ 3 | debian/*.ex 4 | debian/*.log 5 | debian/libjruby-jsvc-java/**/* 6 | debian/libjruby-jsvc-ruby/**/* 7 | debian/jruby-jsvc-initd/**/* 8 | debian/jruby-jsvc-example.init 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'erubis' 4 | 5 | group :test do 6 | gem 'POpen4' 7 | gem 'rspec' 8 | end -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | POpen4 (0.1.4) 5 | Platform (>= 0.4.0) 6 | open4 7 | Platform (0.4.0) 8 | diff-lcs (1.1.3) 9 | erubis (2.7.0) 10 | open4 (1.2.0) 11 | rspec (2.7.0) 12 | rspec-core (~> 2.7.0) 13 | rspec-expectations (~> 2.7.0) 14 | rspec-mocks (~> 2.7.0) 15 | rspec-core (2.7.1) 16 | rspec-expectations (2.7.0) 17 | diff-lcs (~> 1.1.2) 18 | rspec-mocks (2.7.0) 19 | 20 | PLATFORMS 21 | java 22 | ruby 23 | 24 | DEPENDENCIES 25 | POpen4 26 | erubis 27 | rspec 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010 Media Service Provider Ltd 2 | 3 | Licensed under the Apache License, Version 2.0 (the License); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an AS IS BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## jruby-jsvc 2 | 3 | Run your jruby application as a unix daemon, via 4 | [jsvc](http://commons.apache.org/daemon/). Works around the fact that you 5 | can't really `fork` from jruby/java with all the nice features you'd expect 6 | in a unix daemon. 7 | 8 | ## Features 9 | 10 | - Check your application has started properly before it is backgrounded. 11 | - Pidfiles! 12 | - Friendly process name for `ps` output 13 | - Build initd scripts that work properly and that your sys-admin will like 14 | - Debian packaging 15 | - Works on Windows, apparently :) 16 | 17 | ## How to create a jruby-jsvc daemon 18 | 19 | These instructions are aimed at someone using a debian based system, although 20 | you can use jruby-jsvc with any *nix that can run jsvc, you just need to delve 21 | a bit deeper. 22 | 23 | There is a working example that can be installed as a debian package. It is very 24 | simple, but should give you a complete guide to how to deploy apps on to debian 25 | with jruby-jsvc. 26 | 27 | 1. Install jsvc. Your system may come with it, or you may have to build it 28 | yourself from http://commons.apache.org/daemon/. It isn't the most difficult 29 | thing to get running. By default, jruby-jsvc expects the `jsvc` excecutable 30 | to be on your path, and expects the `commons-daemon` jar to be installed in to 31 | `/usr/share/java/commons-daemon.jar`. 32 | 33 | 1. Install jruby-jsvc. You can either check it out from source and build it, 34 | or install the debian packages from the downloads page. 35 | 36 | 1. Take a look at `example/lib/crazy_daemon.rb` - you need to create an object 37 | called `Daemon` underneath your application's namespace, something like 38 | `Crazy::Daemon`. This should respond to `setup?`, `start` and `stop` methods. 39 | See comments in that file for details on the interface. 40 | 41 | 1. Create a start-up script - the entry point in to your application. This 42 | should load your daemon module and initialize your application so 43 | that it is ready to start serving once `Daemon.start` is called. There are a 44 | couple of examples in example/bin - one which succeeds, the other fails 45 | (Demonstrating the DaemonInitException). 46 | 47 | 1. Create an init.d script using the jruby-jsvc-initd command. Take a look at 48 | bin/make-debian-initd-files.sh for an example. The jruby-jsvc-initd command has 49 | some help output to aid you. 50 | 51 | 1. Start/stop the daemon with your init.d script. Crazy. 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'date' 2 | require 'rubygems' 3 | 4 | desc "creates a rubygem" 5 | task :build => ['clean', 'target/gem/lib/jruby-jsvc.rb'] do 6 | `mvn install` 7 | 8 | jars = FileList['target/jruby-jsvc-*.jar'] 9 | version = jars.first.gsub(/(?:.+)jruby\-jsvc\-(.+).jar/, '\1').sub(/-SNAPSHOT/, '') 10 | 11 | cp FileList['README.markdown', 'LICENSE'], 'target/gem' 12 | cp jars, 'target/gem/lib' 13 | cp File.expand_path('~/.m2/repository/commons-daemon/commons-daemon/1.0.6/commons-daemon-1.0.6.jar'), 'target/gem/lib' 14 | 15 | Dir.chdir('target/gem') do 16 | gemspec = Gem::Specification.new do |s| 17 | s.name = "jruby-jsvc" 18 | s.version = version 19 | s.date = Date.today.to_s 20 | s.description = "Use jsvc to run a jruby app as an init.d style daemon" 21 | s.summary = "Use jsvc to run a jruby app as an init.d style daemon" 22 | s.homepage = 'http://github.com/nicobrevin/jruby-jsvc' 23 | s.has_rdoc = false 24 | s.require_paths = %w{lib} 25 | 26 | s.files = FileList['./**/*'].exclude('*.gem') 27 | end 28 | 29 | Gem::Builder.new(gemspec).build 30 | mv FileList['*.gem'], '..' 31 | end 32 | end 33 | 34 | task :clean do 35 | rm_rf 'target' 36 | end 37 | 38 | file 'target/gem/lib/jruby-jsvc.rb' do |t| 39 | mkdir_p File.dirname(t.name) 40 | File.open(t.name, 'wb') do |f| 41 | f.write("Dir.glob('*.jar').each {|jar| require jar}") 42 | end 43 | end 44 | 45 | require 'rubygems' 46 | require 'rake/testtask' 47 | 48 | Rake::TestTask.new do |t| 49 | t.libs << 'test' 50 | t.test_files = FileList['test/*test.rb'] 51 | t.verbose = true 52 | end 53 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | - tighten up package dependencies - installing on to a plain 5.0.8 is broken, probably also on to debian 6.0 2 | - allow jruby-jsvc-initd to work with jruby as well as ruby1.8 3 | - use 'profiles' with initd generator to supply sensible defaults 4 | - help for the initd generator and better error messages when it fails 5 | - build debian packages for the example app to show how it's done 6 | - win32/procrun support - I believe this is done, but I can't test it (not a windows user) 7 | - support for older versions of commons-daemon (i.e no DaemonUserSignal class present) 8 | -------------------------------------------------------------------------------- /bin/jruby-jsvc-initd: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | begin 4 | require 'jsvc' 5 | rescue LoadError 6 | require 'rubygems' 7 | require 'jsvc' 8 | end 9 | 10 | JSVC.init 11 | 12 | require 'jsvc/initd_cli' 13 | 14 | JSVC::InitdCLI.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /bin/jsvc-wrapper.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # Generic script for running ruby scripts as daemons using 3 | # jsvc and a java class to control the daemon. 4 | # 5 | # Contains common parameters and start/stop 6 | 7 | # Things you'll need to set on a per script/daemon basis: 8 | # SCRIPT_NAME - Path to the ruby script which creates a Daemon 9 | # object for jsvc to control 10 | # APP_NAME - Name of your application 11 | # 12 | # Things you can set: 13 | # PROG_OPTS - Arguments to send to the program. A few defaults are appended to this. 14 | 15 | if [ -z ${SCRIPT_NAME} ]; then 16 | echo "SCRIPT_NAME not set" 17 | exit 1 18 | fi 19 | 20 | if [ -z ${MODULE_NAME} ]; then 21 | echo "MODULE_NAME not set" 22 | exit 1 23 | fi 24 | 25 | # Local development - uncomment these to use jsvc-daemon from a working copy 26 | if [ "${JRUBY_DAEMON_DEV}" ]; then 27 | echo "jsvc-wrapper, in development mode" 28 | JSVC=`which jsvc` 29 | JAVA_HOME=`jruby -e 'puts Java::JavaLang::System.get_property("java.home")'` 30 | JRUBY_HOME=`jruby -e 'puts Java::JavaLang::System.get_property("jruby.home")'` 31 | APP_HOME=. 32 | PIDFILE=jsvc-$SCRIPT_NAME.pid 33 | LOG_DIR=log 34 | # Uncomment for debugging the daemon script 35 | JSVC_ARGS_EXTRA="-debug -nodetach " 36 | JAVA_PROPS="-DJRubyDaemon.debug=true" 37 | 38 | echo "JAVA_HOME : $JAVA_HOME" 39 | echo "JRUBY_HOME : $JRUBY_HOME" 40 | else 41 | # Standard install 42 | JSVC=/usr/bin/jsvc 43 | JAVA_HOME=/usr/lib/jvm/java-6-sun 44 | JRUBY_HOME=/usr/lib/jruby1.4 45 | APP_HOME=/usr/lib/$APP_NAME 46 | USER=$APP_NAME 47 | PIDFILE=/var/run/$APP_NAME/jsvc-$SCRIPT_NAME.pid 48 | LOG_DIR=/var/log/$APP_NAME 49 | fi 50 | 51 | # If you want your programs to run as or not as daemons pass a flag to tell them which they are 52 | PROG_OPTS="$PROG_OPTS --no-log-stdout --daemon" 53 | 54 | # Implements the jsvc Daemon interface. 55 | MAIN_CLASS=com.msp.jsvc.JRubyDaemon 56 | 57 | RUBY_SCRIPT=$APP_HOME/bin/$SCRIPT_NAME.rb 58 | 59 | # Set some jars variables if they aren't already there 60 | if [ ${#JRUBY_JSVC_JAR} -eq 0 ]; then 61 | JRUBY_JSVC_JAR=/usr/share/java/jruby-jsvc.jar 62 | fi 63 | if [ ${#DAEMON_JAR} -eq 0 ]; then 64 | DAEMON_JAR=/usr/share/java/commons-daemon.jar 65 | fi 66 | 67 | CLASSPATH=$JRUBY_HOME/lib/jruby.jar:$JRUBY_HOME/lib/profile.jar:$DAEMON_JAR:$JRUBY_JSVC_JAR 68 | 69 | echo "CLASSPATH : $CLASSPATH" 70 | 71 | JAVA_PROPS="$JAVA_PROPS -Djruby.memory.max=500m \ 72 | -Djruby.stack.max=1024k \ 73 | -Djna.boot.library.path=$JRUBY_HOME/lib/native/linux-i386:$JRUBY_HOME/lib/native/linux-amd64 \ 74 | -Djffi.boot.library.path=$JRUBY_HOME/lib/native/i386-Linux:$JRUBY_HOME/jruby/lib/native/s390x-Linux:$JRUBY_HOME/lib/native/x86_64-Linux \ 75 | -Djruby.home=$JRUBY_HOME \ 76 | -Djruby.lib=$JRUBY_HOME/lib \ 77 | -Djruby.script=jruby \ 78 | -Djruby.shell=/bin/sh 79 | -Djruby.daemon.module.name=$MODULE_NAME" 80 | 81 | JAVA_OPTS="-Xmx500m -Xss1024k -Xbootclasspath/a:$JRUBY_HOME/lib/jruby.jar:$JRUBY_HOME/lib/bsf.jar" 82 | 83 | JSVC_ARGS="-home $JAVA_HOME \ 84 | $JSVC_ARGS_EXTRA \ 85 | -wait 20 \ 86 | -pidfile $PIDFILE \ 87 | -user $USER \ 88 | -procname jsvc-$SCRIPT_NAME \ 89 | -jvm server" 90 | 91 | if [ ! "${JRUBY_DAEMON_DEV}" ]; then 92 | JSVC_ARGS="$JSVC_ARGS \ 93 | -outfile $LOG_DIR/jsvc-$SCRIPT_NAME.log \ 94 | -errfile &1" 95 | fi 96 | 97 | # 98 | # Stop/Start 99 | # 100 | 101 | STOP_COMMAND="$JSVC $JSVC_ARGS -stop $MAIN_CLASS" 102 | START_COMMAND="$JSVC $JSVC_ARGS -cp $CLASSPATH $JAVA_PROPS $JAVA_OPTS $MAIN_CLASS $RUBY_SCRIPT $PROG_OPTS" 103 | 104 | case "$1" in 105 | start) 106 | if [ -e "$PIDFILE" ]; then 107 | echo "Pidfile already exists, not starting." 108 | exit 1 109 | else 110 | echo "Starting $SCRIPT_NAME daemon..." 111 | $START_COMMAND 112 | EXIT_CODE=$? 113 | if [ "$EXIT_CODE" != 0 ]; then 114 | echo "Daemon exited with status: $EXIT_CODE. Check pidfile and log" 115 | fi 116 | fi 117 | ;; 118 | stop) 119 | if [ -e "$PIDFILE" ]; then 120 | echo "Stopping $SCRIPT_NAME daemon..." 121 | $STOP_COMMAND 122 | else 123 | echo "No pid file, not stopping." 124 | exit 1 125 | fi 126 | ;; 127 | restart) 128 | if [ -e "$PIDFILE" ]; then 129 | echo "Stopping $SCRIPT_NAME daemon..." 130 | $STOP_COMMAND 131 | fi 132 | if [ -e "$PIDFILE" ]; then 133 | echo "Pidfile still present, $SCRIPT_NAME hasn't stopped" 134 | exit 1 135 | else 136 | $START_COMMAND 137 | EXIT_CODE=$? 138 | if [ "$EXIT_CODE" != 0 ]; then 139 | echo "Daemon exited with status: $EXIT_CODE. Check pidfile and log" 140 | fi 141 | fi 142 | ;; 143 | status) 144 | if [ "$PIDFILE" ]; then 145 | PID=`cat $PIDFILE` 146 | OUTPUT=`ps $PID | egrep "^$PID "` 147 | if [ ${#OUTPUT} -gt 0 ]; then 148 | echo "Service running with pid: $PID" 149 | else 150 | echo "Pidfile present, but process not running" 151 | fi 152 | else 153 | echo "No pidfile present" 154 | fi 155 | ;; 156 | *) 157 | echo "Unrecognised command. Usage jsvc-daemon [ start | stop ]" 158 | ;; 159 | esac -------------------------------------------------------------------------------- /bin/make-debian-initd-files.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | COMMON_PARAMS="debian --param-app-home=/usr/share/jruby-jsvc/example --param-module-name=Crazy --param-debug=false --param-app-user=root --param-jruby-jsvc-jar=/usr/share/java/jruby-jsvc.jar --param-jsvc=/usr/bin/jsvc --param-jruby-home=/usr/lib/jruby --param-java-home=/usr/lib/jvm/java-6-openjdk" 6 | 7 | NAME=$1 8 | OUT_TO=$2 9 | 10 | ruby -Ilib bin/jruby-jsvc-initd $COMMON_PARAMS --param-app-name=$NAME > $OUT_TO 11 | -------------------------------------------------------------------------------- /debian/README: -------------------------------------------------------------------------------- 1 | The Debian Package jruby-jsvc 2 | ---------------------------- 3 | 4 | This package builds the java library for running jruby-jsvc daemons and a ruby library for building initd scripts suitable for use with jruby-jsvc 5 | 6 | -- Nick Griffiths Sun, 27 Nov 2011 22:02:30 +0000 7 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | jruby-jsvc (0.5.1-2) stable; urgency=low 2 | 3 | * Update debian version for jruby-jsvc.jar symlink fix 4 | 5 | -- Nick Griffiths Wed, 30 Jan 2013 13:36:04 +0000 6 | 7 | jruby-jsvc (0.5.1) stable; urgency=low 8 | 9 | * jruby-jsvc#6 - sourcing works with squeeze 10 | 11 | -- Nick Griffiths Wed, 30 Jan 2013 13:35:39 +0000 12 | 13 | jruby-jsvc (0.5.0) stable; urgency=low 14 | 15 | * jruby-jsvc#5 - configurable wait time 16 | 17 | -- Nick Griffiths Wed, 24 Oct 2012 21:37:11 +0100 18 | 19 | jruby-jsvc (0.4.0-1) stable; urgency=low 20 | 21 | * small initd script change 22 | 23 | -- Nick Griffiths Wed, 24 Oct 2012 20:27:41 +0100 24 | 25 | jruby-jsvc (0.3.3) stable; urgency=low 26 | 27 | * Another bugfix release 28 | 29 | -- Nick Griffiths Tue, 07 Feb 2012 20:13:28 +0000 30 | 31 | jruby-jsvc (0.3.2-1) stable; urgency=low 32 | 33 | * Another bugfix release 34 | 35 | -- Nick Griffiths Tue, 07 Feb 2012 18:40:04 +0000 36 | 37 | jruby-jsvc (0.3.1-1) stable; urgency=low 38 | 39 | * Bugfix release 40 | 41 | -- Nick Griffiths Tue, 07 Feb 2012 17:09:12 +0000 42 | 43 | jruby-jsvc (0.3.0ubuntu1) oneiric; urgency=low 44 | 45 | * Release with debian support, should work on squeeze 46 | 47 | -- Nick Griffiths Sat, 24 Dec 2011 17:23:22 +0000 48 | 49 | jruby-jsvc (0.2.0) unstable; urgency=low 50 | 51 | * Initial Release. 52 | 53 | -- Nick Griffiths Sun, 27 Nov 2011 22:02:30 +0000 54 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: jruby-jsvc 2 | Section: libs 3 | Priority: extra 4 | Maintainer: Nick Griffiths 5 | Build-Depends: debhelper (>= 8.0.0), maven2, liberubis-ruby, ruby 6 | Standards-Version: 3.9.2 7 | Homepage: 8 | #Vcs-Git: git://git.debian.org/collab-maint/jruby-jsvc.git 9 | #Vcs-Browser: http://git.debian.org/?p=collab-maint/jruby-jsvc.git;a=summary 10 | 11 | Package: libjruby-jsvc-ruby 12 | Architecture: all 13 | Depends: ruby, liberubis-ruby 14 | Recommends: jruby, jsvc 15 | Description: Library for initd scripts for jruby-jsvc 16 | Library used to build initd scripts for jruby-jsvc daemons. Not actually needed to be installed in order to run jruby-jsvc daemons, but useful for generating initd scripts, possibly when building your own debian packages 17 | 18 | Package: libjruby-jsvc-java 19 | Architecture: all 20 | Depends: default-jdk | openjdk-6-jre | openjdk-7-jre, jruby | jruby1.4 | jruby1.5 | jruby1.6, libcommons-daemon-java (>= 1.0.6), jsvc (>= 1.0.6) 21 | Description: Java library for jruby-jsvc daemons 22 | Java library used for starting up and controlling jruby daemons with jsvc 23 | 24 | Package: jruby-jsvc-initd 25 | Architecture: all 26 | Depends: libjruby-jsvc-ruby 27 | Description: Command for building initd scripts for jruby-jsvc 28 | Command, written in plain ruby, for building initd scripts that you can use with jruby-jsvc. This doesn't actually use jruby, as it doesn't need to, and there is better package support on debian for ruby-1.8.7 29 | 30 | Package: jruby-jsvc-example 31 | Architecture: all 32 | Depends: libjruby-jsvc-ruby, jruby-jsvc-initd, openjdk-6-jre 33 | Description: Example daemon application using jruby-jsvc 34 | Basic application that brings all the pieces together, showing you how to deploy a ruby daemon using jruby-jsvc 35 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: jruby-jsvc 3 | Source: 4 | 5 | Files: * 6 | Copyright: 7 | 8 | License: GPL-3.0+ 9 | 10 | Files: debian/* 11 | Copyright: 2011 Nick Griffiths 12 | License: GPL-3.0+ 13 | 14 | License: GPL-3.0+ 15 | This program is free software: you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License as published by 17 | the Free Software Foundation, either version 3 of the License, or 18 | (at your option) any later version. 19 | . 20 | This package is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | . 25 | You should have received a copy of the GNU General Public License 26 | along with this program. If not, see . 27 | . 28 | On Debian systems, the complete text of the GNU General 29 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 30 | 31 | # Please also look if there are files or directories which have a 32 | # different copyright/license attached and list them here. 33 | -------------------------------------------------------------------------------- /debian/jruby-jsvc-example.install: -------------------------------------------------------------------------------- 1 | example/* usr/share/jruby-jsvc/example 2 | -------------------------------------------------------------------------------- /debian/jruby-jsvc-initd.install: -------------------------------------------------------------------------------- 1 | bin/jruby-jsvc-initd usr/bin -------------------------------------------------------------------------------- /debian/libjruby-jsvc-java.install: -------------------------------------------------------------------------------- 1 | target/jruby-jsvc-*.jar usr/share/java 2 | -------------------------------------------------------------------------------- /debian/libjruby-jsvc-java.links: -------------------------------------------------------------------------------- 1 | usr/share/java/jruby-jsvc-0.5.1.jar usr/share/java/jruby-jsvc-0.5.jar 2 | usr/share/java/jruby-jsvc-0.5.1.jar usr/share/java/jruby-jsvc.jar 3 | -------------------------------------------------------------------------------- /debian/libjruby-jsvc-java.substvars: -------------------------------------------------------------------------------- 1 | misc:Depends= 2 | -------------------------------------------------------------------------------- /debian/libjruby-jsvc-ruby.install: -------------------------------------------------------------------------------- 1 | lib/* usr/lib/ruby/1.8 2 | templates/linux/* usr/share/jruby-jsvc/templates 3 | -------------------------------------------------------------------------------- /debian/libjruby-jsvc-ruby.substvars: -------------------------------------------------------------------------------- 1 | misc:Depends= 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # 5 | # This file was originally written by Joey Hess and Craig Small. 6 | # As a special exception, when this file is copied by dh-make into a 7 | # dh-make output file, you may use that output file without restriction. 8 | # This special exception was added by Craig Small in version 0.37 of dh-make. 9 | # 10 | # Modified to make a template file for a multi-binary package with separated 11 | # build-arch and build-indep targets by Bill Allombert 2001 12 | 13 | # Uncomment this to turn on verbose mode. 14 | export DH_VERBOSE=1 15 | 16 | # This has to be exported to make some magic below work. 17 | export DH_OPTIONS 18 | JAR_VERSION=0.2.0 19 | 20 | %: 21 | dh $@ 22 | 23 | override_dh_auto_build: 24 | mvn clean 25 | mvn package 26 | bin/make-debian-initd-files.sh good_daemon debian/jruby-jsvc-example.init 27 | -------------------------------------------------------------------------------- /example/bin/bad_daemon.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))) 2 | require 'crazy_daemon' 3 | 4 | JRUBY_JSVC_FAIL = true 5 | Crazy::Daemon.init 6 | -------------------------------------------------------------------------------- /example/bin/good_daemon.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))) 2 | require 'crazy_daemon' 3 | 4 | JRUBY_JSVC_FAIL = false 5 | Crazy::Daemon.init 6 | 7 | -------------------------------------------------------------------------------- /example/lib/crazy_daemon.rb: -------------------------------------------------------------------------------- 1 | # 2 | # I'm defining the daemon here, but you could require it from somewhere 3 | # else, or pull in a ruby gem or anything 4 | # 5 | 6 | module Crazy 7 | module Daemon 8 | 9 | # Initialise your application to the point you are confident that you 10 | # should be able to do some useful work (get a DB connection, bind to a 11 | # socket, open config files, etc). Not actually part of the jruby-jsvc 12 | # Daemon interface, but it is useful to put this somewhere. 13 | def init 14 | puts "Pretending to setup stuff I need, and bind to sockets, etc" 15 | if JRUBY_JSVC_FAIL 16 | # if you can't initialize your environment and you know why, 17 | # you can fail and give an error message using the DaemonInitError. 18 | raise JSVC::DaemonInitError, "My database hurts, please mend it." 19 | else 20 | @setup = true 21 | end 22 | end 23 | 24 | # Detection method for the jruby-jsvc library to determine whether you're 25 | # daemon is ready to start serving. If false, jruby-jsvc will not call 26 | # `start` and your daemon will not start. 27 | def setup? 28 | @setup 29 | end 30 | 31 | # jruby-jsvc is telling you to start doing whatever it is your daemon does, 32 | # such as serving http requests, sending emails, sorting text files or 33 | # whatever it is your daemon is supposed to do. 34 | def start 35 | while not @stopped 36 | puts "Waiting for something to happen" 37 | sleep 1 38 | end 39 | end 40 | 41 | # Respond to SIGUSR2. An opportunity to reload your application, roll log 42 | # files, or whatever else it is you'd like to do. Optional. 43 | def signal 44 | puts "Reloading my config files, starting again" 45 | end 46 | 47 | # jruby-jsvc is asking you politely to stop. Finish serving any http 48 | # requests, don't start any new jobs. This should cause the main thread of your 49 | # application, the one that called `start`, to return back to the caller, 50 | # however this method should exit immediately - don't join the main thread 51 | # or anything like that. 52 | def stop 53 | @stopped = true 54 | end 55 | 56 | extend self 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/jsvc.rb: -------------------------------------------------------------------------------- 1 | module JSVC 2 | 3 | def self.init 4 | begin 5 | require 'erubis' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'erubis' 9 | end 10 | 11 | require 'jsvc/initd' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/jsvc/initd.rb: -------------------------------------------------------------------------------- 1 | class JSVC::Initd 2 | require 'jsvc/initd/template_binding' 3 | require 'jsvc/initd/param_dsl' 4 | require 'jsvc/initd/parameters' 5 | 6 | def initialize(options={}) 7 | @template_dir = options.fetch(:template_dir, default_template_dir) 8 | end 9 | 10 | def default_template_dir 11 | File.join(Dir.pwd, 'templates/linux') 12 | end 13 | 14 | attr_reader :template_dir 15 | 16 | def template_string 17 | 18 | raise "template dir: #{template_dir} does not exist or not a directory" unless 19 | File.directory?(template_dir) 20 | 21 | template_files = Dir[File.join(template_dir, '*')]. 22 | sort 23 | 24 | raise "no template parts in directory #{template_dir}" if 25 | template_files.empty? 26 | 27 | template_files. 28 | map {|fn|File.open(fn, 'r') {|f| f.read } }. 29 | inject {|a,b| a+b } 30 | end 31 | 32 | def write(out, mode, template_params) 33 | template = Erubis::Eruby.new(template_string) 34 | binding = Context.new(mode, JSVC::Initd.defined_params, template_params).get_binding 35 | 36 | result = template.result(binding) 37 | out.write(result) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/jsvc/initd/param_dsl.rb: -------------------------------------------------------------------------------- 1 | class JSVC::Initd 2 | 3 | def self.define_params(&block) 4 | dsl = ParamDSL.new 5 | dsl.instance_eval(&block) 6 | @parameters = (@parameters || []) + dsl.parameters 7 | end 8 | 9 | def self.defined_param_names 10 | @parameters.map {|p| p.name } 11 | end 12 | 13 | def self.defined_params 14 | @parameters && Hash[*@parameters.map {|p| [p.name, p] }.flatten] 15 | end 16 | 17 | def self.declare_defaults(name) 18 | (@declared_defaults ||= []) << name 19 | end 20 | 21 | def self.declared_defaults 22 | @declared_defaults.dup 23 | end 24 | 25 | def self.clear_defined! 26 | @parameters = nil 27 | end 28 | 29 | class ParamDSL 30 | 31 | def initialize 32 | @parameters = [] 33 | end 34 | 35 | def doc(string) 36 | @next_doc = string 37 | end 38 | 39 | def string(name, &defaults) 40 | define(:string, name, &defaults) 41 | end 42 | 43 | def path(name, &defaults) 44 | define(:path, name, &defaults) 45 | end 46 | 47 | def boolean(name, &defaults) 48 | define(:boolean, name, &defaults) 49 | end 50 | 51 | def define(type, name, &defaults) 52 | doc = @next_doc 53 | @next_doc = nil 54 | @parameters << Parameter.new(type, name, doc, &defaults) 55 | end 56 | 57 | def parameters 58 | @parameters.clone 59 | end 60 | 61 | private 62 | 63 | def get_default_java_property(prop_name) 64 | fail_unless_jruby_present! 65 | command = "jruby -e 'puts Java::JavaLang::System.get_property(\"#{prop_name}\")'" 66 | `#{command}` 67 | end 68 | 69 | def fail_unless_jruby_present! 70 | `which jruby` 71 | raise "jruby interpreter needed and not installed or not in PATH" if $? != 0 72 | end 73 | 74 | end 75 | 76 | class Parameter 77 | 78 | attr_reader :type 79 | attr_reader :name 80 | attr_reader :doc 81 | 82 | def initialize(type, name, doc, &defaults) 83 | @type = type 84 | @name = name 85 | @doc = doc 86 | @defaults_proc = defaults 87 | end 88 | 89 | def default(context) 90 | 91 | return nil if @defaults_proc.nil? 92 | 93 | result = @defaults_proc.call(context) 94 | 95 | if result.is_a?(Hash) 96 | result[context.mode] 97 | else 98 | result 99 | end 100 | end 101 | end 102 | 103 | class ParamError < StandardError 104 | 105 | attr_reader :param_name 106 | 107 | def initialize(name) 108 | @param_name = name 109 | super(@param_name) 110 | end 111 | end 112 | 113 | # Raised when a value is looked for a parameter that isn't defined and 114 | # no value could be found for it either 115 | class UnknownParamError < ParamError ; end 116 | 117 | # Raised when a value is looked for for which no value has been given and 118 | # default has been specified 119 | class MissingParamError < ParamError ; end 120 | 121 | 122 | class Context 123 | 124 | attr_reader :mode 125 | 126 | def initialize(mode, parameters, values) 127 | @mode = mode 128 | @parameters = parameters 129 | @values = values 130 | end 131 | 132 | def method_missing(m, *args, &block) 133 | key = m 134 | if @values.has_key?(key) 135 | @values[key] 136 | else 137 | p = @parameters[key] 138 | raise UnknownParamError.new(key) if p.nil? 139 | 140 | if (v = p.default(self)).nil? 141 | raise MissingParamError.new(key) 142 | else 143 | v 144 | end 145 | end 146 | end 147 | 148 | def get_binding 149 | binding 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /lib/jsvc/initd/parameters.rb: -------------------------------------------------------------------------------- 1 | JSVC::Initd.declare_defaults :debian 2 | JSVC::Initd.declare_defaults :dev 3 | 4 | JSVC::Initd.define_params do 5 | 6 | doc "Name of your application" 7 | string :app_name 8 | 9 | doc "Name of the ruby constant under which the Daemon module can be found" 10 | string :module_name 11 | 12 | doc "Name of the ruby script that is called to start the application" 13 | string :script_name do |d| 14 | d.app_name 15 | end 16 | 17 | doc "Home location of the application. Some defaults are based from this location" 18 | path :app_home do |d| 19 | { 20 | :dev => File.expand_path(Dir.pwd), 21 | :debian => "/usr/lib/#{d.app_name}" 22 | } 23 | end 24 | 25 | doc "Path to the JRuby installation" 26 | path :jruby_home do |d| 27 | { 28 | :dev => get_default_java_property("jruby.home"), 29 | :debian => "/usr/lib/jruby" 30 | } 31 | end 32 | 33 | doc "Path to the jsvc binary" 34 | path :jsvc do |d| 35 | { 36 | :dev => `which jsvc`, 37 | :debian => "/usr/bin/jsvc" 38 | } 39 | end 40 | 41 | doc "Java home directory" 42 | path :java_home do |d| 43 | { 44 | :dev => get_default_java_property("java.home"), 45 | :debian => "/usr/lib/jvm/default-java" 46 | } 47 | end 48 | 49 | doc "Path to script which starts the application" 50 | path :script_path do |d| 51 | File.join(d.app_home, 'bin', d.script_name + '.rb') 52 | end 53 | 54 | doc "Path to jruby-jsvc.jar" 55 | path :jruby_jsvc_jar do |d| 56 | { 57 | :debian => '/usr/share/java/jruby-jsvc.jar', 58 | :dev => (t = Dir['target/jruby-jsvc*.jar'].sort.last) && File.expand_path(t) 59 | } 60 | end 61 | 62 | doc "Path to commons-daemon.jar" 63 | path :commons_daemon_jar do |d| 64 | '/usr/share/java/commons-daemon.jar' 65 | end 66 | 67 | doc "Unix user that the daemon process will eventually run as" 68 | string :app_user do |d| 69 | { 70 | :debian => d.app_name, 71 | :dev => ENV['USER'] 72 | } 73 | end 74 | 75 | doc "Path to pidfile location" 76 | path :pidfile do |d| 77 | { 78 | :debian => "/var/run/#{d.app_name}/#{d.script_name}.pid", 79 | :dev => File.expand_path(d.script_name + '.pid') 80 | } 81 | end 82 | 83 | doc "Options supplied to ARGV of your program" 84 | string :program_options do |d| 85 | "" 86 | end 87 | 88 | doc "Run the daemon in debug mode" 89 | boolean :debug do |d| 90 | { 91 | :debian => "false", 92 | :dev => "true" 93 | } 94 | end 95 | 96 | doc "Not sure..." 97 | boolean :logging_enabled do 98 | true 99 | end 100 | 101 | end 102 | -------------------------------------------------------------------------------- /lib/jsvc/initd/template_binding.rb: -------------------------------------------------------------------------------- 1 | module JSVC 2 | 3 | class Initd::ParamError < StandardError 4 | 5 | attr_reader :param_name 6 | 7 | def initialize(msg, name) 8 | @param_name = name 9 | super(@param_name) 10 | end 11 | end 12 | 13 | # Raised when a value is looked for a parameter that isn't defined and 14 | # no value could be found for it either 15 | class Initd::UnknownParamError < Initd::ParamError ; end 16 | 17 | # Raised when a value is looked for for which no value has been given and 18 | # default has been specified 19 | class Initd::MissingParamError < Initd::ParamError ; end 20 | 21 | class Initd::TemplateBinding 22 | 23 | def initialize(context) 24 | @context = context 25 | end 26 | 27 | def method_missing(method_name, *args, &block) 28 | @context.send(method_name) 29 | end 30 | 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/jsvc/initd_cli.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Command line interface for generating init.d scripts for controlling 4 | # jruby-jsvc daemons 5 | # 6 | class JSVC::InitdCLI 7 | 8 | class ExitException < Exception ; end 9 | 10 | require 'jsvc/initd_cli/params' 11 | 12 | def initialize 13 | end 14 | 15 | def run(argv) 16 | params, *extra = Params.new.parse(argv) 17 | 18 | options = { 19 | :template_dir => find_template_dir 20 | } 21 | 22 | mode = extra.find {|arg| /^[^\-]/.match(arg) } 23 | mode = mode && mode.to_sym 24 | 25 | if extra.include? "--help" 26 | print_template_parameter_help(options, params) 27 | else 28 | build_from_template(options, mode, params) 29 | end 30 | end 31 | 32 | private 33 | 34 | def print_template_parameter_help(options, params) 35 | $stderr.puts "Usage: jruby-jsvc-initd [DEFAULT-STYLE] [OPTIONS] --param-[PARAM_NAME]=PARAM_VALUE" 36 | $stderr.puts 37 | $stderr.puts "Output an initd script, suitable for controlling your jruby-jsvc daemon, to stdout." 38 | $stderr.puts "Pick a default style to supply sensible defaults for your script, and override inividual parameters using --param-[PARAM_NAME]=VALUE" 39 | $stderr.puts 40 | $stderr.puts "Available default styles:" 41 | JSVC::Initd.declared_defaults.each do |mode| 42 | $stderr.puts " #{mode}" 43 | end 44 | $stderr.puts 45 | $stderr.puts "Parameters:" 46 | to_format = JSVC::Initd.defined_param_names.map do |name| 47 | param = JSVC::Initd.defined_params[name] 48 | 49 | [param.name.to_s, param.doc] 50 | end 51 | 52 | max_widths = to_format.transpose.map {|col| col.map {|v| v.length}.max } 53 | 54 | to_format.each do |row| 55 | $stderr.puts " " + row.zip(max_widths).map {|v, w| v.ljust(w) }.join(" ") 56 | end 57 | end 58 | 59 | # and output it on stdout 60 | def build_from_template(options, mode, params) 61 | begin 62 | JSVC::Initd.new(options).write($stdout, mode, params) 63 | rescue JSVC::Initd::UnknownParamError => e 64 | msg = 65 | "Template tried to reference parameter '#{e.param_name}', but this was " + 66 | "not supplied via --param-PARAM-NAME. See the help for more details" 67 | 68 | $stderr.puts msg 69 | exit 1 70 | rescue JSVC::Initd::MissingParamError => e 71 | msg = "You must supply a value for #{e.param_name} via --param-PARAM-NAME=VALUE\nSee the help for more details" 72 | 73 | $stderr.puts msg 74 | exit 1 75 | end 76 | end 77 | 78 | def parse(argv) 79 | 80 | end 81 | 82 | def find_template_dir 83 | ['/usr/share/jruby-jsvc/templates', 'templates/linux']. 84 | find {|dirname| File.directory?(dirname) } 85 | end 86 | 87 | end 88 | 89 | -------------------------------------------------------------------------------- /lib/jsvc/initd_cli/params.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Parses command line options, turning them in to a key value hash 4 | # 5 | class JSVC::InitdCLI::Params 6 | 7 | # 8 | # Returns a hash containing parsed options, and other unused options. Used like so: 9 | # `options, *unused = parse(ARGV)` 10 | # 11 | # Warning: this method blindly converts string input in to symbols 12 | # 13 | def parse(args) 14 | matching, extra = args. 15 | map {|elem| [elem, pattern.match(elem)] }. 16 | partition {|elem, match| ! match.nil? } 17 | 18 | options = matching. 19 | map {|elem, match| [match[1], match[2]]}. 20 | map {|rawkey, value| [rawkey.gsub('-', '_').intern, value] }. 21 | inject({}) {|m,(k,v)| m.merge(k => v) } 22 | 23 | [options, *extra.map {|e,m| e} ] 24 | end 25 | 26 | def pattern 27 | /^--param-([^=]+)=(.*)$/ 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.msp.jsvc 6 | jruby-jsvc 7 | 0.5.1 8 | jar 9 | 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 16 | 17 | 1.5 18 | 1.5 19 | 20 | 21 | 22 | 23 | 24 | jsvc 25 | http://maven.apache.org 26 | 27 | 28 | UTF-8 29 | 30 | 31 | 32 | 33 | junit 34 | junit 35 | 3.8.1 36 | test 37 | 38 | 39 | commons-daemon 40 | commons-daemon 41 | 1.0.6 42 | provided 43 | 44 | 45 | org.jruby 46 | jruby 47 | 1.4.1 48 | provided 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /spec/daemon_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'spec/helpers' 3 | 4 | describe "Controlling a daemon" do 5 | 6 | include InitdHelper 7 | 8 | let(:script_fname) { '/tmp/test-jsvc' } 9 | 10 | let(:example_app_dir) { File.join(File.dirname(__FILE__), '..', 'example') } 11 | 12 | before do 13 | JSVC.init 14 | 15 | write_script(script_fname, :dev, {:app_name => 'example', 16 | :script_name => 'good_daemon', :module_name => 'Crazy', 17 | :debug => false, 18 | :app_home => example_app_dir}) 19 | 20 | File.chmod(0755, script_fname) 21 | end 22 | 23 | def consume_stream(s, &block) 24 | Thread.new do 25 | begin 26 | while true 27 | begin 28 | puts 'a' 29 | line = s.readline 30 | puts 'b' 31 | puts line 32 | result = yield line if block_given? 33 | break if result 34 | rescue EOFError 35 | break 36 | end 37 | end 38 | rescue 39 | puts "error: #{$!}" 40 | end 41 | end 42 | end 43 | 44 | # pending until I can gather the mental strength to complete it 45 | it "can start a daemon up, and then stop" 46 | #do 47 | # start_command = "#{script_fname} start" 48 | 49 | # IO.popen3(start_command) do |stdin, sout, serr| 50 | # begin 51 | # consume_stream(sout) {|l| 52 | # if l.strip == 'Waiting for something to happen' 53 | # true 54 | # end 55 | # }.join 56 | 57 | # sleep 10 58 | # 59 | # puts "thread returned, pid: #{pid.inspect}" 60 | # `#{script_fname} stop` 61 | # end 62 | # end 63 | #end 64 | 65 | end 66 | 67 | -------------------------------------------------------------------------------- /spec/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'jsvc' 2 | require 'jsvc/initd' 3 | 4 | RSpec::Matchers.define :exit_with do |expected, _| 5 | match do |actual| 6 | actual[:status].exitstatus == expected 7 | end 8 | 9 | failure_message_for_should do |actual| 10 | "expected that `#{actual[:command]}` would have exited with: #{expected}\nexited with: #{actual[:status].exitstatus}\nstderr:\n#{actual[:stderr]}\nstdout:\n#{actual[:stdout]}" 11 | end 12 | 13 | end 14 | 15 | require 'popen4' 16 | 17 | module ExecHelper 18 | 19 | def run(cmd) 20 | @last_command = cmd 21 | 22 | @last_status = POpen4.popen4(@last_command) do |stdout, stderr, stdin, pid| 23 | @last_stdout = stdout.read.strip 24 | @last_stderr = stderr.read.strip 25 | @last_pid = pid 26 | end 27 | end 28 | 29 | def last_run 30 | { 31 | :command => @last_command, 32 | :stdout => @last_stdout, 33 | :stderr => @last_stderr, 34 | :pid => @last_pid, 35 | :status => @last_status 36 | } 37 | end 38 | 39 | def exec(cmd) 40 | run cmd 41 | if @last_status.exitstatus != 0 42 | raise "command: #{cmd} failed with #{@last_status.exitstatus}\n#{@all_output}" 43 | end 44 | true 45 | end 46 | 47 | def all_output 48 | @last_stdout + "\n" + @last_stderr 49 | end 50 | 51 | attr_reader :last_stdout, :last_stderr, :last_status 52 | end 53 | 54 | module InitdHelper 55 | def write_script(fname, mode, template_options) 56 | File.open(fname, 'w') do |f| 57 | JSVC::Initd.new.write(f, mode, template_options) 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/initd_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec/helpers' 2 | require 'jsvc/initd_cli' 3 | 4 | describe 'building an init.d style daemon controller script' do 5 | 6 | describe 'JSVC::InitdCLI::Params' do 7 | 8 | def parse(args) 9 | JSVC::InitdCLI::Params.new.parse(args) 10 | end 11 | 12 | it 'returns an empty hash and no extras when you pass it an empty array' do 13 | parse([]).should == [{}] 14 | end 15 | 16 | it 'converts parameters in the form "--param-[KEY]=[VALUE]" to key value pairs' do 17 | parse(['--param-cat=dog']).should == [{:cat => 'dog'}] 18 | parse(['--param-cat=dog', '--param-cheese=edam']).should == 19 | [{:cat => 'dog', :cheese => 'edam'}] 20 | end 21 | 22 | it 'converts hyphens in to underscores' do 23 | parse(['--param-top-hat=none']).should == [{:top_hat => 'none'}] 24 | end 25 | 26 | it 'leaves underscores in parameter names alone' do 27 | parse(['--param-shoe_laces=long']).should == [{:shoe_laces => 'long'}] 28 | end 29 | 30 | it 'returns any non-matching parameters' do 31 | parse(['log', '--param-jvm=sun', '--verbose', '--param-cmd=nil', 'bob']).should == 32 | [{:jvm => 'sun', :cmd => 'nil'}, 'log', '--verbose', 'bob'] 33 | end 34 | end 35 | 36 | describe "output" do 37 | 38 | include ExecHelper 39 | 40 | def create_script(args) 41 | run("ruby -Ilib bin/jruby-jsvc-initd #{args}") 42 | end 43 | 44 | it "can create a script to run the daemon in debug mode" do 45 | create_script "dev --param-app-name=test --param-module-name=MyModule" 46 | 47 | last_run.should exit_with(0) 48 | 49 | last_stdout.should_not include("-outfile") 50 | last_stdout.should_not include("-errfile") 51 | 52 | last_stdout.should include("-nodetach") 53 | last_stdout.should include("-debug") 54 | end 55 | 56 | it "can be created in debian mode" do 57 | create_script "debian --param-app-name=test --param-module-name=MyModule" 58 | 59 | last_run.should exit_with(0) 60 | 61 | last_stdout.should include("-outfile") 62 | last_stdout.should include("-errfile") 63 | 64 | last_stdout.should_not include("-nodetach") 65 | last_stdout.should_not include("-debug") 66 | 67 | # FIXME some more assertions would be nice 68 | end 69 | 70 | 71 | it "fails with a nice error message if you miss a required arg" do 72 | create_script "dev --param-app-name --param-module-name=MyModule --param-debug=true" 73 | 74 | last_run.should exit_with(1) 75 | last_stderr.should include("You must supply a value for app_name via --param-PARAM-NAME=VALUE") 76 | last_stderr.should include("See the help for more details") 77 | end 78 | 79 | it "can be created without a mode" do 80 | all_args = JSVC::Initd.defined_params.map {|n, p| "--param-#{p.name}=false" }. 81 | join(" ") 82 | 83 | create_script " #{all_args}" 84 | 85 | last_run.should exit_with(0) 86 | end 87 | end 88 | 89 | # IDEA: use vagrant to do more realistic testing? 90 | 91 | it 'runs in dev mode, loading templates from the working directory' 92 | it 'barfs if you do not pass all the required parameters in' 93 | it 'can take non-standard parameters from the command line in the form "--param-[NAME]=[VALUE]"' 94 | it 'can run in debian mode, loading templates from /usr/share/jruby-jsvc/templates' 95 | 96 | end 97 | -------------------------------------------------------------------------------- /spec/param_dsl_spec.rb: -------------------------------------------------------------------------------- 1 | require 'jsvc' 2 | require 'jsvc/initd/param_dsl' 3 | 4 | describe "JSVC::Initd::ParamDSL" do 5 | 6 | before do 7 | JSVC::Initd.clear_defined! 8 | end 9 | 10 | def define_params(&block) 11 | JSVC::Initd.define_params(&block) 12 | end 13 | 14 | def defined 15 | JSVC::Initd.defined_params 16 | end 17 | 18 | def context(mode=:dev) 19 | JSVC::Initd::Context.new(mode, defined, {}) 20 | end 21 | 22 | describe "defining parameters" do 23 | 24 | it "defined with no docs" do 25 | 26 | define_params do 27 | string :test 28 | end 29 | 30 | param = defined[:test] 31 | param.should_not be_nil 32 | param.name.should eq(:test) 33 | param.doc.should be_nil 34 | param.default(context).should be_nil 35 | end 36 | 37 | it "defined with a doc string" do 38 | define_params do 39 | doc 'this is only a test' 40 | string :test 41 | end 42 | 43 | param = defined[:test] 44 | param.should_not be_nil 45 | param.name.should eq(:test) 46 | param.doc.should eq('this is only a test') 47 | param.default(context).should be_nil 48 | end 49 | 50 | it "defined with a default" do 51 | define_params do 52 | string :test do 53 | 'this is merely a test' 54 | end 55 | end 56 | 57 | param = defined[:test] 58 | param.should_not be_nil 59 | param.name.should eq(:test) 60 | param.doc.should be_nil 61 | param.default(context).should eq('this is merely a test') 62 | end 63 | 64 | it "defined with mode-switched defaults" do 65 | define_params do 66 | string :test do 67 | {:dev => 'this is merely a test', :prod => 'this is no longer a test'} 68 | end 69 | end 70 | 71 | param = defined[:test] 72 | param.should_not be_nil 73 | param.name.should eq(:test) 74 | param.doc.should be_nil 75 | param.default(context).should eq('this is merely a test') 76 | param.default(context(:prod)).should eq('this is no longer a test') 77 | end 78 | 79 | it "defined with defaults that refer to other parameters" do 80 | 81 | define_params do 82 | string(:cat) { "Tabby" } 83 | string(:pussy) { |d| "Fat #{d.cat}" } 84 | end 85 | 86 | param = defined[:pussy] 87 | param.should_not be_nil 88 | param.name.should eq(:pussy) 89 | param.doc.should be_nil 90 | param.default(context).should eq('Fat Tabby') 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /src/main/java/com/msp/jsvc/JRubyDaemon.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Media Service Provider Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License./* 15 | * 16 | */ 17 | package com.msp.jsvc; 18 | 19 | import java.io.IOException; 20 | import java.net.URL; 21 | 22 | import org.apache.commons.daemon.Daemon; 23 | import org.apache.commons.daemon.DaemonContext; 24 | import org.apache.commons.daemon.DaemonController; 25 | import org.apache.commons.daemon.DaemonInitException; 26 | import org.apache.commons.daemon.DaemonUserSignal; 27 | import org.jruby.Ruby; 28 | import org.jruby.RubyException; 29 | import org.jruby.RubyInstanceConfig; 30 | import org.jruby.RubyModule; 31 | import org.jruby.runtime.builtin.IRubyObject; 32 | import org.jruby.RubyString; 33 | import org.jruby.exceptions.MainExitException; 34 | import org.jruby.exceptions.RaiseException; 35 | import org.jruby.javasupport.JavaEmbedUtils; 36 | 37 | /** 38 | * Implements {@link Daemon} to bootstrap a jruby instance, initialize your 39 | * application, and then tell the application to start doing whatever it does. 40 | * 41 | * @author Nick Griffiths 42 | */ 43 | public class JRubyDaemon implements Daemon, DaemonUserSignal { 44 | 45 | private static final String PROP_MODULE_NAME = "jruby.daemon.module.name"; 46 | private static final String DAEMON = "Daemon"; 47 | private Ruby runtime; 48 | private Thread thread; 49 | private RubyModule appModule; 50 | private boolean debug; 51 | private RubyModule daemon; 52 | private String appModuleName; 53 | private RubyInstanceConfig rubyConfig; 54 | private DaemonController controller; 55 | 56 | public JRubyDaemon() {} 57 | 58 | /** 59 | */ 60 | public void init(DaemonContext arguments) throws Exception { 61 | this.controller = arguments.getController(); 62 | 63 | init(arguments.getArguments()); 64 | } 65 | 66 | public void init(String[] arguments) throws Exception { 67 | initParams(); 68 | initJRuby(arguments); 69 | loadScript(); 70 | checkDaemon(); 71 | } 72 | 73 | private void loadScript() throws DaemonInitException { 74 | if (debug) log("Executing script from: " + rubyConfig.getScriptFileName()); 75 | 76 | // boot her up. The script should yield control back to us so we 77 | // can give control back to jsvc once 78 | try { 79 | runtime.runFromMain(rubyConfig.getScriptSource(), rubyConfig.getScriptFileName()); 80 | } catch (RaiseException e) { 81 | RubyException re = e.getException(); 82 | if (re.getType().getName().equals("JSVC::DaemonInitError")) { 83 | throw new DaemonInitException(re.message.toString()); 84 | } else { 85 | log("Script raised an error: " + e); 86 | throw e; 87 | } 88 | } catch (RuntimeException e) { 89 | log("Error executing script: " + e); 90 | throw e; 91 | } 92 | 93 | appModule = runtime.getModule(appModuleName); 94 | if (appModule == null) { throw new RuntimeException("Couldn't get module '" + appModuleName + "' from JRuby runtime"); } 95 | daemon = (RubyModule) appModule.getConstantAt(DAEMON); 96 | if (daemon == null) { throw new RuntimeException("Couldn't get " + DAEMON + " module from " + appModuleName); } 97 | } 98 | 99 | private void initJRuby(String[] arguments) { 100 | // mimicking startup from org.jruby.Main 101 | this.rubyConfig = new RubyInstanceConfig(); 102 | rubyConfig.processArguments(arguments); 103 | runtime = Ruby.newInstance(rubyConfig); 104 | Thread.currentThread().setContextClassLoader(runtime.getJRubyClassLoader()); 105 | loadSupportScripts(); 106 | } 107 | 108 | private void loadSupportScripts() { 109 | String errorsPath = "/ruby/lib/jsvc/errors.rb"; 110 | URL scriptResource = getClass().getResource(errorsPath); 111 | try { 112 | runtime.loadFile(scriptResource.toString(), scriptResource.openStream(), false); 113 | } catch (IOException e) { 114 | throw new RuntimeException("Couldn't load script from " + errorsPath); 115 | } 116 | } 117 | 118 | private void initParams() throws Exception { 119 | this.debug = "true".equals(System.getProperty("JRubyDaemon.debug")); 120 | this.appModuleName = System.getProperty(PROP_MODULE_NAME); 121 | if (appModuleName == null) { 122 | throw new Exception("Property " + PROP_MODULE_NAME + " must be set"); 123 | } 124 | } 125 | 126 | private String daemonName() { 127 | return this.appModuleName + "::" + DAEMON; 128 | } 129 | 130 | public void start() throws Exception { 131 | // do it in a separate thread, because jsvc expects this method to return. 132 | thread = new Thread("jsvc-daemon-main") { 133 | @Override 134 | public void run() { 135 | if (debug) log("thread " + thread + " starting daemon..."); 136 | daemon.callMethod("start"); 137 | } 138 | }; 139 | thread.setDaemon(false); 140 | thread.setContextClassLoader(runtime.getJRubyClassLoader()); 141 | thread.start(); 142 | } 143 | 144 | public void stop() throws Exception { 145 | if (debug) log("Stopping..."); 146 | try { 147 | daemon.callMethod("stop"); 148 | } catch (MainExitException e) { 149 | } 150 | if (debug) 151 | log("Stopped"); 152 | } 153 | 154 | public void destroy() { 155 | runtime.tearDown(); 156 | } 157 | 158 | public void signal() { 159 | if (debug) log("received signal"); 160 | 161 | RubyString reloadString = RubyString.newString(runtime, "signal"); 162 | 163 | if (isTrue(daemon.callMethod("respond_to?", reloadString))) { 164 | daemon.callMethod("signal"); 165 | } else { 166 | if (debug) log("jruby daemon doesn't respond to signal"); 167 | } 168 | } 169 | 170 | private Boolean isTrue(IRubyObject ro) { 171 | return (Boolean) JavaEmbedUtils.rubyToJava(runtime, ro, Boolean.class); 172 | } 173 | 174 | private void checkDaemon() { 175 | // check it has come up OK 176 | Boolean wasSetup = isTrue(daemon.callMethod("setup?")); 177 | 178 | if (wasSetup == null || !wasSetup.booleanValue()) { 179 | throw new RuntimeException("Daemon script did not call " + daemonName() + ".setup - can't tell if init succeeded."); 180 | } 181 | 182 | } 183 | 184 | private void log(String msg) { 185 | System.err.println("JRubyDaemon: " + msg); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/msp/procrun/JRubyService.java: -------------------------------------------------------------------------------- 1 | package com.msp.procrun; 2 | 3 | import com.msp.jsvc.JRubyDaemon; 4 | 5 | public class JRubyService { 6 | 7 | private static JRubyDaemon daemon = new JRubyDaemon(); 8 | 9 | public static void start(String[] arguments) { 10 | try { 11 | System.out.println("Daemon arguments: " + java.util.Arrays.asList(arguments).toString()); 12 | daemon.init(arguments); 13 | daemon.start(); 14 | } catch (Exception e) { 15 | e.printStackTrace(); 16 | } 17 | } 18 | 19 | public static void stop(String[] arguments) { 20 | try { 21 | daemon.stop(); 22 | daemon.destroy(); 23 | } catch (Exception e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/ruby/lib/jsvc/errors.rb: -------------------------------------------------------------------------------- 1 | module JSVC 2 | class DaemonInitError < RuntimeError; end 3 | end 4 | -------------------------------------------------------------------------------- /templates/linux/010-head.sh.erb: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # Script generated by jruby-jsvc 3 | # DO NOT EDIT 4 | 5 | # inserv stuff for squeeze 6 | ### BEGIN INIT INFO 7 | # Provides: <%= script_name %> 8 | # Required-Start: $local_fs $network 9 | # Required-Stop: $local_fs $network 10 | # Default-Start: 2 3 4 5 11 | # Default-Stop: 0 1 6 12 | # Short-Description: starts the <%= script_name %> daemon for <%= app_name %> 13 | # Description: starts the <%= script_name %> daemon for <%= app_name %> using jruby-jsvc 14 | ### END INIT INFO 15 | -------------------------------------------------------------------------------- /templates/linux/050-vars.sh.erb: -------------------------------------------------------------------------------- 1 | APP_NAME=<%= app_name %> 2 | APP_HOME=<%= app_home %> 3 | SCRIPT_NAME=<%= script_name %> 4 | MODULE_NAME=<%= module_name %> 5 | JSVC=<%= jsvc %> 6 | JAVA_HOME=<%= java_home %> 7 | JRUBY_HOME=<%= jruby_home %> 8 | RUBY_SCRIPT=<%= script_path %> 9 | JRUBY_JSVC_JAR=<%= jruby_jsvc_jar %> 10 | DAEMON_JAR=<%= commons_daemon_jar %> 11 | PIDFILE=<%= pidfile %> 12 | 13 | <% if debug == 'true' %> 14 | JSVC_ARGS_EXTRA="-debug -nodetach " 15 | JAVA_PROPS="-DJRubyDaemon.debug=true" 16 | 17 | echo "JAVA_HOME : $JAVA_HOME" 18 | echo "JRUBY_HOME : $JRUBY_HOME" 19 | <% end %> 20 | 21 | APP_USER=<%= app_user %> 22 | 23 | <% if logging_enabled %> 24 | LOG_DIR=/var/log/$APP_NAME 25 | <% end %> 26 | 27 | # Implements the jsvc Daemon interface. 28 | MAIN_CLASS=com.msp.jsvc.JRubyDaemon 29 | 30 | # most important jvm parameters! 31 | JVM_MAX_HEAP_SIZE=512m 32 | JVM_INITIAL_HEAP_SIZE=512m 33 | JVM_THREAD_STACK_SIZE=1024k 34 | 35 | # Wait this long for daemon to start up before considering it to have failed. 36 | # Must be in multiples of 10, minimum of 10 seconds. 37 | # Default is high (ten minutes) enough to be considered wait forever. 38 | JSVC_WAIT_TIME_SECONDS=600 39 | 40 | # local overrides 41 | if [ -e /etc/default/${SCRIPT_NAME} ]; then 42 | . /etc/default/${SCRIPT_NAME} 43 | fi 44 | 45 | -------------------------------------------------------------------------------- /templates/linux/080-form-args.sh.erb: -------------------------------------------------------------------------------- 1 | # If you want your programs to run as or not as daemons pass a flag to tell them which they are 2 | PROG_OPTS="$PROG_OPTS <%= program_options %>" 3 | 4 | if [ ! -e $JRUBY_JSVC_JAR ] ; then 5 | echo "fatal: JRUBY_JSVC_JAR $JRUBY_JSVC_JAR is missing" 6 | exit 1 7 | fi 8 | if [ ! -e $DAEMON_JAR ] ; then 9 | echo "fatal: DAEMON_JAR $DAEMON_JAR is missing" 10 | exit 1 11 | fi 12 | 13 | 14 | CLASSPATH=$JRUBY_HOME/lib/jruby.jar:$JRUBY_HOME/lib/profile.jar:$DAEMON_JAR:$JRUBY_JSVC_JAR 15 | 16 | <% if debug == "true" %> 17 | echo "CLASSPATH : $CLASSPATH" 18 | <% end %> 19 | 20 | JAVA_NATIVE_PROPS="-Djna.boot.library.path=$JRUBY_HOME/lib/native/linux-i386:$JRUBY_HOME/lib/native/linux-amd64 \ 21 | -Djffi.boot.library.path=$JRUBY_HOME/lib/native/i386-Linux:$JRUBY_HOME/lib/native/s390x-Linux:$JRUBY_HOME/lib/native/x86_64-Linux" 22 | 23 | JAVA_PROPS="$JAVA_PROPS -Djruby.memory.max=$JVM_MAX_HEAP_SIZE \ 24 | -Djruby.stack.max=$JVM_THREAD_STACK_SIZE \ 25 | $JAVA_NATIVE_PROPS \ 26 | -Djruby.home=$JRUBY_HOME \ 27 | -Djruby.lib=$JRUBY_HOME/lib \ 28 | -Djruby.script=jruby \ 29 | -Djruby.shell=/bin/sh 30 | -Djruby.daemon.module.name=$MODULE_NAME" 31 | 32 | JAVA_OPTS="-Xmx$JVM_MAX_HEAP_SIZE \ 33 | -Xms$JVM_INITIAL_HEAP_SIZE \ 34 | -Xss$JVM_THREAD_STACK_SIZE \ 35 | -Xbootclasspath/a:$JRUBY_HOME/lib/jruby.jar" 36 | 37 | JSVC_ARGS="-home $JAVA_HOME \ 38 | $JSVC_ARGS_EXTRA \ 39 | -wait $JSVC_WAIT_TIME_SECONDS \ 40 | -pidfile $PIDFILE \ 41 | -user $APP_USER \ 42 | -procname jsvc-$SCRIPT_NAME \ 43 | -jvm server" 44 | 45 | <% if debug == "false" %> 46 | JSVC_ARGS="$JSVC_ARGS \ 47 | -outfile $LOG_DIR/jsvc-$SCRIPT_NAME.log \ 48 | -errfile &1" 49 | <% end %> 50 | -------------------------------------------------------------------------------- /templates/linux/100-start-stop.sh.erb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Stop/Start 4 | # 5 | 6 | STOP_COMMAND="$JSVC $JSVC_ARGS -stop $MAIN_CLASS" 7 | START_COMMAND="$JSVC $JSVC_ARGS -cp $CLASSPATH $JAVA_PROPS $JAVA_OPTS $MAIN_CLASS $RUBY_SCRIPT $PROG_OPTS" 8 | 9 | case "$1" in 10 | start) 11 | if [ -e "$PIDFILE" ]; then 12 | echo "Pidfile already exists, not starting." 13 | exit 1 14 | else 15 | echo "Starting $SCRIPT_NAME daemon..." 16 | $START_COMMAND 17 | EXIT_CODE=$? 18 | if [ "$EXIT_CODE" != 0 ]; then 19 | echo "Daemon exited with status: $EXIT_CODE. Check pidfile and log" 20 | fi 21 | fi 22 | ;; 23 | stop) 24 | if [ -e "$PIDFILE" ]; then 25 | echo "Stopping $SCRIPT_NAME daemon..." 26 | $STOP_COMMAND 27 | else 28 | echo "No pid file, not stopping." 29 | exit 1 30 | fi 31 | ;; 32 | restart) 33 | if [ -e "$PIDFILE" ]; then 34 | echo "Stopping $SCRIPT_NAME daemon..." 35 | $STOP_COMMAND 36 | fi 37 | if [ -e "$PIDFILE" ]; then 38 | echo "Pidfile still present, $SCRIPT_NAME hasn't stopped" 39 | exit 1 40 | else 41 | echo "Starting $SCRIPT_NAME daemon..." 42 | $START_COMMAND 43 | EXIT_CODE=$? 44 | if [ "$EXIT_CODE" != 0 ]; then 45 | echo "Daemon exited with status: $EXIT_CODE. Check pidfile and log" 46 | fi 47 | fi 48 | ;; 49 | status) 50 | if [ "$PIDFILE" ]; then 51 | PID=`cat $PIDFILE` 52 | OUTPUT=`ps $PID | egrep "^$PID "` 53 | if [ ${#OUTPUT} -gt 0 ]; then 54 | echo "Service running with pid: $PID" 55 | else 56 | echo "Pidfile present, but process not running" 57 | fi 58 | else 59 | echo "No pidfile present" 60 | fi 61 | ;; 62 | *) 63 | echo "Unrecognised command. Usage jsvc-daemon [ start | stop ]" 64 | ;; 65 | esac --------------------------------------------------------------------------------