bitnami でインストールした Redmine を Docker に移設した

bitnami でインストールした Redmine 2.6.0 を 3.3.3 にアップグレードしたものがあり、これを別サーバーの Docker に移設した記録。

2.x 系から 3.x 系へのアップグレードは別途手順が存在するため、別の記事を参照して対応して欲しい。

## バックアップ
```bash
grep -v '#' /opt/redmine-2.6.0-3/apps/redmine/htdocs/config/database.yml
ssh old_redmine "mysqldump --socket=/opt/redmine-2.6.0-3/mysql/tmp/mysql.sock -u bitnami -p bitnami_redmine | gzip > ~/redmine_`date +%F`.sql.gz" && \
ssh old_redmine "cd /opt/redmine-2.6.0-3/apps/redmine/htdocs && tar cvf ~/redmine_`date +%F`.tar.gz {config,files,public/themes,plugins}"
```
Redmine のルートディレクトリは `/opt/redmine-2.6.0-3/apps/redmine/htdocs` になる。
データベースの接続情報はルートディレクトリ配下の `config/database.yml` に記載されている。

Bitnami でインストールした Redmine 2.6.0 の MySQL Server は `/opt/redmine-2.6.0-3/mysql/tmp/` 配下にあるソケットを指定する必要がある。


ここでは Bitnami でインストールした Redmine のあるサーバーは `ssh old_redmine` で接続できることとする。

## バックアップの転送
```bash
scp old_redmine:./redmine_`date +%F`.sql.gz new_redmine:./ && \
scp old_redmine:./redmine_`date +%F`.tar.gz new_redmine:./ && \
ssh new_redmine
```
`scp` コマンド[^scp]でバックアップファイルを旧サーバーから新サーバーへコピーする。

Docker に移設する新しいサーバーは `ssh new_redmine` で接続できることとする。

