Redmine 6.x移行後の運用記(前編) Passengerが静かにならない。Redmine 6.1移行後に始まった高負荷の正体を追う

Redmine 6.1への移行は無事に終わりました。

Redmine 5.1からのメジャーアップグレードということもあり、今回は不要になったプラグインをかなり整理しています。長年使い続けてきた環境だっただけに、「使っていないものは思い切って切る」という判断をしました。

しかし、Webで公開していると思わぬトラブルが発生しました。

CPUが一向に下がらない

切り替えた Redmine 6.1 を監視していると、Passenger の CPU 使用率が妙な動きをしています。

%CPU   %MEM   PID      USER     COMMAND
--------------------------------------------------------------------------------------
78.1   4.7    560493   www-data Passenger RubyApp: /home/www-data/redmine_v6 (prod)
38.4   3.8    560422   www-data Passenger AppPreloader: /home/www-data/redmine_v6

何も触っていないのに 70~90% を行ったり来たりしています。移行直後なので、

  • 「まだ何か設定を忘れているのか?」
  • 「プラグインを外した影響が残っているのか?」

と考えながら調査を始めました。

まず Passenger を見ようとした

最初に確認したのは Passenger 自身です。普段であれば passenger-status を実行すれば状況はある程度分かります。

ところが今回は、それすら使えませんでした。UNIX ドメインソケットのパス長制限(108バイト)に引っ掛かり、ArgumentError が発生してしまいます。

管理コマンドが使えない以上、地道にログを追うしかありません。そこで production.log と netstat を確認してみることにしました。

最初に見えたもの

production.log を眺めていると、最初に目に入ったのは見覚えのある URL でした。

Knowledgebase。

v5.1→v6.1へ移行で完全に削除したプラグインです。ところが、その URL へ今でもアクセスが来ています。

「検索エンジンが昔の URL を覚えているのか。」

最初はそう考えました。しかし、ログを読み進めていくと、それだけでは説明が付きません。

他にも出てくる動き

アクセスされているのは Knowledgebase だけではありません。

チケット一覧。

しかも普通に開いているわけではなく、

  • sort=
  • page=
  • set_filter=

こういったパラメータ付きの URL が大量に飛んできます。

さらに、

  • PDF
  • CSV
  • Atom

といったエクスポートまで巡回しています。

ここまで来ると、「404 が増えている」という話ではなくなってきました。クローラーがかなり重い処理を次々と要求しています。その頃、netstat を見ると HTTPS セッションが大量に張り付いたままになっていました。

ここでようやく、一つの可能性が頭に浮かびます。

「もしかして、これ全部クローラーなのでは……?」
「というか、悪質クローラーは止めている(includeでシャットダウンしている)はずでは……?」

そして、もしそうだとしたら、Rails 側で受け止め続けていいのか。

次回はログをさらに追い掛けながら、最終的に Googlebot による旧 URL の巡回と、動的クエリの総当たりが Passenger を疲弊させていた こと、そして Apache の mod_rewrite410 Gone を使って水際で止めるまでの経緯を書いていこうと思います。

刺身とフライ。

いつも訪れる定食屋にて。

鰹の刺身定食、そして

あじフライを注文。

たっぷり脂の乗った戻り鰹。ここは丁寧に生姜とネギが別添えなので味変にリズムつけられます。

ここで揚げ物を食べないのはこの店への敬意が欠けるとしてサクサクのアジフライ。ドラムスティックのように尻尾を持って食べられるのが最高です。

ですが、真に驚くべきは、最後に口を支配するのがご飯の甘みと香り。刺身もフライも味噌汁•漬物すら上書きする主食の破壊力はまさに定食屋の面目躍如でした。

【WAF検知ログ解析】攻撃者が探しているのは wp-config.php だけではない。そのバックアップでもある。(not only A but also B)

はじめに

以前、.env ファイルを狙った大量スキャンについて記事を書きました。

そのとき紹介したのは、「設定ファイルが公開されていないか」を世界中で機械的に探し回るボットの話です。

今回観測したログは、その続編とも言えるものでした。

狙われていたのは wp-config.php ……ではありません。

/wp-config.php.bak
/wp-config.php.old

つまり、人間が「作業時に残しておいたバックアップ」です。

今回もホスト名・IPアドレス・ディレクトリ名などは無害化しています。例によって、テロリストに名前を与えていません。

観測されたログ(マスキング済み)

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 920440
URL file extension is restricted by policy
URI: /wp-config.php.bak

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 930130
Restricted File Access Attempt
URI: /wp-config.php.bak

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 920440
URL file extension is restricted by policy
URI: /wp-config.php.old

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 930130
Restricted File Access Attempt
URI: /wp-config.php.old

数百ミリ秒という短時間で、.bak.old の両方を試しています。ブラウザで人間が操作しているとは考えにくく、自動化されたスキャナーによる探索と見てよいでしょう。

なぜ .bak.old を探すのか?

攻撃者が欲しいのは、バックアップファイルそのものではありません。その中に書かれている情報と「運用者の癖」です。

wp-config.php には、

  • データベースの接続情報
  • WordPressの認証キー(AUTH_KEY や SECURE_AUTH_KEY)
  • Cookieの暗号化キー
  • テーブルプレフィックス
  • サイト固有の設定

などが保存されています。本来の wp-config.php はPHPとして実行されるため、その内容をブラウザから読むことはできません。

しかし、

  • wp-config.php.bak
  • wp-config.php.old

になると話は別です。WebサーバーはこれらをPHPとして扱わず、単なるテキストファイルとして返してしまう場合があります。

このテキストファイルが表示されてしまえば、攻撃者はWordPressの脆弱性を探す必要すらありません。データベースへ直接接続するための情報が、そのまま手に入ってしまうからです。

玄関の鍵を壊そうとしているのではありません。玄関脇の植木鉢に、合鍵が入っていないかを確認している状態です。

本当に見られているのは「運用」

ここで興味深いのは、攻撃者が .bak を探していることではありません。

「そのようなファイルを公開領域へ置く運用をしているか」

を見ています。例えば

cp wp-config.php wp-config.php.bak
  • 編集前の退避。
  • FTPソフトが自動生成したバックアップ。
  • エディタが保存した一時ファイル。

どれも作業中であれば珍しいものではありません。問題なのは、それを公開ディレクトリへ置いたままにしてしまうことです。

もし .bak が見つかったら、多くのスキャナーはそこで終わりません。

続けて

  • wp-config.php.save
  • wp-config.php.tmp
  • wp-config.php.orig
  • backup/
  • old/
  • test/

などを次々に探し始めます。理由は単純です。

「バックアップを公開ディレクトリへ置く運用をしているなら、他にも何か残っている可能性が高い。」

と判断するからです。つまり、.bak はゴールではありません。攻撃者にとっては

「このサーバーは、まだ掘る価値がある」

という判断材料です。

ModSecurityは何を見ていたのか

今回ヒットしたのは二つのルールでした。

