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,551 @@
1
+ require 'find'
2
+
3
+ module RedmineInstaller
4
+ class Redmine < TaskModule
5
+
6
+ attr_reader :database
7
+ attr_accessor :root
8
+
9
+ REQUIRED_FILES = [
10
+ 'app',
11
+ 'lib',
12
+ 'config',
13
+ 'public',
14
+ 'db',
15
+ 'Gemfile',
16
+ 'Rakefile',
17
+ 'config.ru',
18
+ File.join('lib', 'redmine'),
19
+ File.join('lib', 'redmine.rb'),
20
+ ]
21
+
22
+ DEFAULT_BACKUP_ROOT = File.join(Dir.home, 'redmine-backups')
23
+ BACKUP_EXCLUDE_FILES = ['log/', 'tmp/']
24
+
25
+ CHECK_N_INACCESSIBLE_FILES = 10
26
+
27
+ def initialize(task, root=nil)
28
+ super(task)
29
+ @root = root.to_s
30
+
31
+ if (dump = task.options.database_dump)
32
+ @database_dump_to_load = File.expand_path(dump)
33
+ end
34
+ end
35
+
36
+ def database_yml_path
37
+ File.join(root, 'config', 'database.yml')
38
+ end
39
+
40
+ def configuration_yml_path
41
+ File.join(root, 'config', 'configuration.yml')
42
+ end
43
+
44
+ def files_path
45
+ File.join(root, 'files')
46
+ end
47
+
48
+ def plugins_path
49
+ File.join(root, 'plugins')
50
+ end
51
+
52
+ def easy_plugins_path
53
+ File.join(plugins_path, 'easyproject', 'easy_plugins')
54
+ end
55
+
56
+ def log_path
57
+ File.join(root, 'log')
58
+ end
59
+
60
+ def pids_files
61
+ Dir.glob(File.join(root, 'tmp', 'pids', '*'))
62
+ end
63
+
64
+ def running?
65
+ pids_files.any?
66
+ end
67
+
68
+ def load_profile(profile)
69
+ @root = profile.redmine_root if root.empty?
70
+ @backup_type = profile.backup_type
71
+ @backup_root = profile.backup_root || profile.backup_dir
72
+
73
+ # Convert setting from v1
74
+ case @backup_type
75
+ when :full_backup
76
+ @backup_type = :full
77
+ when :backup, :only_database
78
+ @backup_type = :database
79
+ end
80
+
81
+ # Only valid setting
82
+ unless [:full, :database, :nothing].include?(@backup_type)
83
+ @backup_type = nil
84
+ end
85
+ end
86
+
87
+ def save_profile(profile)
88
+ profile.redmine_root = @root
89
+ profile.backup_type = @backup_type
90
+ profile.backup_root = @backup_root
91
+ end
92
+
93
+ # Ask for REDMINE_ROOT (if wasnt set) and check access rights
94
+ #
95
+ def ensure_and_valid_root
96
+ if root.empty?
97
+ puts
98
+ @root = prompt.ask('Path to redmine root:', required: true, default: 'redmine')
99
+ end
100
+
101
+ @root = File.expand_path(@root)
102
+
103
+ unless Dir.exist?(@root)
104
+ create_dir(@root)
105
+ end
106
+
107
+ logger.info("REDMINE_ROOT: #{@root}")
108
+
109
+ inaccessible_files = []
110
+
111
+ Find.find(@root).each do |item|
112
+ if !File.writable?(item) || !File.readable?(item)
113
+ inaccessible_files << item
114
+ end
115
+
116
+ if inaccessible_files.size > CHECK_N_INACCESSIBLE_FILES
117
+ break
118
+ end
119
+ end
120
+
121
+ if inaccessible_files.any?
122
+ error "Redmine root contains inaccessible files. Make sure that all files in #{@root} are readable/writeable for user #{env_user} (limit #{CHECK_N_INACCESSIBLE_FILES} files: #{inaccessible_files.join(', ')})"
123
+ end
124
+ end
125
+
126
+ # Check if redmine is running based on PID files.
127
+ #
128
+ def check_running_state
129
+ if running?
130
+ if prompt.yes?("Your app is running based on PID files (#{pids_files.join(', ')}). Do you want continue?", default: false)
131
+ logger.warn("App is running (pids: #{pids_files.join(', ')}). Ignore it and continue.")
132
+ else
133
+ error('App is running')
134
+ end
135
+ end
136
+ end
137
+
138
+ # Create and configure rails database
139
+ #
140
+ def create_database_yml
141
+ print_title('Creating database configuration')
142
+
143
+ @database = Database.create_config(self)
144
+ logger.info("Database initialized #{@database}")
145
+ end
146
+
147
+ # Create and configure configuration
148
+ # For now only email
149
+ #
150
+ def create_configuration_yml
151
+ print_title('Creating email configuration')
152
+
153
+ @configuration = Configuration.create_config(self)
154
+ logger.info("Configuration initialized #{@configuration}")
155
+ end
156
+
157
+ # Run install commands (command might ask for additional informations)
158
+ #
159
+ def install
160
+ print_title('Redmine installing')
161
+
162
+ Dir.chdir(root) do
163
+ # Gems can be locked on bad version
164
+ FileUtils.rm_f('Gemfile.lock')
165
+
166
+ # Install new gems
167
+ bundle_install
168
+
169
+ # Ensuring database
170
+ rake_db_create
171
+
172
+ # Load database dump (if was set via CLI)
173
+ load_database_dump
174
+
175
+ # Migrating
176
+ rake_db_migrate
177
+
178
+ # Plugin migrating
179
+ rake_redmine_plugin_migrate
180
+
181
+ # Generate secret token
182
+ rake_generate_secret_token
183
+
184
+ # Install easyproject
185
+ rake_easyproject_install if easyproject?
186
+ end
187
+ end
188
+
189
+ def upgrade
190
+ print_title('Redmine upgrading')
191
+
192
+ Dir.chdir(root) do
193
+ # Gems can be locked on bad version
194
+ FileUtils.rm_f('Gemfile.lock')
195
+
196
+ # Install new gems
197
+ bundle_install
198
+
199
+ # Migrating
200
+ rake_db_migrate
201
+
202
+ # Plugin migrating
203
+ rake_redmine_plugin_migrate
204
+
205
+ # Generate secret token
206
+ rake_generate_secret_token
207
+
208
+ # Install easyproject
209
+ rake_easyproject_install if easyproject?
210
+ end
211
+ end
212
+
213
+ # # => ['.', '..']
214
+ # def empty_root?
215
+ # Dir.entries(root).size <= 2
216
+ # end
217
+
218
+ def delete_root
219
+ Dir.chdir(root) do
220
+ Dir.entries('.').each do |entry|
221
+ next if entry == '.' || entry == '..'
222
+ FileUtils.remove_entry_secure(entry)
223
+ end
224
+ end
225
+
226
+ logger.info("#{root} content was deleted")
227
+ end
228
+
229
+ def move_from(other_redmine)
230
+ Dir.chdir(other_redmine.root) do
231
+ Dir.entries('.').each do |entry|
232
+ next if entry == '.' || entry == '..'
233
+ FileUtils.mv(entry, root)
234
+ end
235
+ end
236
+
237
+ logger.info("Copyied from #{other_redmine.root} into #{root}")
238
+ end
239
+
240
+ # Copy important files which cannot be deleted
241
+ #
242
+ def copy_importants_from(other_redmine)
243
+ Dir.chdir(root) do
244
+ # Copy database.yml
245
+ FileUtils.cp(other_redmine.database_yml_path, database_yml_path)
246
+
247
+ # Copy configuration.yml
248
+ if File.exist?(other_redmine.configuration_yml_path)
249
+ FileUtils.cp(other_redmine.configuration_yml_path, configuration_yml_path)
250
+ end
251
+
252
+ # Copy files
253
+ FileUtils.cp_r(other_redmine.files_path, root)
254
+
255
+ # Copy old logs
256
+ FileUtils.mkdir_p(log_path)
257
+ Dir.glob(File.join(other_redmine.log_path, 'redmine_installer_*')).each do |log|
258
+ FileUtils.cp(log, log_path)
259
+ end
260
+ end
261
+
262
+ # Copy 'keep' files (base on options)
263
+ Array(task.options.keep).each do |path|
264
+ origin_path = File.join(other_redmine.root, path)
265
+ next unless File.exist?(origin_path)
266
+
267
+ # Ensure folder
268
+ target_dir = File.join(root, File.dirname(path))
269
+ FileUtils.mkdir_p(target_dir)
270
+
271
+ # Copy recursive
272
+ FileUtils.cp_r(origin_path, target_dir)
273
+ end
274
+
275
+ logger.info('Important files was copyied')
276
+ end
277
+
278
+ # New package may not have all plugins
279
+ #
280
+ def copy_missing_plugins_from(other_redmine)
281
+ # Copy missing redmine plugins
282
+ Dir.chdir(other_redmine.plugins_path) do
283
+ Dir.entries('.').each do |plugin|
284
+ next if plugin == '.' || plugin == '..'
285
+
286
+ # Plugin is not directory
287
+ unless File.directory?(plugin)
288
+ next
289
+ end
290
+
291
+ to = File.join(plugins_path, plugin)
292
+
293
+ # Plugins does not exist
294
+ unless Dir.exist?(to)
295
+ FileUtils.cp_r(plugin, to)
296
+ end
297
+ end
298
+ end
299
+
300
+ # Copy missing client modification plugin
301
+ if easyproject?
302
+ old_modifications = Dir.glob(File.join(other_redmine.easy_plugins_path, 'modification_*'))
303
+ old_modifications.each do |old_modification_path|
304
+ next if !File.directory?(old_modification_path)
305
+
306
+ basename = File.basename(old_modification_path)
307
+
308
+ new_modification_path = File.join(easy_plugins_path, basename)
309
+ next if File.exist?(new_modification_path)
310
+
311
+ FileUtils.cp_r(old_modification_path, new_modification_path)
312
+ end
313
+ end
314
+ end
315
+
316
+ def validate
317
+ # Check for required files
318
+ Dir.chdir(root) do
319
+ REQUIRED_FILES.each do |path|
320
+ unless File.exist?(path)
321
+ error "Redmine #{root} is not valid. Missing #{path}."
322
+ end
323
+ end
324
+ end
325
+
326
+ # Plugins are in right dir
327
+ Dir.glob(File.join(root, 'vendor', 'plugins', '*')).each do |path|
328
+ if File.directory?(path)
329
+ error "Plugin should be on plugins dir. On vendor/plugins is #{path}"
330
+ end
331
+ end
332
+ end
333
+
334
+ # Backup:
335
+ # - full redmine (except log, tmp)
336
+ # - production database
337
+ def make_backup
338
+ print_title('Data backup')
339
+
340
+ @backup_type ||= prompt.select('What type of backup do you want?',
341
+ 'Full (redmine root and database)' => :full,
342
+ 'Only database' => :database,
343
+ 'Nothing' => :nothing)
344
+
345
+ logger.info("Backup type: #{@backup_type}")
346
+
347
+ # Dangerous option
348
+ if @backup_type == :nothing
349
+ if prompt.yes?('Are you sure you dont want backup?', default: false)
350
+ logger.info('Backup option nothing was confirmed')
351
+ return
352
+ else
353
+ @backup_type = nil
354
+ return make_backup
355
+ end
356
+ end
357
+
358
+ @backup_root ||= prompt.ask('Where to save backup:', required: true, default: DEFAULT_BACKUP_ROOT)
359
+ @backup_root = File.expand_path(@backup_root)
360
+
361
+ @backup_dir = File.join(@backup_root, Time.now.strftime('backup_%d%m%Y_%H%M%S'))
362
+ create_dir(@backup_dir)
363
+
364
+ files_to_backup = []
365
+ Dir.chdir(root) do
366
+ case @backup_type
367
+ when :full
368
+ files_to_backup = Dir.glob(File.join('**', '{*,.*}'))
369
+ end
370
+ end
371
+
372
+ if files_to_backup.any?
373
+ files_to_backup.delete_if do |path|
374
+ path.start_with?(*BACKUP_EXCLUDE_FILES)
375
+ end
376
+
377
+ @backup_package = File.join(@backup_dir, 'redmine.zip')
378
+
379
+ Dir.chdir(root) do
380
+ puts
381
+ puts 'Files backuping'
382
+ Zip::File.open(@backup_package, Zip::File::CREATE) do |zipfile|
383
+ progressbar = TTY::ProgressBar.new(PROGRESSBAR_FORMAT, total: files_to_backup.size, frequency: 2, clear: true)
384
+
385
+ files_to_backup.each do |entry|
386
+ zipfile.add(entry, entry)
387
+ progressbar.advance(1)
388
+ end
389
+
390
+ progressbar.finish
391
+ end
392
+ end
393
+
394
+ puts "Files backed up on #{@backup_package}"
395
+ logger.info('Files backed up')
396
+ end
397
+
398
+ @database = Database.init(self)
399
+ @database.make_backup(@backup_dir)
400
+
401
+ puts "Database backed up on #{@database.backup}"
402
+ logger.info('Database backed up')
403
+ end
404
+
405
+ def valid_options
406
+ if @database_dump_to_load && !File.exist?(@database_dump_to_load)
407
+ error "Database dump #{@database_dump_to_load} does not exist (path is expanded)."
408
+ end
409
+ end
410
+
411
+ def clean_up
412
+ end
413
+
414
+ private
415
+
416
+ def bundle_install
417
+ gemfile = File.join(root, 'Gemfile')
418
+ status = run_command("bundle install #{task.options.bundle_options} --gemfile #{gemfile}", 'Bundle install')
419
+
420
+ # Even if bundle could not install all gem EXIT_SUCCESS is returned
421
+ if !status || !File.exist?('Gemfile.lock')
422
+ puts
423
+ selected = prompt.select("Gemfile.lock wasn't created. Please choose one option:",
424
+ 'Try again' => :try_again,
425
+ 'Change bundle options' => :change_options,
426
+ 'Cancel' => :cancel)
427
+
428
+ case selected
429
+ when :try_again
430
+ bundle_install
431
+ when :change_options
432
+ task.options.bundle_options = prompt.ask('New options:', default: task.options.bundle_options)
433
+ bundle_install
434
+ when :cancel
435
+ error('Operation canceled by user')
436
+ end
437
+ end
438
+ end
439
+
440
+ def rake_db_create
441
+ # Always return 0
442
+ run_command('RAILS_ENV=production bundle exec rake db:create', 'Database creating')
443
+ end
444
+
445
+ def rake_db_migrate
446
+ status = run_command('RAILS_ENV=production bundle exec rake db:migrate', 'Database migrating')
447
+
448
+ unless status
449
+ puts
450
+ selected = prompt.select('Migration end with error. Please choose one option:',
451
+ 'Try again' => :try_again,
452
+ 'Create database first' => :create_database,
453
+ 'Change database configuration' => :change_configuration,
454
+ 'Cancel' => :cancel)
455
+
456
+ case selected
457
+ when :try_again
458
+ rake_db_migrate
459
+ when :create_database
460
+ rake_db_create
461
+ rake_db_migrate
462
+ when :change_configuration
463
+ create_database_yml
464
+ rake_db_migrate
465
+ when :cancel
466
+ error('Operation canceled by user')
467
+ end
468
+ end
469
+ end
470
+
471
+ def rake_redmine_plugin_migrate
472
+ status = run_command('RAILS_ENV=production bundle exec rake redmine:plugins:migrate', 'Plugins migration')
473
+
474
+ unless status
475
+ puts
476
+ selected = prompt.select('Plugin migration end with error. Please choose one option:',
477
+ 'Try again' => :try_again,
478
+ 'Continue' => :continue,
479
+ 'Cancel' => :cancel)
480
+
481
+ case selected
482
+ when :try_again
483
+ rake_redmine_plugin_migrate
484
+ when :continue
485
+ logger.warn('Plugin migration end with error but step was skipped.')
486
+ when :cancel
487
+ error('Operation canceled by user')
488
+ end
489
+ end
490
+ end
491
+
492
+ def rake_generate_secret_token
493
+ status = run_command('RAILS_ENV=production bundle exec rake generate_secret_token', 'Generating secret token')
494
+
495
+ unless status
496
+ puts
497
+ selected = prompt.select('Secret token could not be created. Please choose one option:',
498
+ 'Try again' => :try_again,
499
+ 'Continue' => :continue,
500
+ 'Cancel' => :cancel)
501
+
502
+ case selected
503
+ when :try_again
504
+ rake_generate_secret_token
505
+ when :continue
506
+ logger.warn('Secret token could not be created but step was skipped.')
507
+ when :cancel
508
+ error('Operation canceled by user')
509
+ end
510
+ end
511
+ end
512
+
513
+ def rake_easyproject_install
514
+ status = run_command('RAILS_ENV=production bundle exec rake easyproject:install', 'Installing easyproject')
515
+
516
+ unless status
517
+ puts
518
+ selected = prompt.select('Easyproject could not be installed. Please choose one option:',
519
+ 'Try again' => :try_again,
520
+ 'Cancel' => :cancel)
521
+
522
+ case selected
523
+ when :try_again
524
+ rake_easyproject_install
525
+ when :cancel
526
+ error('Operation canceled by user')
527
+ end
528
+ end
529
+ end
530
+
531
+ def easyproject?
532
+ Dir.entries(plugins_path).include?('easyproject')
533
+ end
534
+
535
+ def load_database_dump
536
+ return if @database_dump_to_load.nil?
537
+
538
+ selected = prompt.select('Database dump will be loaded. Before that all data must be destroy.',
539
+ 'Skip dump loading' => :cancel,
540
+ 'I am aware of this. Want to continue' => :continue)
541
+
542
+ if selected == :continue
543
+ @database.do_restore(@database_dump_to_load)
544
+ logger.info('Database dump was loaded.')
545
+ else
546
+ logger.info('Database dump loading was skipped.')
547
+ end
548
+ end
549
+
550
+ end
551
+ end