PokuG stdio.h

ポクジィと読みます

Windows で Redmine 6 をサービス化する手順(Puma+win32-service)


Redmine を Windows 上で運用していると、サーバー起動用のコマンドを毎回実行するのは面倒ですし、OS 再起動時に自動で立ち上がらないのも不便です。

本記事では、Puma + win32-service を利用して Redmine 6 を Windows サービスとして登録し、GUI や sc コマンドから開始・停止できるようにする手順をまとめます。

2. 前提環境

以下環境を前提としています。

OS Windows 11 (25H2)
Redmine 6.1.0
Redmine のインストール場所 C:\redmine
Ruby RubyInstaller-DevKit 3.4.6-1 (x64)
PostgreSQL 18.0
Web サーバ Puma

■ 起動コマンド
PowerShell で以下コマンドでRedmine を起動している環境で確認しました。

$env:RAILS_RELATIVE_URL_ROOT="/redmine"
bundle exec puma -C config/puma.rb

以下ページで構築している環境です。
Windows 11 (25H2) に Redmine 6.1.0 をインストール(Ruby 3.4 + PostgreSQL 18) - PokuG stdio.h
【Redmine】Puma 設定で同時接続数を調整する(Windows 前提) - PokuG stdio.h
Apache で Redmine をサブURL /redmine に公開する方法 - PokuG stdio.h


3. サービス化に必要な gem のインストール

Railsをサービス化するために必要なツールをインストールします。

ツールのインストールには"お手軽パターン"と"行儀がよい?パターン"の 2 種類記載します。
どちらを選んでも問題ないと思います。

パターンの違いはサービス化に必要なツールをシステム全体の見える場所に置く(前者パターン)か。redmine単体の場所に置く(後者パターン)かの違いだと思います。

3.1 win32-service のインストール(お手軽パターン)

管理者権限で以下コマンドを実行します。

gem install win32-service


上記コマンドで以下3つのモジュールが入ります。
・ffi-1.17.0
・ffi-win32-extensions-1.1.0
・win32-service-2.3.2


■ モジュールが3つ入らなかった場合
なぜか「gem install win32-service」で「win32-service」の一つしかモジュールが入らなかった場合は、以下コマンドを実行したらうまく入りました。

gem install ffi -v 1.17.0
gem install ffi-win32-extensions
gem install win32-service
3.1.1 service.rb の作成

C:\redmine 以下に service.rb を作成します。
参考にした URL サンプルを変更しています。

# C:\redmine\service.rb
REDMINE_DIR = 'C:\redmine'
LOG_FILE    = "#{REDMINE_DIR}\\log\\service.log"

begin
  require 'win32/daemon'
  include Win32

  class RedmineService < Daemon
    def service_init
      File.open(LOG_FILE, 'a'){ |f| f.puts "Initializing service #{Time.now}" }

      # ==== ここで環境変数を固定 ====
      ENV['RAILS_RELATIVE_URL_ROOT'] = '/redmine'      # サブURL指定

      # Puma 起動
      @server_pid = Process.spawn(
        'bundle', 'exec', 'puma', '-C', 'config/puma.rb',
        chdir: REDMINE_DIR,
        out:   [LOG_FILE, 'a'],
        err:   [LOG_FILE, 'a']
    )
    end

    def service_main
      File.open(LOG_FILE, 'a'){ |f| f.puts "Service is running #{Time.now} pid=#{@server_pid}" }
      while running?
        sleep 10
      end
    end

    def service_stop
      File.open(LOG_FILE, 'a'){ |f| f.puts "Stopping #{Time.now}" }
      system "taskkill /PID #{@server_pid} /T /F"
      Process.wait(@server_pid) rescue nil
      File.open(LOG_FILE, 'a'){ |f| f.puts "Stopped #{Time.now}" }
      # exit! ←やめる
      return
    end
  end

  RedmineService.mainloop
rescue Exception => e
  File.open(LOG_FILE,'a+'){ |f| f.puts "***Daemon failure #{Time.now} #{e.inspect}\n#{e.backtrace.join($/)}" }
  raise
