Last-modified: 2020-12-17 (木) 09:44:37 (1220d)
 

Redmineの導入

 Redmineについては、下記3種の導入方法を検討した。

  1. 公式サイトの本体コード一式 + RHELのRPM + gemコマンドによるライブラリ導入
    • 各々のライブラリ依存性管理が非常に大変。特にnokogiriとopensslが鬼門。
  2. bitnami-redmineを用いた一括導入
    • Git, Redmine, PhpMyAdminなども導入されるが、今回は使わない
    • Redmine公式ではない
  3. 公式dockerイメージを用いた導入

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

導入サーバの前提条件

  1. OSはRHEL7(or CentOS7)かそれ以降の版とする。
  2. 事前導入するソフトウェアパッケージは最小構成。
  3. インターネットに接続可能 ※必要ならproxy設定
     /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}
  4. パッケージの更新が事前に実行されている
    dnf clean all && dnf -y update && reboot
  5. Redmine Webサイト用のFully Qualified Domain Name(FQDN)が用意されている。

Redmine用 configuration.ymlの作成

  • 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の作成

  • 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ファイルの作成

  • 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用環境変数ファイルの作成

  • 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の作成

  • Ansibleサーバ上の一般ユーザで実行
  • sudoの場合、become_method: sudo
    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
    
        - 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
          dnf:
            name:
              - httpd
              - docker-ce
            state: latest
    
        - 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
    
        - 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.stdout == 'httpd_can_network_connect --> off'
          notify:
            - restart Apache
    
        - name: open ports
          firewalld:
            service: "{{ item }}"
            permanent: true
            state: enabled
            immediate: yes
          loop:
            - http
            - https
    
        - file: path={{ redmine_path }}/../backup state=directory
    
        - 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: "{{ 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の実行

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

補足事項

  1. mysqlコンテナを削除すると、DBデータはどうなる?
     ホストの /var/lib/docker/volumes/mysql/_data/ にMySQLのDBデータは永続化されている。mysqlコンテナを削除しても、これらは削除されず、コンテナを再導入すると再利用される。
  2. mysql_native_passwordの指定は必要?
     defaultのsha2_passwordにrailsが対応していない場合を考慮している。
  3. MySQLやmemcashedは、コンテナではなく、ホスト側にrpm導入したらどうか。
     dockerコンテナからホストのプロセスに通信するには、便利な宛先指定方法が無く、ホストのIPアドレスを指定することになり、ポータビリティに欠ける。
     同一のcompose.ymlファイルに記述されたコンテナは、同一ネットワークに属しコンテナ名で指定できる。また、RedmineのWebサイトでも、公式dockerイメージはコンテナ化DBを利用するようガイドされている。
  4. RedmineのURLに、コンテキストパス(/redmine)を設定できる?
     RedmineのWebサイトに紹介されている。dockerコンテナ内のpassengerを再設定することで、技術的には可能だが、難易度が高い。また、コンテナの更新を考慮し、できるだけコンテナの編集は避けたい。
     今回は、「基本的に全てのURL要求をredmineコンテナにproxyしつつ、/gitだけ除外する」という方式としている。
  5. 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;
  6. Redmine導入用のplaybookに、git用のタスクがあるんだけど
     gitリポジトリ用のSELinux設定は、Redmine用のdocker-compose upを実行する前に実施する必要があるため、仕方なく。
  7. ログの循環
     Apache HTTP Serverのaccess_log, error_logは、logrotatedで循環される(defaultのまま)。
     Redmineのproduction.logは、additional_environment.rbで循環設定している(playbookに記載)。

Pluginの導入

playbookの作成

  • 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の実行

  • Ansibleサーバ上の一般ユーザで実行
    ansible-playbook -i inventory.yml pb_redmine_server-plugin.yml

Gitの導入

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

Apache用設定ファイルの作成

  • Ansibleサーバ上の一般ユーザで実行
    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にしてもいい。

playbookの作成

  • Ansibleサーバ上の一般ユーザで実行
  • sudoの場合、become_method: sudo
    cat << "_EOF_" > pb_redmine_server-git.yml
    # install git
    # 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: 
              - httpd
              - git
              - mod_perl
              - perl-Digest-SHA
              - perl-DBI
              - perl-DBD-mysql
    #          - 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.d/git.conf
            force: no
            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 repos-git.conf
          copy:
            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/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'
    
        - 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の実行

  • Ansibleサーバ上の一般ユーザで実行
    ansible-playbook -i inventory.yml pb_redmine_server-git.yml

Subversionの導入

 Git嫌な人のために、Subversionも入れちゃう。

Apache用設定ファイルの作成

  • Ansibleサーバ上の一般ユーザで実行
    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の作成

  • 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:
    
        - 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: 
              - httpd
              - subversion
              - mod_dav_svn
              - mod_perl
              - perl-Digest-SHA
              - perl-DBI
              - perl-DBD-mysql
            state: latest
    
        - 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
    
        - 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 repos-svn.conf
          copy:
            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
    
        - file: path={{ redmine_path }}/../backup state=directory
    
        - name: backup-svn-repos.service
          copy:
            dest: /etc/systemd/system/backup-svn-repos.service
            content: |
              [Unit]
              Description=backup svn repositories
    
              [Service]
              Type=oneshot
              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-svn-repos.timer
          copy:
            dest: /etc/systemd/system/backup-svn-repos.timer
            content: |
              [Unit]
              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-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の実行

  • Ansibleサーバ上の一般ユーザで実行
    ansible-playbook -i inventory.yml pb_redmine_server-svn.yml

管理作業

Gitリポジトリの新規作成

  • 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 }}/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"
    _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での見栄えが良くなるので(gitwebを使う場合は)お勧め。
    1. README.html : リポジトリの説明をHTMLで記述する。
    2. description : リポジトリの説明を簡単に1行程度で記述する。ファイルは既にある。
    3. cloneurl : リポジトリアクセス用のURLを記述する。有用。

添付ファイル: filepb_redmine_server-2.yml 1265件 [詳細] filegitrepos.conf 1198件 [詳細] filepb_redmine_server-1.yml 1213件 [詳細] filedocker-compose.yml 1105件 [詳細] filesettings.sql 1054件 [詳細]

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS