Open8

Xcode: 個人開発用にredmineを導入する

kabeyakabeya

個人で開発をしています。
バグや追加機能一覧を管理したいと思って、redmineを使うことにしました。[1]

経緯や作業手順を忘れることになると思うので、メモしておきます。

やりたいこと

やりたいこととしては、以下のようなものになります。

  • バグや機能をチケットとして一覧管理する
  • 修正内容(リポジトリへのコミット内容)と、チケットを紐付ける
  • チケットのステータスを管理する(テストが終わったとか)
  • プロジェクト横断でチケットを俯瞰できるように

欲しい機能とか思いつくことか気になった不具合とかが色々あって、自分の性格的に思いついたらすぐ着手しがちです。そのうえ個人開発だと自由度が高すぎて抑制が効かなくなって来ました。
チケットとして書いておけば、いったんは頭から追い出して安心できるし、後で戻ることもできます。

redmineのインストールとか

素のredmineを直接Macにインストールしようかとも思って、以下の記事を見てやってみたのですが、途中で断念しました。

https://note.com/szzl/n/nbff5d8c77685

上記の記事は、rubyやgemのバージョンに依存するので、バージョンを切り替えられるようローカルに環境を作る、というようなストーリーです。やってみると最後のApache+passengerというところで詰まりました。

詳しく調べる前に断念したのであれですが、httpd.confへpassengerのLoadModule(自分のホームディレクトリ配下のファイル)を追加するとhttpdの起動に失敗するようになってしまったので、アクセス権の問題ではないかと推測しています。自分のホームディレクトリは700で、そのために755とかにするのは何となく(たぶん実害はないのでしょうが)ヤだなと感じたというのが大きいのですが、他にも「ここでこういうつまづきがあるとするとこの先大変そう」とかもあって考え直しました。

dockerを使う

個人なのでDocker Desktop for Macを使う方向で考え直しました。
「個人なので」というのは、個人〜小規模事業者でない場合はDocker for Macが有償利用のみになるからです。

Docker Personal (Docker Desktop for Mac)は以下からサインアップすればダウンロードできます。

https://www.docker.com

ダウンロードした.dmgをマウントして、アプリアイコンをApplicationsフォルダにドラッグ&ドロップするだけです。
Docker Desktopをアイコンから起動すると諸々の処理が行われて、docker-composeとかもインストールされた状態になります。

それ以降のredmineのインストール作業は以下の記事を参考にしました。

https://zenn.dev/isi00141/articles/c8c883f7e33647

ただ、この記事の状態だとXcodeにコミットした内容とチケットとを紐付ける、というようなことができないので、いくつか変更をしました。

続く。

脚注
  1. いまどきのチケット管理とか懸案管理とかトラッキングシステムってどんなのがあるのかな?と調べたら、10年(下手したら15年?)ぐらい前とそんなに状況が変わってなくてびっくりしました。 ↩︎

kabeyakabeya

GitHubとかにリポジトリを用意していると別のやり方があるのかも知れませんが、redmine標準ではredmineのインストールされているサーバと同じサーバにあるgitのbareリポジトリが連携対象となります。

個人開発なので、GitHubで毎回どうのこうのというのも一手間ありそうです。[1]

dockerのredmineとローカルのgitリポジトリを連携させる

連携させるために必要なことは、以下の通りになります。

  • docker内からローカルのフォルダ/ファイルを見えるようにする
  • ローカルにbareリポジトリを作る
  • docker内のredmineから、ローカルのbareリポジトリを参照する
  • Xcodeからコミットしたらbareリポジトリにそれが入るようにする

docker内からローカルのフォルダ/ファイルを見えるようにする

(Docker Desktop for Macのバージョン:4.20.1)
Docker Desktop for Macのデフォルトは、Mac側の/Users, /Volumes, /private, /tmp, /var/foldersを、docker側から見えるようにすることができるようになっています。
追加する場合は、ウィンドウのタイトルバーの右端にあるギヤマーク→Resources→File shareingで「/path/to/exported/directory」の部分にMac側のパスを入力→Enterで追加できます。
私の場合、/opt/dockerというフォルダをMac側に作り、それを追加しました。
/opt/dockerのパーミッションやオーナーなどにも気をつけてください。

そのうえで

https://zenn.dev/isi00141/articles/c8c883f7e33647

の記事のdocker-compose.ymlの以下の部分を

    volumes:
      - vol_redmine:/usr/src/redmine/files
    volumes:
      - /opt/docker/redmine/files:/usr/src/redmine/files
      - /opt/docker/redmine/plugins:/usr/src/redmine/plugins
      - /opt/docker/redmine/themes:/usr/src/redmine/public/themes
      - /opt/docker/gitrepos:/opt/gitrepos

