Hashdot: Java-platform Script Launcher: 1.4.0

Summary

Hashdot elevates Java-platform script interpreters to first class status on Unix-like operating systems. It provides a script aware replacement to the stock 'java' launcher, and thus avoids numerous issues in using the 'java' launcher to bootstrap a script interpreter. All relevant interpreter and JVM options (i.e: Java heap size) may be specified directly in a script header and/or via system profiles, without resorting to environment variables, command line arguments, and the additional wrapper shell scripts needed to maintain them.

Hashdot can support any Java-platform language. Sample profiles are provided for Jython, JRuby, Scala, Groovy, Rhino Javascript, and Clojure. Examples below are given using JRuby.

Hashdot is released under GPL v3 with a linking exception. See License.

See also relevent usage examples and news in the void * blog.

Features

Hashbang

Hashdot supports direct Unix hashbang interpreter declarations without the need to use 'env', embedded shell scripts, or other novelties required with the 'java' launcher:

#!/usr/bin/hashdot

...the script...

Properties

Hashdot properties may be set in a script header or in profiles. Properties are used to control Hashdot and JVM startup. Hashdot properties are also mapped to Java system properties, and thus may influence the JDK, script interpreter, or other script components. Example script header:

#!/usr/bin/hashdot
# Example Header
#
# Add Java VM options:
#.hashdot.vm.options += -Xmx500m -Xss1024k
#
# Add jars to the system classpath from my.app.home:
#.my.app.home = /opt/myapp
#.java.class.path += ${my.app.home}/lib/*.jar
#
# Set default encoding Java system property:
#.file.encoding = UTF-8

...the script...

Profiles

A profile is a set of properties in a *.hdp file under the system profile directory (default: /etc/hashdot/profiles). A profile is referenced via the special "hashdot.profile" directive. The following example is a complete JRuby script:

#!/usr/bin/hashdot
#.hashdot.profile = jruby
#.hashdot.vm.options += -Xmx256m

puts "hello world!"

Where the script overrides of the jruby.hdp default heap size of 500m.

Profile Links

If hashdot is started using an executable name other than 'hashdot', it attempts to load an initial profile of the same name. So if the 'jruby.hdp' profile is installed, and 'jruby' symlinked to 'hashdot' under /usr/bin, as in:

hashdot*
jruby -> hashdot*

Then the above script example can be further reduced to:

#!/usr/bin/jruby
#.hashdot.vm.options += -Xmx256m

puts "hello world!"

Use of a profile-specific symlink is the preferred approach and is required for script languages needing an alternative hashdot.header.comment, since a profile could not otherwise be specified in a script header.

On the PATH

Provided that the above "/usr/bin/jruby" symlink is the first "jruby" in PATH, the following will also be launched by hashdot:

#!/usr/bin/env jruby

puts "hello world!"

Hashdot launching can thus be made completely transparent and compatible with the hashbang directives in existing scripts, without modification. Similarly, various command line invocations of a profile symlink can be successfully launched:

% jruby -d hello.rb
hello world!
% cat hello.rb | jruby
hello world!
% jruby -e 'puts "hello world!"'
hello world!

Script as UNIX Process

Hashdot invoked script processes are reported by UNIX utilities such as "ps", "pgrep", or "top" using the script name and script arguments instead of as "java" or the full java arguments gore. Thus Java script interpreters under hashdot are reported the same as with native interpreters like bash. Example script "myservice":

[ "ps -p #{Process.pid}", "ps -f -p #{Process.pid}" ].each do |command|
  puts command
  IO.popen( command, "r" ) do |f|
    puts f.readlines
  end
  puts
end

When run using the "jruby" symlink to hashdot:

% /usr/bin/jruby ./myservice
ps -p 16774
  PID TTY          TIME CMD
16774 pts/2    00:00:01 myservice

ps -f -p 16774
UID        PID  PPID  C STIME TTY          TIME CMD
david    16774 27647 99 14:25 pts/2    00:00:01 /usr/bin/jruby ./myservice

When run using the current JRuby provided wrapper script and 'java' launcher (line breaks added):

% /opt/dist/jruby-1.1.4/bin/jruby ./myservice
ps -p 16792
  PID TTY          TIME CMD
16792 pts/2    00:00:00 java

ps -f -p 16792
UID        PID  PPID  C STIME TTY          TIME CMD
david    16792 27647 89 14:26 pts/2    00:00:00 /opt/java/jdk_sun_1.6.0_05_x32/bin/java\
-client -Djruby.memory.max=500m -Djruby.stack.max=1024k -Xmx500m -Xss1024k \
-Xbootclasspath/a:/opt/dist/jruby-1.1.4/lib/jruby.jar -classpath \
/opt/dist/jruby-1.1.4/lib/bsf.jar:/opt/dist/jruby-1.1.4/lib/jruby.jar:\
/opt/dist/jruby-1.1.4/lib/profile.jar -Djruby.home=/opt/dist/jruby-1.1.4 \
-Djruby.lib=/opt/dist/jruby-1.1.4/lib -Djruby.script=jruby \
-Djruby.shell=/bin/sh org.jruby.Main ./myservice

Daemonize

Hashdot supports hashdot.daemonize, hashdot.io_redirect.*, and hashdot.pid_file properties, conveniently packaged in a provided "daemon" profile, to invoke a script as a detached UNIX daemon process. This includes full support for directing process STDERR and STDOUT (including JVM) to a specified file.

Platforms

Hashdot has been tested under Linux (x86_32 and x86_64) and Mac OS X. See INSTALL in the source distribution root directory for additional notes on building.