Running a Ruby Program as a Windows Service?

Is it possible to run a ruby application as a Windows Service? I see that there is a related question which discusses running a Java Application as a Windows Service, how can you do this with a Ruby application?


Check out the following library: Win32Utils. You can create a simple service that you can start/stop/restart at your leisure. I'm currently using it to manage a Mongrel instance for a Windows hosted Rails app and it works flawlessly.


When trying the Win32Utils one really need to studie the doc and look over the net before finding some simple working example. This seems to work today 2008-10-02:

gem install win32-service

Update 2012-11-20: According to https://stackoverflow.com/users/1374569/paul the register_bar.rb should now be

Service.create( :service_name => 'some_service',
                :host => nil,
                :service_type       => Service::WIN32_OWN_PROCESS,
                :description        => 'A custom service I wrote just for fun',
                :start_type         => Service::AUTO_START,
                :error_control      => Service::ERROR_NORMAL,
                :binary_path_name   => 'c:\usr\ruby\bin\rubyw.exe -C c:\tmp\ bar.rb',
               :load_order_group   => 'Network',
               :dependencies       => ['W32Time','Schedule'],
               :display_name       => 'This is some service'       )

bar.rb

create the application/daemon

LOG_FILE = 'C:\\test.log'

begin
  require "rubygems"
  require 'win32/daemon'

  include Win32

  class DemoDaemon < Daemon

    def service_main
      while running?
      sleep 10
      File.open("c:\\test.log", "a"){ |f| f.puts "Service is running #{Time.now}" } 
    end
  end 

    def service_stop
      File.open("c:\\test.log", "a"){ |f| f.puts "***Service stopped #{Time.now}" }
      exit! 
    end
  end

  DemoDaemon.mainloop
rescue Exception => err
  File.open(LOG_FILE,'a+'){ |f| f.puts " ***Daemon failure #{Time.now} err=#{err} " }
  raise
end 

bar.rb is the service but we must create and register first! this can be done with sc create some_service

but if we are going to use ruby and win32utils we should do a

register_bar.rb

 require "rubygems"
require "win32/service"
   include Win32



   # Create a new service
   Service.create('some_service', nil,
      :service_type       => Service::WIN32_OWN_PROCESS,
      :description        => 'A custom service I wrote just for fun',
      :start_type         => Service::AUTO_START,
      :error_control      => Service::ERROR_NORMAL,
      :binary_path_name   => 'c:\usr\ruby\bin\rubyw.exe -C c:\tmp\ bar.rb',
      :load_order_group   => 'Network',
      :dependencies       => ['W32Time','Schedule'],
     
      :display_name       => 'This is some service'
   )

Note, there is a space between c:\tmp\ bar.rb in 'c:\usr\ruby\bin\rubyw.exe -C c:\tmp\ bar.rb'

Run ruby register_bar.rb and now one can start the service either from the windows service control panel or

sc start some_service

and watch c:test.log be filled with Service is running Thu Oct 02 22:06:47 +0200 2008

For the simple of have something to work with it is easier to remove the service register and create a new one instead of modifying a existing one

unregister_bar.rb

 require "rubygems"
    require "win32/service"
       include Win32
    
    Service.delete("some_service")

Credits to the people http://rubypane.blogspot.com/2008/05/windows-service-using-win32-service-and_29.html

http://rubyforge.org/docman/view.php/85/595/service.html


Here is a code template to do firedeamon :)

#####################################################################
#  runneur.rb :  service which run (continuously) a process
#                   'do only one simple thing, but do it well'
#####################################################################
# Usage:
#   .... duplicate this file : it will be the core-service....
#   .... modify constantes in beginning of this script....
#   .... modify stop_sub_process() at end  of this script for clean stop of sub-application..
#
#   > ruby runneur.rb install   foo     ; foo==name of service, 
#   > ruby runneur.rb uninstall foo
#   > type d:\deamon.log"       ; runneur traces
#   > type d:\d.log             ; service traces
#
#####################################################################
class String; def to_dos() self.tr('/','\\') end end
class String; def from_dos() self.tr('\\','/') end end

rubyexe="d:/usr/Ruby/ruby19/bin/rubyw.exe".to_dos

# example with spawn of a ruby process...

