HOWTO - virtualbox as a service on Windows (cygrunsrv.exe)

Discussions related to using VirtualBox on Windows hosts.
Post Reply
sagemintblue
Posts: 6
Joined: 3. Oct 2009, 07:25
Primary OS: MS Windows 7
VBox Version: PUEL
Guest OSses: Ubuntu 9.0.4 64bit

HOWTO - virtualbox as a service on Windows (cygrunsrv.exe)

Post by sagemintblue »

Yet another virtual-machine-as-Windows-service howto, this time with Cygwin's cygrunsrv utility.

Tested with:
  • VirtualBox 3.0.8
  • Windows 7 RC 64 host
  • Cygwin 1.7b
Cygwin requirements:
  • default packages (bash, grep, sed)
  • cygrunsrv (wrapper enabling registration and execution of cygwin binaries as Windows services)
Steps:
  1. Create and configure your virtual machine.
  2. Ensure all VirtualBox files (vm, vdi, config) are modifiable by the user you wish the service to run as (e.g. SYSTEM).
  3. Install Cygwin for all users and include default packages + those mentioned above.
  4. Save the bash scripts (see the bottom of this post) somewhere handy (e.g. /home/user/bin) and make sure they're executable.
    • vboxd-install (helper script for service installation via cygrunsrv)
    • vboxd (script executed by installed services)
    • .libcommon (shared function definitions)
  5. Open a cygwin shell with admin rights.
  6. Run vboxd-install. Arguments are vmName, vmPort (the port on which VRDP access to the vm is exposed), and vmUser (optional; defaults to SYSTEM):

    Code: Select all

    $ VBOX_USER_HOME="/path/to/.VirtualBox/" vboxd-install myvm 3333
    
    Note that VBOX_USER_HOME must reference the path containing your VirtualBox.xml file. This path is registered during service installation, so if you move your VirtualBox.xml file you'll have to reinstall the service to ensure the correct path is defined. Alternately, hard code this variable within the vboxd script.
Test:
  1. Open a cygwin shell with admin rights.
  2. List all cygrunsrv services (you should see vboxd-vmName in the output list):

    Code: Select all

    $ cygrunsrv --list
    vboxd-myvm
    
  3. Query the status of your vboxd-vmName service:

    Code: Select all

    $ cygrunsrv --query vboxd-myvm
    Service             : vboxd-myvm
    Description         : VirtualBox virtual machine 'myvm' on port 3333
    Current State       : Stopped
    Command             : /home/bob/bin/vboxd
    
  4. Start the vboxd-vmName service manually:

    Code: Select all

    $ cygrunsrv --start vboxd-myvm
    
  5. Look at the log for the vboxd-vmName service:

    Code: Select all

    $ cat /var/log/vboxd-myvm.log
    vboxd 2009-10-20 12:12:59 [INFO]  Querying virtual machine 'myvm' state
    vboxd 2009-10-20 12:13:00 [INFO]  Virtual machine 'myvm' is powered off (since 2009-10-20T15:53:58.000000000)
    vboxd 2009-10-20 12:13:01 [INFO]  Starting virtual machine 'myvm' on port 3333
    vboxd 2009-10-20 12:13:01 [INFO]  Waiting on VBoxHeadless child process 1076
    
  6. Stop the vboxd-vmName service:

    Code: Select all

    $ cygrunsrv --stop vbox-myvm
    
  7. Look again at the log for the vboxd-vmName service:

    Code: Select all

    $ tail -n 12 /var/log/vboxd-myvm.log 
    vboxd 2009-10-20 12:13:01 [INFO]  Waiting on VBoxHeadless child process 1076
    vboxd 2009-10-20 12:18:50 [WARN]  Stopping virtual machine 'myvm'
    VirtualBox Command Line Management Interface Version 3.0.8
    (C) 2005-2009 Sun Microsystems, Inc.
    All rights reserved.
    
    0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
    VirtualBox Headless Interface 3.0.8
    (C) 2008-2009 Sun Microsystems, Inc.
    All rights reserved.
    
    Listening on port 3333
    
    Note that console output from VBoxHeadless isn't flushed to the log until the process terminates. This is why we see "Listening on port 3333" at the very end.
  8. Check the state of the vmName virtual machine:

    Code: Select all

    $ VBoxManage showvminfo myvm | grep 'State'
    State:           saved (since 2009-10-20T16:18:51.000000000)
    
Scripts:
  • vboxd-install

    Code: Select all

    #!/bin/bash
    
    ##
    ## Registers a VirtualBox virtual machine to start as a service via cygrunsrv
    ##
    
    ## load common functions
    basedir="$(readlink -f $(dirname $0))"
    source "$basedir/.libcommon" || exit 1
    
    ## test for presence of cygrunsrv utility
    if [ ! -x "$(which cygrunsrv)" ]; then
        die "Utility 'cygrunsrv' is not in path"
    fi
    
    ## test VirtualBox configuration
    if [ -z "$VBOX_USER_HOME" ]; then
        die "Required environment variable 'VBOX_USER_HOME' is undefined. " \
    	"Please ensure this variable is set to point to the directory " \
    	"containing your VirtualBox.xml configuration file."
    fi
    configFile=$(cygpath -u "$VBOX_USER_HOME\\VirtualBox.xml")
    if [ ! -e "$configFile" ]; then
        die "VirtualBox configuration file '$(cygpath -w $configFile)' not found"
    fi
    
    ## parse arguments
    parseArg vmName "$1"
    parseArg vmPort "$2"
    parseArg vmUser "$3" "SYSTEM"
    
    ## if vmUser is not SYSTEM, update userSpec
    userSpec="--interactive"
    if [ "$vmUser" != "SYSTEM" ]; then
        ## "interactive" option disallowed when user is specified
        userSpec="--user \"$vmUser\""
    fi
    
    ## install the service
    cygrunsrv \
        --install "vboxd-$vmName" \
        --path "$basedir/vboxd" \
        --env "VBOXD_VM_NAME=$vmName" \
        --env "VBOXD_VM_PORT=$vmPort" \
        --env "VBOX_USER_HOME=$VBOX_USER_HOME" \
        --desc "VirtualBox virtual machine '$vmName' on port $vmPort" \
        $userSpec \
        --type auto \
        --termsig TERM \
        --shutsig TERM \
        --neverexits \
        --preshutdown \
        || die "Failed to install service" 
    
  • vboxd

    Code: Select all

    #!/bin/bash
    
    ##
    ## Manages start / stop of VirtualBox virtual machines
    ##
    
    ## load common functions
    basedir="$(readlink -f $(dirname $0))"
    source "$basedir/.libcommon" || exit 1
    
    ## parse arguments
    parseArg vmName "$1" "$VBOXD_VM_NAME"
    parseArg vmPort "$2" "$VBOXD_VM_PORT"
    
    ## define signal handler
    function onHalt {
        warn "Stopping virtual machine '$vmName'"
        "$VBOX_INSTALL_PATH/VBoxManage" controlvm "$vmName" savestate
        exit 0
    }
    
    ## install signal handler; cygrunsrv uses SIGTERM by default
    trap 'onHalt' TERM
    
    ## hardcode this path if you like; it's required for VBox* utils to
    ## find the correct VirtualBox.xml config file and is usually set
    ## during a call to vboxd-install.
    #export VBOX_USER_HOME="$USERPROFILE\\.VirtualBox"
    
    ## default VBoxHeadless port specification
    portSpec="-p $vmPort"
    
    ## determine vm state
    info "Querying virtual machine '$vmName' state"
    vmState=$( \
        "$VBOX_INSTALL_PATH/VBoxManage" showvminfo "$vmName" \
        | grep '^State:' \
        | sed 's/State: *//' )
    info "Virtual machine '$vmName' is $vmState"
    
    ## if vm state is saved, we can't specify port without an exception,
    ## as port spec requires modification of the (immutable) saved machine
    ## state. See http://www.virtualbox.de/ticket/3609
    if  [ "${vmState##saved}" != "$vmState" ]; then
        ## state is saved; clear port specification
        warn "Port specification is not allowed for saved vms"
        portSpec=""
    fi
    
    ## start the VM
    info "Starting virtual machine '$vmName' on port $vmPort"
    "$VBOX_INSTALL_PATH/VBoxHeadless" -s "$vmName" $portSpec &
    
    ## record pid of VBoxHeadless child process and wait on it
    pid="$!"
    info "Waiting on VBoxHeadless child process $pid"
    wait "$pid"
    
  • .libcommon

    Code: Select all

    # -*-shell-script-*-
    
    SCRIPT="$(basename $0)"
    BASEDIR="$(readlink -f $(dirname $0))"
    [ -z "$LOGLEVEL" ] && LOGLEVEL=2
    [ -z "$LOGDATEFORMAT" ] && LOGDATEFORMAT="%Y-%m-%d %H:%M:%S "
    
    function log {
        local now=""
        [ -n "$LOGDATEFORMAT" ] && now=$(date +"$LOGDATEFORMAT")
        echo "$SCRIPT $now$@" >&2
    }
    
    function debug {
        [ "$LOGLEVEL" -lt 3 ] && return
        log "[DEBUG] $@"
    }
    
    function info {
        [ "$LOGLEVEL" -lt 2 ] && return
        log "[INFO]  $@"
    }
    
    function warn {
        [ "$LOGLEVEL" -lt 1 ] && return
        log "[WARN]  $@"
    }
    
    function error {
        log "[ERROR] $@"
    }
    
    function die {
        error "$@"
        exit 1
    }
    
    function parseArg {
        local _name="$1"
        local _value="$2"
        local _default="$3"
        if [ -z "$_value" ]; then
            if [ -z "$_default" ]; then
                die "Required argument '$_name' is undefined"
            fi
    	if [ "$_default" = "*EMPTY*" ]; then
    	    _value=""
    	else
                _value="$_default"
    	fi
        fi
        debug "$_name=\"$_value\""
        eval "$_name=\"$_value\""
    }
    
Post Reply