redmine-installer 1.0.7 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +3 -15
  5. data/README.md +49 -177
  6. data/bin/redmine +4 -5
  7. data/lib/redmine-installer/backup.rb +13 -40
  8. data/lib/redmine-installer/cli.rb +92 -61
  9. data/lib/redmine-installer/command.rb +63 -117
  10. data/lib/redmine-installer/configuration.rb +148 -0
  11. data/lib/redmine-installer/database.rb +204 -0
  12. data/lib/redmine-installer/environment.rb +21 -0
  13. data/lib/redmine-installer/errors.rb +7 -0
  14. data/lib/redmine-installer/install.rb +37 -42
  15. data/lib/redmine-installer/logger.rb +83 -0
  16. data/lib/redmine-installer/package.rb +180 -0
  17. data/lib/redmine-installer/patches/ruby.rb +35 -0
  18. data/lib/redmine-installer/patches/tty.rb +16 -0
  19. data/lib/redmine-installer/profile.rb +24 -55
  20. data/lib/redmine-installer/redmine.rb +551 -0
  21. data/lib/redmine-installer/spec/spec.rb +81 -0
  22. data/lib/redmine-installer/task.rb +18 -77
  23. data/lib/redmine-installer/task_module.rb +12 -0
  24. data/lib/redmine-installer/upgrade.rb +51 -59
  25. data/lib/redmine-installer/utils.rb +46 -233
  26. data/lib/redmine-installer/version.rb +2 -4
  27. data/lib/redmine-installer.rb +69 -56
  28. data/redmine-installer.gemspec +20 -19
  29. data/spec/custom_matchers.rb +21 -0
  30. data/spec/installer_helper.rb +107 -0
  31. data/spec/installer_process.rb +82 -0
  32. data/spec/lib/backup_restore_spec.rb +81 -0
  33. data/spec/lib/install_spec.rb +125 -36
  34. data/spec/lib/upgrade_spec.rb +73 -52
  35. data/spec/packages/redmine-3.1.0.zip +0 -0
  36. data/spec/packages/redmine-3.2.0.zip +0 -0
  37. data/spec/packages/redmine-3.3.0-bad-migration.zip +0 -0
  38. data/spec/packages/redmine-3.3.0.zip +0 -0
  39. data/spec/packages/something-else.zip +0 -0
  40. data/spec/packages_helper.rb +19 -0
  41. data/spec/shared_contexts.rb +13 -0
  42. data/spec/spec_helper.rb +34 -18
  43. metadata +62 -63
  44. data/lib/redmine-installer/config_param.rb +0 -100
  45. data/lib/redmine-installer/error.rb +0 -5
  46. data/lib/redmine-installer/exec.rb +0 -158
  47. data/lib/redmine-installer/ext/module.rb +0 -7
  48. data/lib/redmine-installer/ext/string.rb +0 -15
  49. data/lib/redmine-installer/git.rb +0 -51
  50. data/lib/redmine-installer/helper.rb +0 -5
  51. data/lib/redmine-installer/helpers/generate_config.rb +0 -29
  52. data/lib/redmine-installer/locales/cs.yml +0 -147
  53. data/lib/redmine-installer/locales/en.yml +0 -154
  54. data/lib/redmine-installer/plugin.rb +0 -9
  55. data/lib/redmine-installer/plugins/base.rb +0 -24
  56. data/lib/redmine-installer/plugins/database.rb +0 -180
  57. data/lib/redmine-installer/plugins/email_sending.rb +0 -82
  58. data/lib/redmine-installer/plugins/redmine_plugin.rb +0 -82
  59. data/lib/redmine-installer/plugins/web_server.rb +0 -26
  60. data/lib/redmine-installer/step.rb +0 -16
  61. data/lib/redmine-installer/steps/backup.rb +0 -125
  62. data/lib/redmine-installer/steps/base.rb +0 -79
  63. data/lib/redmine-installer/steps/database_config.rb +0 -15
  64. data/lib/redmine-installer/steps/email_config.rb +0 -11
  65. data/lib/redmine-installer/steps/env_check.rb +0 -20
  66. data/lib/redmine-installer/steps/install.rb +0 -23
  67. data/lib/redmine-installer/steps/load_package.rb +0 -226
  68. data/lib/redmine-installer/steps/move_redmine.rb +0 -22
  69. data/lib/redmine-installer/steps/redmine_root.rb +0 -52
  70. data/lib/redmine-installer/steps/upgrade.rb +0 -57
  71. data/lib/redmine-installer/steps/validation.rb +0 -38
  72. data/lib/redmine-installer/steps/webserver_config.rb +0 -22
  73. data/spec/load_redmine.rb +0 -24