Rule 920440

まず反応したのは

URL file extension is restricted by policy

というルールです。.bak.old といった、本来ブラウザから取得されるべきではない拡張子を検知しています。

つまり、「その拡張子をHTTPで取りに来る時点で怪しい」という考え方です。

Rule 930130

続いて

Restricted File Access Attempt

が反応しています。こちらはファイル名そのものを見ています。

  • 危険な拡張子
  • 危険なファイル名

という二方向から同じリクエストを評価していました。一つのルールだけではなく、多層で検査するOWASP CRSらしい構成です。

ボットは「運用者の癖」を学習している

以前の記事では、

「多くの攻撃は、意志を持たず、目標を自動的に追尾するボットが大半を占めています。」

と書きました。今回のログも、まさにその延長線上にあります。ボットは感情を持って

「この管理者は雑そうだ」

と考えているわけではありません。過去の膨大な成功例から、

  • .bak が見つかったサーバーは他にも置き忘れが多い
  • .env.production が見つかる環境は設定管理も甘い
  • .git が公開されているサーバーはソース管理も甘い

という"統計"を利用しているだけです。つまり攻撃者は、サーバーを見ているようで、その向こうにいる運用者の習慣を見ています。

ソフトウェアの脆弱性だけではなく、人間の癖まで含めてスキャン対象になっているということです。

おわりに

以前は、ソフトウェアの脆弱性を突く攻撃が主流でした。もちろん今もゼロデイ攻撃は存在します。

しかしログを眺めていると、それと同じくらい多いのが

「人間が置き忘れたものを探す攻撃」

です。

  • .env
  • .bak
  • .old

どれも高度な脆弱性ではありません。運用の隙です。だからこそ、防御も特別な技術だけではありません。

  • 不要なファイルを公開ディレクトリへ置かない。
  • 作業が終わったら必ず片付ける。
  • 公開前に一度確認する。

そうした当たり前の積み重ねが、結果として最も強い防御になります。

それを防ぐための筆者の手

/etc/conf_backup

のような、まず公開ディレクトリではないディレクトリを作り、

sudo cp -pi /home/www-data/wordpress/wp-config.php /etc/conf_backup/wp-config.php.conf.$(date +%Y%m%d)

のように別ディレクトリに作ります。(もちろん、/etcがWeb経由で閲覧できる状況になっているのであれば、それは「バックアップが見えるかどうか」を心配する段階ではありません。サーバーそのものの侵害を疑うべき状況です)

また、変数による日付自動付与により

  • 最新版2026-3(承認済み).xlsx
  • 2026確定版(社長の意見反映).xlsm
  • 最新確定版2026_コピー(2)セキュリティ委員修正済み.xlsx
  • 改訂版2026_new_コピー(1)レビュー済み.xlsm
  • 2026-10改訂最新版(消さないこと)重役確認済み.xlsx

のような「だから最新どれだよ」を防ぐことはある程度行えます。ちょっとした工夫で癖は防げるというお話しでした。

【WAF検知ログ解析】難読化された JavaScript 偵察ツールを ModSecurity はどう見抜いたのか

はじめに

Redmine 6.1への移行作業がようやく一段落したので、久しぶりにサーバーログを眺めていました。

すると、トップページ (hoge.example.com) に対して、見慣れない長大なペイロードが飛んできていました。

最近の攻撃は「脆弱性を突く」ことだけが目的ではありません。まずは対象がどんな環境なのかを調べ、その結果によって次の手を変えてきます。

今回は、その偵察段階で送られてきたJavaScriptペイロードがなかなか興味深い内容だったので、ModSecurityのログを追いながら見ていきます。

検出された ModSecurity ログ

まずは実際のログです。

例によってIPアドレスはダミーに置き換えています。テロリストに名前を与えていません。

[Wed Sep 02 02:04:07 2026] [security2:error] [client 198.51.100.24:56250] ModSecurity: Warning. Pattern match "(?i)(?:b[\\"'\\\\)\\\\[\\\\x5c]..." at ARGS:0. [file "REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "205"] [id "932235"] [msg "Remote Command Execution: Unix Command Injection (command without evasion)"] [data "Matched Data: eval)(global[String['from'+'CharCode'](66,117,102,102,101,114)].from('KGFzeW5jIGZ1bmN0aW9uKCl7Ci8vIGZhc3RfcmVjb25fdjY..."] [hostname "hoge.example.com"] [uri "/"]
[Wed Sep 02 02:04:07 2026] [security2:error] [client 198.51.100.24:56250] ModSecurity: Rule [id "932250"] - Execution error - PCRE limits exceeded (-47): (null). [hostname "hoge.example.com"] [uri "/"]
[Wed Sep 02 02:04:07 2026] [security2:error] [client 198.51.100.24:56250] ModSecurity: Warning. Pattern match "(?i)\\\\b\\\\(?[\\"']*(?:assert|eval|exec|passthru)..." at ARGS:0. [file "REQUEST-933-APPLICATION-ATTACK-PHP.conf"] [line "406"] [id "933160"] [msg "PHP Injection Attack: High-Risk PHP Function Call Found"] [hostname "hoge.example.com"] [uri "/"]

最初に目を引くのは Rule 932235 が反応していることです。

Remote Command Execution:
Unix Command Injection

さらに途中では

PCRE limits exceeded (-47)

というログも出ています。

Base64の中身を見てみる

ログ中に埋め込まれていたBase64文字列をデコードすると、次のようなヘッダーが現れました。

