redmine-installer 1.0.0
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.
- 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
|