のように書き換えます。(/opt/docker…の部分は自分の環境に合わせてください。事前にフォルダは作っておく必要があります)
書き換えたら、再度、docker-compose upします。

これでMac側の/opt/docker…の部分が、docker内のVMから見えるようになります。

ローカルにbareリポジトリを作る

Xcodeでgitのリポジトリを作ると、プロジェクトのディレクトリ内に.gitというnon-bareのリポジトリが作成されます。

bare/non-bareについては、以下の記事を参考にさせていただきました。

https://www.nekotricolor.com/entry/theory-of-bare-and-non-bare-repository-manage-wordpress-themes-with-git

個人開発なので、本当はローカルコミットだけあれば充分なのですが、redmineで見るには

  • bareなリポジトリである必要がある
  • dockerから見えるところにある必要がある

という2点の理由からbareなリポジトリをもう1つ作ることにします。
今回はXcodeで作ったgitのリポジトリから、bareなリポジトリを以下のようにして作りました。
プロジェクトを始める前なら、もう少し違う手順もできます。

% mkdir /opt/docker/gitrepos/プロジェクト名
% cd /opt/docker/gitrepos/プロジェクト名
% git clone --bare /Users/…Xcodeで作ったプロジェクトフォルダのパス/.git

これで/opt/docker/gitrepos/プロジェクト名/プロジェクト名.gitができると思います。

ただこれだけだと、/opt/docker/gitrepos→Xcodeの.gitの関係があるものの逆がないので、Xcodeの.gitから/opt/docker/gitreposにプッシュできません。

なので、いったんXcodeのプロジェクトフォルダをリネームしてどっかによけておいて、

% cd //Users/…Xcodeで作ったプロジェクトフォルダの1個上のフォルダのパス
% git clone /opt/docker/gitrepos/プロジェクト名/プロジェクト名.git

で、bareなリポジトリから再度、non-bareのリポジトリ(作業フォルダ)を作成します。

これで、Xcodeからコミット→プッシュで記録されるbareリポジトリができました。

docker内のredmineから、ローカルのbareリポジトリを参照する

docker内のredmineのプロジェクト→リポジトリ→新しいリポジトリ、でバージョン管理システム=Gitにして、リポジトリのパスに「docker側から見えるbareリポジトリのパス」を書きます。
最初、Mac側のパスを書いてはまりました。
そのパスがdocker側からのパスと違う場合、redmineで「リポジトリに、エントリ/リビジョンが存在しません」というメッセージが表示され、ログには「not a git repository」と出力されます。
アクセス権が足りない場合もこのメッセージが出るようです。アクセス権かと思っていろいろ操作しましたが、なんのことはない、パスが間違っていました。

Xcodeからコミットしたらbareリポジトリにそれが入るようにする

個人開発なので、ローカルコミットだけで充分という話はしましたが、なのでプッシュしないとredmineに反映されないとすると面倒くさいのです。

Xcode側プロジェクトフォルダ内の.git/hooksにpost-commitというファイルを作り、chmod 755 post-commitをしておいて、中身を以下のようにします。

#! /bin/sh

exec git push > /dev/null 2> /dev/null
exit 0

これによりXcodeでコミットすると、bareなgitリポジトリに自動でプッシュされて、redmineからも見えるようになります。
2> /dev/nullの部分がないとXcodeのコミット時にエラーメッセージが出ます。

脚注
  1. これについては、今回使ったhooksを使えば別にたいしたことなさそう、という気になりました。 ↩︎

kabeyakabeya

redmineサーバのタイムゾーンがUTCになっていたので日付がずれました。
docker-compose.ymlのvolumesの部分を書き換えます。

    volumes:
      - /opt/docker/redmine/files:/usr/src/redmine/files
      - /opt/docker/redmine/plugins:/usr/src/redmine/plugins
      - /opt/docker/redmine/themes:/usr/src/redmine/public/themes
      - /opt/docker/gitrepos:/opt/gitrepos
      - /opt/docker/localtime:/etc/localtime

最後の行のように、/etc/localtimeを置き換えてやるとよさそうです。

% cd /opt/docker
% ln -s /etc/localtime

でホストの/etc/localtimeにリンクしておきます。
書き換えたらdocker-compose upを再度行います。

dbのほうも同様にします。

kabeyakabeya

作業時間の記録もしたいと思い立ちました。

何時何分に開始して、何時何分に終了して、なので何時間、みたいな記録の仕方ができないかと思って、開始日時・終了日時用のカスタムフィールドを追加しました。
で、システム全体の「設定」→「時間管理」→「作業時間に0時間を入力を許可」をONにしました。

作業開始時点で開始日時・終了日時のところに開始日時を打っておいて作業時間=0で記録しておいて、終了時点でこのレコードに終了日時も入れて、作業時間を(手で計算して)更新する、というような想定です。
(最終的にはこの作業時間も開始・終了日時から自動計算されるようにしたい)

ところが作業時間=0だと、作業時間のタブが表示されず、既存のレコードを更新できないという問題が発生しました。

app/helpers/issues_helper.rbのissue_history_tabsというメソッドが、合計時間が0より大きい場合にタブを表示、という判定を入れているせいだとは分かったのですが、これを上書きする方法が分からない。

とりあえず、自前のプラグイン(のひな形)をbundle exec rails generate redmine_plugin zero_time_tabというような感じで生成し、オリジナルのapp/helpers/issues_helper.rbをコピーしてzero_time_tab/app/helpersに入れたうえで、issue_history_tabsメソッドを上書きして「合計時間が0より大きい場合」の条件を外しました。

これをdockerから見えるpluginsフォルダに入れてredmineを再起動したら表示されるようになりました。

本当は必要なメソッドだけ上書きできると良いと思うのですがrails知識がなく、よく分かりませんでした。

issues_helper.rbのissue_history_tabs以外のメソッドを抜くとエラーになるし(たぶんモジュール全体を上書きしているせいだと思いますが)、といって自前のModuleとするとカスタムissue_history_tabsが呼ばれないし…というようなところです。

kabeyakabeya

最終的にはこの作業時間も開始・終了日時から自動計算されるようにしたい

hooks.rb
require "time"

module CalcTimeFromStartEnd
  class Hooks < Redmine::Hook::Listener
    def controller_timelog_edit_before_save(context)
      params = context[:params]
      time_entry = context[:time_entry]

      start_text_field = time_entry.custom_field_values.detect {|c| c.custom_field.name == "開始日時"}
      end_text_field = time_entry.custom_field_values.detect {|c| c.custom_field.name == "終了日時"}
      return if start_text_field.nil? || end_text_field.nil?
      return if start_text_field.value == "" || end_text_field.value == ""
      start_time = Time.parse(start_text_field.value)
      end_time = Time.parse(end_text_field.value)
      seconds = end_time - start_time
      time_entry.hours = seconds / 3600
    end
  end
end

こんな感じのフックを作って、保存時に自動計算されるようになりました。
(自分で使うだけなのでエラー処理とかもないのですが)

あと、作業時間記録画面の「時間」に初期値0が入るようにしないと…。

kabeyakabeya

ところが作業時間=0だと、作業時間のタブが表示されず、既存のレコードを更新できないという問題が発生しました。

これもプラグイン化しました。
誰かの役に立つかも知れないので置いておきました。

https://github.com/takenori-kabeya/ZeroTimeTab

あと、作業時間記録画面の「時間」に初期値0が入るようにしないと…。

同じくこれもプラグイン化しました。
「開始日時」カスタムフィールドがあれば、そこに現在時刻が初期設定されるようにもしました。

https://github.com/takenori-kabeya/InitialZeroHour

こんなに分ける必要あるのかという気もしますが…。

3つ使うと、なかなか良い感じです。

kabeyakabeya

macOS Sonoma 14.0にあげたら、docker内のVMの時刻が日本時間でなくなってしまいました。
そのうえ、docker-desktopを最新版(v4.24.0)にしたら、VMが起動失敗してしまうようになりました。

エラーメッセージは以下のようなものが出ています。

macOS側の/private/var/db/timezone/tz/に、2023c.1.0というディレクトリを作ろうとして失敗しているのだろうと思います。なぜ作ろうとしてるのかは分かりませんが。

手元にmacOS 13の環境はもうなくて、macOS 11の環境しかないのですが、こちらには/private/var/db/timezone/tz/2023c.1.0というディレクトリがあります。dockerは入ってないのでmacOS自体が用意しているものだと思います。

これがmacOS 14になってなくなった、ということなんでしょうか。
ちなみにsudo mkdir /private/var/db/timezone/tz/2023c.1.0もoperation not permittedで失敗しました。

いったん、docker-compose.ymlの以下のlocaltimeの部分をコメントアウトしました。

- /opt/docker/localtime:/etc/localtime
↓
# - /opt/docker/localtime:/etc/localtime

コメントアウト後にdocker-compose up -dを実行すると、起動するようになりました。

ただしタイムゾーンはずれたままなので、どうするか再調査・再検討です。


追記

ひとまず、docker-compose.ymlのenvironmentにTZを追加すると、VM(OS?)によってタイムゾーンが変えられる、ということだったので試したところ、今回のVMはうまく変わりました。

    environment:
      TZ: Asia/Tokyo

上記のTZ行を各VMのenvironment節に追加し、修正後にdocker-compose up -dを実行します。