From 83f9ab4b2536b330ab2032d78407175911266588 Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Thu, 16 Jul 2015 15:52:51 +0200 Subject: [PATCH 01/10] add migration to add unique index over journals The triplet [journable_type, journable_id, version] should never be duplicated. We don't know for sure how duplicates can occur here, but a unique index will: - prevent data corruption - cause an error when invalid data is about to be added to the database - speed up typical access to the journals table --- ...0716133712_add_unique_index_on_journals.rb | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 db/migrate/20150716133712_add_unique_index_on_journals.rb diff --git a/db/migrate/20150716133712_add_unique_index_on_journals.rb b/db/migrate/20150716133712_add_unique_index_on_journals.rb new file mode 100644 index 0000000000..ec75ae4438 --- /dev/null +++ b/db/migrate/20150716133712_add_unique_index_on_journals.rb @@ -0,0 +1,99 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +class AddUniqueIndexOnJournals < ActiveRecord::Migration + def up + cleanup_duplicate_journals + add_index :journals, [:journable_type, :journable_id, :version], unique: true + end + + def down + remove_index :journals, [:journable_type, :journable_id, :version] + end + + private + + def cleanup_duplicate_journals + duplicate_pairs = find_duplicate_journal_ids + + if duplicate_pairs.any? + say "Found #{duplicate_pairs.count} journals with at least one duplicate!" + say_with_time 'Safely removing duplicates...' do + duplicate_pairs.each do |current_id, duplicate_id| + say "Comparing journals ##{current_id} & ##{duplicate_id} for equality", subitem: true + + current = Journal.find(current_id) + duplicate = Journal.find(duplicate_id) + + if journals_equivalent?(current, duplicate) + say "Deleting journal ##{current.id}...", subitem: true + current.destroy + else + abort_migration(current, duplicate) + end + end + end + end + end + + def find_duplicate_journal_ids + this = Journal.table_name + Journal + .joins("INNER JOIN #{this} other + ON #{this}.journable_id = other.journable_id AND + #{this}.journable_type = other.journable_type AND + #{this}.version = other.version") + .where("#{this}.id < other.id") + .select("#{this}.id id, other.id duplicate_id") + .uniq_by(&:id) + .map { |pair| [pair.id, pair.duplicate_id] } + end + + def journals_equivalent?(a, b) + records_equivalent?(a, b) && records_equivalent?(a.data, b.data) + end + + def records_equivalent?(a, b) + if a.nil? || b.nil? + return a == b + end + + ignored = [:id, :journal_id, :created_at, :updated_at] + a.attributes.symbolize_keys.except(*ignored) == b.attributes.symbolize_keys.except(*ignored) + end + + def abort_migration(current, duplicate) + say "Won't delete ##{current.id}, because its content is different from ##{duplicate.id}", + subitem: true + say 'You have to manually decide whether it is safe to delete one of both journals.', + subitem: true + say 'Aborting migration...', subitem: true + + raise "Can't continue migration safely because of duplicate journals!" + end +end From 2b7d6104fe32afe35b5aee8d33f06c8dabc1a31e Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Fri, 17 Jul 2015 09:19:36 +0200 Subject: [PATCH 02/10] use a minimalistic helper model for migration --- ...0716133712_add_unique_index_on_journals.rb | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/db/migrate/20150716133712_add_unique_index_on_journals.rb b/db/migrate/20150716133712_add_unique_index_on_journals.rb index ec75ae4438..01159c4af2 100644 --- a/db/migrate/20150716133712_add_unique_index_on_journals.rb +++ b/db/migrate/20150716133712_add_unique_index_on_journals.rb @@ -47,8 +47,8 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration duplicate_pairs.each do |current_id, duplicate_id| say "Comparing journals ##{current_id} & ##{duplicate_id} for equality", subitem: true - current = Journal.find(current_id) - duplicate = Journal.find(duplicate_id) + current = MigrationHelperJournal.find(current_id) + duplicate = MigrationHelperJournal.find(duplicate_id) if journals_equivalent?(current, duplicate) say "Deleting journal ##{current.id}...", subitem: true @@ -62,8 +62,8 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration end def find_duplicate_journal_ids - this = Journal.table_name - Journal + this = MigrationHelperJournal.table_name + MigrationHelperJournal .joins("INNER JOIN #{this} other ON #{this}.journable_id = other.journable_id AND #{this}.journable_type = other.journable_type AND @@ -96,4 +96,24 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration raise "Can't continue migration safely because of duplicate journals!" end + + # Using a custom (light weight) implementation of Journal here, because we don't know + # how the original might change in the future. Changes could potentially break our untested + # migrations. By providing a minimal custom implementation, I hope to reduce that risk. + class MigrationHelperJournal < ActiveRecord::Base + self.table_name = 'journals' + + has_many :attachable_journals, + class_name: Journal::AttachableJournal, + foreign_key: :journal_id, + dependent: :destroy + has_many :customizable_journals, + class_name: Journal::CustomizableJournal, + foreign_key: :journal_id, + dependent: :destroy + + def data + @data ||= "Journal::#{journable_type}Journal".constantize.where(journal_id: id).first + end + end end From 5c0630c9fe020da6f1c9243ffd2cd22982e7f8a0 Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Fri, 17 Jul 2015 10:14:34 +0200 Subject: [PATCH 03/10] compare related tables before deleting journals --- ...150716133712_add_unique_index_on_journals.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/db/migrate/20150716133712_add_unique_index_on_journals.rb b/db/migrate/20150716133712_add_unique_index_on_journals.rb index 01159c4af2..1db017c161 100644 --- a/db/migrate/20150716133712_add_unique_index_on_journals.rb +++ b/db/migrate/20150716133712_add_unique_index_on_journals.rb @@ -75,7 +75,15 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration end def journals_equivalent?(a, b) - records_equivalent?(a, b) && records_equivalent?(a.data, b.data) + a_attachments = a.attachable_journals.pluck(:attachment_id).sort + b_attachments = b.attachable_journals.pluck(:attachment_id).sort + a_custom_fields = customizable_journals_to_hash a.customizable_journals + b_custom_fields = customizable_journals_to_hash b.customizable_journals + + records_equivalent?(a, b) && + records_equivalent?(a.data, b.data) && + a_attachments == b_attachments && + a_custom_fields == b_custom_fields end def records_equivalent?(a, b) @@ -87,6 +95,13 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration a.attributes.symbolize_keys.except(*ignored) == b.attributes.symbolize_keys.except(*ignored) end + def customizable_journals_to_hash(customizable_journals) + customizable_journals.inject({}) { |hash, custom_journal| + hash[custom_journal.custom_field_id] = custom_journal.value + hash + } + end + def abort_migration(current, duplicate) say "Won't delete ##{current.id}, because its content is different from ##{duplicate.id}", subitem: true From 19a0e105a172c7a0a87e9913cdb91b4d289529e1 Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Fri, 17 Jul 2015 10:42:56 +0200 Subject: [PATCH 04/10] make migration output more DevOps-friendly --- ...0716133712_add_unique_index_on_journals.rb | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/db/migrate/20150716133712_add_unique_index_on_journals.rb b/db/migrate/20150716133712_add_unique_index_on_journals.rb index 1db017c161..fe2a784791 100644 --- a/db/migrate/20150716133712_add_unique_index_on_journals.rb +++ b/db/migrate/20150716133712_add_unique_index_on_journals.rb @@ -44,6 +44,7 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration if duplicate_pairs.any? say "Found #{duplicate_pairs.count} journals with at least one duplicate!" say_with_time 'Safely removing duplicates...' do + undeleted_pairs = [] duplicate_pairs.each do |current_id, duplicate_id| say "Comparing journals ##{current_id} & ##{duplicate_id} for equality", subitem: true @@ -54,9 +55,11 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration say "Deleting journal ##{current.id}...", subitem: true current.destroy else - abort_migration(current, duplicate) + undeleted_pairs << [current_id, duplicate_id] end end + + abort_on_undeleted_pairs undeleted_pairs end end end @@ -75,15 +78,31 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration end def journals_equivalent?(a, b) + unless records_equivalent?(a, b) + say 'Difference found in table journals', subitem: true + return false + end + + unless records_equivalent?(a.data, b.data) + say 'Difference found in related data table (e.g. work_package_journals)', subitem: true + return false + end + a_attachments = a.attachable_journals.pluck(:attachment_id).sort b_attachments = b.attachable_journals.pluck(:attachment_id).sort + unless a_attachments == b_attachments + say 'Difference found in attachable_journals', subitem: true + return false + end + a_custom_fields = customizable_journals_to_hash a.customizable_journals b_custom_fields = customizable_journals_to_hash b.customizable_journals + unless a_custom_fields == b_custom_fields + say 'Difference found in customizable_journals', subitem: true + return false + end - records_equivalent?(a, b) && - records_equivalent?(a.data, b.data) && - a_attachments == b_attachments && - a_custom_fields == b_custom_fields + true end def records_equivalent?(a, b) @@ -102,12 +121,30 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration } end - def abort_migration(current, duplicate) - say "Won't delete ##{current.id}, because its content is different from ##{duplicate.id}", - subitem: true - say 'You have to manually decide whether it is safe to delete one of both journals.', - subitem: true - say 'Aborting migration...', subitem: true + def abort_on_undeleted_pairs(undeleted_pairs) + return unless undeleted_pairs.any? + + say '', subitem: true + say 'There were journals that had a duplicate, but were not deleted.', subitem: true + say 'You have to manually decide how to proceed with these journals.', subitem: true + say 'Please compare the corresponding entries in the following tables:', subitem: true + say ' * journals', subitem: true + say ' * attachable_journals', subitem: true + say ' * customizable_journals', subitem: true + say ' * {type}_journals, with {type} being indicated by the journable_type', subitem: true + say '', subitem: true + say 'The following table lists the remaining duplicate pairs,', subitem: true + say 'note that only one entry per pair is supposed to be deleted:', subitem: true + + column_width = 20 + say '-' * (column_width * 2 + 7), subitem: true + say "| #{'journal 1'.rjust(column_width)} | #{'journal 2'.rjust(column_width)} |", subitem: true + say '-' * (column_width * 2 + 7), subitem: true + undeleted_pairs.each do |undeleted_id, duplicate_id| + say "| #{undeleted_id.to_s.rjust(column_width)} | #{duplicate_id.to_s.rjust(column_width)} |", + subitem: true + end + say '-' * (column_width * 2 + 7), subitem: true raise "Can't continue migration safely because of duplicate journals!" end From fa751b970dc7dad2468b6c7fbc880e94c9c3243c Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Fri, 17 Jul 2015 13:55:58 +0200 Subject: [PATCH 05/10] increase readability of change-test --- ...0716133712_add_unique_index_on_journals.rb | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/db/migrate/20150716133712_add_unique_index_on_journals.rb b/db/migrate/20150716133712_add_unique_index_on_journals.rb index fe2a784791..fbe08d5d07 100644 --- a/db/migrate/20150716133712_add_unique_index_on_journals.rb +++ b/db/migrate/20150716133712_add_unique_index_on_journals.rb @@ -78,31 +78,41 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration end def journals_equivalent?(a, b) - unless records_equivalent?(a, b) - say 'Difference found in table journals', subitem: true - return false - end + base_journals_equivalent?(a, b) && + specific_journals_equivalent?(a, b) && + attachable_journals_equivalent?(a, b) && + customizable_journals_equivalent?(a, b) + end - unless records_equivalent?(a.data, b.data) + def base_journals_equivalent?(a, b) + result = records_equivalent?(a, b) + say 'Difference found in table journals', subitem: true unless result + result + end + + def specific_journals_equivalent?(a, b) + result = records_equivalent?(a.data, b.data) + unless result say 'Difference found in related data table (e.g. work_package_journals)', subitem: true - return false end + result + end + + def attachable_journals_equivalent?(a, b) a_attachments = a.attachable_journals.pluck(:attachment_id).sort b_attachments = b.attachable_journals.pluck(:attachment_id).sort - unless a_attachments == b_attachments - say 'Difference found in attachable_journals', subitem: true - return false - end + result = a_attachments == b_attachments + say 'Difference found in attachable_journals', subitem: true unless result + result + end + def customizable_journals_equivalent?(a, b) a_custom_fields = customizable_journals_to_hash a.customizable_journals b_custom_fields = customizable_journals_to_hash b.customizable_journals - unless a_custom_fields == b_custom_fields - say 'Difference found in customizable_journals', subitem: true - return false - end - - true + result = a_custom_fields == b_custom_fields + say 'Difference found in customizable_journals', subitem: true unless result + result end def records_equivalent?(a, b) From a1dd15eff7e68697bb7d515fb7130caec19e173a Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Fri, 17 Jul 2015 14:30:45 +0200 Subject: [PATCH 06/10] introduce method shortcut for "say subitem: true" --- ...0716133712_add_unique_index_on_journals.rb | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/db/migrate/20150716133712_add_unique_index_on_journals.rb b/db/migrate/20150716133712_add_unique_index_on_journals.rb index fbe08d5d07..03bcf0778d 100644 --- a/db/migrate/20150716133712_add_unique_index_on_journals.rb +++ b/db/migrate/20150716133712_add_unique_index_on_journals.rb @@ -46,13 +46,13 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration say_with_time 'Safely removing duplicates...' do undeleted_pairs = [] duplicate_pairs.each do |current_id, duplicate_id| - say "Comparing journals ##{current_id} & ##{duplicate_id} for equality", subitem: true + sub_say "Comparing journals ##{current_id} & ##{duplicate_id} for equality" current = MigrationHelperJournal.find(current_id) duplicate = MigrationHelperJournal.find(duplicate_id) if journals_equivalent?(current, duplicate) - say "Deleting journal ##{current.id}...", subitem: true + sub_say "Deleting journal ##{current.id}..." current.destroy else undeleted_pairs << [current_id, duplicate_id] @@ -86,16 +86,13 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration def base_journals_equivalent?(a, b) result = records_equivalent?(a, b) - say 'Difference found in table journals', subitem: true unless result + sub_say 'Difference found in table journals' unless result result end def specific_journals_equivalent?(a, b) result = records_equivalent?(a.data, b.data) - unless result - say 'Difference found in related data table (e.g. work_package_journals)', subitem: true - end - + sub_say 'Difference found in related data table (e.g. work_package_journals)' unless result result end @@ -103,7 +100,7 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration a_attachments = a.attachable_journals.pluck(:attachment_id).sort b_attachments = b.attachable_journals.pluck(:attachment_id).sort result = a_attachments == b_attachments - say 'Difference found in attachable_journals', subitem: true unless result + sub_say 'Difference found in attachable_journals' unless result result end @@ -111,7 +108,7 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration a_custom_fields = customizable_journals_to_hash a.customizable_journals b_custom_fields = customizable_journals_to_hash b.customizable_journals result = a_custom_fields == b_custom_fields - say 'Difference found in customizable_journals', subitem: true unless result + sub_say 'Difference found in customizable_journals' unless result result end @@ -134,31 +131,34 @@ class AddUniqueIndexOnJournals < ActiveRecord::Migration def abort_on_undeleted_pairs(undeleted_pairs) return unless undeleted_pairs.any? - say '', subitem: true - say 'There were journals that had a duplicate, but were not deleted.', subitem: true - say 'You have to manually decide how to proceed with these journals.', subitem: true - say 'Please compare the corresponding entries in the following tables:', subitem: true - say ' * journals', subitem: true - say ' * attachable_journals', subitem: true - say ' * customizable_journals', subitem: true - say ' * {type}_journals, with {type} being indicated by the journable_type', subitem: true - say '', subitem: true - say 'The following table lists the remaining duplicate pairs,', subitem: true - say 'note that only one entry per pair is supposed to be deleted:', subitem: true + sub_say '' + sub_say 'There were journals that had a duplicate, but were not deleted.' + sub_say 'You have to manually decide how to proceed with these journals.' + sub_say 'Please compare the corresponding entries in the following tables:' + sub_say ' * journals' + sub_say ' * attachable_journals' + sub_say ' * customizable_journals' + sub_say ' * {type}_journals, with {type} being indicated by the journable_type' + sub_say '' + sub_say 'The following table lists the remaining duplicate pairs,' + sub_say 'note that only one entry per pair is supposed to be deleted:' column_width = 20 - say '-' * (column_width * 2 + 7), subitem: true - say "| #{'journal 1'.rjust(column_width)} | #{'journal 2'.rjust(column_width)} |", subitem: true - say '-' * (column_width * 2 + 7), subitem: true - undeleted_pairs.each do |undeleted_id, duplicate_id| - say "| #{undeleted_id.to_s.rjust(column_width)} | #{duplicate_id.to_s.rjust(column_width)} |", - subitem: true + sub_say '-' * (column_width * 2 + 7) + sub_say "| #{'journal 1'.rjust(column_width)} | #{'journal 2'.rjust(column_width)} |" + sub_say '-' * (column_width * 2 + 7) + undeleted_pairs.each do |dup_a, dup_b| + sub_say "| #{dup_a.to_s.rjust(column_width)} | #{dup_b.to_s.rjust(column_width)} |" end - say '-' * (column_width * 2 + 7), subitem: true + sub_say '-' * (column_width * 2 + 7) raise "Can't continue migration safely because of duplicate journals!" end + def sub_say(message) + say message, subitem: true + end + # Using a custom (light weight) implementation of Journal here, because we don't know # how the original might change in the future. Changes could potentially break our untested # migrations. By providing a minimal custom implementation, I hope to reduce that risk. From 165e56c7ed271b4a41c7466e5fae88736dbbac92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 20 Jul 2015 11:28:57 +0200 Subject: [PATCH 07/10] Hide the module menu when user is anonymous and login is required. The public permission of `:view_news` causes the Module menu to always render when the user is anonymous, even when the Setting `login_required` is enabled. This workaround hides the module menu in this specific case. Relevant work package: https://community.openproject.org/work_packages/20935 --- config/initializers/menus.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb index 55fc765dbe..d349301dbc 100644 --- a/config/initializers/menus.rb +++ b/config/initializers/menus.rb @@ -39,14 +39,23 @@ Redmine::MenuManager.map :top_menu do |menu| menu.push :work_packages, { controller: '/work_packages', project_id: nil, action: 'index' }, caption: I18n.t('label_work_package_plural'), - if: Proc.new { User.current.allowed_to?(:view_work_packages, nil, global: true) } + if: Proc.new { + (User.current.logged? || !Setting.login_required?) && + User.current.allowed_to?(:view_work_packages, nil, global: true) + } menu.push :news, { controller: '/news', project_id: nil, action: 'index' }, - if: Proc.new { User.current.allowed_to?(:view_news, nil, global: true) } + if: Proc.new { + (User.current.logged? || !Setting.login_required?) && + User.current.allowed_to?(:view_news, nil, global: true) + } menu.push :time_sheet, { controller: '/time_entries', project_id: nil, action: 'index' }, caption: I18n.t('label_time_sheet_menu'), - if: Proc.new { User.current.allowed_to?(:view_time_entries, nil, global: true) } + if: Proc.new { + (User.current.logged? || !Setting.login_required?) && + User.current.allowed_to?(:view_time_entries, nil, global: true) + } menu.push :help, OpenProject::Info.help_url, last: true, caption: '', From 0f98e004c2b28c34e7f382092486d1517c8f293a Mon Sep 17 00:00:00 2001 From: kgalli Date: Mon, 20 Jul 2015 16:02:45 +0000 Subject: [PATCH 08/10] Update guide to current installation dependencies This guide also introduces rbanv and nodenv as replacement for rvm and nvm. --- .../manual/installation-guide.md | 355 +++++++----------- 1 file changed, 146 insertions(+), 209 deletions(-) diff --git a/doc/operation_guides/manual/installation-guide.md b/doc/operation_guides/manual/installation-guide.md index be2ba588c3..442839128b 100644 --- a/doc/operation_guides/manual/installation-guide.md +++ b/doc/operation_guides/manual/installation-guide.md @@ -1,182 +1,171 @@ -# Installation of OpenProject 4.2 with Apache on Debian 7.7 or Ubuntu 14.04 LTS +# Installation of OpenProject 4.2 with Apache on Ubuntu 14.04. LTS -**This tutorial helps you to deploy OpenProject 4.2. Please, aware that:** +This tutorial helps you to deploy OpenProject 4.2. Please, aware that: -1. This guide requires that you have a clean **Debian 7.7 x64** or **Ubuntu 14.04 x64** installation with administrative rights. We have tested the installation guide on a Debian minimal netinstall image and on an Ubuntu Server image, but it should work on any derivative. -2. OpenProject will be installed with a MySQL database (the guide should work analogous with PostgreSQL). -3. OpenProject will be served in a production environment with Apache (this guide should work analogous with other servers, like nginx and others) +This guide requires that you have a clean Ubuntu 14.04 x64 installation +with administrative rights. We have tested the installation guide on a +on an Ubuntu Server image, but it should work on any derivative. -In this guide, we will install **OpenProject 4.2** with a **MySQL** database. Openproject will be served with the **Apache** web server. When your server needs to reboot, OpenProject should start automatically with your server. +OpenProject will be installed with a MySQL database (the guide should +work analogous with PostgreSQL). + +OpenProject will be served in a production environment with Apache +(this guide should work analogous with other servers, like nginx and others) Note: We have highlighted commands to execute like this + + ```bash -[user@host] command +[user@host] command to execute ``` -Where the `user` is the operating system user the command is executed with. The `host` is either `debian` (when the command is Debian-specific), `ubuntu` (when the command is Ubuntu-specific), or `all` (when the command shall be executed on either operating system). +Where the user is the operating system user the command is executed with. +In our case it will be `root` for most of the time or `openproject`. -If you find any bugs or you have any recommendations for improving this tutorial, please, feel free to create a pull request against this guide. +If you find any bugs or you have any recommendations for improving this +tutorial, please, feel free to create a pull request against this guide. -## Prepare Your Environment +# Prepare Your Envoironment -Install tools needed to compile Ruby and run OpenProject: - -### Only on Debian +Create a dedicated user for OpenProject: ```bash -[root@debian] apt-get update -[root@debian] apt-get install git curl build-essential zlib1g-dev libyaml-dev libssl-dev libmysqlclient-dev libpq-dev memcached libffi5 +sudo groupadd openproject +sudo useradd --create-home --gid openproject openproject +sudo passwd openproject #(enter desired password) ``` -### Only on Ubuntu +## Installation of Essentials ```bash -[root@ubuntu] apt-get update -[root@ubuntu] apt-get install git curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libmysqlclient-dev libpq-dev libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties memcached libgdbm-dev libncurses5-dev automake libtool bison libffi-dev +[root@host] apt-get update -y +[root@host] apt-get install -y zlib1g-dev build-essential \ + libssl-dev libreadline-dev \ + libyaml-dev libgdbm-dev \ + libncurses5-dev automake \ + libtool bison libffi-dev git curl \ + libxml2 libxml2-dev libxslt1-dev # nokogiri ``` -### Debian and Ubuntu - -Create a dedicated user for OpenProject: +## Installation of Memcached ```bash -[root@all] groupadd openproject -[root@all] useradd --create-home --gid openproject openproject -[root@all] passwd openproject (enter desired password) +[root@host] apt-get install -y memcached ``` -## Install Database (MySQL) Packages +## Installation of MySQL -During installation, you have to enter a password for the mysql root-user. ```bash -[root@all] apt-get install mysql-server mysql-client +[root@host] apt-get install mysql-server libmysqlclient-dev ``` -As a reference, we have installed the following MySQL version: +During the installation you will be asked to set the root password. -```bash -[root@all] mysql --version - mysql Ver 14.14 Distrib 5.5.40, for debian-linux-gnu (x86_64) using readline 6.3 -``` -Create the OpenProject MySQL-user and database: +We use the following command to open a `mysql` console and create +the OpenProject database. ```bash -[root@all] mysql -u root -p +[root@host] mysql -uroot -p ``` -You may replace the string `"openproject"` with the desired username and database-name. The password `"my_password"` should definitely be changed. +You may replace the string `openproject` with the desired username and +database name. The password `my_password` should definitely be changed. ```sql mysql> CREATE DATABASE openproject CHARACTER SET utf8; mysql> CREATE USER 'openproject'@'localhost' IDENTIFIED BY 'my_password'; mysql> GRANT ALL PRIVILEGES ON openproject.* TO 'openproject'@'localhost'; -mysql> \q +mysql> FLUSH PRIVILEGES; +mysql> QUIT ``` -## Install Node.js +## Installation of Ruby -We will install the latest 0.10.x version of Node.js via [nodeenv](https://pypi.pythn.org/pypi/nodeenv): +The are several possibilities to install Ruby on your machine. We will +use [rbenv](http://rbenv.org/). ```bash -[root@all] apt-get install python python-pip -[root@all] pip install nodeenv -``` - +[root@host] su openproject --login +[openproject@host] git clone https://github.com/sstephenson/rbenv.git ~/.rbenv +[openproject@host] echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.profile +[openproject@host] echo 'eval "$(rbenv init -)"' >> ~/.profile +[openproject@host] source ~/.profile +[openproject@host] git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build -## Install Ruby - -Switch to the dedicated OpenProject-user (user `openproject` in our case): - -```bash -[root@all] su openproject -c "bash -l" +[openproject@host] rbenv install 2.1.6 +[openproject@host] rbenv rehash +[openproject@host] rbenv global 2.1.6 ``` -Switch to the user's home directory ... +To check our Ruby installation we run `ruby --version`. It should output +something very similar to: -```bash -[openproject@all] cd ~ ``` - -... and install RVM (Ruby Version Manager) - -```bash -[openproject@all] \curl -sSL https://get.rvm.io | bash -s stable +ruby 2.1.6p336 (2015-04-13 revision 50298) [x86_64-linux] ``` -It can be that curl fails to download the RVM source, because of the missing GPG key. If that is the case, download the key (as suggested in the error message): +## Installation of Node -```bash -[openproject@all] gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3 -``` - -Then try to download RVM again and continue the installation with: +The are several possibilities to install Node on your machine. We will +use [nodenv](https://github.com/OiNutter/nodenv#installation). Please +run `su openproject --login` if you are the `root` user. If you are +already the `openproject` user you can skip this command. Please be +aware that the actual installation of a specific node version takes some +time to finsih. ```bash -[openproject@all] source $HOME/.rvm/scripts/rvm -[openproject@all] rvm autolibs disable -[openproject@all] rvm install 2.1.5 -[openproject@all] rvm use --default 2.1.5 -[openproject@all] gem install bundler -``` +[openproject@host] git clone https://github.com/OiNutter/nodenv.git ~/.nodenv +[openproject@host] echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.profile +[openproject@host] echo 'eval "$(nodenv init -)"' >> ~/.profile +[openproject@host] source ~/.profile +[openproject@host] git clone git://github.com/OiNutter/node-build.git ~/.nodenv/plugins/node-build -As a reference, we have installed the following version of bundler: - -```bash -[openproject@all] bundle --version - Bundler version 1.7.4 +[openproject@host] nodenv install 0.12.7 +[openproject@host] nodenv rehash +[openproject@host] nodenv global 0.12.7 ``` -## Activate Node.js +To check our Node installation we run `node --version`. It should output +something very similar to: -```bash -[openproject@all] cd ~ -[openproject@all] nodeenv nodeenv -[openproject@all] source ./nodeenv/bin/activate -[openproject@all] npm -g install bower ``` - -As a reference, the following Node.js and NPM versions have been installed on our system: - -```bash -[openproject@all] node --version - v0.10.33 -[openproject@all] npm --version - 1.4.28 -[openproject@all] bower --version - 1.3.12 +v0.12.7 ``` -## Install OpenProject +## Installation of OpenProoject ```bash -[openproject@all] cd ~ -[openproject@all] git clone https://github.com/opf/openproject.git -[openproject@all] cd openproject -[openproject@all] git checkout v4.2.0 # please use actual current stable version v4.2.X -[openproject@all] bundle install -[openproject@all] npm install +[openproject@host] cd ~ +[openproject@host] git clone https://github.com/opf/openproject.git +[openproject@host] cd openproject +[openproject@host] git checkout v4.2.0 # please use actual current stable version v4.2.X +[openproject@host] gem install bundler +[openproject@host] bundle install --deployment --without postgres sqlite rmagick development test therubyracer +[openproject@host] npm install ``` -## Configure OpenProject +## Configure Openproject -Create and configure the database configuration file in `config/database.yml` (relative to the openproject-directory). +Create and configure the database configuration file in config/database.yml +(relative to the openproject-directory). ```bash -[openproject@all] cp config/database.yml.example config/database.yml +[openproject@host] cp config/database.yml.example config/database.yml ``` -Now edit the `config/database.yml` file and insert your database credentials. -It should look like this (just with your database name, username, and password): +Now we edit the `config/database.yml` file and insert our database credentials. +It should look like this (please keep in mind that you have to use the values +you used above as you user, database and password): -```ruby +```yaml production: adapter: mysql2 database: openproject host: localhost username: openproject - password: openproject + password: my_password encoding: utf8 development: @@ -184,19 +173,19 @@ development: database: openproject host: localhost username: openproject - password: openproject + password: my_password encoding: utf8 ``` -Configure email notifications (using a gmail account as an example) by creating configuration.yml in `config` directory. +Next we configure email notifications (this example uses a gmail account) by creating the `configuration.yml` in config directory. ```bash -[openproject@all] cp config/configuration.yml.example config/configuration.yml +[openproject@host] cp config/configuration.yml.example config/configuration.yml ``` -Now, edit the `configuration.yml` file as you like. +Now we edit the `configuration.yml` file to siut out needs. -```ruby +```yaml production: #main level email_delivery_method: :smtp #settings for the production environment smtp_address: smtp.gmail.com @@ -208,132 +197,81 @@ production: #main level smtp_authentication: plain ``` -Add this line into `configuration.yml` file at the of of file for better performance of OpenProject: +Add this line into `configuration.yml` file at the of of file for better +performance of OpenProject: -```ruby +```yaml rails_cache_store: :memcache ``` -**NOTE:** You should validate your .yml-files, for example with http://www.yamllint.com/. Both, the `database.yml` and `configuration.yml` file are sensitive to whitespace. It is pretty easy to write invalid .yml files without seeing the error. Validating those files prevents you from such errors. +__NOTE:__ You should validate your .yml-files, for example with +http://www.yamllint.com/. Both, the database.yml and `configuration.yml` +file are sensitive to whitespace. It is pretty easy to write +`invalid .yml` files without seeing the error. Validating those files +prevents you from such errors. + ## Finish the Installation of OpenProject ```bash -[openproject@all] cd ~/openproject -[openproject@all] bundle exec rake db:create:all -[openproject@all] bundle exec rake generate_secret_token -[openproject@all] RAILS_ENV="production" bundle exec rake db:migrate -[openproject@all] RAILS_ENV="production" bundle exec rake db:seed -[openproject@all] RAILS_ENV="production" bundle exec rake assets:precompile +[openproject@host] cd ~/openproject +[openproject@host] bundle exec rake db:create:all +[openproject@host] bundle exec rake generate_secret_token +[openproject@host] RAILS_ENV="production" bundle exec rake db:migrate +[openproject@host] RAILS_ENV="production" bundle exec rake db:seed +[openproject@host] RAILS_ENV="production" bundle exec rake assets:precompile ``` +## Servce OpenProject with Apache and Passenger -## Serve OpenProject with Apache and Passenger - -OpenProject will be served by the Rails application server "Passenger", and the apache webserver. -We set up the system in a way, that automatically starts OpenProject with the operating system. - -### Only on Debian - -First, exit the current bash session with the `openproject` user, so that we are again in a root shell. -Then, we prepare apache and passenger: +First, we exit the current bash session with the openproject user, +so that we are again in a root shell. ```bash -[openproject@debian] exit -[root@debian] apt-get install apache2 libcurl4-gnutls-dev apache2-threaded-dev libapr1-dev libaprutil1-dev -[root@debian] chmod o+x "/home/openproject" -``` - -Now, the Passenger gem is installed and integrated into apache. - -```bash -[root@debian] su - openproject -c "bash -l" -[openproject@debian] cd ~/openproject -[openproject@debian] gem install passenger -[openproject@debian] passenger-install-apache2-module -``` - -Follow the instructions passenger provides. -The passenger installer will ask you the question in "Which languages are you interested in?". We are interested only in ruby. - -As told by the installer, add this lines to `/etc/apache2/apache2.conf`. -But before copy&pasting the following lines, check if the content (especially the version numbers!) is the same as the `passenger-install-apache2-module` installer said. When you're in doubt, do what passenger tells you. - -```apache -LoadModule passenger_module /home/openproject/.rvm/gems/ruby-2.1.5/gems/passenger-4.0.53/buildout/apache2/mod_passenger.so - - PassengerRoot /home/openproject/.rvm/gems/ruby-2.1.5/gems/passenger-4.0.53 - PassengerDefaultRuby /home/openproject/.rvm/gems/ruby-2.1.5/wrappers/ruby - -``` - -As the root user, create the file `/etc/apache2/conf.d/openproject.conf` with the following contents: - -```apache - - ServerName www.myopenprojectsite.com - # !!! Be sure to point DocumentRoot to 'public'! - DocumentRoot /home/openproject/openproject/public - - # This relaxes Apache security settings. - AllowOverride all - # MultiViews must be turned off. - Options -MultiViews - # Uncomment this if you're on Apache >= 2.4: - #Require all granted - - +[openproject@ubuntu] exit ``` -### Only on Ubuntu - -First, exit the current bash session with the `openproject` user, so that we are again in a root shell. Then, we prepare apache and passenger: ```bash -[openproject@ubuntu] exit -[root@ubuntu] apt-get install apache2 libcurl4-gnutls-dev apache2-threaded-dev libapr1-dev libaprutil1-dev +[root@host] apt-get install -y apache2 libcurl4-gnutls-dev \ + apache2-threaded-dev libapr1-dev \ + libaprutil1-dev [root@ubuntu] chmod o+x "/home/openproject" ``` -As a reference, the following version of apache was installed: - -```bash -[root@ubuntu] apache --version -``` - Now, the Passenger gem is installed and integrated into apache. -```bash -[root@ubuntu] su - openproject -c "bash -l" +[root@ubuntu] su openproject --login [openproject@ubuntu] cd ~/openproject [openproject@ubuntu] gem install passenger [openproject@ubuntu] passenger-install-apache2-module -``` - Follow the instructions passenger provides. -The passenger installer will ask you the question in "Which languages are you interested in?". We are interested only in ruby. +The passenger installer will ask you the question in "Which languages are you +interested in?". We are interested only in ruby. -The passenger installer tells us to edit the apache config files. To do this, continue as the root user: +The passenger installer tells us to edit the apache config files. +To do this, continue as the root user: ```bash -[openproject@ubuntu] exit +[openproject@host] exit ``` -As told by the installer, create the file `/etc/apache2/mods-available/passenger.load` and add the following line. -But before copy&pasting the following lines, check if the content (especially the version numbers!) is the same as the `passenger-install-apache2-module` installer said. When you're in doubt, do what passenger tells you. +As told by the installer, create the file /etc/apache2/mods-available/passenger.load and add the following line. +But before copy&pasting the following lines, check if the content (especially the version numbers!) is the same as the passenger-install-apache2-module installer said. When you're in doubt, do what passenger tells you. + ```apache -LoadModule passenger_module /home/openproject/.rvm/gems/ruby-2.1.5/gems/passenger-4.0.53/buildout/apache2/mod_passenger.so +LoadModule passenger_module /home/openproject/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/passenger-5.0.14/buildout/apache2/mod_passenger.so ``` -Then create the file `/etc/apache2/mods-available/passenger.conf` with the following contents (again, take care of the version numbers!): +Then create the file /etc/apache2/mods-available/passenger.conf with the following contents (again, take care of the version numbers!): ```apache - - PassengerRoot /home/openproject/.rvm/gems/ruby-2.1.5/gems/passenger-4.0.53 - PassengerDefaultRuby /home/openproject/.rvm/gems/ruby-2.1.5/wrappers/ruby - + + PassengerRoot /home/openproject/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/passenger-5.0.14 + PassengerDefaultRuby /home/openproject/.rbenv/versions/2.1.6/bin/ruby + ``` Then run: @@ -342,11 +280,13 @@ Then run: [root@openproject] a2enmod passenger ``` -As the root user, create the file `/etc/apache2/sites-available/openproject.conf` with the following contents: +As the root user, create the file /etc/apache2/sites-available/openproject.conf with the following contents: ```apache +SetEnv EXECJS_RUNTIME Disabled + - ServerName www.myopenprojectsite.com + ServerName yourdomain.com # !!! Be sure to point DocumentRoot to 'public'! DocumentRoot /home/openproject/openproject/public @@ -360,27 +300,23 @@ As the root user, create the file `/etc/apache2/sites-available/openproject.conf ``` -Let's enable our new `openproject` site (and disable the default site, if necessary) +Let's enable our new openproject site (and disable the default site, if necessary) ```bash -[root@ubuntu] a2dissite 000-default -[root@ubuntu] a2ensite openproject +[root@host] a2dissite 000-default +[root@host] a2ensite openproject ``` -### Debian and Ubuntu - Now, we (re-)start Apache: ```bash -[root@all] service apache2 reload +[root@host] service apache2 restart ``` Your OpenProject installation should be accessible on port 80 (http). A default admin-account is created for you having the following credentials: -```bash -Username: admin -Password: admin -``` +Username: `admin` +Password: `admin` Please, change the password on the first login. Also, we highly recommend to configure the SSL module in Apache for https communication. @@ -421,7 +357,7 @@ OpenProject plug-ins are separated in ruby gems. You can install them by listing ```ruby # Required by backlogs -gem "openproject-meeting", git: "https://github.com/finnlabs/openproject-meeting.git", :tag => "v4.1.0" +gem "openproject-meeting", git: "https://github.com/finnlabs/openproject-meeting.git", :tag => "v4.2.2" ``` If you have modified the `Gemfile.plugin` file, always repeat the following steps of the OpenProject installation: @@ -514,3 +450,4 @@ If you need to restart the server (for example after a configuration change), do If you have any further questions, comments, feedback, or an idea to enhance this guide, please tell us at the appropriate community [forum](https://community.openproject.org/projects/openproject/boards/9). [Follow OpenProject on twitter](https://twitter.com/openproject), and follow the news on [openproject.org](http://openproject.org) to stay up to date. + From a731ade28d692db3716334d3333e87e168fa112a Mon Sep 17 00:00:00 2001 From: kgalli Date: Tue, 21 Jul 2015 08:00:32 +0000 Subject: [PATCH 09/10] Fix for a lot of typos --- .../manual/installation-guide.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/operation_guides/manual/installation-guide.md b/doc/operation_guides/manual/installation-guide.md index 442839128b..28cba93add 100644 --- a/doc/operation_guides/manual/installation-guide.md +++ b/doc/operation_guides/manual/installation-guide.md @@ -3,8 +3,8 @@ This tutorial helps you to deploy OpenProject 4.2. Please, aware that: This guide requires that you have a clean Ubuntu 14.04 x64 installation -with administrative rights. We have tested the installation guide on a -on an Ubuntu Server image, but it should work on any derivative. +with administrative rights. We have tested the installation guide on an +Ubuntu Server image, but it should work on any derivative. OpenProject will be installed with a MySQL database (the guide should work analogous with PostgreSQL). @@ -19,13 +19,13 @@ Note: We have highlighted commands to execute like this [user@host] command to execute ``` -Where the user is the operating system user the command is executed with. +The `user` is the operating system user the command is executed with. In our case it will be `root` for most of the time or `openproject`. If you find any bugs or you have any recommendations for improving this tutorial, please, feel free to create a pull request against this guide. -# Prepare Your Envoironment +# Prepare Your Environment Create a dedicated user for OpenProject: @@ -134,7 +134,7 @@ something very similar to: v0.12.7 ``` -## Installation of OpenProoject +## Installation of OpenProject ```bash [openproject@host] cd ~ @@ -146,7 +146,7 @@ v0.12.7 [openproject@host] npm install ``` -## Configure Openproject +## Configure OpenProject Create and configure the database configuration file in config/database.yml (relative to the openproject-directory). @@ -157,7 +157,7 @@ Create and configure the database configuration file in config/database.yml Now we edit the `config/database.yml` file and insert our database credentials. It should look like this (please keep in mind that you have to use the values -you used above as you user, database and password): +you used above: user, database and password): ```yaml production: @@ -183,7 +183,7 @@ Next we configure email notifications (this example uses a gmail account) by cre [openproject@host] cp config/configuration.yml.example config/configuration.yml ``` -Now we edit the `configuration.yml` file to siut out needs. +Now we edit the `configuration.yml` file to suit our needs. ```yaml production: #main level @@ -197,17 +197,17 @@ production: #main level smtp_authentication: plain ``` -Add this line into `configuration.yml` file at the of of file for better -performance of OpenProject: +Add this line into `configuration.yml` file at the end of the file for +a better performance of OpenProject: ```yaml rails_cache_store: :memcache ``` -__NOTE:__ You should validate your .yml-files, for example with -http://www.yamllint.com/. Both, the database.yml and `configuration.yml` +__NOTE:__ You should validate your `yml` files, for example with +http://www.yamllint.com/. Both, the `database.yml` and `configuration.yml` file are sensitive to whitespace. It is pretty easy to write -`invalid .yml` files without seeing the error. Validating those files +invalid `yml` files without seeing the error. Validating those files prevents you from such errors. From 753e3605d84ee89b2d1911c6a3013ca91de6f730 Mon Sep 17 00:00:00 2001 From: kgalli Date: Tue, 21 Jul 2015 09:10:13 +0000 Subject: [PATCH 10/10] Replacement of analogous with similarly --- doc/operation_guides/manual/installation-guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/operation_guides/manual/installation-guide.md b/doc/operation_guides/manual/installation-guide.md index 28cba93add..7ae858eb47 100644 --- a/doc/operation_guides/manual/installation-guide.md +++ b/doc/operation_guides/manual/installation-guide.md @@ -7,10 +7,10 @@ with administrative rights. We have tested the installation guide on an Ubuntu Server image, but it should work on any derivative. OpenProject will be installed with a MySQL database (the guide should -work analogous with PostgreSQL). +work similarly with PostgreSQL). OpenProject will be served in a production environment with Apache -(this guide should work analogous with other servers, like nginx and others) +(this guide should work similarly with other servers, like nginx and others) Note: We have highlighted commands to execute like this