[^scp]: [SCP (1) - OpenSSH 日本語マニュアルページ](https://euske.github.io/openssh-jman/scp.html)

## Docker のインストール
```bash
echo 'LC_ALL="ja_JP.utf-8"' >> /etc/environment && \
yum --assumeyes install epel-release && \
yum --assumeyes update && \
yum --assumeyes install yum-{axelget,plugin-rpm-warm-cache,utils} && \
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && \
yum --assumeyes install docker-ce && \
systemctl start docker && \
systemctl enable $_ && \
curl -L $(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep 'Linux-x86_64"' | grep url | cut -d'"' -f4) -o /usr/local/bin/docker-compose && \
chmod +x $_
```
新サーバーは CentOS 7.5 を使用。

`yum-axelget` および `yum-plugin-rpm-warm-cache` パッケージを利用するために `epel-release` パッケージを先にインストールして EPEL リポジトリを追加しているが、これらは必須ではない。

Docker は標準リポジトリではバージョンが古いため公式リポジトリを導入。

## ファイルの保存領域の作成
```bash
mkdir -p /var/www/redmine && \
cd $_ && \
semanage fcontext -a -t container_file_t "$_(/.*)?" && \
restorecon -Rv /var/www/redmine
```
docker-compose.yml ファイルの配置、および設定ファイル、アップロード（添付）ファイル、テーマ、プラグインなどの永続化のために `/var/www/redmine` を作成。[FHS](http://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s17.html) 的には `/srv/docker/redmine` などでも良いかもしれない。

コンテキストのラベルは CentOS 7.4 までは `svirt_sandbox_file_t` だったが CentOS 7.5 以降は `container_file_t` と指定する必要がある。

## Docker での Redmine のセットアップ
```yaml
cat << "_EOF_" > docker-compose.yml
version: '3.5'
services:
  redmine:
    image: redmine:3.3.3-passenger
    container_name: redmine
    restart: always
    ports:
      - 80:3000
    environment:
      TZ: ${TZ}
      REDMINE_DB_MYSQL: mysql
      REDMINE_DB_DATABASE: ${DB_DATABASE}
      REDMINE_DB_USERNAME: ${DB_USERNAME}
      REDMINE_DB_PASSWORD: ${DB_PASSWORD}
      REDMINE_DB_ENCODING: utf8mb4
    depends_on:
      - mysql
      - memcached
    volumes:
#      - /var/www/redmine/config:/usr/src/redmine/config
      - /var/www/redmine/files:/usr/src/redmine/files
      - /var/www/redmine/log:/usr/src/redmine/log
      - /var/www/redmine/plugins:/usr/src/redmine/plugins
      - /var/www/redmine/public/themes:/usr/src/redmine/public/themes
  mysql:
    image: mysql:5.7
    container_name: mysql
    restart: always
    environment:
      TZ: ${TZ}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    ports:
      - 3306:3306
    volumes:
      - mysql-data:/var/lib/mysql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
  memcached:
    image: memcached
    container_name: memcached
    restart: always
#    ports:
#      - 11211:11211
volumes:
  mysql-data:
    name: mysql-redmine
_EOF_
```
```yaml
cat << "_EOF_" > .env
TZ=Asia/Tokyo
MYSQL_ROOT_PASSWORD=P@ssw0rd
DB_DATABASE=redmine
DB_USERNAME=redmine
DB_PASSWORD=redmine
_EOF_
```
Docker Compose で一括管理するため docker-compose.yml ファイルを作成。
詳細は「[Redmine を Docker 公式イメージで運用する - Qiita](https://qiita.com/bezeklik/items/b5c39136a8db23e2e81c)」を参照。

```bash
time docker-compose up --detach && \
firewall-cmd --permanent --add-service=http{,s} && \
firewall-cmd --reload && \
sleep 20 && \
docker container logs --follow redmine
```
ホスト OS 側の 80 番および 443 番ポートを空ける。
また mysql コンテナ内の MySQL Server が起動するまで 20 秒ほど待機。
その後 redmine コンテナのログをフォローして進捗状況を確認。
`Completed 200` が出力されたら <kbd>Ctrl</kbd> + <kbd>c</kbd> でフォローを中断。

## Memcached
```bash
docker exec redmine sh -c "echo 'config.cache_store = :mem_cache_store, \"memcached\"' > config/additional_environment.rb" && \
docker exec redmine sh -c "echo \"gem 'dalli'\" > Gemfile.local" && \
docker exec redmine bundle install && \
docker exec redmine passenger-config restart-app /usr/src/redmine
```
`config/additional_environment.rb` ファイルに設定を追加することで、セッション管理を [Memcached](https://memcached.org/) で行うことが可能。

## DB 接続認証の省略
```bash
docker exec --interactive --tty mysql mysql_config_editor set --host=localhost --user=redmine --password
```
テスト環境以外で設定しない。

`mysql` コンテナを再起動すると設定は消える。

## バックアップのリストア
```bash
zcat ~/redmine_`date +%F`.sql.gz | docker exec -i mysql mysql redmine && \
cd /var/www/redmine && \
tar xf ~/redmine_`date +%F`.tar.gz && \
docker exec redmine passenger-config restart-app /usr/src/redmine
```
`redmine` コンテナで `passenger-config restart-app` コマンドを実行して [Passenger](https://www.phusionpassenger.com/) を再起動。

## バージョンアップ
Redmine 3.3.3 から直接 3.4.6 にアップデートしても表面上は問題なかったが、念のために 3.3.x 系の最新版にアップデートした後に 3.4.x 系の最新版にアップデートした方が無難だろう。

### 3.3.3 → 3.3 (3.3.8)
```bash
sed -i 's/3.3.3/3.3/' docker-compose.yml && \
docker-compose down && \
docker-compose up --detach --force-recreate && \
docker exec redmine bundle install --jobs=4 --without development test && \
docker exec redmine bundle exec rake db:migrate RAILS_ENV=production && \
docker exec redmine bundle exec rake redmine:plugins:migrate RAILS_ENV=production && \
docker exec redmine bundle exec rake tmp:cache:clear tmp:sessions:clear RAILS_ENV=production && \
docker exec redmine passenger-config restart-app /usr/src/redmine
```

### 3.3 (3.3.8) → 3.4 (3.4.6)
```bash
sed -i 's/3.3/3.4/' docker-compose.yml && \
docker-compose down && \
docker-compose up --detach --force-recreate && \
docker exec redmine bundle install --jobs=4 --without development test && \
docker exec redmine bundle exec rake db:migrate RAILS_ENV=production && \
docker exec redmine bundle exec rake redmine:plugins:migrate RAILS_ENV=production && \
docker exec redmine bundle exec rake tmp:cache:clear tmp:sessions:clear RAILS_ENV=production && \
docker exec redmine passenger-config restart-app /usr/src/redmine
```

## 「非メンバー」と「匿名ユーザー」の権限を剥奪
```bash
cat << '_EOQ_' | docker exec -i mysql mysql redmine
UPDATE `roles` SET `permissions` = NULL WHERE `id` = '1' OR `id` = '2';
_EOQ_
```
念のため SQL 文を実行して「非メンバー」と「匿名ユーザー」の権限を剥奪しておく。

## メール設定
```bash
cat << '_EOF_' | docker exec -i redmine bash -c "cat > /usr/src/redmine/config/configuration.yml" && \
docker exec redmine passenger-config restart-app /usr/src/redmine && \
sed -i /etc/postfix/main.cf \
 -e '/mynetworks = hash/a mynetworks = 127.0.0.0/8, 172.18.0.0/16' \
 -e '/^inet_interfaces/ s/localhost/localhost, 127.0.0.1, 172.18.0.1/' && \
systemctl restart postfix && \
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="172.18.0.0/16" service name="smtp" accept' && \
firewall-cmd --reload
default:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: 172.18.0.1
      port: 25
      domain: redmine.example.com
_EOF_
```
ホスト OS の Postfix を利用する設定。
上記手順は `redmine` コンテナ内に `configuration.yml` を作成するため、コンテナを再起動すると設定が消えるので注意。

## テーマ
```bash
yum --assumeyes install git unzip && \
themes=/var/www/redmine/public/themes && \
token=0c7a1fefdd47f3a96fb57a4f94450dbdf98dd && \
curl -L https://www.redmineup.com/license_manager/40766?token=${token} -o ~/circle_theme.zip && unzip $_ -d ${themes}/ && \
curl -L https://www.redmineup.com/license_manager/40830?token=${token} -o ~/a1_theme.zip && unzip $_ -d ${themes}/ && \
git clone --depth 1 https://github.com/akabekobeko/redmine-theme-minimalflat2 ${themes}/minimalflat2 && cd $_ && rm -fr * && git checkout origin/master -- src && mv src/* . && rmdir -p src && cd - && \
git clone --depth 1 https://github.com/themondays/Dwarf ${themes}/dwarf && cd $_ && rm -fr * && git checkout origin/master -- production/dwarf && mv production/dwarf/* . && rmdir -p production/dwarf && cd - && \
git clone --depth 1 https://github.com/tsi/redmine-theme-flat ${themes}/flat && \
git clone --depth 1 https://github.com/hardpixel/minelab ${themes}/minelab && \
git clone --depth 1 https://github.com/lqez/redmine-theme-basecamp-with-icon ${themes}/basecamp-with-icon && \
git clone --depth 1 https://github.com/makotokw/redmine-theme-gitmike ${themes}/gitmike && \
git clone --depth 1 https://github.com/farend/redmine_theme_farend_basic ${themes}/farend_basic && \
git clone --depth 1 https://github.com/farend/redmine_theme_farend_fancy ${themes}/farend_fancy && \
git clone --depth 1 https://github.com/Nitrino/flatly_light_redmine ${themes}/flatly_light && \
git clone --depth 1 https://github.com/mrliptontea/PurpleMine2 ${themes}/purplemine2 && \
git clone --depth 1 https://github.com/Froiden/fedmine ${themes}/fedmine && \
git clone --depth 1 https://github.com/vsc55/redmine-theme ${themes}/pixel-cookers && \
git clone --depth 1 https://github.com/FabriceSalvaire/redmine-improved-theme ${themes}/sass_theme && \
chown -R polkitd:input -R ${themes}
```
`token` は各自で取得して差し替えること。

ディレクトリが空でないと途中で中断されるので、必要に応じて `rm -fr /var/www/redmine/public/themes/*` を実行する。

## プラグイン
```bash
plugins=/var/www/redmine/plugins && \
token=0c7a1fefdd47f3a96fb57a4f94450dbdf98dd && \
curl -L https://www.redmineup.com/license_manager/25768?token=${token} -o ~/redmine_questions-0_0_7-light.zip && unzip $_ -d ${plugins}/ && \
curl -L https://www.redmineup.com/license_manager/30308?token=${token} -o ~/redmine_favorite_projects-2_0_3-light.zip && unzip $_ -d ${plugins}/ && \
curl -L https://www.redmineup.com/license_manager/48678?token=${token} -o ~/redmine_agile-1_4_6-light.zip && unzip $_ -d ${plugins}/ && \
curl -L https://www.redmineup.com/license_manager/48735?token=${token} -o ~/redmineup_tags-2_0_2-light.zip && unzip $_ -d ${plugins}/ && \
curl -L https://www.redmineup.com/license_manager/42723?token=${token} -o ~/redmine_people-1_3_2-light.zip && unzip $_ -d ${plugins}/ && \
curl -L https://www.redmineup.com/license_manager/48844?token=${token} -o ~/redmine_checklists-3_1_11-light.zip && unzip $_ -d ${plugins}/ && \
curl -L https://www.redmineup.com/license_manager/47729?token=${token} -o ~/redmine_crm-4_1_2-light.zip && unzip $_ -d ${plugins}/ && \
git clone --depth 1 https://github.com/syagawa/redmine_absolute_datetime ${plugins}/redmine_absolute_datetime && \
git clone --depth 1 https://github.com/haru/redmine_wiki_extensions ${plugins}/redmine_wiki_extensions && \
#git clone --depth 1 https://github.com/AlphaNodes/redmine_tweaks ${plugins}/redmine_tweaks && \
git clone --depth 1 https://github.com/cdwertmann/line_numbers ${plugins}/line_numbers && \
git clone --depth 1 https://github.com/suer/redmine_japanese_help ${plugins}/redmine_japanese_help && \
git clone --depth 1 https://github.com/onozaty/redmine-view-customize ${plugins}/view_customize && \
git clone --depth 1 https://github.com/peclik/clipboard_image_paste ${plugins}/clipboard_image_paste && \
git clone --depth 1 https://github.com/akiko-pusu/redmine_issue_templates ${plugins}/redmine_issue_templates && \
git clone --depth 1 https://github.com/akiko-pusu/redmine_issue_badge ${plugins}/redmine_issue_badge && \
git clone --depth 1 https://github.com/akiko-pusu/redmine_banner ${plugins}/redmine_banner && \
git clone --depth 1 https://github.com/tleish/redmine_editor_preview_tab ${plugins}/redmine_editor_preview_tab && \
git clone --depth 1 https://github.com/stpl/redmine_issue_history ${plugins}/redmine_issue_history && \
git clone --depth 1 https://github.com/haru/redmine_theme_changer ${plugins}/redmine_theme_changer && \
git clone --depth 1 --branch redmine-3.4-support https://github.com/codeout/redmine_already_read ${plugins}/redmine_already_read && \
docker exec -i redmine /bin/bash -c "chown -R redmine. /usr/src/redmine/{plugins,public/plugin_assets}" && \
docker exec redmine bundle exec rake redmine:plugins:migrate RAILS_ENV=production && \
docker exec redmine bundle exec rake tmp:cache:clear tmp:sessions:clear RAILS_ENV=production && \
docker exec redmine passenger-config restart-app /usr/src/redmine
```
`token` は各自で取得して差し替えること。

## 自動バックアップ設定
```ini
cat << '_EOF_' > /etc/systemd/system/backup-redmine-db.service
[Unit]
Description=Backup Redmine database

[Service]
Type=oneshot
ExecStart=/bin/bash -c "docker exec mysql mysqldump redmine | gzip > /var/www/redmine/backup/redmine_db_`date +%F`.sql.gz" && \
 find /var/www/redmine/backup -name redmine_db_*.tar.gz -mtime +14 | xargs rm
_EOF_
```
```ini
cat << '_EOF_' > /etc/systemd/system/backup-redmine-db.timer
[Unit]
Description=Backup Redmine database

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
_EOF_
```
```ini
cat << '_EOF_' > /etc/systemd/system/backup-redmine-files.service
[Unit]
Description=Backup Redmine files

[Service]
Type=oneshot
ExecStart=/bin/bash -c "cd /var/www/redmine && tar -cf backup/redmine_files_`date +%F`.tar.gz files" && \
 find /var/www/redmine/backup -name redmine_files_*.tar.gz -mtime +14 | xargs rm
_EOF_
```
```ini
cat << '_EOF_' > /etc/systemd/system/backup-redmine-files.timer
[Unit]
Description=Backup Redmine files

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
_EOF_
```
```bash
mkdir /var/www/redmine/backup && \
systemctl daemon-reload && \
systemctl enable backup-redmine-{db,files}.timer
```