@@ -0,0 +1,21 @@
1
+ module RedmineInstaller
2
+ class Environment < TaskModule
3
+
4
+ def check
5
+ if user_is_root? && !task.options.enable_user_root
6
+ error 'You cannot install redmine under root. Please change user.'
7
+ end
8
+
9
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new(RedmineInstaller::MIN_SUPPORTED_RUBY)
10
+ error "You are using unsupported ruby. Please install at least #{RedmineInstaller::MIN_SUPPORTED_RUBY}."
11
+ end
12
+
13
+ logger.info 'Environemnt checked'
14
+ end
15
+
16
+ def user_is_root?
17
+ Process.euid == 0
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module RedmineInstaller
2
+ class Error < StandardError
3
+ end
4
+
5
+ class ProfileError < Error
6
+ end
7
+ end
@@ -1,50 +1,45 @@
1
- ##
2
- # Install redmine
3
- #
4
- # You can instal redmine package from archive or git.
5
- #
6
- # == Steps:
7
- # 1. Redmine root - where should be new redmine located
8
- # 2. Load package - extract package
9
- # 3. Database configuration - you can choose type of DB which you want to use
10
- # 4. Email sending configuration - email sending configuration
11
- # 5. Install - install commands are executed
12
- # 6. Moving redmine - redmine is moved from temporarily folder to given redmine_root
13
- # 7. Webserve configuration - generating webserver configuration
14
- #
15
- # == Usage:
16
- #
17
- # From archive::
18
- # Supported archives are .zip and .tar.gz.
19
- #
20
- # # minimal
21
- # redmine install PATH_TO_PACKAGE
22
- # redmine install REDMINE_VERSION
23
- #
24
- # # full
25
- # redmine install PATH_TO_PACKAGE --env ENV1,ENV2,ENV3
26
- #
27
- module Redmine::Installer
1
+ module RedmineInstaller
28
2
  class Install < Task
29
3
 
30
- STEPS = [
31
- step::EnvCheck,
32
- step::RedmineRoot,
33
- step::LoadPackage,
34
- step::DatabaseConfig,
35
- step::EmailConfig,
36
- step::Install,
37
- step::MoveRedmine,
38
- step::WebserverConfig
39
- ]
4
+ def initialize(package, redmine_root, **options)
5
+ super(**options)
40
6
 
41
- attr_accessor :package
7
+ @environment = Environment.new(self)
8
+ @package = Package.new(self, package)
9
+ @target_redmine = Redmine.new(self, redmine_root)
10
+ @temp_redmine = Redmine.new(self)
11
+ end
12
+
13
+ def up
14
+ @temp_redmine.valid_options
15
+ @environment.check
16
+ @target_redmine.ensure_and_valid_root
17
+ @package.ensure_and_valid_package
18
+ @package.extract
19
+
20
+ @temp_redmine.root = @package.redmine_root
21
+
22
+ @temp_redmine.create_database_yml
23
+ @temp_redmine.create_configuration_yml
24
+ @temp_redmine.install
25
+
26
+ print_title('Finishing installation')
27
+ ok('Cleaning root'){ @target_redmine.delete_root }
28
+ ok('Moving redmine to target directory'){ @target_redmine.move_from(@temp_redmine) }
29
+ ok('Cleanning up'){ @package.clean_up }
30
+ ok('Moving installer log'){ logger.move_to(@target_redmine, suffix: 'install') }
31
+
32
+ puts
33
+ puts pastel.bold('Redmine was installed')
34
+ logger.info('Redmine was installed')
35
+ end
42
36
 
43
- def initialize(package, options={})
44
- self.package = package
45
- super(options)
37
+ def down
38
+ @temp_redmine.clean_up
39
+ @package.clean_up
46
40
 
47
- check_package
41
+ puts
42
+ puts "(Log is located on #{pastel.bold(logger.path)})"
48
43
  end
49
44
 
50
45
  end
