Redmine
redmine_plugin

イントラ環境向け複数Redmine構築メモと使用プラグイン一覧

ベストな方法かどうかはわからないですが、どのように環境構築しているのかを自分のメモ代わりに書いています。
もし、もっとこうした方がいいよと言うのがあればコメントお願いします。

Redmineの利用目的

社内でRedmineを20サイト以上運用管理しています。社内におけるRedmineの利用目的としては

  • 情報共有
  • 文書管理
  • ナレッジ共有
  • 案件管理・出荷業務管理
  • ITIL的運用(サポート、問い合わせ)
  • プロジェクト管理
  • 在庫・出荷情報管理

となっていて、Redmine本来のプロジェクト管理として使っているところは少ないです。
ExcelからAccess使ってマクロ組んで...の代わりに、RedmineのチケットをDBとして利用している使い方が多い状態です。
また、使用する部署、部門横断的な管理等があり、それぞれ管理ポリシーが異なるため同一ホストでは運用が困難なため結果として複数ホストになっています。

サーバー環境

OS: CentOS 7.4
HTTP Server: Apache 2.4
DB: MariaDB
DB管理: phpmyadmin (PHPは7.1)
Ruby: 2.3

Rubyはソースコードからコンパイルしています。Rubyが最新バージョンでないのは一部のプラグインでRuby 2.4に対応していないものがあったので2.3にしています。
他は、yumでインストールしています。

インストール時の流れは
http://blog.redmine.jp/articles/3_4/install/centos/
http://blog.redmine.jp/articles/3_0/installation_centos/
を参照してください。

Apacheの設定

複数のRedmineを1台のサーバーで構築するので、VirtualHostを使用します。
passengerの設定とVirtualHostのファイル構成は以下のようにしています。

/etc/httpd/conf.d/
/etc/httpd/conf.d/passenger.conf
/etc/httpd/conf.d/virtualhost.conf
/etc/httpd/conf.d/virtualhost/<ホスト名>.conf

VirtualHostの設定ファイルを1ホストに対して1ファイルとすることで、複数建てる時にコピーして使用できるので便利です。

/etc/httpd/conf.d/passenger.conf
LoadModule passenger_module /usr/local/lib/ruby/gems/2.3.0/gems/passenger-5.1.11/buildout/apache2/mod_passenger.so
<IfModule mod_passenger.c>
  PassengerRoot /usr/local/lib/ruby/gems/2.3.0/gems/passenger-5.1.11
  PassengerDefaultRuby /usr/local/bin/ruby
</IfModule>

# Passengerが追加するHTTPヘッダを削除するための設定(任意)。
#
Header always unset "X-Powered-By"
Header always unset "X-Runtime"

