In this blog post we learn how to install a Hudson JNLP slave agent that is started headlessly as a service on Ubuntu. There are many tutorials and how-to's available but for me this was the easiest way to install the Hudson slave agent. We use the default Hudson installation for Ubuntu and use the provided scripts as templates for the Hudson slave scripts.
First we start with installing Hudson following the steps from Hudson Debian packages:
$ wget -q -O - http://pkg.hudson-labs.org/debian/hudson-labs.org.key | sudo apt-key add -
Next we must open /etc/apt/sources.list
and add the following line:
deb http://pkg.hudson-labs.org/debian binary/
Next we can update apt-get and install Hudson using apt-get:
$ sudo apt-get update $ sudo apt-get install hudson
After the installation via apt-get a Hudson user account is created and the several directories used by Hudson, like /usr/share/hudson
and /var/lib/hudson
. Now it is time to create the init scripts so the Hudson JNLP slave agent is started and stopped with scripts. We have to copy the files /etc/default/hudson
and /etc/init.d/hudson
to hudson-slave
variants:
$ sudo cp /etc/default/hudson /etc/default/hudson-slave $ sudo cp /etc/init.d/hudson /etc/init.d/hudson-slave
Now we can edit these files and change the contents. It is important to notice we must define a HUDSON_MASTER
variable that references the Hudson master server. Also we must define a HUDSON_SLAVE
variable that contains the node name as defined at the Hudson master configuration.
# /etc/default/hudson-slave # defaults for hudson continuous integration server slave agent # pulled in from the init script; makes things easier. NAME=hudson-slave # location of java JAVA=/usr/bin/java # arguments to pass to java #JAVA_ARGS="-Xmx256m" PIDFILE=/var/run/hudson/hudson-slave.pid # user id to be invoked as (otherwise will run as root; not wise!) HUDSON_USER=hudson # location of the hudson jar file HUDSON_JAR=/usr/share/hudson/hudson.jar # hudson home location HUDSON_HOME=/var/lib/hudson # log location. this may be a syslog facility.priority HUDSON_LOG=/var/log/hudson/$NAME.log #HUDSON_LOG=daemon.info # OS LIMITS SETUP # comment this out to observe /etc/security/limits.conf # this is on by default because http://github.com/feniix/hudson/commit/d13c08ea8f5a3fa730ba174305e6429b74853927 # reported that Ubuntu's PAM configuration doesn't include pam_limits.so, and as a result the # of file # descriptors are forced to 1024 regardless of /etc/security/limits.conf MAXOPENFILES=8192 # server name of hudson HUDSON_MASTER=hudson-master # name of hudson slave HUDSON_SLAVE=hudson-slave-01 # arguments to pass to hudson. HUDSON_ARGS="-jnlpUrl http://$HUDSON_MASTER/computer/$HUDSON_SLAVE/slave-agent.jnlp"
In the init script we don't change much compared to the original script. In the do_start() method we use the wget command to get the slave.jar
from the Hudson master, so we always use an up-to-date version if the script is started.
#!/bin/bash # /etc/init.d/hudson-slave # debian-compatible hudson slave agent startup script. # Amelia A Lewis <alewis@ibco.com> # ### BEGIN INIT INFO # Provides: hudson-slave # Required-Start: $remote_fs $syslog $network # Required-Stop: $remote_fs $syslog $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start hudson slave agent at boot time # Description: Controls the hudson continuous integration slave agent. ### END INIT INFO PATH=/bin:/usr/bin:/sbin:/usr/sbin DESC="Hudson Continuous Integration Server Slave Agent" NAME=hudson-slave SCRIPTNAME=/etc/init.d/$NAME [ -r /etc/default/$NAME ] && . /etc/default/$NAME #DAEMON=$HUDSON_SH DAEMON=/usr/bin/daemon DAEMON_ARGS="--name=$NAME --inherit --env=HUDSON_HOME=$HUDSON_HOME --output=$HUDSON_LOG --pidfile=$PIDFILE" SU=/bin/su # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # load environments if [ -r /etc/default/locale ]; then . /etc/default/locale export LANG LANGUAGE elif [ -r /etc/environment ]; then . /etc/environment export LANG LANGUAGE fi # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # Make sure we run as root, since setting the max open files through # ulimit requires root access if [ `id -u` -ne 0 ]; then echo "The $NAME init script can only be run as root" exit 1 fi # # Function that starts the daemon/service # do_start() { # get slave.jar from server wget -O $HUDSON_JAR http://$HUDSON_MASTER/jnlpJars/slave.jar > /dev/null 2>&1 # the default location is /var/run/hudson/hudson.pid but the parent directory needs to be created mkdir `dirname $PIDFILE` > /dev/null 2>&1 || true chown $HUDSON_USER `dirname $PIDFILE` # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started $DAEMON $DAEMON_ARGS --running && return 1 # If the var MAXOPENFILES is enabled in /etc/default/hudson then set the max open files to the # proper value if [ -n "$MAXOPENFILES" ]; then [ "$VERBOSE" != no ] && echo Setting up max open files limit to $MAXOPENFILES ulimit -n $MAXOPENFILES fi # --user in daemon doesn't prepare environment variables like HOME, USER, LOGNAME or USERNAME, # so we let su do so for us now $SU -l $HUDSON_USER --shell=/bin/bash -c "$DAEMON $DAEMON_ARGS -- $JAVA $JAVA_ARGS -jar $HUDSON_JAR $HUDSON_ARGS" || return 2 } # # Verify that all hudson processes have been shutdown # and if not, then do killall for them # get_running() { return `ps -U $HUDSON_USER --no-headers -f | egrep -e '(java|daemon)' | grep -c . ` } force_stop() { get_running if [ $? -ne 0 ]; then killall -u $HUDSON_USER java daemon || return 3 fi } # Get the status of the daemon process get_daemon_status() { $DAEMON $DAEMON_ARGS --running || return 1 } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred get_daemon_status case "$?" in 0) $DAEMON $DAEMON_ARGS --stop || return 2 # wait for the process to really terminate while true; do sleep 1 $DAEMON $DAEMON_ARGS --running || break done ;; *) force_stop || return 3 ;; esac # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return 0 } case "$1" in start) log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; stop) log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; status) get_daemon_status case "$?" in 0) echo "Hudson slave agent is running with the pid `cat $PIDFILE`";; *) get_running procs=$? if [ $procs -eq 0 ]; then echo -n "Hudson slave agent is not running" if [ -f $PIDFILE ]; then echo ", but the pidfile ($PIDFILE) still exists" else echo fi else echo "$procs instances of hudson slaves are running at the moment" echo "but the pidfile $PIDFILE is missing" fi ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac exit 0
We are almost done. We only have to configure the correct runlevels for our init script and disable the hudson scripts that were installed by the apt-get installation:
$ sudo /etc/init.d/hudson stop $ sudo update-rc.d -f hudson remove $ sudo update-rc.d hudson-slave defaults
After we have configured the slave node in the Hudson master configuration we can start our Hudson JNLP slave agent:
$ sudo /etc/init.d/hudson-slave start
To change environment variables for the Hudson build processes we only have to create or modify the settings for the Hudson user. For example we can add a GRAILS_HOME
environment variable to the .profile
file in /var/lib/hudson
and it is available in the build process.