@@ -0,0 +1,83 @@
1
+ require 'digest'
2
+
3
+ module RedmineInstaller
4
+ class Logger
5
+
6
+ def self.verify(log_file)
7
+ log_file = log_file.to_s
8
+
9
+ unless File.exist?(log_file)
10
+ puts "File '#{log_file}' does not exist."
11
+ exit(false)
12
+ end
13
+
14
+ content = File.open(log_file, &:to_a)
15
+ digest1 = content.pop
16
+ digest2 = Digest::SHA256.hexdigest(content.join)
17
+
18
+ if digest1 == digest2
19
+ puts RedmineInstaller.pastel.green("Logfile is OK. Digest verified.")
20
+ else
21
+ puts RedmineInstaller.pastel.red("Logfile is not OK. Digest wasn't verified.")
22
+ end
23
+ end
24
+
25
+ def initialize
26
+ if ENV['REDMINE_INSTALLER_LOGFILE']
27
+ @output = File.open(ENV['REDMINE_INSTALLER_LOGFILE'], 'w')
28
+ else
29
+ @output = Tempfile.create('redmine_installer.log')
30
+ end
31
+ end
32
+
33
+ def path
34
+ @output.path
35
+ end
36
+
37
+ def finish
38
+ close
39
+ digest = Digest::SHA256.file(path).hexdigest
40
+ File.open(path, 'a') { |f| f.write(digest) }
41
+ end
42
+
43
+ def close
44
+ @output.flush
45
+ @output.close
46
+ end
47
+
48
+ def move_to(redmine, suffix: '%d%m%Y_%H%M%S')
49
+ close
50
+
51
+ new_path = File.join(redmine.log_path, Time.now.strftime("redmine_installer_#{suffix}.log"))
52
+
53
+ FileUtils.mkdir_p(redmine.log_path)
54
+ FileUtils.mv(path, new_path)
55
+ @output = File.open(new_path, 'a+')
56
+ end
57
+
58
+ def info(*messages)
59
+ log(' INFO', *messages)
60
+ end
61
+
62
+ def error(*messages)
63
+ log('ERROR', *messages)
64
+ end
65
+
66
+ def warn(*messages)
67
+ log(' WARN', *messages)
68
+ end
69
+
70
+ def std(*messages)
71
+ log(' STD', *messages)
72
+ end
73
+
74
+ def log(severity, *messages)
75
+ messages.each do |message|
76
+ @output.puts("#{severity}: #{message}")
77
+ end
78
+
79
+ @output.flush
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,180 @@
1
+ require 'rubygems/package'
2
+ require 'net/http'
3
+ require 'zlib'
4
+ require 'uri'
5
+
6
+ module RedmineInstaller
7
+ class Package < TaskModule
8
+
9
+ SUPPORTED_ARCHIVE_FORMATS = ['.zip', '.gz', '.tgz']
10
+ TAR_LONGLINK = '././@LongLink'
11
+
12
+ attr_reader :package
13
+
14
+ def initialize(task, package)
15
+ super(task)
16
+ @package = package.to_s
17
+ end
18
+
19
+ def ensure_and_valid_package
20
+ if package.empty?
21
+ @package = prompt.ask('Path to package:', required: true)
22
+ end
23
+
24
+ if !File.exist?(@package)
25
+ if @package =~ /\Av?(\d\.\d\.\d)\Z/
26
+ @package = download_redmine($1)
27
+ else
28
+ error "File doesn't exist #{@package}"
29
+ end
30
+ end
31
+
32
+ @type = File.extname(@package)
33
+ unless SUPPORTED_ARCHIVE_FORMATS.include?(@type)
34
+ error "File #{@package} must have format: #{SUPPORTED_ARCHIVE_FORMATS.join(', ')}"
35
+ end
36
+ end
37
+
38
+ def extract
39
+ print_title('Extracting redmine package')
40
+
41
+ @temp_dir = Dir.mktmpdir
42
+
43
+ case @type
44
+ when '.zip'
45
+ extract_zip
46
+ when '.gz', '.tgz'
47
+ extract_tar_gz
48
+ end
49
+
50
+ logger.info("Package was loaded into #{@temp_dir}.")
51
+ end
52
+
53
+ # Move files from temp dir to target. First check if folder contains redmine
54
+ # or contains folder which contains redmine.
55
+ #
56
+ # Package can have format:
57
+ # |-- redmine-2
58
+ # |-- app
59
+ # `-- config
60
+ # ...
61
+ #
62
+ def redmine_root
63
+ root = @temp_dir
64
+
65
+ loop {
66
+ ls = Dir.glob(File.join(root, '*'))
67
+
68
+ if ls.size == 1
69
+ root = ls.first
70
+ else
71
+ break
72
+ end
73
+ }
74
+
75
+ root
76
+ end
77
+
78
+ def clean_up
79
+ @temp_dir && FileUtils.remove_entry_secure(@temp_dir)
80
+ @temp_file && FileUtils.remove_entry_secure(@temp_file)
81
+ end
82
+
83
+ private
84
+
85
+ def download_redmine(version)
86
+ @temp_file = Tempfile.new(['redmine', '.zip'])
87
+ @temp_file.binmode
88
+
89
+ uri = URI("http://www.redmine.org/releases/redmine-#{version}.zip")
90
+
91
+ Net::HTTP.start(uri.host, uri.port) do |http|
92
+ head = http.request_head(uri)
93
+
94
+ unless head.is_a?(Net::HTTPSuccess)
95
+ error "Cannot download redmine #{version}"
96
+ end
97
+
98
+ print_title("Downloading redmine #{version}")
99
+ progressbar = TTY::ProgressBar.new(PROGRESSBAR_FORMAT, total: head['content-length'].to_i, frequency: 2, clear: true)
100
+
101
+ http.get(uri) do |data|
102
+ @temp_file.write(data)
103
+ progressbar.advance(data.size)
104
+ end
105
+
106
+ progressbar.finish
107
+ end
108
+
109
+ logger.info("Redmine #{version} downloaded")
110
+
111
+ @temp_file.close
112
+ @temp_file.path
113
+ end
114
+
115
+ def extract_zip
116
+ Zip::File.open(@package) do |zip_file|
117
+ # Progressbar
118
+ progressbar = TTY::ProgressBar.new(PROGRESSBAR_FORMAT, total: zip_file.size, frequency: 2, clear: true)
119
+
120
+ zip_file.each do |entry|
121
+ dest_file = File.join(@temp_dir, entry.name)
122
+ FileUtils.mkdir_p(File.dirname(dest_file))
123
+
124
+ entry.extract(dest_file)
125
+ progressbar.advance(1)
126
+ end
127
+
128
+ progressbar.finish
129
+ end
130
+
131
+ end
132
+
133
+ # Extract .tar.gz archive
134
+ # based on http://dracoater.blogspot.cz/2013/10/extracting-files-from-targz-with-ruby.html
135
+ #
136
+ # Originally tar did not support paths longer than 100 chars. GNU tar is better and they
137
+ # implemented support for longer paths, but it was made through a hack called ././@LongLink.
138
+ # Shortly speaking, if you stumble upon an entry in tar archive which path equals to above
139
+ # mentioned ././@LongLink, that means that the following entry path is longer than 100 chars and
140
+ # is truncated. The full path of the following entry is actually the value of the current entry.
141
+ #
142
+ def extract_tar_gz
143
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(@package)) do |tar|
144
+
145
+ # Progressbar
146
+ progressbar = TTY::ProgressBar.new(PROGRESSBAR_FORMAT, total: tar.count, frequency: 2, clear: true)
147
+
148
+ # tar.count move position pointer to end
149
+ tar.rewind
150
+
151
+ dest_file = nil
152
+ tar.each do |entry|
153
+ if entry.full_name == TAR_LONGLINK
154
+ dest_file = File.join(@temp_dir, entry.read.strip)
155
+ next
156
+ end
157
+ dest_file ||= File.join(@temp_dir, entry.full_name)
158
+ if entry.directory?
159
+ FileUtils.rm_rf(dest_file) unless File.directory?(dest_file)
160
+ FileUtils.mkdir_p(dest_file, mode: entry.header.mode, verbose: false)
161
+ elsif entry.file?
162
+ FileUtils.rm_rf(dest_file) unless File.file?(dest_file)
163
+ File.open(dest_file, 'wb') do |f|
164
+ f.write(entry.read)
165
+ end
166
+ FileUtils.chmod(entry.header.mode, dest_file, verbose: false)
167
+ elsif entry.header.typeflag == '2' # symlink
168
+ File.symlink(entry.header.linkname, dest_file)
169
+ end
170
+
171
+ dest_file = nil
172
+ progressbar.advance(1)
173
+ end
174
+
175
+ progressbar.finish
176
+ end
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,35 @@
1
+ class Object
2
+
3
+ def blank?
4
+ false
5
+ end
6
+
7
+ def present?
8
+ true
9
+ end
10
+
11
+ end
12
+
13
+ class String
14
+
15
+ def blank?
16
+ empty?
17
+ end
18
+
19
+ def present?
20
+ !blank?
21
+ end
22
+
23
+ end
24
+
25
+ class NilClass
26
+
27
+ def blank?
28
+ true
29
+ end
30
+
31
+ def present?
32
+ false
33
+ end
34
+
35
+ end
@@ -0,0 +1,16 @@
1
+ # module TTY
2
+ # class ProgressBar
3
+ # # Every progressbar register new signal handlers
4
+ # def register_signals
5
+ # end
6
+ # end
7
+ # end
8
+
9
+ # module TTY
10
+ # class Prompt
11
+ # class List
12
+ # def keyescape(*)
13
+ # end
14
+ # end
15
+ # end
16
+ # end
@@ -1,73 +1,42 @@
1
- require 'fileutils'
1
+ require 'ostruct'
2
2
  require 'yaml'