# 必要に応じてPassengerのチューニングのための設定を追加(任意)。
# 詳しくはPhusion Passenger users guide(https://www.phusionpassenger.com/library/config/apache/reference/)参照。
PassengerMaxPoolSize 20
PassengerMaxInstancesPerApp 4
PassengerPoolIdleTime 864000
PassengerHighPerformance on
PassengerStatThrottleRate 10
PassengerSpawnMethod smart
PassengerFriendlyErrorPages off
PassengerStartTimeout 120
/etc/httpd/conf.d/virtualhost.conf
# VirtualHost設定群読み込み
IncludeOptional conf.d/virtualhost/*.conf
/etc/httpd/conf.d/virtualhost/ホスト名.conf
<VirtualHost *:80>
        ServerName <ホスト名>.example.local
        DocumentRoot /var/www/redmine/site/<ホスト名>/public
        ErrorLog logs/<ホスト名>-error_log
        CustomLog logs/<ホスト名>-access_log combined
</VirtualHost>

PassengerPreStart http://<ホスト名>.example.local

RedmineのVirtualHostは、/var/www/redmine/site以下にホスト名をフォルダ名にしてRedmineをインストールしています。

MariaDBの設定

物忘れが激しいので、RedmineのDBを作成する際は、ユーザ名とDB名を同じにしています。
パフォーマンスチューニングは、 https://github.com/major/MySQLTuner-perl を利用して適宜調整しています。

/etc/my.cnf.d/server.cnf
#
# These groups are read by MariaDB server.
# Use it for options that only the server (but not clients) should see
#
# See the examples of server my.cnf files in /usr/share/mysql/
#

# this is read by the standalone daemon and embedded servers
[server]

# this is only for the mysqld standalone daemon
[mysqld]
character-set-server = utf8
skip-name-resolve
thread_cache_size=4
query_cache_size=0
query_cache_type=0
query_cache_limit=8M
innodb_file_per_table=1
innodb_log_file_size=16M
tmp_table_size=32M
max_heap_table_size=32M
table_open_cache=512
join_buffer_size=256K

# スロークエリの出力設定
slow_query_log=ON
# スロークエリと判定する秒数
long_query_time=3
# スロークエリログの場所
log-slow-queries=/var/log/mariadb/mariadb_slow.log

performance_schema=on

# this is only for embedded server
[embedded]

# This group is only read by MariaDB-5.5 servers.
# If you use the same .cnf file for MariaDB of different versions,
# use this group for options that older servers don't understand
[mysqld-5.5]

# These two groups are only read by MariaDB servers, not by MySQL.
# If you use the same .cnf file for MySQL and MariaDB,
# you can put MariaDB-only options here
[mariadb]

[mariadb-5.5]

チューニング中の値になっていますので、数字は参考にしないでください。
適宜、mysqltunerを使用してパラメータ設定を行ってください。

使用しているプラグインとテーマ

テーマと多数のプラグインを各Redmineのサイトに適用するのは困難なため、/var/www/redmine/theme, /var/www/redmine/plugin のフォルダ以下にそれぞれ使用するテーマとプラグインを入れます。
各Redmineのサイト用のフォルダ(/var/www/redmine/site/<ホスト名>/)のplugin, public/theme フォルダ以下で、先のテーマ、プラグインをシンボリックリンクします。シンボリックリンクを行う際は相対パスにせず絶対パスにします。(フォルダ移動させた時も不整合が起こらないようにするため)
このようにすることで、同一のプラグインを使用していてパッチの適用忘れやバージョンアップ忘れを防ぎます。

必ず入れているプラグインの方では、Redmine3.4.2で動作確認(簡単に)していますが、一部でしか使用していないプラグインはまだ未検証なものがあります。

使用しているテーマ

Redmineのテーマは、
https://github.com/farend/redmine_theme_farend_fancy
を使用しています

使用しているプラグイン

ほぼ必ず入れれているプラグイン

プラグイン名称 機能 備考
Clipboard image paste クリップボードイメージの貼り付け
Redmine Banner plugin バナー表示をさせる。 管理者からの通知に便利
Redmine Default Custom Query プロジェクト毎にデフォルトのカスタムクエリーを指定できる
DMSF 文書、画像の管理を可能にする ZIP圧縮で文字化けするのでパッチ必要。easyredmine社でもメンテされている
Knowledgebase ナレッジベース メンテ復活!!
Redmine LDAP Sync LDAP情報の同期、参照フィールドの追加、グループ割り当てが可能
Redmine Multiple Projects per Issue plugin 一つのチケットを複数プロジェクトに割り当て
Redmine Startpage plugin Redmineのデフォルトの概要ページを任意のページに変更できる TOPページプロジェクトを作成してWikiページをStartPageにしているケースが多い
Redmine Tags チケット、Wikiへのタグ追加 redmineup(旧redmine crm)社がサポート?
Redmine Wiki Extensions plugin Wikiの機能拡張 Tag機能はRedmine Tagsと被るので無効にする
Redmine Wiki Lists plugin Wikiページにチケット一覧を表示
Sidebar Hide Plugin サイドバーを隠すボタン追加 view customizeでも可能
Spectator plugin 管理者が他人に成りすますことができる
View Customize plugin 神プラグイン! javascriptができればなんでもできる。
WikiNG 視覚的に便利なマクロ追加と、オリジナルのmacro追加可能 本家 3.4.x対応

一部で使用しているプラグイン

プラグイン名称 機能 備考
Computed custom field カスタムフィールドで計算ができる チケット作成後に計算結果が反映される。view customizeでできない計算も可能(内部データ参照可能)。easyredmine社でもメンテされている。
Lycheeプラグイン ガントチャート、EVM、リソース管理等
Redmine Agile タスクカンバン
Redmine Checklist チケットにチェックリスト追加 Pro版はテンプレート登録可能
Redmine Contact コンタクトリスト(会社、顧客)を作成できる
Redmine Introductions plugin チュートリアルを作成できる
Redmine Issue Templates plugin チケットテンプレート作成 個人的にはView Customizeでやる方が好きだが、View Customize触れない人でもテンプレートが作成できる
Redmine Products 製品情報を登録できる
Redmine Q&A plugin QAサイト構築
Redmine Wiki Unc plugin fileリンクをサポートできる できるだけFileリポジトリを使用しているが、アクセス権の関係上できない場合はこれを使う。ただし、IEのみ対応になる

プラグインパッチ

DMSF

DMSFの保存フォルダ指定は、最新のものは相対パスで指定するようになっています。
Redmineのフォルダ移行する際にも相対パスの方が都合がよいのでDMSFを最新のものにアップデートしたら変更をお勧めします。(デフォルトは、dmsfになっていますが、旧DMSFからアップグレードした場合は、files/dmsf になると思います)

DMSFで複数ファイル選択してダウンロードもしくはフォルダーのダウンロードを行った際に、UTF-8のままZIP圧縮を行うので、Windowsで展開した際に文字化けが起こるので、SJISに変換する必要があります。

diff -urN ../../redmine/plugins/redmine_dmsf/app/controllers/dmsf_controller.rb redmine_dmsf/app/controllers/dmsf_controller.rb
--- ../../redmine/plugins/redmine_dmsf/app/controllers/dmsf_controller.rb       2017-10-30 17:37:11.602348740 +0900
+++ redmine_dmsf/app/controllers/dmsf_controller.rb     2017-11-09 09:47:57.086032137 +0900
@@ -413,11 +413,15 @@

   def zip_entries(zip, selected_folders, selected_files)
     member = Member.where(:user_id => User.current.id, :project_id => @project.id).first
+    encoding = "utf-8"
+    encoding = "sjis" if request.env["HTTP_USER_AGENT"] =~ /Windows/ && request.env["HTTP_ACCEPT_LANGUAGE"] =~ /^ja/
+
     if selected_folders && selected_folders.is_a?(Array)
       selected_folders.each do |selected_folder_id|
         folder = DmsfFolder.visible.find_by_id selected_folder_id
         if folder
-          zip.add_folder(folder, member, (folder.dmsf_folder.dmsf_path_str if folder.dmsf_folder))
+#         zip.add_folder(folder, member, (folder.dmsf_folder.dmsf_path_str if folder.dmsf_folder))
+          zip.add_folder(folder, member, (@folder.dmsf_path_str if @folder), encoding)
         else
           raise FileNotFound
         end
@@ -432,7 +436,8 @@
         unless (file.project == @project) || User.current.allowed_to?(:view_dmsf_files, file.project)
           raise DmsfAccessError
         end
-        zip.add_file(file, member, (file.dmsf_folder.dmsf_path_str if file.dmsf_folder)) if file
+#       zip.add_file(file, member, (file.dmsf_folder.dmsf_path_str if file.dmsf_folder)) if file
+        zip.add_file(file, member, (@folder.dmsf_path_str if @folder), encoding) if file
       end
     end
     max_files = Setting.plugin_redmine_dmsf['dmsf_max_file_download'].to_i
diff -urN ../../redmine/plugins/redmine_dmsf/lib/dmsf_zip.rb redmine_dmsf/lib/dmsf_zip.rb
--- ../../redmine/plugins/redmine_dmsf/lib/dmsf_zip.rb  2017-10-30 17:37:11.640347920 +0900
+++ redmine_dmsf/lib/dmsf_zip.rb        2017-11-09 09:53:10.165323530 +0900
@@ -20,6 +20,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

 require 'zip'
+require 'kconv'

 class DmsfZip

@@ -43,7 +44,8 @@
     @zip.close if @zip
   end

-  def add_file(file, member, root_path = nil)
+# def add_file(file, member, root_path = nil)
+  def add_file(file, member, root_path = nil, encoding = "utf-8")
     unless @files.include?(file)
       string_path = file.dmsf_folder.nil? ? '' : "#{file.dmsf_folder.dmsf_path_str}/"
       string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path
@@ -53,6 +55,11 @@
       else
         string_path += file.formatted_name(Setting.plugin_redmine_dmsf['dmsf_global_title_format'])
       end
+
+     if encoding == "sjis"
+        string_path = string_path.tosjis()
+     end
+
       @zip_file.put_next_entry(string_path)
       File.open(file.last_revision.disk_file, 'rb') do |f|
         while (buffer = f.read(8192))
@@ -63,15 +70,25 @@
     end
   end

-  def add_folder(folder, member, root_path = nil)
+# def add_folder(folder, member, root_path = nil)
+  def add_folder(folder, member, root_path = nil, encoding = "utf-8")
+
     unless @folders.include?(folder)
       string_path = "#{folder.dmsf_path_str}/"
       string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path
+
+     if encoding == "sjis"
+        string_path = string_path.tosjis()
+     end
+
       @zip_file.put_next_entry(string_path)
       @folders << folder
-      folder.dmsf_folders.visible.each { |subfolder| self.add_folder(subfolder, member, root_path) }
-      folder.dmsf_files.visible.each { |file| self.add_file(file, member, root_path) }
+#     folder.dmsf_folders.visible.each { |subfolder| self.add_folder(subfolder, member, root_path) }
+#     folder.dmsf_files.visible.each { |file| self.add_file(file, member, root_path) }
+      folder.dmsf_folders.visible.each { |subfolder| self.add_folder(subfolder, member, root_path, encoding) }
+      folder.dmsf_files.visible.each { |file| self.add_file(file, member, root_path, encoding) }
+
     end
   end

knowledgebase

イントラ環境下だとインターネット側にアクセスする場合だけだとおもいます。Gemfileを以下のようにします

不具合修正を含めたものは
https://github.com/crosspoints/redmine_knowledgebase
で公開しています
2018/1/12時点で本家側に取り込まれましたので本家のものを使用ください。
以下不要です

gem 'redmine_acts_as_taggable_on', :git => "https://github.com/alexbevi/redmine_acts_as_taggable_on"
gem 'ya2yaml'
gem 'awesome_nested_set'

不具合修正パッチ

--- redmine_knowledgebase-master/app/views/articles/show.html.erb       2017-11-19 22:57:32.000000000 +0900
+++ redmine_knowledgebase/app/views/articles/show.html.erb      2018-01-11 16:13:19.261609100 +0900
@@ -18,8 +18,9 @@
   <% end %>

                <li><%= link_to_if_authorized l(:label_new_comment), { :controller => "articles", :action => "comment",
:article_id => @article.id, :project_id => @project}, :class => "icon icon-comment", :remote => true, :method => :get %>
</li>
+               <li> <%= watcher_link(@article, User.current) %></li>
                <li>
-      <%= render :partial => 'watchers/watchers', :locals => {:watched => @article} %>
+      <%= render :partial => 'articles/watchers', :locals => {:watched => @article} %>
     </li>
        </ul>
        <br />
diff -uprN redmine_knowledgebase-master/app/views/categories/show.html.erb redmine_knowledgebase/app/views/categories/sh
ow.html.erb
--- redmine_knowledgebase-master/app/views/categories/show.html.erb     2017-11-19 22:57:32.000000000 +0900
+++ redmine_knowledgebase/app/views/categories/show.html.erb    2018-01-11 16:13:19.385997800 +0900
@@ -20,7 +20,8 @@
     <li><%= link_to_if_authorized l(:label_new_category), { :controller => 'categories', :action => 'new', :parent_id =
> @category.id, :project_id => @project}, :class => 'icon icon-add' %></li>
     <li><%= link_to_if_authorized l(:label_edit_category), { :controller => 'categories', :action => 'edit', :id => @ca
tegory.id, :project_id => @project}, :class => 'icon icon-edit' %></li>
     <li><%= link_to_if_authorized l(:label_delete_category), { :controller => 'categories', :action => 'destroy', :id =
> @category.id, :project_id => @project}, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-de
l' %></li>              <li>
-      <%= render :partial => 'watchers/watchers', :locals => {:watched => @category} %>
+    <li><%= watcher_link(@category, User.current) %></li>
+      <%= render :partial => 'categories/watchers', :locals => {:watched => @category} %>
     </li>
        </ul>
        <h3><%= l(:title_browse_by_category) %></h3>

WikiNG

buttonマクロが動作しない場合は以下のようにする

wiking.css
a.wiki-class-button {
    border-width: 1px;
    border-style: solid;
    -moz-border-radius: 6px;
    border-radius: 6px;
    background-color: #507aaa;
    border-color: #809fc3 #2C4056 #2C4056 #809fc3;
    -moz-text-shadow: 1px 1px 1px #2C4056;
    text-shadow: 1px 1px 1px #2C4056;
    padding: 0.5em 1.25em;
    text-decoration: none;
    color: #fff;
    display: inline-block;
}
a:active.wiki-class-button {
    border-color: #2C4056 #809fc3 #809fc3 #2C4056;
}

View Customizeで定義してもOK