(async function(){
// fast_recon_v6 — signature-rotated recon payload
// Changes from v5:
//   - Randomized top-level JSON keys (no fixed schema to fingerprint)
//   - Variable output structure per invocation
//   - IMDS calls use randomized User-Agent + jittered timeouts
//   - File reads are shuffled order (no deterministic sequence)
...

名前からも分かるように、これは偵察を目的としたJavaScriptです。面白いのは「何を調べるか」よりも、「どうやってWAFの検知を避けようとしているか」の方でした。

難読化の目的はコードを隠すことではない

例えば、

global[String['from'+'CharCode'](...)]

という書き方。普通なら

Buffer

と書けば済むものを、わざわざ文字コードから組み立てています。さらに

(0,eval)(...)

という間接呼び出しも使われています。どちらもJavaScriptとしては動作は同じですが、単純な文字列検索やシグネチャ検知を避けるための定番手法です。

コードを読めなくすることより、「機械に見つかりにくくすること」の方が目的と言った方が近いでしょう。

偵察対象も今どきらしい

コードを見ると、次のような情報収集も行おうとしていました。

  • クラウドメタデータ(IMDS)へのアクセス
  • IAMロールなど認証情報の取得
  • ファイル探索順序のランダム化
  • User-Agentやタイムアウト値のランダム化

固定パターンをできるだけ作らないよう工夫されており、「同じ攻撃を毎回少しずつ変える」という最近の攻撃らしい作りになっています。

PCRE が限界を迎えても、防御は止まらない

途中で

PCRE limits exceeded (-47)

が記録されています。これは巨大な入力に対して、ある正規表現がバックトラック上限へ達したことを示しています。

しかし、OWASP CRSは一つの巨大なルールだけで防御しているわけではありません。このリクエストでは、

  • 932235
  • 932260
  • 933160
  • 933210

と、別方向から検査するルールが続いてヒットしました。

つまり、一つの正規表現が処理を諦めても、他のルールが引き継ぐ設計になっています。

多層防御という言葉はよく聞きますが、今回のログはその動きを非常に分かりやすく見せてくれました。

おわりに

最近は攻撃そのものより、「検知をどう回避するか」の工夫を見る機会が増えてきました。

とはいえ、こうして落ち着いて中身を眺められるのは、防御基盤が先に仕事をしてくれているからです。

「サーバー運用はサファリパークに似ている」ようなものです。猛獣を間近で観察できるのは面白いですが、それは檻や装甲車があるからこそです。

今回も同じでした。難読化されたJavaScriptをじっくり読めたのは、攻撃がアプリケーションまで届かなかったからです。

運用者としては、その「観察できる余裕」こそが、一番ありがたい成果だったのかもしれません。

WasabiをRedmineの添付ファイル保存先にしたら、画像の貼り付けだけ「早期削除」が気になり始めた話

Redmine の添付ファイル保存先として、Wasabi オブジェクトストレージを s3fs でマウントして利用しています。

運用自体は安定していましたが、ある日 Wasabi のダッシュボードを眺めていて違和感を覚えました。

Timed Object Storage(早期削除)の対象が、思ったより増えている。

以前、Nextcloud や Growi とオブジェクトストレージの組み合わせで、Timed Object Storage に長期間悩まされた経験(150日の亡霊)があります。

そのとき学んだのは、「動いている」ことと、「そのストレージに適した運用」であることは別だということでした。

その経験があったからこそ、今回も「また何か起きているのではないか」と考え、一つずつ確認してみることにしました。

添付方法によって違いがあるのでは?

まず比較したのは、画像の添付方法です。

確認した結果、少なくとも現時点では次のような傾向が見えました。

添付方法Timed Object Storage の発生
ファイル選択からアップロード確認できず
Ctrl+Vでクリップボード貼り付け確認
Enhanced UIなどの貼り付け機能確認

もちろん、これだけで原因が断定できるわけではありません。「画像を添付する」という同じ操作でも、添付方法によって内部処理が違う可能性は十分考えられます。

クリップボード貼り付けでは一時ファイルを生成・削除しているのかもしれませんし、Enhanced UI 側の実装が影響しているのかもしれません。

現時点では、そこまで踏み込んだ検証はできていません。

今回試してみる対策

以前の経験から、オブジェクトストレージへ直接細かなアクセスを繰り返す構成は、あまり相性が良くないと感じています。

そこで今回は、s3fs のキャッシュ機能を利用して、ローカル SSD をワンクッション挟む構成へ変更してみることにしました。

追加した主なオプションは次の3つです。

  • use_cache
  • stat_cache_expire=60
  • enable_content_md5

まずキャッシュディレクトリを作成します。

sudo mkdir -p /var/cache/s3fs_bucket_a /var/cache/s3fs_bucket_b
sudo chown -R www-data:www-data /var/cache/s3fs_bucket_a /var/cache/s3fs_bucket_b
sudo chmod 750 /var/cache/s3fs_bucket_a /var/cache/s3fs_bucket_b

続いて /etc/fstab を更新します。

cd /etc
sudo cp -pi fstab /etc/conf_backup/fstab.20260830
sudo nano fstab
--- /etc/conf_backup/fstab.20260830     2025-08-07 11:01:54.000000000 +0900
+++ fstab                               2026-08-30 19:58:37.000000000 +0900
@@ -2,8 +2,9 @@
 LABEL=BOOT      /boot   ext4    defaults        0 2
 LABEL=UEFI      /boot/efi       vfat    umask=0077      0 1
 /swapfile       none    swap    sw      0 0
-# Wasabi Bucket A (storage.example.com)
-s3fs#storage.example.com /mnt/wasabi fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33 0 0
 
-# Wasabi Bucket B (counter.example.org)
-s3fs#counter.example.org /mnt/wasabi2 fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33 0 0
+# Wasabi Bucket A (storage.example.com - ap-northeast-1)
+s3fs#storage.example.com /mnt/wasabi fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33,use_cache=/var/cache/s3fs_bucket_a,stat_cache_expire=60,enable_content_md5 0 0
+
+# Wasabi Bucket B (counter.example.org - ap-northeast-1)
+s3fs#counter.example.org /mnt/wasabi2 fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33,use_cache=/var/cache/s3fs_bucket_b,stat_cache_expire=60,enable_content_md5 0 0

変更後は再マウントします。(daemon-reloadしないと怒られました)

sudo umount /mnt/wasabi
sudo umount /mnt/wasabi2
sudo systemctl daemon-reload
sudo mount /mnt/wasabi
sudo mount /mnt/wasabi2

これで本当に改善するのか?

正直なところ、この記事を書いている時点ではまだ分かりません。今回の変更は、「クリップボード貼り付け時の一時ファイルが原因ではないか」という仮説に基づく対策です。

実際に Timed Object Storage の発生が止まるのか、それとも別の要因があるのかは、しばらく Wasabi のダッシュボードを見ながら経過観察する必要があります。

もし改善が確認できれば追記しますし、変化がなければ別の原因を探ることになります。

まとめ

今回の目的は、「原因を突き止めた」という報告ではありません。

Wasabi のダッシュボードで小さな違和感を見つけ、その原因として添付方法の違いに着目し、対策を試し始めたという記録です。

以前、オブジェクトストレージとの組み合わせで大きく痛い目を見た経験があるからこそ、「いつもと違う」を見逃さずに済みました。

サーバー運用では、エラーが出てから対応するよりも、違和感の段階で調べ始める方が結果として被害は小さく済みます。

今回の変更が正解かどうかは、これからの経過観察で判断したいと思います。

【Redmine 6.1移行】DBマイグレーションで「Table already exists」連発? 本体統合された機能との競合を解消して移行を完走した記録

Redmine 5.1からRedmine 6.1への移行では、できるだけ本番環境へ影響を出さないよう、検証環境で一つずつ作業を進めてきました。

今回は以前のように「一気にアップグレードして問題を追う」のではなく、

  • 検証環境を作る
  • 問題が起きたら原因を調べる
  • 記録として残す
  • 次の工程へ進む

という流れで進められたため、最終的にはかなり安心して6.1環境を完成させることができました。

その途中で遭遇したのが、データベースマイグレーション時の Table already exists エラーです。

最初は単純なテーブル重複かと思いましたが、原因を追っていくとRedmine 6.0で行われた「プラグイン機能の本体統合」が関係していました。

今回は、このマイグレーションエラーの内容と対処手順を記録しておきます。

実データを流し込んだ直後にマイグレーションが止まる

今回の手順では、Redmine 5.1で整理・純化しておいたデータベースをMySQLダンプから復元し、そのままRedmine 6.1側でマイグレーションを実行しました。

cd /home/www-data/redmine_v6
sudo -u www-data RAILS_ENV=production bundle exec rake db:migrate

ところが途中で処理が停止します。

最初に止まったのは、リアクション機能です。

== 20250423065135 CreateReactions: migrating ==================================
-- create_table(:reactions)

Mysql2::Error:
Table 'reactions' already exists

この時点では、

「どこかでマイグレーションを実行し忘れたかな?」

程度に考えていました。ところが、修正して再実行すると、今度はこちら。

== 20250611092155 CreateDoorkeeperTables: migrating ===========================

Mysql2::Error:
Table 'oauth_applications' already exists

また別のテーブルが既に存在すると言われます。つまり偶然ではなく、何か共通した原因がありそうでした。

原因は「昔はプラグイン、今は標準機能」

調べてみると、どちらもRedmine 6.0で本体へ取り込まれた機能でした。

今回衝突したのは、

  • リアクション機能
  • OAuth認証(Doorkeeper)

の二つです。どちらも以前はプラグインとして利用していましたが、Redmine 6ではコア機能になっています。つまり、

Redmine 5.1時代

プラグイン
    ↓
DBにテーブル作成

だったものが、Redmine 6.1では、

Redmine本体
    ↓
同じ名前のテーブルを作成

という流れに変わっています。そのため、旧環境からDBを持ってくると、

既に存在するテーブルを、Redmine本体がもう一度作ろうとする

という状態になっていました。

なぜDROPしてよいのか

ここで少し悩みました。

「既存テーブルを削除してしまって本当に大丈夫なのか?」

しかし今回は、本体側へ正式に統合された機能です。つまり最終的に利用するのはRedmine本体が管理するスキーマになります。古いプラグイン時代のテーブルを残していても、最終的には使われません。

そこで今回は、競合しているテーブルだけを削除し、本体マイグレーションに改めて生成してもらうことにしました。

競合しているテーブルを削除する

MySQLから競合しているテーブルを削除します。

mysql -u redmine_v6 -p redmine_v6 -e "
DROP TABLE IF EXISTS reactions;
DROP TABLE IF EXISTS oauth_access_tokens;
DROP TABLE IF EXISTS oauth_access_grants;
DROP TABLE IF EXISTS oauth_applications;
"

これで、本体側が新しくテーブルを作れる状態になります。

改めてマイグレーションを実行する

続いて再度マイグレーションを実行します。

cd /home/www-data/redmine_v6
sudo -u www-data RAILS_ENV=production bundle exec rake db:migrate

今度は問題なく進みます。

== CreateReactions: migrated
== EnsureWikiTablesortSettingIsStoredInDb: migrated
== CreateDoorkeeperTables: migrated

最後まで完走し、正常終了しました。

プラグイン側も忘れずに更新

本体が終わったら、続いてプラグイン側のマイグレーションも実行します。

sudo -u www-data RAILS_ENV=production bundle exec rake redmine:plugins:migrate

本体だけ更新して安心しがちですが、ここまで実行して初めてプラグイン側も新しい環境へ追従できます。

configuration.ymlも忘れずに引き継ぐ

今回の環境ではSMTP設定も引き継ぐ必要がありました。

既存環境から configuration.yml をコピーします。

sudo -u www-data cp -p \
/home/www-data/redmine/config/configuration.yml \
/home/www-data/redmine_v6/config/configuration.yml

その後、Passengerを再起動します。

sudo touch /home/www-data/redmine_v6/tmp/restart.txt
sudo systemctl reload apache2

最後に管理画面からテストメールを送信し、Zoho Mail経由で正常に届くことまで確認しました。

移行後の確認

今回の検証では、最終的に以下を確認できました。

  • 過去のチケット・Wiki・添付ファイルを正常に閲覧できる
  • ガントチャートも問題なく表示される
  • kodomo テーマもRedmine 6.1環境で正常動作
  • テストメールを送信し、Zoho Mailで受信できることを確認
  • DMSFを利用しなくても、標準添付機能で画像を配置できることを確認

ここまで確認できれば、検証環境としては十分安心できる状態になりました。

今回の移行を振り返って

今回のRedmine 6.1移行では、「作業そのもの」よりも「途中で何が起きたか」を残しながら進められたことが大きかったように思います。

以前であれば、エラーを解消して先へ進むことを優先していた場面でも、

  • なぜ起きたのか
  • Redmine側の仕様変更なのか
  • プラグイン由来なのか
  • 次に同じ作業をするとき、何を確認すればよいのか

という視点で整理しながら進められました。

結果として、今回遭遇した Table already exists も「たまたま起きたエラー」ではなく、Redmine 6でプラグイン機能が本体へ統合されたことによる仕様変更だと理解できました。

移行作業では、どうしてもエラーそのものへ目が向きがちですが、「なぜそのエラーが起きたのか」まで追っておくと、次回以降の作業はずっと楽になります。

今回の6.1環境は、そうした記録を積み重ねながら構築できたこともあり、これまでで一番安心して仕上げられたバージョンアップだったように感じています。

【Redmine 6.1移行】テーマが出ない・画面が崩壊する? Propshaft移行に伴うテーマディレクトリ変更とCSSパス修正の記録

Redmine 5.1からRedmine 6.1(Rails 7.2)への移行検証を進めていると、プラグイン以外にもいくつか「以前と同じやり方では動かない」箇所が出てきます。

その中でも、意外と厄介だったのがカスタムテーマの移行でした。

今回、長年使ってきた kodomo テーマをRedmine 6.1環境へ移そうとしたところ、

  • 管理画面のテーマ一覧にカスタムテーマが出てこない
  • 何とか設定を通してみると、今度はCSSが適用されず画面が崩壊する

という、二段構えの問題に遭遇しました。

最初は「テーマをコピーすれば終わりだろう」と考えていたのですが、Redmine 6ではその前提自体が変わっています。

調べていくと、Redmine 6.0で行われたアセットパイプラインの変更、つまりSprocketsからPropshaftへの移行が、この問題の根っこにありました。

今回は、このテーマ移行で実際に引っかかった箇所と、Redmine 6.1側で動く状態まで戻した手順を記録しておきます。


1. まず何が起きたのか

今回の移行では、kodomo テーマをRedmine 5.1環境からRedmine 6.1環境へ持ってきました。

ところが、Redmine 6.1を起動して管理画面の

設定 > 表示 > テーマ

を確認してみても、表示されているのは標準テーマの AlternateClassic だけ。

カスタムテーマの Kodomo がありません。

テーマそのものは、

public/themes/kodomo

に存在しています。

ディレクトリやファイルの所有者・権限にも問題はありません。Passengerを再起動しても出てこない。

そこでRedmine内部のテーマスキャンを確認すると、どうもそもそもテーマとして認識されていないようでした。

さらに、DB側の設定を直接変更してテーマを適用させてみると、今度は別の問題が発生しました。

テーマ名は設定できた。しかしCSSが適用されない。

ブラウザに表示されるのは、文字とリンクとリストが並んだだけの、いわゆる「素のHTML」。見事に画面が崩壊しました。

つまり今回の問題は、

  1. テーマそのものをRedmineが見つけられない
  2. 見つけさせてもCSSの読み込み方が古い

という二つの問題が重なっていたわけです。

2. 原因はRedmine 6.0からのアセットパイプライン変更

ここで重要になるのが、Redmine 6.0から導入されたPropshaftです。

Redmine 5.xまでの環境では、Railsのアセット管理に主として Sprockets が使われていました。

SprocketsはCSSやJavaScriptなどのアセットを処理・結合・コンパイルする仕組みで、Redmineでも長らく使われてきたものです。

一方、Redmine 6.0ではこの部分がPropshaftへ移行しました。

PropshaftもRailsのアセットパイプラインを担う仕組みですが、Sprocketsとはアセットの探索や扱い方が異なります。

Redmine 5.x時代に作られたテーマは、

「以前のRedmineでは、この場所にテーマがあり、この相対パスなら標準CSSを読み込める」

という前提で作られています。ところがRedmine 6.xでは、その前提が崩れています。今回のテーマ移行で問題になったのも、まさにそこでした。

3. テーマの置き場所が変わっている

Redmine 5.xまで使っていたテーマは、基本的に

public/themes/

以下へ配置していました。ところがRedmine 6.xでは、テーマの標準配置場所が変わっています。

Redmine本体のルートディレクトリから見て、

themes/

です。今回のRedmine 6.1環境は、

/home/www-data/redmine_v6

なので、テーマは最終的に

/home/www-data/redmine_v6/themes/kodomo

となる形です。つまり、Redmine 5.1からそのまま

public/themes/kodomo

をコピーしただけでは、Redmine 6.1側のテーマローダーから見つけてもらえません。これが、最初の

「テーマを置いたのに管理画面に出てこない」

という問題の原因でした。

4. テーマを themes/ へ移す

ということで、まずはRedmine 6.1側にテーマを配置します。

sudo -u www-data cp -pir public/themes/kodomo themes/
sudo chown -R www-data:www-data themes
sudo chmod -R 755 themes

これで、

/home/www-data/redmine_v6/themes/kodomo

という構造になります。テーマの中には、少なくとも今回のケースでは、

themes/
└── kodomo/
    └── stylesheets/
        └── application.css

という構造が必要です。ここまでやってから、もう一度Redmine側からテーマを認識できるか確認します。

5. しかし、これだけではまだ画面は直らない

ここが今回のもう一つの罠でした。テーマを正しい場所へ移したことで、Redmineからテーマとして認識されるようになります。しかし、実際にテーマを適用してみると、

CSSが読み込まれません。

結果として、画面はこうなります。

文字
リンク
箇条書き
フォーム
表

……以上。

ブラウザとしては正しくHTMLを表示しているのですが、Redmineとして見ると完全に「何かがおかしい」。これはテーマ側の application.css が、Redmine本体のCSSを正しく参照できていないためでした。

6. application.css の相対パスも変更する

Redmineのカスタムテーマでは、標準のCSSを読み込んだ上で、自分のテーマ固有のCSSを追加する構造になっているものがあります。今回の kodomo もそのタイプでした。

Redmine 5.x時代のテーマでは、

@import url(../../../stylesheets/application.css);

という指定になっていました。ところが、Redmine 6.xではテーマの配置場所そのものが変わっています。そのため、この相対パスでは標準CSSへ到達できません。

Redmine 6.x側では、

@import url(../../application.css);

へ変更します。今回の場合は、

themes/kodomo/stylesheets/application.css

から見て、Redmine側のCSSを参照するためのパスが変わった、と考えると分かりやすいです。

7. application.css を修正する

まずは念のためバックアップを取得します。

cd /home/www-data/redmine_v6/themes/kodomo/stylesheets
sudo -u www-data cp -p application.css application.css.bak

そして、旧パスを新しいパスへ置き換えます。

sudo -u www-data sed -i \
's|@import url(../../../stylesheets/application.css);|@import url(../../application.css);|g' \
application.css

念のため、修正後の内容も確認しておきます。

grep '@import' application.css

ここで、

@import url(../../application.css);

となっていればOKです。

8. Railsからテーマを認識できるか確認する

ここまで来たら、Webブラウザで確認する前にRails側からテーマを認識できるか確認しておきます。

cd /home/www-data/redmine_v6
sudo -u www-data RAILS_ENV=production bundle exec rails runner "
  Redmine::Themes.rescan
  Redmine::Themes.themes.each do |t|
    puts 'Loaded Theme -> ID: ' + t.id + ' | Name: ' + t.name
  end
"

正常に読み込まれていれば、例えば次のように表示されます。

Loaded Theme -> ID: alternate | Name: Alternate
Loaded Theme -> ID: classic | Name: Classic
Loaded Theme -> ID: kodomo | Name: Kodomo

ここで kodomo が出てくれば、少なくともRedmineのテーマローダーからは認識されています。

9. 最後にPassengerを再起動する

テーマを修正したら、Webアプリケーション側も再起動しておきます。Passenger環境では、

sudo touch /home/www-data/redmine_v6/tmp/restart.txt
sudo systemctl reload apache2

としておきます。その後、Redmineへアクセスして、

管理 > 設定 > 表示 > テーマ

を確認します。ここに、

Alternate
Classic
Kodomo

と表示されれば、テーマ自体は認識されています。あとは Kodomo を選択して画面を確認します。

CSSが正常に読み込まれていれば、今度はRedmine 5.1時代の見慣れた画面が戻ってきます。

10. 今回の問題を整理すると

今回のトラブルは、最初は「Redmine 6.1で古いテーマが使えなくなった」と見えました。

しかし、実際には二つの問題がありました。

問題1:テーマを置く場所が変わった

Redmine 5.xでは、

public/themes/kodomo

でした。Redmine 6.xでは、

themes/kodomo

です。そのため、旧環境からテーマをそのままコピーしただけでは、Redmineから認識されません。

問題2:テーマ内部のCSS参照も古い

さらに、テーマがRedmineから認識されたとしても、古い application.css のままでは標準CSSを読み込めません。

Redmine 5.xでは、

@import url(../../../stylesheets/application.css);

だったものを、Redmine 6.xでは、

@import url(../../application.css);

へ変更する必要がありました。つまり、

「テーマを移動する」だけではなく、「テーマからRedmine本体を参照しているパスも見直す」必要がある

ということです。

11. Redmine 6.xへテーマを移行するときのチェックリスト

今回の作業をチェックリストにすると、こんなところでしょうか。

  • public/themes/ に置きっぱなしになっていないか
  • Redmineルート直下の themes/ にテーマを配置したか
  • themes/<テーマ名>/stylesheets/application.css が存在するか
  • application.css の古い相対パスが残っていないか
  • @import url(../../application.css); になっているか
  • テーマディレクトリの所有者・権限を確認したか
  • Redmine::Themes.rescan でテーマが認識されているか
  • Passengerを再起動したか
  • ブラウザのキャッシュだけでなく、CSSの読み込み自体が成功しているか

まとめ

Redmine 5.1から6.1への移行というと、どうしてもデータベースやプラグインの互換性に目が行きます。

実際、そちらの方が重要ではあるのですが、今回のようにテーマもRedmine本体の変更に巻き込まれることがあります。

特にRedmine 6.0からは、RailsのアセットパイプラインがSprocketsからPropshaftへ移行しています。

その結果として、テーマの配置場所やCSSの参照方法について、Redmine 5.x時代のテーマをそのまま持ってきても動かないケースが出てきます。

今回の kodomo については、

public/themes/kodomo
        ↓
themes/kodomo

への移動と、

@import url(../../../stylesheets/application.css);

から

@import url(../../application.css);

への変更で復旧できました。

Redmine 6.xへの移行では、プラグインだけを確認して「よし、動いた」とするのではなく、テーマもまたバージョンアップの影響を受ける部品として確認しておくのが安全そうです。

今回のように、

「テーマが一覧に出ない」

「何とか指定できた」

「今度はCSSが全部消えた」

という二段コンボを食らうと、原因が同じ場所にあるとはなかなか気づきにくいところでした。

移行作業では、こういう「前のバージョンでは当たり前だった場所」を一つずつ疑っていく必要がありそうです。

Redmine 6.1環境に Additionals と Additional Tags を導入する ― 3つの落とし穴と解決の記録

はじめに

Redmine 6.1(Rails 7.2系)の検証環境構築に続き、今回は各種UI拡張やマクロ機能の土台となる Additionals および Additional Tags プラグインを導入しました。

依存関係の親玉とも言えるこの2つのプラグインですが、最新版(main ブランチ)をそのままクローンしてマイグレーションを実行したところ、複数の予期せぬエラーに遭遇しました。

今回は、遭遇した3つのトラブルと、それを解決して Redmine 6.1 上で正常認識させるまでのメモです。

今回の前提条件

  • Redmine: 6.1.4.stable (Rails 7.2.3.2)
  • Ruby: 3.2.x
  • 対象プラグイン:
  • additionals (AlphaNodes製 / 共通ライブラリ)
  • additional_tags (AlphaNodes製 / タグ機能拡張)

遭遇した3つの罠と対処法

罠1: 最新版が「Redmine 7.0以上」を要求して abort

GitHubから main ブランチをそのままクローンしてマイグレーションを実行したところ、以下のエラーが発生しました。

Redmine::PluginRequirementError: additional_tags plugin requires Redmine 7.0 or higher but current is 6.1.4.stable.25001
  • 原因: リポジトリ先端(main)の開発が既に次世代の Redmine 7.0 / Rails 8系 向けに進んでおり、6.1環境が弾かれていました。
    • 対処: Redmine 6.x に対応している安定版タグ(v3.4.0 など)へチェックアウトを切り替えます。

罠2: Gitの「dubious ownership」セキュリティエラー

sudo -u www-data git checkout を実行した際、Git のセキュリティ機構(所有権チェック)に阻まれました。

fatal: detected dubious ownership in repository at '/home/www-data/redmine_v6/plugins/additionals'
  • 原因: 実行ユーザーとディレクトリ所有者(www-data)の不一致による安全装置の作動。
    • 対処: www-data ユーザーの Git グローバル設定にセーフディレクトリを登録して回避しました。
sudo -u www-data git config --global --add safe.directory '*'

罠3: プラグイン依存 Gem の未調合(Could not find compatible versions)

タグを切り替えて rake redmine:plugins:migrate を実行したところ、今度は Bundler が停止しました。

Because every version of additionals depends on render_async >= 0
  and render_async >= 0 could not be found in locally installed gems,
  additionals cannot be used.
  • 原因: プラグインが要求する Gem(render_async 等)が、ローカルの vendor/bundle にインストールされていませんでした。
    • 対処: マイグレーション前に必ず bundle install を通す必要があります。

成功した導入手順まとめ

1. リポジトリのクローンと安全設定

  • Redmine(検証用)ルートディレクトリに移動
cd /home/www-data/redmine_v6/plugins
  • プラグインのgit clone
sudo -u www-data git clone https://github.com/alphanodes/additionals.git
sudo -u www-data git clone https://github.com/alphanodes/additional_tags.git
  • Git 所有権警告の解除
sudo -u www-data git config --global --add safe.directory '*'

2. Redmine 6.x 対応タグへのチェックアウト

  • additionalsでチェックアウト
cd /home/www-data/redmine_v6/plugins/additionals
sudo -u www-data git checkout $(sudo -u www-data git tag -l | grep -E '^v?3\.' | tail -n 1)
  • additional_tagsでチェックアウト
cd /home/www-data/redmine_v6/plugins/additional_tags

```bash
sudo -u www-data git checkout $(sudo -u www-data git tag -l | grep -E '^v?3\.' | tail -n 1)

3. 依存 Gem のローカルインストール(最重要)

cd /home/www-data/redmine_v6
sudo -u www-data bundle install

4. マイグレーションと再起動

sudo -u www-data bundle exec rake redmine:plugins:migrate RAILS_ENV=production
sudo -u www-data bundle exec rake assets:precompile RAILS_ENV=production

おわりに

構築中のRedmine 6.1サイトにアクセスし、管理画面(/admin/plugins)を開くと、Additional Tags (3.4.0)Additionals (3.4.0) が無事に認識され、エラーなく設定画面を開くことができました。

プラグイン導入時は、「最新ブランチを追うのではなく適切なリリースバージョン(タグ)を選ぶこと」と「DBマイグレーションの前に bundle install で Gem 依存を揃えること」が鉄則であると再認識させられた検証でした。

Redmine 5.1を壊さずに、同じサーバーへRedmine 6.1検証環境を構築する

Redmine 6.xへの移行を見据え、現在運用しているRedmine 5.1とは別に、同一サーバー上へRedmine 6.1の検証環境(redmine-v6)を構築しました。

「同じサーバーなら、そのまま新しいRedmineを入れればいい」と考えがちですが、不用意にシステム全体のRuby環境やWebサーバー設定を書き換えると、既存の稼働中サービスを巻き込んで停止(500エラー等)させるリスクがあります。

今回の構築では、「Redmine 6.1を動かすこと」以上に「現在動いている既存環境(5.1)に一切影響を与えないこと」を最優先に設計しました。そのための設計思想と具体的な構築手順をまとめます。

既存環境を壊さないための3つの原則

RedmineはRubyやGem、Webサーバー(Apache)など多くの基盤を利用するRailsアプリケーションです。既存環境との共存にあたり、特に以下の3点を徹底しました。

  1. sudo gem install を安易に使わず、ライブラリをプロジェクト内に隔離する
    システム全体にGemをインストールすると、既存Redmineが依存するGemバージョンと競合する恐れがあります。Bundlerを利用し、依存ライブラリはすべて検証環境のディレクトリ配下(vendor/bundle)へ閉じ込めます。
  2. Bundlerの設定をローカル(プロジェクト単位)に限定する
    グローバル設定(~/.bundle/config)への書き込みを避け、bundle config set --local を使用して対象ディレクトリ内でのみ設定を完結させます。
  3. Apacheの既存設定には触れず、新しいVirtualHostを追加するのみにとどめる
    既存のサイト設定(000-default.conf や既存Redmine用設定)の無効化(a2dissite)は行わず、新規VirtualHostの作成と有効化(a2ensite)だけで対応します。

検証環境・前提条件

  • OS: Ubuntu 24.04 LTS
  • Ruby: 3.2.x系(Ubuntu標準パッケージ / 5.1系と6.1系の両方に対応)
  • DB: MySQL 8.x
  • Web: Apache 2.4 + Passenger
  • Redmine: 6.1-stable(新規検証用)
  • パッケージ管理: aptitude(※aptでも同様に実行可能です)
  • 名前解決: 検証用ドメイン/サブドメインのDNS名前解決が完了していること

多分さっくりとはしていない手順

  1. Redmine-v6用のDBを作成します。
  2. Redmine 6.1を配置します。
  3. データベース接続設定を行います。
  4. Gemの初期化と設定を行います。 (※既存環境を壊さないように注意!!!)
  5. Apacheのvirtualhostを設定します。
  6. 設定を反映します。
  7. 動作確認を行います。

1. データベースと専用ユーザーの作成

既存のデータベースと完全に分離するため、redmine_v6 専用のDBおよびユーザーを作成します。

sudo mysql -u root -p
-- データベース作成
CREATE DATABASE redmine_v6 CHARACTER SET utf8mb4;

-- 専用ユーザー作成(パスワードは任意のものに変更してください)
CREATE USER 'redmine_v6'@'localhost' IDENTIFIED BY 'your_secure_password';

-- 権限付与
GRANT ALL ON redmine_v6.* TO 'redmine_v6'@'localhost';
FLUSH PRIVILEGES;
EXIT;

作成したユーザーで接続できるか確認します。

mysql -u redmine_v6 -p -e "SHOW DATABASES;"

2. Redmine 6.1 の配置

配置ディレクトリを既存環境と分けて作成し、ソースコードを取得します。

  • ディレクトリ作成
sudo mkdir -p /home/www-data/redmine-v6

※自分の環境に合わせます。

  • ディレクトリの所有者変更
sudo chown -R www-data:www-data /home/www-data/redmine-v6
  • Redmine 6.1-stable の取得
sudo -u www-data svn co https://svn.redmine.org/redmine/branches/6.1-stable /home/www-data/redmine-v6

3. データベース接続設定

設定ファイルのサンプルをコピーし、先ほど作成した redmine_v6 の情報を設定します。

sudo -u www-data cp -pi /home/www-data/redmine-v6/config/database.yml.example /home/www-data/redmine-v6/config/database.yml

/home/www-data/redmine-v6/config/database.yml を編集します。

production:
  adapter: mysql2
  database: redmine_v6
  host: localhost
  username: redmine_v6
  password: "your_secure_password"
  encoding: utf8mb4

4. Gemのローカルインストールと初期化

Gemの競合を防ぐため、必ずローカル設定で vendor/bundle 配下にインストールします。

  • Redmine(v6)のルートディレクトリに移動
cd /home/www-data/redmine-v6 && pwd

旧環境に移動すると偉い目に遭います。(遭いました

  • Bundlerの設定をローカルに限定
sudo -u www-data bundle config set --local path 'vendor/bundle'
sudo -u www-data bundle config set --local without 'development test'
  • 依存関係の調整とインストール
sudo -u www-data bundle update stringio
sudo -u www-data bundle install
  • シークレットトークンの生成
sudo -u www-data bundle exec rake generate_secret_token RAILS_ENV=production
  • DBマイグレーション
sudo -u www-data bundle exec rake db:migrate RAILS_ENV=production
  • デフォルトデータのロード(日本語)
sudo -u www-data RAILS_ENV=production REDMINE_LANG=ja bundle exec rake redmine:load_default_data
  • アセットのプリコンパイル
sudo -u www-data bundle exec rake assets:precompile RAILS_ENV=production

5. Apache VirtualHost の設定

既存の稼働中サイトには一切触れず、Redmine 6.1用の設定ファイルのみを新規作成・有効化します。

cat <<- __EOF__ | sudo tee /etc/apache2/sites-available/redmine-v6.conf
<VirtualHost *:80>
    ServerName 【v6.example.com】
    DocumentRoot /home/www-data/redmine-v6/public
    <Directory /home/www-data/redmine-v6/public>
        Options -MultiViews
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
__EOF__

【v6.example.com】 はご自身の検証用ドメインに書き換えてください。
※ これはあくまでもhttpd専用であり、ログもありません。適宜、セキュリティポリシーに沿って運用します。

6. 設定の反映

  • 新規サイト設定のみ有効化
sudo a2ensite redmine-v6.conf
  • 構文チェック(Syntax OK を確認)
sudo apache2ctl configtest
  • Apacheの再読み込み
sudo systemctl restart apache2.service

7. 動作確認

ブラウザから http://設定した検証用ドメイン にアクセスし、Redmine 6.1 のトップページが表示されれば構築完了です。

重要: 初期管理者アカウント(admin / admin)で即座にログインし、パスワードを強固なものへ変更してください。

おわりに

今回の検証環境構築では、Redmine 5.1(Rails 6.1系)とRedmine 6.1(Rails 7.2系)が共にRuby 3.2系をサポートしていたため、Ruby本体を共用しながらディレクトリ・Gem・DB・VirtualHostのレベルで環境を安全に分離できました。

サーバー運用においては、「新しい環境を動かすこと」以上に「稼働中の資産を壊さない境界線を引くこと」が重要になります。

Redmine 5.1環境で Knowledgebase を削除したら500エラーが消えない ― 真犯人は別のプラグインだった

はじめに

前回の記事では、Redmine 6.xへの移行準備として、redmine_dmsf を完全に切除しました。

その流れで、「それならKnowledgebaseも同じ手順で片付くだろう」と考えたのが、今回の始まりです。

長年ナレッジ管理として活躍してくれた redmine_knowledgebase ですが、現在はMarkdownネイティブな BookStack を主に利用しており、今後も利用予定はありません。

Redmine 6.xへのメジャーバージョンアップを見据え、依存関係を少しでも整理するため、完全アンインストールを行うことにしました。

前回の教訓もあります。

「マイグレーションを一つずつ戻す」のではなく、

  • データベース
  • マイグレーション履歴
  • プラグイン本体

をまとめて切除する外科手術方式です。

ところが今回は、それだけでは終わりませんでした。

Knowledgebaseを削除したはずなのに、ブラウザには何度アクセスしても

We're sorry, but something went wrong.

と表示され続けます。

今回の記事は、この500エラーの犯人を追い掛けた記録です。

今回の前提

今回の作業も、本番環境ではなく前回構築した Side-by-Side 構成の検証環境 (redmine-clone) で実施しています。

項目内容
対象環境検証環境(redmine-clone)
Redmine5.1系
Ruby3.2系
Rails6.1
DatabaseMySQL 8.x
対象プラグインredmine_knowledgebase

今回も、「失敗してもやり直せること」を前提に作業を進めています。

まずはバックアップ

前回と同様、テーブル削除を伴うため、最初にMySQLのダンプを取得します。

mysqldump \
  --single-transaction \
  --routines \
  --triggers \
  --default-character-set=utf8mb4 \
  -u redmine_clone \
  -p \
  redmine_clone \
  > redmine_clone_before_knowledgebase.sql

このバックアップが、最後の保険です。

Step1 Knowledgebaseを切除する

今回は最初から外科手術方式を採用しました。

まずは関連テーブルを削除します。

USE redmine_clone;

SET FOREIGN_KEY_CHECKS = 0;

DROP TABLE IF EXISTS
    kb_articles,
    kb_article_versions,
    kb_categories;

SET FOREIGN_KEY_CHECKS = 1;

続いて、マイグレーション履歴を削除します。

DELETE
FROM schema_migrations
WHERE version LIKE '%-redmine_knowledgebase%';

注意

この方法は「Knowledgebaseを今後利用しない」ことを前提としています。

将来的に再利用する予定がある場合は、通常の移行手順を検討してください。

最後にプラグイン本体を退避します。

cd /home/www-data/redmine-clone

sudo mv plugins/redmine_knowledgebase \
    /home/www-data/retired_plugins/

sudo touch tmp/restart.txt

ここまでは、前回のDMSFとほぼ同じ流れです。

そして私は、この時点で終わったと思っていました。

……終わらなかった

ブラウザを更新すると、

We're sorry, but something went wrong.

The issue has been logged for investigation.
Please try again later.

何度更新しても500エラー。

Knowledgebaseは削除したはずです。

それなのにRedmineは起動しません。

ここから、本当の原因調査が始まりました。

容疑者その1 ― ログが出ていない

まず最初にproduction.logを確認します。

……ところが、ログが増えません。おかしい。

シンボリックリンクを確認すると、

/home/www-data/redmine-clone/log
    ↓
/var/log/redmine

なんと、検証環境のログが本番環境を向いたままでした。

クローン作成時に張り替えたつもりだったシンボリックリンクが、そのまま残っていたのです。

修正します。

cd /home/www-data/redmine-clone

sudo rm log

sudo ln -sf /var/log/redmine-clone log

これでようやく検証環境のログが取得できるようになりました。

……しかし、500エラーは変わりません。容疑者その1、シロでした。

容疑者その2 ― Passenger

次に、Passengerを完全再起動します。

そこで、

passenger-config restart-app

を実行すると、

ArgumentError:
too long unix socket path
(116 bytes given but 108 bytes max)

というエラー。

UNIXドメインソケットの108バイト制限に引っ掛かっていました。

結局、

sudo touch tmp/restart.txt

sudo systemctl reload apache2

という昔ながらの方法で再起動することにしました。

……それでも500エラー。Passengerも違いました。

真犯人

ログが正常に取得できるようになったため、

rake コマンドで環境初期化を実行すると、ついに原因が現れました。

NoMethodError:

undefined method
`requires_redmineup'

plugins/redmine_questions/init.rb

Knowledgebaseではありませんでした。

真犯人は redmine_questions です。

Redmineは起動時に、すべてのプラグインの init.rb を読み込みます。

Knowledgebaseを削除しても、同居していた redmine_questionsredmineup のメソッドを要求し、その時点で初期化が停止していました。

つまり、Knowledgebaseを削除したことで壊れたのではなく、

以前から潜んでいた依存関係の問題が表面化しただけだったのです。

決断 ― 疑わしき依存はすべて切り離す

redmine_questions の利用状況を改めて確認した結果、今後利用する予定はありませんでした。依存関係を抱えたままRedmine 6.xへ進むリスクを考え、こちらもKnowledgebaseと同様に完全切除することにしました。

Questionsを切除する

まずは関連テーブルを削除します。

mysql -u redmine_clone -p
USE redmine_clone;

SET FOREIGN_KEY_CHECKS = 0;

DROP TABLE IF EXISTS
    questions,
    questions_answers,
    questions_sections,
    questions_statuses;

SET FOREIGN_KEY_CHECKS = 1;

続いてマイグレーション履歴を削除します。

DELETE
FROM schema_migrations
WHERE version LIKE '%-redmine_questions%';

最後にプラグイン本体を退避し、キャッシュを削除します。

sudo mv \
plugins/redmine_questions \
/home/www-data/retired_plugins/

sudo rm -rf tmp/cache/*

sudo touch tmp/restart.txt

sudo systemctl reload apache2

動作確認

ブラウザを更新すると……

500エラーは完全に解消しました。確認できた内容は次のとおりです。

  • Redmineが正常起動する
  • トップページが表示される
  • 管理画面へログインできる
  • Plugin一覧から
    • DMSF
    • Knowledgebase
    • Questions
      が消えている
  • エラーログに新たな例外が出ていない

ここまで確認できれば、今回の切除作業は完了です。

おわりに

今回の500エラーは、Knowledgebaseそのものが原因ではありませんでした。

長年運用してきたプラグイン同士の依存関係が、Knowledgebaseの切除をきっかけに姿を現しただけだったのです。

一つ外せば終わると思っていたものが、実際には別のプラグインを炙り出す。長期間運用してきたRedmineらしい現象だったと感じています。

結果として、

  • redmine_dmsf
  • redmine_knowledgebase
  • redmine_questions

という、今後利用予定のない大型プラグインを整理することができました。

Redmine本体はまだ5.1のままですが、不要な依存を抱えない、できるだけ素の状態へ近づけることができています。

そして今回も、問題の切り分けが落ち着いてできたのは、前回構築した redmine-clone の存在があったからです。

本番環境だったら、500エラーが出るたびに焦っていたかもしれません。しかし検証環境だからこそ、一つずつ仮説を立て、ログを確認し、原因を潰していくことができました。

いよいよ次は、Redmine 6.xへのメジャーバージョンアップの下準備です。

ここまで整理してきた環境が、どこまで素直に新しいバージョンを受け入れてくれるのか。それを確認する段階まで、ようやくたどり着きました。

Page 2 of 304

Powered by WordPress & Theme by Anders Norén