redmine-installer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +273 -0
- data/Rakefile +7 -0
- data/bin/redmine +8 -0
- data/lib/redmine-installer.rb +72 -0
- data/lib/redmine-installer/backup.rb +48 -0
- data/lib/redmine-installer/cli.rb +90 -0
- data/lib/redmine-installer/command.rb +142 -0
- data/lib/redmine-installer/config_param.rb +90 -0
- data/lib/redmine-installer/error.rb +5 -0
- data/lib/redmine-installer/exec.rb +158 -0
- data/lib/redmine-installer/ext/module.rb +7 -0
- data/lib/redmine-installer/ext/string.rb +15 -0
- data/lib/redmine-installer/git.rb +51 -0
- data/lib/redmine-installer/helper.rb +5 -0
- data/lib/redmine-installer/helpers/generate_config.rb +29 -0
- data/lib/redmine-installer/install.rb +56 -0
- data/lib/redmine-installer/locales/cs.yml +145 -0
- data/lib/redmine-installer/locales/en.yml +146 -0
- data/lib/redmine-installer/plugin.rb +9 -0
- data/lib/redmine-installer/plugins/base.rb +28 -0
- data/lib/redmine-installer/plugins/database.rb +168 -0
- data/lib/redmine-installer/plugins/email_sending.rb +82 -0
- data/lib/redmine-installer/plugins/redmine_plugin.rb +30 -0
- data/lib/redmine-installer/plugins/web_server.rb +26 -0
- data/lib/redmine-installer/profile.rb +74 -0
- data/lib/redmine-installer/step.rb +15 -0
- data/lib/redmine-installer/steps/backup.rb +120 -0
- data/lib/redmine-installer/steps/base.rb +60 -0
- data/lib/redmine-installer/steps/database_config.rb +15 -0
- data/lib/redmine-installer/steps/email_config.rb +11 -0
- data/lib/redmine-installer/steps/install.rb +21 -0
- data/lib/redmine-installer/steps/load_package.rb +180 -0
- data/lib/redmine-installer/steps/move_redmine.rb +22 -0
- data/lib/redmine-installer/steps/redmine_root.rb +29 -0
- data/lib/redmine-installer/steps/upgrade.rb +55 -0
- data/lib/redmine-installer/steps/validation.rb +38 -0
- data/lib/redmine-installer/steps/webserver_config.rb +22 -0
- data/lib/redmine-installer/task.rb +85 -0
- data/lib/redmine-installer/upgrade.rb +68 -0
- data/lib/redmine-installer/utils.rb +233 -0
- data/lib/redmine-installer/version.rb +5 -0
- data/redmine-installer.gemspec +25 -0
- data/spec/lib/install_spec.rb +46 -0
- data/spec/lib/upgrade_spec.rb +62 -0
- data/spec/load_redmine.rb +24 -0
- data/spec/spec_helper.rb +30 -0
- metadata +130 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
##
|
2
|
+
# Parent for all commands (Install, Upgrade, Backup)
|
3
|
+
#
|
4
|
+
module Redmine::Installer
|
5
|
+
class Task
|
6
|
+
|
7
|
+
attr_accessor :redmine_root
|
8
|
+
attr_accessor :tmp_redmine_root
|
9
|
+
attr_accessor :options
|
10
|
+
attr_accessor :settings
|
11
|
+
attr_accessor :env
|
12
|
+
|
13
|
+
attr_reader :steps
|
14
|
+
|
15
|
+
def initialize(options={})
|
16
|
+
self.options = options
|
17
|
+
self.settings = {}
|
18
|
+
self.env = options[:env]
|
19
|
+
|
20
|
+
# Initialize steps for task
|
21
|
+
@steps = {}
|
22
|
+
index = 1
|
23
|
+
self.class::STEPS.each do |step|
|
24
|
+
@steps[index] = step.new(index, self)
|
25
|
+
index += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
@steps.each do |id, step|
|
31
|
+
step.print_title
|
32
|
+
step.print_header
|
33
|
+
step.up
|
34
|
+
step.print_footer
|
35
|
+
step.ran = true
|
36
|
+
puts # new line
|
37
|
+
end
|
38
|
+
|
39
|
+
@steps.each do |id, step|
|
40
|
+
step.final_step
|
41
|
+
end
|
42
|
+
rescue Redmine::Installer::Error => e
|
43
|
+
# Rescue from error comes from installer
|
44
|
+
# run steps again for cleaning
|
45
|
+
@steps.values.reverse.each do |step|
|
46
|
+
next unless step.ran
|
47
|
+
step.down
|
48
|
+
end
|
49
|
+
|
50
|
+
$stderr.puts(ANSI.red, e.message, ANSI.clear)
|
51
|
+
$stderr.flush
|
52
|
+
exit(1)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Package is required for install task and
|
56
|
+
# upgrade with source file
|
57
|
+
def check_package
|
58
|
+
if package.nil?
|
59
|
+
raise Redmine::Installer::Error, I18n.translate(:error_argument_package_is_missing)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.step
|
64
|
+
Redmine::Installer::Step
|
65
|
+
end
|
66
|
+
|
67
|
+
# Creating methods for recognition type of task
|
68
|
+
#
|
69
|
+
# == Examples:
|
70
|
+
# class Install < Task
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# Install.new.install? #=> true
|
74
|
+
#
|
75
|
+
def self.inherited(child)
|
76
|
+
method_name = "#{child.class_name.downcase}?".to_sym
|
77
|
+
|
78
|
+
define_method(method_name) { false }
|
79
|
+
child.send(:define_method, method_name) { true }
|
80
|
+
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
##
|
2
|
+
# Backup redmine
|
3
|
+
#
|
4
|
+
# You can upgrade current redmine by archive or currently defined git repository.
|
5
|
+
# If your redmine contain plugins which are not part of new package - all these
|
6
|
+
# plugins will be kept otherwise are replaced with those from package.
|
7
|
+
#
|
8
|
+
# Final step will ask you if you want save steps configuration.
|
9
|
+
# If you say YES, configuration will be stored as profile so next time
|
10
|
+
# you can upgrade redmine faster.
|
11
|
+
#
|
12
|
+
# redmine upgrade PACKAGE --profile PROFILE_ID
|
13
|
+
# Profiles are stored on HOME_FOLDER/.redmine-installer-profiles.yml.
|
14
|
+
#
|
15
|
+
# == Steps:
|
16
|
+
# 1. Redmine root - where should be new redmine located
|
17
|
+
# 2. Load package - extract package
|
18
|
+
# 3. Validation - current redmine should be valid
|
19
|
+
# 4. Backup - backup current redmine (see backup section)
|
20
|
+
# 5. Upgrading - install commands are executed
|
21
|
+
# 6. Moving redmine - redmine is moved from temporarily folder to given redmine_root
|
22
|
+
# 7. Profile saving - generating profile (see profile section)
|
23
|
+
#
|
24
|
+
# == Usage:
|
25
|
+
#
|
26
|
+
# From archive::
|
27
|
+
# # minimal
|
28
|
+
# redmine upgrade PATH_TO_PACKAGE
|
29
|
+
#
|
30
|
+
# # full
|
31
|
+
# redmine upgrade PATH_TO_PACKAGE --env ENV1,ENV2,ENV3
|
32
|
+
#
|
33
|
+
# From git::
|
34
|
+
# # minimal
|
35
|
+
# redmine upgrade --source git
|
36
|
+
#
|
37
|
+
# # full
|
38
|
+
# redmine upgrade --source git --env ENV1,ENV2,ENV3
|
39
|
+
#
|
40
|
+
module Redmine::Installer
|
41
|
+
class Upgrade < Task
|
42
|
+
|
43
|
+
STEPS = [
|
44
|
+
step::RedmineRoot,
|
45
|
+
step::LoadPackage,
|
46
|
+
step::Validation,
|
47
|
+
step::Backup,
|
48
|
+
step::Upgrade,
|
49
|
+
step::MoveRedmine
|
50
|
+
]
|
51
|
+
|
52
|
+
attr_accessor :package
|
53
|
+
|
54
|
+
def initialize(package, options={})
|
55
|
+
self.package = package
|
56
|
+
super(options)
|
57
|
+
|
58
|
+
check_package if options[:source] == 'file'
|
59
|
+
end
|
60
|
+
|
61
|
+
def run
|
62
|
+
Redmine::Installer::Profile.load(self, options[:profile])
|
63
|
+
super
|
64
|
+
Redmine::Installer::Profile.save(self) if options[:profile].nil?
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'notifier'
|
3
|
+
require 'ansi'
|
4
|
+
|
5
|
+
module Redmine::Installer
|
6
|
+
module Utils
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.send :extend, Methods
|
10
|
+
base.send :include, Methods
|
11
|
+
|
12
|
+
base.class_eval do
|
13
|
+
self.send(:const_set, 'Git', Redmine::Installer::Git)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Methods
|
18
|
+
|
19
|
+
# =======================================================================
|
20
|
+
# Generals
|
21
|
+
|
22
|
+
def plugin
|
23
|
+
Redmine::Installer::Plugin
|
24
|
+
end
|
25
|
+
|
26
|
+
def command
|
27
|
+
Redmine::Installer::Command.instance
|
28
|
+
end
|
29
|
+
|
30
|
+
def error(*args)
|
31
|
+
# Translate message
|
32
|
+
if args.first.is_a?(Symbol)
|
33
|
+
message = translate(*args)
|
34
|
+
else
|
35
|
+
message = args.first
|
36
|
+
end
|
37
|
+
|
38
|
+
# Colorize message
|
39
|
+
colorize(message)
|
40
|
+
|
41
|
+
raise Redmine::Installer::Error, message
|
42
|
+
end
|
43
|
+
|
44
|
+
# Try create a dir
|
45
|
+
# When mkdir raise an error (permission problem) method
|
46
|
+
# ask user if wants exist or try again
|
47
|
+
def try_create_dir(dir)
|
48
|
+
begin
|
49
|
+
FileUtils.mkdir_p(dir)
|
50
|
+
rescue
|
51
|
+
choices = {}
|
52
|
+
choices[:exit] = t(:exit)
|
53
|
+
choices[:try_again] = t(:try_again)
|
54
|
+
|
55
|
+
answer = choose(t(:dir_not_exist_and_cannot_be_created, dir: dir), choices, default: :exit)
|
56
|
+
|
57
|
+
case answer
|
58
|
+
when :exit
|
59
|
+
error ''
|
60
|
+
when :try_again
|
61
|
+
try_create_dir(dir)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Check if there are plugins in plugin dir
|
67
|
+
def some_plugins?
|
68
|
+
Dir.glob(File.join('plugins', '*')).select{|record| File.directory?(record)}.any?
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# =======================================================================
|
73
|
+
# Input, output
|
74
|
+
|
75
|
+
# Print message to stdout
|
76
|
+
def say(message, lines=0)
|
77
|
+
# Translate message
|
78
|
+
if message.is_a?(Symbol)
|
79
|
+
message = translate(message)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Colorize message
|
83
|
+
colorize(message)
|
84
|
+
|
85
|
+
$stdout.print(message)
|
86
|
+
lines.times { $stdout.puts }
|
87
|
+
$stdout.flush
|
88
|
+
end
|
89
|
+
|
90
|
+
# Colorize text based on XML marks
|
91
|
+
#
|
92
|
+
# == Examples:
|
93
|
+
# colorize("<bright><on_black><white>text</white></on_black></bright>")
|
94
|
+
# # => "\e[1m\e[40m\e[37mtext\e[0m\e[0m\e[0m"
|
95
|
+
#
|
96
|
+
def colorize(text)
|
97
|
+
return unless text.is_a?(String)
|
98
|
+
|
99
|
+
text.gsub!(/<([a-zA-Z0-9_]+)>/) do
|
100
|
+
if ANSI::CHART.has_key?($1.to_sym)
|
101
|
+
ANSI.send($1)
|
102
|
+
else
|
103
|
+
"<#{$1}>"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
text.gsub!(/<\/([a-zA-Z0-9_]+)>/) do
|
107
|
+
if ANSI::CHART.has_key?($1.to_sym)
|
108
|
+
ANSI.clear
|
109
|
+
else
|
110
|
+
"</#{$1}>"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Instead of `super` take only what I need
|
116
|
+
def gets
|
117
|
+
$stdin.gets.to_s.chomp
|
118
|
+
end
|
119
|
+
|
120
|
+
# Asking on 1 line
|
121
|
+
def ask(message=nil, options={})
|
122
|
+
# Translate message
|
123
|
+
if message.is_a?(Symbol)
|
124
|
+
message = translate(message)
|
125
|
+
end
|
126
|
+
default = options[:default]
|
127
|
+
|
128
|
+
if default
|
129
|
+
message << " [#{default}]"
|
130
|
+
end
|
131
|
+
|
132
|
+
if !options[:without_colon]
|
133
|
+
message << ': '
|
134
|
+
end
|
135
|
+
|
136
|
+
say(message)
|
137
|
+
input = gets
|
138
|
+
|
139
|
+
# Ctrl-D or enter was pressed
|
140
|
+
return default if input.empty?
|
141
|
+
|
142
|
+
input
|
143
|
+
end
|
144
|
+
|
145
|
+
# User can choose from selection
|
146
|
+
def choose(message, choices, options={})
|
147
|
+
choices = choices.to_a
|
148
|
+
default = options[:default]
|
149
|
+
index = 1
|
150
|
+
|
151
|
+
say(message, 1) unless message.nil?
|
152
|
+
choices.each do |(key, message)|
|
153
|
+
if key == default
|
154
|
+
pre = '*'
|
155
|
+
else
|
156
|
+
pre = ' '
|
157
|
+
end
|
158
|
+
|
159
|
+
say(" #{pre}#{index}) #{message}", 1)
|
160
|
+
index += 1
|
161
|
+
end
|
162
|
+
|
163
|
+
input = ask('> ', without_colon: true).to_i
|
164
|
+
puts
|
165
|
+
|
166
|
+
# Without default is input 0
|
167
|
+
return default if input.zero? || input > choices.size
|
168
|
+
|
169
|
+
choices[input-1][0]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Say 'Do you want?'
|
173
|
+
# -> YES, NO
|
174
|
+
#
|
175
|
+
def confirm(message, default=true)
|
176
|
+
# Translate message
|
177
|
+
if message.is_a?(Symbol)
|
178
|
+
message = translate(message)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Colorize message
|
182
|
+
colorize(message)
|
183
|
+
|
184
|
+
yes = t(:yes_t)
|
185
|
+
no = t(:no_t)
|
186
|
+
|
187
|
+
if default
|
188
|
+
yes.upcase!
|
189
|
+
else
|
190
|
+
no.upcase!
|
191
|
+
end
|
192
|
+
|
193
|
+
message << " (#{yes}/#{no}): "
|
194
|
+
|
195
|
+
$stdout.print(message)
|
196
|
+
answer = gets
|
197
|
+
|
198
|
+
return default if answer.empty?
|
199
|
+
|
200
|
+
if answer[0].downcase == yes[0].downcase
|
201
|
+
return true
|
202
|
+
else
|
203
|
+
return false
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# =======================================================================
|
209
|
+
# Localizations
|
210
|
+
|
211
|
+
def translate(*args)
|
212
|
+
I18n.translate(*args)
|
213
|
+
end
|
214
|
+
alias_method :t, :translate
|
215
|
+
|
216
|
+
|
217
|
+
# =======================================================================
|
218
|
+
# Notifications
|
219
|
+
|
220
|
+
def notif(title, message=nil, image=nil)
|
221
|
+
return unless Redmine::Installer.config.notif
|
222
|
+
|
223
|
+
thread = ::Notifier.notify(
|
224
|
+
title: title.to_s,
|
225
|
+
message: message.to_s,
|
226
|
+
image: image.to_s
|
227
|
+
)
|
228
|
+
thread.join
|
229
|
+
end
|
230
|
+
|
231
|
+
end # Methods
|
232
|
+
end # Utils
|
233
|
+
end # Redmine::Installer
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'redmine-installer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "redmine-installer"
|
8
|
+
spec.version = Redmine::Installer::VERSION
|
9
|
+
spec.authors = ["Ondřej Moravčík"]
|
10
|
+
spec.email = ["moravcik.ondrej@gmail.com"]
|
11
|
+
spec.summary = %q{Easy way how install/upgrade redmine or plugin.}
|
12
|
+
spec.description = %q{Redmine-installer can fully install/upgrade redmine and will
|
13
|
+
generate template for different server. All actions can be saved
|
14
|
+
for further use.}
|
15
|
+
spec.homepage = "https://github.com/ondra-m/redmine-installer"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Redmine::Installer::Install do
|
4
|
+
|
5
|
+
before(:example) do
|
6
|
+
@dir = Dir.mktmpdir
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:example) do
|
10
|
+
FileUtils.remove_entry_secure(@dir)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:package1) { LoadRedmine.get('2.4.7') }
|
14
|
+
|
15
|
+
context 'mysql' do
|
16
|
+
let(:host) { RSpec.configuration.mysql[:host] }
|
17
|
+
let(:port) { RSpec.configuration.mysql[:port] }
|
18
|
+
let(:username) { RSpec.configuration.mysql[:username] }
|
19
|
+
let(:password) { RSpec.configuration.mysql[:password] }
|
20
|
+
|
21
|
+
before(:example) do
|
22
|
+
system("mysql -h #{host} --port #{port} -u #{username} -p#{password} -e 'drop database test1'")
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'install' do
|
26
|
+
# redmine root -> temp dir
|
27
|
+
# type of db -> mysql
|
28
|
+
# database -> test1
|
29
|
+
# host -> configuration
|
30
|
+
# username -> configuration
|
31
|
+
# password -> configuration
|
32
|
+
# encoding -> utf8
|
33
|
+
# port -> configuration
|
34
|
+
# email configuration -> skip
|
35
|
+
# webserver -> skip
|
36
|
+
|
37
|
+
allow($stdin).to receive(:gets).and_return(
|
38
|
+
@dir, '1', 'test1', host, username, password, 'utf8', port, '999', '999'
|
39
|
+
)
|
40
|
+
|
41
|
+
r_installer = Redmine::Installer::Install.new(package1, {})
|
42
|
+
expect { r_installer.run }.to_not raise_error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|