#author("2020-10-12T16:28:55+09:00","default:jomura","jomura")
#author("2020-12-17T09:44:36+09:00","default:jomura","jomura")
#contents

#br

* Redmineの導入 [#wa722c9e]

 Redmineについては、下記3種の導入方法を検討した。
+ 公式サイトの本体コード一式 + RHELのRPM + gemコマンドによるライブラリ導入
-- 各々のライブラリ依存性管理が非常に大変。特にnokogiriとopensslが鬼門。
+ bitnami-redmineを用いた一括導入
-- Git, Redmine, PhpMyAdminなども導入されるが、今回は使わない
-- Redmine公式ではない
+ 公式dockerイメージを用いた導入
-- dockerを意識して維持管理する手間が生じる
-- ライブラリ間の依存性に悩む必要がなくなる
-- cf. https://qiita.com/bezeklik/items/b9d75ee74e0ae4c6d42c

 今回は「公式dockerイメージを用いた導入」を採用する。

** 導入サーバの前提条件 [#la5e25ea]
+ OSはRHEL7(or CentOS7)とする。RHEL8にはdockerイメージが対応していない?
+ OSはRHEL7(or CentOS7)かそれ以降の版とする。
+ 事前導入するソフトウェアパッケージは最小構成。
+ インターネットに接続可能   ※必要ならproxy設定
 /etc/yum.conf
 /etc/dnf/dnf.conf
 proxy=http://proxy.jomura.net:8080/     #as your own
 ~/.bashrc
 export HTTP_PROXY=http://proxy.jomura.net:8080/     #as your own
 export HTTPS_PROXY=${HTTP_PROXY}
+ パッケージの更新が事前に実行されている
 yum clean all && yum -y update && reboot
 dnf clean all && dnf -y update && reboot
+ Redmine Webサイト用のFully Qualified Domain Name(FQDN)が用意されている。

** Redmine用 configuration.ymlの作成 [#z6fa156d]
- Ansibleサーバ上の一般ユーザで実行
- SMTP情報は as your own で
 cat << "_EOF_" > configuration.yml
 default:
   email_delivery:
     delivery_method: :smtp
     smtp_settings:
 #      tls: true
       enable_starttls_auto: true
       address: "smtp.gmail.com"
       port: 587
       domain: "jomura.net"
       authentication: :login
       user_name: "user@jomura.net"
       password: "********"
 _EOF_

** Redmine初期設定用SQLの作成 [#t656e6ad]
- Ansibleサーバ上の一般ユーザで実行
 cat << "_EOF_" > settings.sql
  INSERT INTO `settings` (`name`, `value`) VALUES
  ('search_results_per_page','30'), -- ページごとの検索結果表示件数 (10)
  ('host_name','cxits.tg-group.tokyo-gas.co.jp'), -- ホスト名とパス (localhost:3000)
  ('protocol','http'), -- プロトコル (http)
  ('text_formatting','textile'), -- テキスト書式 (textile)
  ('default_language','ja'), -- デフォルトの言語 (en)
  ('force_default_language_for_anonymous','0'), -- 匿名ユーザーにデフォルトの言語を強制 (0)
  ('force_default_language_for_loggedin','0'), -- ログインユーザーにデフォルトの言語を強制 (0)
  ('user_format','lastname_firstname'), -- ユーザー名の表示形式 (firstname_lastname)
  ('thumbnails_enabled','1'), -- 添付ファイルのサムネイル画像を表示 (0)
  ('login_required','1'), -- 認証が必要 (0)
  ('autologin','7'), -- 自動ログイン (0)
  ('max_additional_emails','3'), -- 追加メールアドレス数の上限 (5)
  ('session_lifetime','86400'), -- 有効期間の最大値 (0)
  ('session_timeout','240'), -- 無操作タイムアウト (0)
  ('default_users_time_zone','Tokyo'), -- タイムゾーン ()
  ('rest_api_enabled','1'), -- RESTによるWebサービスを有効にする (0)
  ('jsonp_enabled','1'), -- JSONPを有効にする (0)
  ('default_projects_public','0'), -- デフォルトで新しいプロジェクトは公開にする (1)
  ('default_projects_modules','---
  - issue_tracking
  - time_tracking
  - wiki
  - repository
  - calendar
  - gantt
  '), -- 新規プロジェクトにおいてデフォルトで有効になるモジュールチケットトラッキング
  ('default_projects_tracker_ids','---
  - \'1\'
  - \'2\'
  - \'3\'
  '), -- 新規プロジェクトにおいてデフォルトで有効になるトラッカー
  ('cross_project_issue_relations','1'), -- 異なるプロジェクトのチケット間で関連の設定を許可 (0)
  ('default_issue_start_date_to_creation_date','1'), -- 現在の日付を新しいチケットの開始日とする (1)
  ('issue_done_ratio','issue_status'), -- 進捗率の算出方法 (issue_field)
  ('issue_list_default_totals','---
  - estimated_hours
  - spent_hours
  '), -- チケットの一覧で表示する項目(合計)
  ('attachment_max_size','51200'), -- 添付ファイルサイズの上限 (5120)
  ('repositories_encodings','utf-8,cp932,euc-jp'), -- 添付ファイルとリポジトリのエンコーディング ()
  ('mail_from','user@jomura.net'), -- 送信元メールアドレス (redmine@example.net)
  ('enabled_scm','---
  - Subversion
  - Git
  '), -- 使用するバージョン管理システム
  ('commit_ref_keywords','refs,references,IssueID,*'), -- 参照用キーワード (refs,references,IssueID)
  ('commit_cross_project_ref','1'); -- 異なるプロジェクトのチケットの参照/修正を許可 (0)
 _EOF_

** docker-compose.ymlファイルの作成 [#z4d0976f]
- Ansibleサーバ上の一般ユーザで実行
 cat << "_EOF_" > docker-compose.yml
  version: '3.7'
  services:
    redmine:
      container_name: ${REDMINE:-redmine}
      image: redmine:4-passenger
      restart: always
      ports:
        - 3000:3000
      depends_on:
        - ${REDMINE_DB_MYSQL:-mysql}
        - ${REDMINE_MEMCACHED:-memcached}
      environment:
        TZ: ${TZ}
        REDMINE_DB_MYSQL: ${REDMINE_DB_MYSQL:-mysql}
        REDMINE_DB_DATABASE: ${REDMINE_DB_DATABASE}
        REDMINE_DB_USERNAME: ${REDMINE_DB_USERNAME}
        REDMINE_DB_PASSWORD: ${REDMINE_DB_PASSWORD}
        REDMINE_DB_ENCODING: ${REDMINE_DB_ENCODING:-utf8mb4}
 {% if proxy_env.http_proxy is defined %}
        HTTP_PROXY: {{ proxy_env.http_proxy }}
        HTTPS_PROXY: {{ proxy_env.https_proxy }}
 {% endif %}
      volumes:
        - ${REDMINE_PATH:-.}/config/additional_environment.rb:/usr/src/redmine/config/additional_environment.rb
        - ${REDMINE_PATH:-.}/config/configuration.yml:/usr/src/redmine/config/configuration.yml
        - ${REDMINE_PATH:-.}/Gemfile.local:/usr/src/redmine/Gemfile.local
        - ${REDMINE_PATH:-.}/files:/usr/src/redmine/files:z
        - ${REDMINE_PATH:-.}/log:/usr/src/redmine/log:Z
        - ${REDMINE_PATH:-.}/plugins:/usr/src/redmine/plugins
        - ${REDMINE_PATH:-.}/public/themes:/usr/src/redmine/public/themes
        - /var/lib/svn:/var/lib/svn:z
        - /var/lib/git:/var/lib/git:z
    mysql:
      container_name: ${REDMINE_DB_MYSQL:-mysql}
      image: mysql:8
      command: --default-authentication-plugin=mysql_native_password
      restart: always
      ports:
        - 3306:3306
      environment:
        TZ: ${TZ}
        MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
        MYSQL_DATABASE: ${REDMINE_DB_DATABASE}
        MYSQL_USER: ${REDMINE_DB_USERNAME}
        MYSQL_PASSWORD: ${REDMINE_DB_PASSWORD}
      volumes:
        - mysql-data:/var/lib/mysql
        - ${REDMINE_PATH:-.}/../mysql/conf.d/redmine.cnf:/etc/mysql/conf.d/redmine.cnf
    memcached:
      container_name: ${REDMINE_MEMCACHED:-memcached}
      image: memcached
      restart: always
  volumes:
    mysql-data:
      name: mysql-data
 _EOF_

** docker用環境変数ファイルの作成 [#mef56985]
- Ansibleサーバ上の一般ユーザで実行
 cat << _EOF_ > docker-env
 REDMINE_PATH={{ redmine_path }}
 TZ=Asia/Tokyo
 MYSQL_ROOT_PASSWORD=$(< /dev/urandom tr -dc 'A-Za-z0-9!$%&()*+,-./:;<=>?@[\]^_{|}~' | head -c 16; echo)
 REDMINE_DB_MYSQL=mysql
 REDMINE_DB_DATABASE=redmine
 REDMINE_DB_USERNAME=redmine
 REDMINE_DB_PASSWORD=$(< /dev/urandom tr -dc 'A-Za-z0-9!$%&()*+,-./:;<=>?@[\]^_{|}~' | head -c 16; echo)
 REDMINE_DB_ENCODING=utf8mb4
 REDMINE_MEMCACHED=memcached
 _EOF_
 chmod go-rwx docker-env
- catに指定する _EOF_ を"でくくってはいけない。urandamコマンド等が実行されなくなる。

** playbookの作成 [#y22e4e40]
- Ansibleサーバ上の一般ユーザで実行
- sudoの場合、become_method: sudo
 cat << "_EOF_" > pb_redmine_server-1.yml
 cat << "_EOF_" > pb_redmine_server-redmine.yml
 # install redmine
 
 - hosts: redmine_servers
   become: true
   become_method: su
   environment: "{{ proxy_env }}"
 
   vars:
     redmine_path: /srv/redmine
 
   tasks:
 
     - name: check facts
       fail:
         msg: "Not compatible with [{{ ansible_os_family }}] \
           {{ ansible_distribution }} \
           {{ ansible_distribution_major_version }}."
       when: >
         ansible_os_family != 'RedHat'
         or ansible_distribution_major_version|int != 7
         or ansible_distribution_major_version|int < 7
 
     - name: add docker repo
       shell: dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
       args:
         warn: false
       changed_when: false
 
     - name: install RPMs
       yum:
       dnf:
         name:
           - yum-utils
           - httpd
           - docker-ce
         state: latest
 
     - name: install docker
       shell: yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
       changed_when: False
     - yum: name=docker-ce state=latest
       notify:
         - restart Docker
 
     - name: set proxy for docker
       file: path=/etc/systemd/system/docker.service.d state=directory
       when: proxy_env.http_proxy is defined
     - copy:
         dest: /etc/systemd/system/docker.service.d/http-proxy.conf
         force: no
         content: "[Service]\n \
           Environment = \"http_proxy={{ proxy_env.http_proxy }}\" \
           \"https_proxy={{ proxy_env.https_proxy }}\"\n"
       when: proxy_env.http_proxy is defined
 
     - meta: flush_handlers
     - name: restart Docker
       systemd:
         name: docker
         state: restarted
         daemon_reload: yes
         enabled: yes
 
     - name: install docker-compose
       stat: path=/usr/local/bin/docker-compose
       register: result01
     - shell: |
         curl --location --output /usr/local/bin/docker-compose \
           $(curl --silent --show-error \
             https://api.github.com/repos/docker/compose/releases/latest \
             | grep 'Linux-x86_64"' \
             | grep url \
             | cut --delimiter='"' --fields=4 \
            )
         chmod +x /usr/local/bin/docker-compose
       args:
         warn: false
       when: result01.stat.exists == false
 
     - name: "create {{ redmine_path }}"
       file: path={{ redmine_path }} state=directory
       register: result02
     - shell: |
         /usr/sbin/matchpathcon {{ redmine_path }}
         /usr/sbin/semanage fcontext --add --type container_file_t {{ redmine_path }}
         /usr/sbin/restorecon -v {{ redmine_path }}
         /usr/sbin/matchpathcon {{ redmine_path }}
       args:
         warn: false
       when: result02.changed == true
 
     - name: create config files
       file: path={{ redmine_path }}/config state=directory
     - copy:
         src: configuration.yml
         dest: "{{ redmine_path }}/config/configuration.yml"
         force: no
     - copy:
         dest: "{{ redmine_path }}/config/additional_environment.rb"
         force: no
         content: |
           config.cache_store = :mem_cache_store, "memcached"
           config.logger = Logger.new("#{Rails.root}/log/#{ENV['RAILS_ENV']}.log", 50, 1000000)
           config.logger.level = Logger::INFO
     - copy:
         dest: "{{ redmine_path }}/Gemfile.local"
         force: no
         content: "gem 'dalli'\n "
     - file: path={{ redmine_path }}/../mysql/conf.d state=directory
     - copy:
         dest: "{{ redmine_path }}/../mysql/conf.d/redmine.cnf"
         force: no
         content: |
           [mysqld]
           innodb_buffer_pool_size = 536870912
           innodb_log_file_size = 201326592
 
     - name: create a parent dir of svn-repos
       file:
         path: /var/lib/svn
         owner: nobody
         group: users
         state: directory
         mode: 02775
       register: result03
     - shell: |
         /usr/sbin/semanage fcontext -a -t git_rw_content_t "/var/lib/svn(/.*)?"
         /usr/sbin/restorecon -Rv /var/lib/svn
       args:
         warn: false
       when: result03.changed == true
 
     - name: create a parent dir of git-repos
       file:
         path: /var/lib/git
         owner: nobody
         group: users
         state: directory
         mode: 02775
       register: result03
     - shell: |
         /usr/sbin/semanage fcontext -a -t git_rw_content_t "/var/lib/git(/.*)?"
         /usr/sbin/restorecon -Rv /var/lib/git
       args:
         warn: false
       when: result03.changed == true
 
     - name: docker-compose up
       template:
         src: docker-compose.yml
         dest: "{{ redmine_path }}/docker-compose.yml"
         force: no
     - template:
         src: docker-env
         dest: "{{ redmine_path }}/.env"
         force: no
         mode: 0400
     - shell: docker-compose --project-directory {{ redmine_path }} up --detach
       args:
         chdir: "{{ redmine_path }}"
       register: result04
       changed_when: '" is up-to-date" not in result04.stderr'
     - name: wait for Completed 200 OK
       shell: docker container logs redmine 2>/dev/null | tail -15
       register: result05
       changed_when: false
       until: '"Completed 200 OK " in result05.stdout'
       retries: 100
       delay: 5
 
     - name: set db password file
       slurp:
         src: "{{ redmine_path }}/.env"
       register: result06
     - copy:
         dest: ~/.my.cnf.org
         force: no
         mode: 0400
         content: "[client]\nuser = redmine\n \
           password = {{ result06['content'] | b64decode | \
           regex_findall('REDMINE_DB_PASSWORD=(.+)\\n') | first }}\n \
           host = localhost\n"
       register: result07
     - shell: docker cp ~/.my.cnf.org mysql:root/.my.cnf
       when: result07.changed
 
     - name: load default data
       shell: docker exec {% if proxy_env.http_proxy is defined -%}
         -e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
         redmine bundle exec rake redmine:load_default_data RAILS_ENV=production REDMINE_LANG=ja
       register: result08
       changed_when: '" is already loaded." not in result08.stdout'
 
     - name: update roles
       shell: |
         cat << '_EOQ_' | docker exec -i mysql mysql redmine
         UPDATE `roles` SET `permissions` = NULL WHERE `id` = '1' OR `id` = '2';
         _EOQ_
       changed_when: false
 
     - name: insert settings
       shell: |
         cat << '_EOQ_' | docker exec -i mysql mysql redmine
         SELECT count(*) FROM `settings`
         _EOQ_
       changed_when: false
       register: result09
     - shell: |
         cat settings.sql | docker exec --interactive mysql mysql redmine && \
         docker exec redmine passenger-config restart-app /usr/src/redmine
       when: result09.stdout_lines[1] == "0"
 
     - name: clear rails cache
       shell: docker exec {% if proxy_env.http_proxy is defined -%}
         -e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
         redmine bundle exec rails runner 'Rails.cache.clear'
       changed_when: false
 
     - name: bundle install
       shell: docker exec {% if proxy_env.http_proxy is defined -%}
         -e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
         redmine bundle install
       changed_when: false
 
     - name: restart passenger
       shell: docker exec {% if proxy_env.http_proxy is defined -%}
         -e HTTP_PROXY={{ proxy_env.http_proxy }} -e HTTPS_PROXY={{ proxy_env.https_proxy }} {% endif -%}
         redmine passenger-config restart-app /usr/src/redmine
       changed_when: false
 
     - name: modify httpd.conf for redmine
       copy:
         dest: /etc/httpd/conf.d/proxy-redmine.conf
         force: no
         mode: 0644
         content: |
           <IfModule !proxy_module>
             LoadModule proxy_module modules/mod_proxy.so
           </IfModule>
           <IfModule !proxy_http_module>
             LoadModule proxy_http_module modules/mod_proxy_http.so
           </IfModule>
           ProxyPassMatch /git.* !
           ProxyPassMatch /svn.* !
           ProxyPassMatch /kibana.* !
           ProxyPass / http://localhost:3000/
           ProxyPassReverse / http://localhost:3000/
 
     - name: httpd_can_network_connect
       shell: /usr/sbin/getsebool httpd_can_network_connect
       register: result10
     - shell: /usr/sbin/setsebool -P httpd_can_network_connect 1
       when: result10.changed == true
       when: result10.stdout == 'httpd_can_network_connect --> off'
       notify:
         - restart Apache
 
     - name: open ports
       firewalld:
         service: "{{ item }}"
         permanent: true
         state: enabled
         immediate: yes
       loop:
         - http
         - https
 
   handlers:
     - file: path={{ redmine_path }}/../backup state=directory
 
     - name: restart Docker
     - name: backup-redmine-db.service
       copy:
         dest: /etc/systemd/system/backup-redmine-db.service
         content: |
           [Unit]
           Description=backup redmine db
 
           [Service]
           Type=oneshot
           ExecStart=/bin/sh -c "docker exec mysql mysqldump --no-tablespaces redmine \
             | gzip > {{ redmine_path }}/../backup/redmine_db_`date +%%F`.sql.gz"
           ExecStartPost=/bin/find {{ redmine_path }}/../backup \
             -name "redmine_db_*.sql.gz" -mtime +30 -delete
 
     - name: backup-redmine-db.timer
       copy:
         dest: /etc/systemd/system/backup-redmine-db.timer
         content: |
           [Unit]
           Description=backup redmine db
 
           [Timer]
           OnCalendar=daily
           RandomizedDelaySec=1h
           Persistent=true
 
           [Install]
           WantedBy=timers.target
 
     - name: backup-redmine-files.service
       copy:
         dest: /etc/systemd/system/backup-redmine-files.service
         content: |
           [Unit]
           Description=backup redmine files
 
           [Service]
           Type=oneshot
           ExecStart=/bin/sh -c "tar -C {{ redmine_path }} \
             -czf {{ redmine_path }}/../backup/redmine_files_$(date +%%F).tar.gz \
             files"
           ExecStartPost=/bin/find {{ redmine_path }}/../backup \
             -name "redmine_files_*.tar.gz" -mtime +30 -delete
 
     - name: backup-redmine-files.timer
       copy:
         dest: /etc/systemd/system/backup-redmine-files.timer
         content: |
           [Unit]
           Description=backup redmine files
 
           [Timer]
           OnCalendar=daily
           RandomizedDelaySec=1h
           Persistent=true
 
           [Install]
           WantedBy=timers.target
 
     - name: enable timers
       systemd:
         name: docker
         state: restarted
         name: "{{ item }}"
         daemon_reload: yes
         enabled: yes
         state: started
       loop:
         - backup-redmine-db.timer
         - backup-redmine-files.timer
 
     - name: check
       shell: systemctl list-unit-files | egrep "STATE|backup" && systemctl list-timers --all
       changed_when: false
       register: result01
     - debug: msg="{{ result01.stdout_lines }}"
 
   handlers:
 
     - name: restart Apache
       systemd:
         name: httpd
         state: restarted
         daemon_reload: yes
         enabled: yes
 _EOF_
- docker_composeはRPMパッケージではなく最新版を使う

** playbookの実行 [#x17c57c6]
- Ansibleサーバ上の一般ユーザで実行
- 実行前に文法チェックしよう
 ansible-playbook -i inventory.yml pb_redmine_server-1.yml --syntax-check
 ansible-playbook -i inventory.yml pb_redmine_server-redmine.yml --syntax-check
- 問題なければ実効
 ansible-playbook -i inventory.yml pb_redmine_server-1.yml
 ansible-playbook -i inventory.yml pb_redmine_server-redmine.yml

** 補足事項 [#nccd8e9f]

+ mysqlコンテナを削除すると、DBデータはどうなる?
 ホストの /var/lib/docker/volumes/mysql/_data/ にMySQLのDBデータは永続化されている。mysqlコンテナを削除しても、これらは削除されず、コンテナを再導入すると再利用される。
+ mysql_native_passwordの指定は必要?
 defaultのsha2_passwordにrailsが対応していない場合を考慮している。
+ MySQLやmemcashedは、コンテナではなく、ホスト側にrpm導入したらどうか。
 dockerコンテナからホストのプロセスに通信するには、便利な宛先指定方法が無く、ホストのIPアドレスを指定することになり、ポータビリティに欠ける。
 同一のcompose.ymlファイルに記述されたコンテナは、同一ネットワークに属しコンテナ名で指定できる。また、RedmineのWebサイトでも、公式dockerイメージはコンテナ化DBを利用するようガイドされている。
+ RedmineのURLに、コンテキストパス(/redmine)を設定できる?
 RedmineのWebサイトに紹介されている。dockerコンテナ内のpassengerを再設定することで、技術的には可能だが、難易度が高い。また、コンテナの更新を考慮し、できるだけコンテナの編集は避けたい。
 今回は、「基本的に全てのURL要求をredmineコンテナにproxyしつつ、/gitだけ除外する」という方式としている。
+ MySQLへの設定追加
 ホストの/srv/mysql/conf.d/redmine.cnf に永続化されているMySQLの設定ファイルに追記し、mysqlコンテナを再起動すると反映される。
 ファイルに記述する前に、"set global …"コマンドを用いて稼働中に動的設定し、要否の判断をするとよい。
 また、slow queryの常時出力設定は不要と考えている。必要な時に、動的に設定(set global slow_query=1)すればよい。
 set global slow_query_log_file = 'slow.log';
 set global long_query_time = 5;
 set global slow_query_log = ON;
+ Redmine導入用のplaybookに、git用のタスクがあるんだけど
 gitリポジトリ用のSELinux設定は、Redmine用のdocker-compose upを実行する前に実施する必要があるため、仕方なく。
+ ログの循環
 Apache HTTP Serverのaccess_log, error_logは、logrotatedで循環される(defaultのまま)。
 Redmineのproduction.logは、additional_environment.rbで循環設定している(playbookに記載)。


* Pluginの導入 [#y8a09536]

** playbookの作成 [#p6694df9]
- Ansibleサーバ上の一般ユーザで実行する。
 cat << "_EOF_" > pb_redmine_server-plugin.yml
 # install redmine plugins
 # dependencies: pb_redmine_server-redmine.yml
 
 - hosts: redmine_servers
   become: true
   become_method: su
   environment: "{{ proxy_env }}"
 
   vars:
     redmine_path: /srv/redmine
 
   tasks:
 
     - name: check facts
       fail:
         msg: "Not compatible with [{{ ansible_os_family }}] \
           {{ ansible_distribution }} \
           {{ ansible_distribution_major_version }}."
       when: >
         ansible_os_family != 'RedHat'
         or ansible_distribution_major_version|int < 7
 
     - name: check redmine
       stat: path={{ redmine_path }}
       register: result01
     - fail:
         msg: "pb_redmine_server.yml has been executed yet."
       when: result01.stat.exists == false
 
     - name: install RPMs
       dnf:
         name: 
           - git
         state: latest
 
     - name: redmine_pivot_table
       shell: >
         git clone --depth 1
         https://github.com/deecay/redmine_pivot_table
         ./plugins/redmine_pivot_table
       args:
         chdir: "{{ redmine_path }}"
       notify:
         - restart Redmine
 
   handlers:
 
     - name: restart Redmine
       shell: |
         docker exec redmine bundle exec rake redmine:plugins:migrate RAILS_ENV=production
         docker exec redmine passenger-config restart-app /usr/src/redmine
 _EOF_

** playbookの実行 [#d1fba2df]
- Ansibleサーバ上の一般ユーザで実行
 ansible-playbook -i inventory.yml pb_redmine_server-plugin.yml


* Gitの導入 [#af224b39]

 GitLabは「Redmineとの連携は結構面倒」なのと「リソース(主にメモリ)を大量に消費し、しばしば動作不安定を招くほど」との評判があることから、導入を断念。CGI"git-http-backend"とRedmine.pmを用いた、従来どおりのGitリポジトリサービスを選択。ついでに、GitWebも導入。

** Apache用設定ファイルの作成 [#f8c6ab05]
- Ansibleサーバ上の一般ユーザで実行
 cat << "_EOF_" > gitrepos.conf
 <IfModule mod_rewrite.c>
     RewriteEngine On
     RewriteCond %{REQUEST_URI} ^/git/?$
     RewriteRule ^/git /gitweb [R=301,L]
 #    RewriteRule ^/git/$ /gitweb/ [L,R]
 </IfModule>
 cat << "_EOF_" > repos-git.conf
 #<IfModule mod_rewrite.c>
 #    RewriteEngine On
 #    RewriteCond %{REQUEST_URI} ^/git/?$
 #    RewriteRule ^/git /gitweb [R=301,L]
 ##    RewriteRule ^/git/$ /gitweb/ [L,R]
 #</IfModule>
 
 PerlLoadModule Apache::Authn::Redmine
 
 SetEnv GIT_PROJECT_ROOT /var/lib/git
 SetEnv GIT_HTTP_EXPORT_ALL
 
 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
 
 <Location /git/>
   Order allow,deny
   Allow from all
 
   PerlAccessHandler Apache::Authn::Redmine::access_handler
   PerlAuthenHandler Apache::Authn::Redmine::authen_handler
 
   AuthType Basic
   AuthName Git
 #  <LimitExcept GET PROPFIND OPTIONS REPORT>
 #    SSLRequireSSL
 #  </LimitExcept>
 
   # for Redmine Authentication
   RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1"
   RedmineDbUser "redmine"
   RedmineDbPass ""
   RedmineGitSmartHttp yes
 
   Require valid-user
 
 #  SetEnvIf Request_URI "^/git/$" allow
 #  Order allow,deny
 #  Allow from env=allow
 #  Satisfy any
 </Location>
 _EOF_
- contextpath"/git"ではなく、VirtualHostにしてもいいけど、その時はGitWebもそちらのHostにしたいのよね。
- contextpath"/git"ではなく、VirtualHostにしてもいい。

** playbookの作成 [#t40d7aae]
- Ansibleサーバ上の一般ユーザで実行
- sudoの場合、become_method: sudo
 cat << "_EOF_" > pb_redmine_server-2.yml
 cat << "_EOF_" > pb_redmine_server-git.yml
 # install git
 # dependencies: pb_redmine_server-1.yml
 # dependencies: pb_redmine_server-redmine.yml
 
 - hosts: redmine_servers
   become: true
   become_method: su
   environment: "{{ proxy_env }}"
 
   vars:
     redmine_path: /srv/redmine
 
   tasks:
 
     - name: check facts
       fail:
         msg: "Not compatible with [{{ ansible_os_family }}] \
           {{ ansible_distribution }} \
           {{ ansible_distribution_major_version }}."
       when: >
         ansible_os_family != 'RedHat'
         or ansible_distribution_major_version|int != 7
         or ansible_distribution_major_version|int < 7
 
     - name: check redmine
       stat: path={{ redmine_path }}
       register: result01
     - fail:
         msg: "pb_redmine_server.yml has been executed yet."
       when: result01.stat.exists == false
 
     - name: install RPMs
       yum:
       dnf:
         name: 
           - httpd
           - git
           - mod_perl
           - perl-Digest-SHA
           - perl-DBI
           - perl-DBD-mysql
           - gitweb
 #          - gitweb
         state: latest
 
     - name: modify apache user for git
       user: name=apache groups=users append=yes
       notify:
         - restart Apache
     - name: set env for git
       file: path=/etc/systemd/system/httpd.service.d state=directory
     - copy:
         dest: /etc/systemd/system/httpd.service
         dest: /etc/systemd/system/httpd.service.d/git.conf
         force: no
         content: ".include /lib/systemd/system/httpd.service\n[Service]\nUMask=002\n"
         content: "[Service]\nUMask=002\n"
       notify:
         - restart Apache
 
     - name: copy Redmine.pm
       file: path=/etc/httpd/Apache/Authn state=directory
     - shell: docker cp redmine:/usr/src/redmine/extra/svn/Redmine.pm /etc/httpd/Apache/Authn/Redmine.pm
 
     - name: create gitrepos.conf
     - name: create repos-git.conf
       copy:
         src: gitrepos.conf
         dest: /etc/httpd/conf.d/gitrepos.conf
         src: repos-git.conf
         dest: /etc/httpd/conf.d/repos-git.conf
         force: no
         mode: 0640
     - slurp:
         src: "{{ redmine_path }}/.env"
       register: result03
     - lineinfile:
         dest: /etc/httpd/conf.d/gitrepos.conf
         dest: /etc/httpd/conf.d/repos-git.conf
         regexp: "^  RedmineDbPass"
         line: "  RedmineDbPass {{ result03['content'] | b64decode \
           | regex_findall('REDMINE_DB_PASSWORD=(.+)\\n') | first }}"
       notify:
         - restart Apache
 
     - name: modify gitweb
       lineinfile:
         dest: /etc/httpd/conf.d/git.conf
         regexp: '^Alias /git /var/www/git$'
         line: 'Alias /gitweb /var/www/git'
 #    - name: modify gitweb
 #      lineinfile:
 #        dest: /etc/httpd/conf.d/git.conf
 #        regexp: '^Alias /git /var/www/git$'
 #        line: 'Alias /gitweb /var/www/git'
 
     - file: path={{ redmine_path }}/../backup state=directory
 
     - name: backup-git-repos.service
       copy:
         dest: /etc/systemd/system/backup-git-repos.service
         content: |
           [Unit]
           Description=backup git repositories
 
           [Service]
           Type=oneshot
           ExecStart=/bin/sh -c "tar -C /var/lib/git \
             -czf {{ redmine_path }}/../backup/git_repos_$(date +%%F).tar.gz \
             ."
           ExecStartPost=/bin/find {{ redmine_path }}/../backup \
             -name "git_repos_*.tar.gz" -mtime +30 -delete
 
     - name: backup-git-repos.timer
       copy:
         dest: /etc/systemd/system/backup-git-repos.timer
         content: |
           [Unit]
           Description=backup git repositories
 
           [Timer]
           OnCalendar=daily
           RandomizedDelaySec=1h
           Persistent=true
 
           [Install]
           WantedBy=timers.target
 
     - name: enable timers
       systemd:
         name: "{{ item }}"
         daemon_reload: yes
         enabled: yes
         state: started
       loop:
         - backup-git-repos.timer
 
     - name: check
       shell: systemctl list-unit-files | egrep "STATE|backup" && systemctl list-timers --all
       changed_when: false
       register: result01
     - debug: msg="{{ result01.stdout_lines }}"
 
   handlers:
 
     - name: restart Apache
       systemd:
         name: httpd
         state: restarted
         daemon_reload: yes
         enabled: yes
 _EOF_

** playbookの実行 [#ie6760c5]
- Ansibleサーバ上の一般ユーザで実行
 ansible-playbook -i inventory.yml pb_redmine_server-2.yml
 ansible-playbook -i inventory.yml pb_redmine_server-git.yml


* 初期設定 [#j09af147]
* Subversionの導入 [#x030af9c]

** 定期バックアップ設定 [#q2d1fe37]
 Git嫌な人のために、Subversionも入れちゃう。

** Apache用設定ファイルの作成 [#t0ca81c9]
- Ansibleサーバ上の一般ユーザで実行
 cat << "_EOF_" > pb_redmine_server-3.yml
 # configure backup of redmine servers
 cat << "_EOF_" > repos-svn.conf
 LimitRequestFieldSize 12392
 
 LoadModule perl_module        modules/mod_perl.so
 PerlLoadModule Apache::Authn::Redmine
 
 LoadModule dav_svn_module     modules/mod_dav_svn.so
 LoadModule authz_svn_module   modules/mod_authz_svn.so
 
 RequestHeader edit Destination ^https http early
 <Location /svn>
   LimitXMLRequestBody 0
 
   DAV svn
   SVNParentPath /var/lib/svn
   SVNListParentPath on
 #  SVNIndexXSLT "/svnindex/svnindex.xsl"
 #  SVNPathAuthz off
      # http://www.redmine.org/boards/2/topics/7593
 
   AuthType Basic
   AuthName Subversion
   PerlAccessHandler Apache::Authn::Redmine::access_handler
   PerlAuthenHandler Apache::Authn::Redmine::authen_handler
 
   RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1"
   RedmineDbUser "redmine"
   RedmineDbPass ""
 
   ## Optional where clause (fulltext search would be slow and
   ## database dependant).
 #  RedmineDbWhereClause "and exists (select * from groups_users
 #where users.id = groups_users.user_id and groups_users.group_id
 #= (select id from users where type = 'Group' and lastname
 #= 'committers'));"
   ## Optional credentials cache size
   # RedmineCacheCredsMax 50
 
   SetEnvIf REQUEST_URI "^/svn/?$" parent
   <RequireAny>
     Require env parent
     Require valid-user
   </RequireAny>
 
 </Location>
 _EOF_
- contextpath"/svn"ではなく、VirtualHostにしてもいい。

** playbookの作成 [#x639382d]
- Ansibleサーバ上の一般ユーザで実行
- sudoの場合、become_method: sudo
 cat << "_EOF_" > pb_redmine_server-svn.yml
 # install svn
 # dependencies: pb_redmine_server-redmine.yml
 
 - hosts: redmine_servers
   become: true
   become_method: su
   environment: "{{ proxy_env }}"
 
   vars:
     redmine_path: /srv/redmine
 
   tasks:
 
     - file: path={{ redmine_path }}/../backup state=directory
     - name: check facts
       fail:
         msg: "Not compatible with [{{ ansible_os_family }}] \
           {{ ansible_distribution }} \
           {{ ansible_distribution_major_version }}."
       when: >
         ansible_os_family != 'RedHat'
         or ansible_distribution_major_version|int < 7
 
     - name: backup-redmine-db.service
       copy:
         dest: /etc/systemd/system/backup-redmine-db.service
         content: |
           [Unit]
           Description=backup redmine db
     - name: check redmine
       stat: path={{ redmine_path }}
       register: result01
     - fail:
         msg: "pb_redmine_server.yml has been executed yet."
       when: result01.stat.exists == false
 
           [Service]
           Type=oneshot
           ExecStart=/bin/bash -c "docker exec mysql mysqldump --no-tablespaces redmine \
             | gzip > {{ redmine_path }}/../backup/redmine_db_`date +%F`.sql.gz" && \
             find {{ redmine_path }}/../backup -name redmine_db_*.sql.gz -mtime +30 \
             | xargs --no-run-if-empty rm
     - name: install RPMs
       dnf:
         name: 
           - httpd
           - subversion
           - mod_dav_svn
           - mod_perl
           - perl-Digest-SHA
           - perl-DBI
           - perl-DBD-mysql
         state: latest
 
     - name: backup-redmine-db.timer
       copy:
         dest: /etc/systemd/system/backup-redmine-db.timer
         content: |
           [Unit]
           Description=backup redmine db
     - name: modify apache user for svn
       user: name=apache groups=users append=yes
       notify:
         - restart Apache
     - name: set env for svn
       file: path=/etc/systemd/system/httpd.service.d state=directory
     - copy:
         dest: /etc/systemd/system/httpd.service.d/svn.conf
         force: no
         content: "[Service]\nEnvironment=LANG='ja_JP.UTF-8'\nUMask=002\n"
       notify:
         - restart Apache
 
           [Timer]
           OnCalendar=daily
           RandomizedDelaySec=1h
           Persistent=true
     - name: copy Redmine.pm
       file: path=/etc/httpd/Apache/Authn state=directory
     - shell: docker cp redmine:/usr/src/redmine/extra/svn/Redmine.pm /etc/httpd/Apache/Authn/Redmine.pm
 
           [Install]
           WantedBy=timers.target
 
     - name: backup-redmine-files.service
     - name: create repos-svn.conf
       copy:
         dest: /etc/systemd/system/backup-redmine-files.service
         content: |
           [Unit]
           Description=backup redmine files
         src: repos-svn.conf
         dest: /etc/httpd/conf.d/repos-svn.conf
         force: no
         mode: 0640
     - slurp:
         src: "{{ redmine_path }}/.env"
       register: result03
     - lineinfile:
         dest: /etc/httpd/conf.d/repos-svn.conf
         regexp: "^  RedmineDbPass"
         line: "  RedmineDbPass {{ result03['content'] | b64decode \
           | regex_findall('REDMINE_DB_PASSWORD=(.+)\\n') | first }}"
       notify:
         - restart Apache
 
           [Service]
           Type=oneshot
           ExecStart=/bin/bash -c "cd {{ redmine_path }} && \
             tar -cf ../backup/redmine_files_`date +%F`.tar.gz files" && \
             find {{ redmine_path }}/../backup -name redmine_files_*.tar.gz -mtime +30 \
             | xargs --no-run-if-empty rm
     - file: path={{ redmine_path }}/../backup state=directory
 
     - name: backup-redmine-files.timer
     - name: backup-svn-repos.service
       copy:
         dest: /etc/systemd/system/backup-redmine-files.timer
         dest: /etc/systemd/system/backup-svn-repos.service
         content: |
           [Unit]
           Description=backup redmine files
           Description=backup svn repositories
 
           [Timer]
           OnCalendar=daily
           RandomizedDelaySec=1h
           Persistent=true
 
           [Install]
           WantedBy=timers.target
 
     - name: backup-git-repos.service
       copy:
         dest: /etc/systemd/system/backup-git-repos.service
         content: |
           [Unit]
           Description=backup git repositories
 
           [Service]
           Type=oneshot
           ExecStart=/bin/bash -c "cd /var/lib/git && \
             tar -cf {{ redmine_path }}/../backup/git_repos_`date +%F`.tar.gz ." && \
             find {{ redmine_path }}/../backup -name git_repos_*.tar.gz -mtime +30 \
             | xargs --no-run-if-empty rm
           ExecStart=/bin/sh -c "tar -C /var/lib/svn \
             -czf {{ redmine_path }}/../backup/svn_repos_$(date +%%F).tar.gz \
             ."
           ExecStartPost=/bin/find {{ redmine_path }}/../backup \
             -name "svn_repos_*.tar.gz" -mtime +30 -delete
 
     - name: backup-git-repos.timer
     - name: backup-svn-repos.timer
       copy:
         dest: /etc/systemd/system/backup-git-repos.timer
         dest: /etc/systemd/system/backup-svn-repos.timer
         content: |
           [Unit]
           Description=backup git repositories
           Description=backup svn repositories
 
           [Timer]
           OnCalendar=daily
           RandomizedDelaySec=1h
           Persistent=true
 
           [Install]
           WantedBy=timers.target
 
     - name: enable timers
       systemd:
         name: "{{ item }}"
         daemon_reload: yes
         enabled: yes
         state: started
       loop:
         - backup-redmine-db.timer
         - backup-redmine-files.timer
         - backup-git-repos.timer
         - backup-svn-repos.timer
 
     - name: check
       shell: systemctl list-unit-files | egrep "STATE|backup" && systemctl list-timers --all
       changed_when: false
       register: result01
     - debug: msg="{{ result01.stdout_lines }}"
 
   handlers:
 
     - name: restart Apache
       systemd:
         name: httpd
         state: restarted
         daemon_reload: yes
         enabled: yes
 _EOF_

** playbookの実行 [#afa695a9]
- Ansibleサーバ上の一般ユーザで実行
 ansible-playbook -i inventory.yml pb_redmine_server-3.yml
 ansible-playbook -i inventory.yml pb_redmine_server-svn.yml


* 管理作業 [#bed50813]

** リポジトリの新規作成 [#x46884bc]
** Gitリポジトリの新規作成 [#x46884bc]
- Ansibleで実施すると簡便か?
- Ansibleサーバ上の一般ユーザで実行。対象サーバの実行アカウント(ansible_user)は、"users"グループに所属していないとリポジトリを作成できない。
 cat << "_EOF_" > pb_create_git_repository.yml
 # create git repository
 
 - hosts: all
 
   vars:
     gitrepos_path: /var/lib/git
     ansible_user: user         #as your own
     ansible_password: user     #as your own
 
   vars_prompt:
     - name: repos_name
       prompt: "Enter the reposotory name. "
       default: myrepos.git
       private: no
 
   tasks:
 
     - debug:
         msg: "The repos_name is {{ repos_name }}."
 
     - fail: msg="The string you enter is not a valid repository name."
       when: repos_name is not regex("[a-z].+\..+")
 
     - stat: path="{{ gitrepos_path }}/{{ repos_name }}"
       register: result00
 
     - fail: msg="Already exists."
       when: result00.stat.exists == true
 
     - shell: groups
       register: result01
 
     - debug:
         msg: "Your groups is {{ result01.stdout.split(' ') }}."
 
     - fail: msg="You do not have permission to create a repository (group 'users')."
       when: "'users' not in result01.stdout.split(' ')"
 
     - shell: "git init --bare --shared {{ gitrepos_path }}/{{ repos_name }}"
 
     - shell: >
         git update-server-info
         && cp hooks/post-update.sample hooks/post-update
         && git config --local core.ignorecase false
         && git config --local core.quotepath false
       args:
         chdir: "{{ gitrepos_path }}/{{ repos_name }}/"
 
     - copy:
         dest: "{{ gitrepos_path }}/{{ repos_name }}/cloneurl"
         content: "<a href=\"http://{{ inventory_hostname }}/git/{{ repos_name }}\" \
           >http://{{ inventory_hostname }}/git/{{ repos_name }}</a>"
 #    - copy:
 #        dest: "{{ gitrepos_path }}/{{ repos_name }}/cloneurl"
 #        content: "<a href=\"http://{{ inventory_hostname }}/git/{{ repos_name }}\" \
 #          >http://{{ inventory_hostname }}/git/{{ repos_name }}</a>"
 
     - copy:
         dest: "{{ gitrepos_path }}/{{ repos_name }}/description"
         content: "((TODO: descriptionファイルへ、リポジトリ和名[or 簡単な説明]を記述してください。))"
 #    - copy:
 #        dest: "{{ gitrepos_path }}/{{ repos_name }}/description"
 #        content: "((TODO: descriptionファイルへ、リポジトリ和名[or 簡単な説明]を記述してください。))"
 
     - copy:
         dest: "{{ gitrepos_path }}/{{ repos_name }}/README.html"
         content: "<h3>概要</h3>\n \
                   ((TODO: ここにはリポジトリの説明を書いてください))\n\n \
                   <h3>ルール・留意点</h3>\n \
                   ((TODO: ここにはリポジトリ利用に際しての注意点を書いてください))\n\n \
                   <h3>問い合わせ先・管理者</h3>\n \
                   ((TODO: ここにはリポジトリに対しての問合せ先、管理者の連絡先を書いてください))\n\n \
                   <hr />\n \
                   ※TODO: このテンプレートにこだわらず、書きたいことを書いてください。\n"
 #    - copy:
 #        dest: "{{ gitrepos_path }}/{{ repos_name }}/README.html"
 #        content: "<h3>概要</h3>\n \
 #                  ((TODO: ここにはリポジトリの説明を書いてください))\n\n \
 #                  <h3>ルール・留意点</h3>\n \
 #                  ((TODO: ここにはリポジトリ利用に際しての注意点を書いてください))\n\n \
 #                  <h3>問い合わせ先・管理者</h3>\n \
 #                  ((TODO: ここにはリポジトリに対しての問合せ先、管理者の連絡先を書いてください))\n\n \
 #                  <hr />\n \
 #                  ※TODO: このテンプレートにこだわらず、書きたいことを書いてください。\n"
 _EOF_

- Ansibleサーバ上の一般ユーザで実行
 ansible-playbook -i its.jomura.net, pb_create_git_repository.yml
- リポジトリ名を問われるので、入力する
-- リポジトリ名を問われるので、入力する
 Enter the repository name. [myrepos.git]:
- または、"-e"オプションでリポジトリ名を指定することもできる。
 ansible-playbook -i its.jomura.net, -e repos_name=myrepos.git pb_create_git_repository.yml
- リポジトリ名の"."より前は、Redmineプロジェクトの"識別子"と同一でなければ、http経由で利用できない。"."より後は、リポジトリを区別できるような文字列とすること。

- /var/lib/git/{リポジトリ名}/ の直下に、下記のファイルを配備すると、gitwebでの見栄えが良くなるのでお勧め。
- /var/lib/git/{リポジトリ名}/ の直下に、下記のファイルを配備すると、gitwebでの見栄えが良くなるので(gitwebを使う場合は)お勧め。
++ README.html : リポジトリの説明をHTMLで記述する。
++ description : リポジトリの説明を簡単に1行程度で記述する。ファイルは既にある。
++ cloneurl : リポジトリアクセス用のURLを記述する。有用。


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS