redmine-installer 1.0.7 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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