end

変更点は以下です。

  • ENV['RAILS_RELATIVE_URL_ROOT'] = '/redmine' を追加し redmine の URL をサブURLに変更しています
  • Puma の起動に変更しています
  • 停止時は exit! ではなく return にしています(GUIでサービスを止めるとメッセージ出るため)

3.2 win32-service のインストール(行儀がよい?パターン)

3.2.1 Gemfileの編集

Gemfile の ffi の行をコメントにします。

3.2.2 Gemfile.localの編集

Gemfile.local に以下3つの記述を記載します。

gem 'ffi', '1.17.0'
gem 'ffi-win32-extensions', '1.1.0'
gem 'win32-service', '2.3.2'


3.2.3 bundle install の実行

パワーシェルの管理者権限で「C:\redmine」へ移動し以下コマンドを実行します。

bundle install --without development test


3.2.4 service.rb の作成

C:\redmine 以下に service.rb を作成します。

# C:\redmine\service.rb
REDMINE_DIR = 'C:\redmine'
LOG_FILE    = "#{REDMINE_DIR}\\log\\service.log"

begin
  ENV['BUNDLE_GEMFILE'] ||= 'C:/redmine/Gemfile'  # Gemfile位置を記載
  require 'bundler/setup'                         # これで vendor/bundle が自動で読まれる
  require 'win32/daemon'
  include Win32

  class RedmineService < Daemon
    def service_init
      File.open(LOG_FILE, 'a'){ |f| f.puts "Initializing service #{Time.now}" }

      # ==== ここで環境変数を固定 ====
      ENV['RAILS_RELATIVE_URL_ROOT'] = '/redmine'      # サブURL指定

      # Puma 起動
      @server_pid = Process.spawn(
        'bundle', 'exec', 'puma', '-C', 'config/puma.rb',
        chdir: REDMINE_DIR,
        out:   [LOG_FILE, 'a'],
        err:   [LOG_FILE, 'a']
    )
    end

    def service_main
      File.open(LOG_FILE, 'a'){ |f| f.puts "Service is running #{Time.now} pid=#{@server_pid}" }
      while running?
        sleep 10
      end
    end

    def service_stop
      File.open(LOG_FILE, 'a'){ |f| f.puts "Stopping #{Time.now}" }
      system "taskkill /PID #{@server_pid} /T /F"
      Process.wait(@server_pid) rescue nil
      File.open(LOG_FILE, 'a'){ |f| f.puts "Stopped #{Time.now}" }
      # exit! ←やめる
      return
    end
  end

  RedmineService.mainloop
rescue Exception => e
  File.open(LOG_FILE,'a+'){ |f| f.puts "***Daemon failure #{Time.now} #{e.inspect}\n#{e.backtrace.join($/)}" }
  raise
end

変更点は以下です。

  • ENV['RAILS_RELATIVE_URL_ROOT'] = '/redmine' を追加し redmine の URL をサブURLに変更しています
  • Puma の起動に変更しています
  • 停止時は exit! ではなく return にしています(GUIでサービスを止めるとメッセージ出るため)
  • C:\redmine\vendor\bundle を見に行くように Gemfile の位置を環境変数を設定

4. サービスに登録

コマンドプロンプトを管理者権限で起動し、以下コマンドを実行します。

sc create "Redmine" binPath= "C:\Ruby34-x64\bin\rubyw -C C:\redmine\ service.rb"

ちなみに、サービスを削除したい時は以下コマンドになります。

sc delete "Redmine"

5. サービスの起動

以下コマンドを使用し確認します。
■ コマンドプロンプトの場合

sc start "Redmine"
sc stop "Redmine"

■ PowerShell の場合

Start-Service -Name "Redmine"
Stop-Service -Name "Redmine"

6. おわり

これで Redmine 6 を Windows サービスとして安定運用できるようになりました。
OS 再起動後も自動で立ち上がり、PowerShell から Start-Service / Stop-Service で簡単に制御できます。
同じように Windows 環境で Redmine を使っている方の参考になれば幸いです。