3
3
 
4
- module Redmine::Installer
5
- class Profile
4
+ module RedmineInstaller
5
+ class Profile < OpenStruct
6
+ PROFILES_FILE = File.join(Dir.home, '.redmine-installer-profiles.yml')
6
7
 
7
- include Redmine::Installer::Utils
8
+ def self.get!(profile_id)
9
+ data = YAML.load_file(PROFILES_FILE) rescue nil
8
10
 
9
- CONFIG_FILE = File.join(Dir.home, '.redmine-installer-profiles.yml')
10
-
11
- def self.save(task)
12
- return unless check_writable
13
- return unless confirm(:do_you_want_save_step_for_further_use, true)
14
-
15
- profile = Profile.new(task)
16
- profile.save
17
-
18
- say t(:your_profile_can_be_used_as, id: profile.id), 2
19
- end
20
-
21
- def self.load(task, id)
22
- profile = Profile.new(task)
23
- profile.load(id)
24
- end
25
-
26
- def self.check_writable
27
- FileUtils.touch(CONFIG_FILE)
28
- File.writable?(CONFIG_FILE)
11
+ if data.is_a?(Hash) && data.has_key?(profile_id)
12
+ Profile.new(profile_id, data[profile_id])
13
+ else
14
+ raise RedmineInstaller::ProfileError, "Profile ID=#{profile_id} does not exist"
15
+ end
29
16
  end