SERVICE_SCRIPT="D:/usr/Ruby/local/text.rb"
SERVICE_DIR="D:/usr/Ruby/local".to_dos
SERVICE_LOG="d:/d.log".to_dos           # log of stdout/stderr of sub-process
RUNNEUR_LOG="d:/deamon.log"             # log of runneur

LCMD=[rubyexe,SERVICE_SCRIPT]   # service will do system('ruby text.rb')
SLEEP_INTER_RUN=4               # at each dead of sub-process, wait n seconds before rerun

################### Installation / Desintallation ###################
if ARGV[0]
    require 'win32/service'
    include Win32

    name= ""+(ARGV[1] || $0.split('.')[0])
    if ARGV[0]=="install"
        path = "#{File.dirname(File.expand_path($0))}/#{$0}".tr('/', '\\')
        cmd = rubyexe + " " + path
        print "Service #{name} installed with\n cmd=#{cmd} ? " ; rep=$stdin.gets.chomp
        exit! if rep !~ /[yo]/i

        Service.new(
         :service_name     => name,
         :display_name     => name,
         :description      => "Run of #{File.basename(SERVICE_SCRIPT.from_dos)} at #{SERVICE_DIR}",
         :binary_path_name => cmd,
         :start_type       => Service::AUTO_START,
         :service_type     => Service::WIN32_OWN_PROCESS | Service::INTERACTIVE_PROCESS
        )
        puts "Service #{name} installed"
        Service.start(name, nil)
        sleep(3)
        while Service.status(name).current_state != 'running'
            puts 'One moment...' + Service.status(name).current_state
            sleep 1
        end
        while Service.status(name).current_state != 'running'
            puts ' One moment...' + Service.status(name).current_state
            sleep 1
        end
        puts 'Service ' + name+ ' started'      
    elsif ARGV[0]=="desinstall" || ARGV[0]=="uninstall"
        if Service.status(name).current_state != 'stopped'
            Service.stop(name)
            while Service.status(name).current_state != 'stopped'
                puts 'One moment...' + Service.status(name).current_state
                sleep 1
            end
        end
        Service.delete(name)
        puts "Service #{name} stopped and uninstalled"

    else
        puts "Usage:\n > ruby #{$0} install|desinstall [service-name]"
    end 
    exit!
end

#################################################################
#  service runneur : service code 
#################################################################
require 'win32/daemon'
include Win32

Thread.abort_on_exception=true
class Daemon
    def initialize
        @state='stopped'
        super
        log("******************** Runneur #{File.basename(SERVICE_SCRIPT)} Service start ***********************")
    end
    def log(*t)
        txt= block_given?()  ? (yield() rescue '?') : t.join(" ")
        File.open(RUNNEUR_LOG, "a"){ |f| f.puts "%26s | %s" % [Time.now,txt] } rescue nil
    end
    def service_pause
        #put activity in pause
        @state='pause'
        stop_sub_process
        log { "service is paused" }
    end
    def service_resume
        #quit activity from pause
        @state='run'
        log { "service is resumes" }
    end
    def service_interrogate
        # respond to quistion status
        log { "service is interogate" }
    end
    def service_shutdown 
        # stop activities before shutdown
        log { "service is stoped for shutdown" }
    end

    def service_init
        log { "service is starting" }
    end
    def service_main
        @state='run'
        while running?
        begin
            if @state=='run'
                log { "starting subprocess #{LCMD.join(' ')} in #{SERVICE_DIR}" }
                @pid=::Process.spawn(*LCMD,{
                    chdir: SERVICE_DIR, 
                    out: SERVICE_LOG, err: :out
                }) 
                log { "sub-process is running : #{@pid}" }
                a=::Process.waitpid(@pid)
                @pid=nil
                log { "sub-process is dead (#{a.inspect})" }
                sleep(SLEEP_INTER_RUN) if @state=='run'
            else
                sleep 3
                log { "service is sleeping" } if @state!='run'
            end
        rescue Exception => e
            log { e.to_s + " " + e.backtrace.join("\n   ")}
            sleep 4
        end
        end
    end

    def service_stop
     @state='stopped'
     stop_sub_process
     log { "service is stoped" }
     exit!
    end
    def stop_sub_process
        ::Process.kill("KILL",@pid) if @pid
        @pid=nil
    end
end

Daemon.mainloop