30
17
 
31
- attr_accessor :task
32
-
33
- def initialize(task)
34
- self.task = task
18
+ attr_reader :id
35
19
 
36
- # Load profiles
37
- @data = YAML.load_file(CONFIG_FILE) rescue nil
38
-
39
- # Make empty Hash if there is no profiles
40
- @data = {} unless @data.is_a?(Hash)
41
- end
42
-
43
- def id
44
- @id ||= @data.keys.map(&:to_i).max.to_i + 1
20
+ def initialize(id=nil, data={})
21
+ super(data)
22
+ @id = id
45
23
  end
46
24
 
47
25
  def save
48
- # All steps save configuration which can be use again
49
- configuration = {}
50
- task.steps.each do |_, step|
51
- step.save(configuration)
52
- end
26
+ FileUtils.touch(PROFILES_FILE)
53
27
 
54
- @data[id] = configuration
28
+ all_data = YAML.load_file(PROFILES_FILE)
29
+ all_data = {} unless all_data.is_a?(Hash)
55
30
 
56
- File.open(CONFIG_FILE, 'w') {|f| f.puts(YAML.dump(@data))}
57
- end
58
-
59
- def load(id)
60
- @id = id.to_i
31
+ @id ||= all_data.keys.last.to_i + 1
61
32
 
62
- configuration = @data[@id]
33
+ all_data[@id] = to_h
63
34
 
64
- return {} if configuration.nil?
65
-
66
- task.steps.each do |_, step|
67
- step.load(configuration)
68
- end
35
+ File.write(PROFILES_FILE, YAML.dump(all_data))
69
36
 
70
- return configuration
37
+ puts "Profile was saved under ID=#{@id}"
38
+ rescue => e
39
+ puts "Profile could not be save due to #{e.message}"
71
40
  end
72
41
 
73
42
  end