le1t0-deprec 2.1.6.006 → 2.1.6.007
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +18 -0
- data/lib/deprec/capistrano_extensions.rb +2 -0
- data/lib/deprec/recipes/deprec.rb +6 -1
- data/lib/deprec/recipes/god.rb +19 -0
- data/lib/deprec/recipes/imagemagick/imagemagick_bin.rb +4 -2
- data/lib/deprec/recipes/imagemagick/imagemagick_src.rb +3 -1
- data/lib/deprec/recipes/iptables.rb +1 -0
- data/lib/deprec/recipes/java.rb +12 -0
- data/lib/deprec/recipes/keepalived.rb +31 -9
- data/lib/deprec/recipes/nagios.rb +36 -18
- data/lib/deprec/recipes/profiles.rb +123 -4
- data/lib/deprec/recipes/redhat_cluster.rb.unmaintained +5 -1
- data/lib/deprec/recipes/rvm.rb +2 -2
- data/lib/deprec/recipes/s3utils.rb +2 -2
- data/lib/deprec/recipes/ssh.rb +22 -17
- data/lib/deprec/templates/iptables/firewall-init.erb +16 -1
- data/lib/deprec/templates/keepalived/keepalived.conf.erb +32 -12
- metadata +3 -3
data/CHANGELOG
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# deprec changelog
|
2
2
|
|
3
|
+
= 2.1.6.007 (Jun 10, 2010)
|
4
|
+
|
5
|
+
* some adjustments to nagios recipe
|
6
|
+
* support setting known_hosts for monitoring user in nagios recipe
|
7
|
+
* added easy setting of known_hosts for deploy_user in ssh recipe
|
8
|
+
* added default profiles
|
9
|
+
* rewrote keepalived recipe, added possibility of manually switching master + added configurability of scripts and instances
|
10
|
+
* small bugfix for loading god configs
|
11
|
+
* added comments for profile recipe
|
12
|
+
* commented redhat-cluster recipe
|
13
|
+
* small bugfix for rvm recipe
|
14
|
+
* commented s3utils recipe
|
15
|
+
* commented java recipe
|
16
|
+
* added comments to iptables recipe
|
17
|
+
* added comments to imagemagick recipe
|
18
|
+
* added config_project + comments to god recipe
|
19
|
+
* added comments + some small modifications + small bugfix to ssh recipe
|
20
|
+
|
3
21
|
= 2.1.6.006 (Jun 4, 2010)
|
4
22
|
|
5
23
|
* again fixed variable names in rails recipe for being compatible with modify-alternatives-registration branch
|
@@ -198,7 +198,9 @@ module Deprec2
|
|
198
198
|
END
|
199
199
|
end
|
200
200
|
|
201
|
+
# allow string substitutions in files on the server
|
201
202
|
def substitute_in_file(filename, old_value, new_value, sep_char='/')
|
203
|
+
# XXX sort out single quotes in 'value' - they'l break command!
|
202
204
|
sudo <<-END
|
203
205
|
sh -c "
|
204
206
|
perl -p -i -e 's#{sep_char}#{old_value}#{sep_char}#{new_value}#{sep_char}' #{filename}
|
@@ -27,6 +27,8 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
27
27
|
|
28
28
|
# Service defaults
|
29
29
|
#
|
30
|
+
# load all directories under lib/deprec/recipes, and set :none to the default selection for the respective
|
31
|
+
# recipe collection type (i.e. db, app, web, etc)
|
30
32
|
Dir.glob("#{File.dirname(__FILE__)}/*").each do |entry|
|
31
33
|
default "#{File.basename(entry)}_choice".to_sym, :none if File.directory?(entry)
|
32
34
|
end
|
@@ -81,7 +83,10 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
81
83
|
# link application specific recipes into canonical task names
|
82
84
|
# e.g. deprec:web:restart => deprec:nginx:restart
|
83
85
|
|
84
|
-
|
86
|
+
# load all directories under lib/deprec/recipes, create a two element array for each, setting the first value
|
87
|
+
# to the directory name (as a symbol; the canonicalized namespace), the second to the variable which sets the
|
88
|
+
# selection for this recipe collection type. These arrays are collected, empty entries removed and then
|
89
|
+
# everything is flattened to a single array, leaving alternating key-values for conversion to a hash.
|
85
90
|
namespaces_to_connect = Hash[*(Dir.glob("#{File.dirname(__FILE__)}/*").collect do |entry|
|
86
91
|
[ File.basename(entry).to_sym, "#{File.basename(entry)}_choice".to_sym ] if File.directory?(entry)
|
87
92
|
end.compact.flatten)]
|
data/lib/deprec/recipes/god.rb
CHANGED
@@ -36,9 +36,28 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
36
36
|
|
37
37
|
desc "Push god config files to server"
|
38
38
|
task :config, :roles => :god do
|
39
|
+
config_system
|
40
|
+
config_project
|
41
|
+
end
|
42
|
+
|
43
|
+
task :config_system, :roles => :god do
|
39
44
|
sudo "install -d /etc/god/conf.d"
|
40
45
|
deprec2.push_configs(:god, SYSTEM_CONFIG_FILES[:god])
|
41
46
|
end
|
47
|
+
|
48
|
+
# Push any files named *.god.#{rails_env} in directory config/god/ to servers with :god role,
|
49
|
+
# remove the .#{rails_env} extension and put them in #{deploy_to}/god/. Next, link them to /etc/god/conf.d/
|
50
|
+
task :config_project, :roles => :god do
|
51
|
+
Dir.new(File.join("config", "god")).entries.select { |e| e =~ /\.god\.#{rails_env}$/ }.each do |entry|
|
52
|
+
base_entry = File.basename(entry, ".#{rails_env}")
|
53
|
+
file = File.join("config", "god", entry)
|
54
|
+
full_remote_path = File.join(deploy_to, 'god', base_entry)
|
55
|
+
run "mkdir -p #{File.join(deploy_to, 'god')}"
|
56
|
+
std.su_put File.read(file), full_remote_path, '/tmp/', :mode=>0644
|
57
|
+
sudo "chown root:root #{full_remote_path}"
|
58
|
+
sudo "ln -nsf #{full_remote_path} /etc/god/conf.d/#{application}-#{base_entry}"
|
59
|
+
end if File.directory?(File.join("config", "god"))
|
60
|
+
end
|
42
61
|
|
43
62
|
desc "Start God"
|
44
63
|
task :start, :roles => :god do
|
@@ -7,8 +7,10 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
7
7
|
|
8
8
|
desc "Install imagemagick & rmagick"
|
9
9
|
task :install, :roles => :app do
|
10
|
-
|
11
|
-
|
10
|
+
# make sure binary package is uninstalled (if there is any), before attempting source uninstall, since source
|
11
|
+
# uninstall would also remove binary package files
|
12
|
+
uninstall
|
13
|
+
# uninstall source (forced, might not be there) so we are sure no old files are left behind
|
12
14
|
top.deprec.imagemagick_src.uninstall
|
13
15
|
apt.install( {:base => %w(imagemagick libmagick9-dev libmagick10)}, :stable )
|
14
16
|
gem2.install 'rmagick' if imagemagick_include_rmagick
|
@@ -13,6 +13,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
13
13
|
|
14
14
|
desc "Install imagemagick & rmagick"
|
15
15
|
task :install, :roles => :app do
|
16
|
+
# make sure there is no binary package (force uninstall), since we install in the same location
|
16
17
|
top.deprec.imagemagick_bin.uninstall
|
17
18
|
install_deps
|
18
19
|
deprec2.download_src(SRC_PACKAGES[:imagemagick], src_dir)
|
@@ -30,7 +31,8 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
30
31
|
task :install_deps, :roles => :app do
|
31
32
|
# install binary packages, so all needed dependencies are installed
|
32
33
|
apt.install( {:base => %w(imagemagick libmagick9-dev libperl-dev libmagick10)}, :stable )
|
33
|
-
# remove binary packages, leaving the dependencies
|
34
|
+
# remove binary packages, leaving the dependencies, so we can install from src without needing all deps from
|
35
|
+
# source as well
|
34
36
|
apt.install( {:base => %w(imagemagick- libmagick9-dev- libmagick10-)}, :stable )
|
35
37
|
end
|
36
38
|
|
@@ -3,6 +3,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
3
3
|
namespace :deprec do
|
4
4
|
namespace :iptables do
|
5
5
|
|
6
|
+
# see iptables-init script and iptables-default file for syntax
|
6
7
|
set :iptables_allowed, "tcp:22,80,443"
|
7
8
|
set :iptables_forwards, ""
|
8
9
|
set :iptables_binary, "/sbin/iptables"
|
data/lib/deprec/recipes/java.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
def accept_license
|
2
|
+
# don't ask to accept the license if already preset
|
2
3
|
return if java_dlj_11_license_accepted
|
3
4
|
in_license = false
|
5
|
+
# read license text from bottom of this file
|
4
6
|
Capistrano::CLI.ui.say(IO.readlines(__FILE__).collect do |line|
|
5
7
|
in_license = false if line =~ /^# END_LICENSE/
|
6
8
|
current_line = in_license ? line.strip.gsub(/^#[ ]?/, '') : nil
|
7
9
|
in_license = true if line =~ /^# START_LICENSE/
|
8
10
|
current_line
|
9
11
|
end.compact.join("\n"))
|
12
|
+
# ask whether user accepts the license
|
10
13
|
prompt = "Accept license (YES, [NO])? "
|
11
14
|
answer = Capistrano::CLI.ui.ask(prompt) do |q|
|
12
15
|
q.overwrite = false
|
@@ -14,6 +17,7 @@ def accept_license
|
|
14
17
|
q.validate = /(yes)|(no)|\n/i
|
15
18
|
q.responses[:not_valid] = prompt
|
16
19
|
end
|
20
|
+
# set variable to true if license is accepted
|
17
21
|
if answer.upcase == 'YES'
|
18
22
|
set :java_dlj_11_license_accepted, true
|
19
23
|
end
|
@@ -25,13 +29,16 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
25
29
|
namespace :java do
|
26
30
|
|
27
31
|
set :java_include_jdk, false
|
32
|
+
# Set this variable to true in your deployment recipe to accept the license contained below
|
28
33
|
set :java_dlj_11_license_accepted, false
|
29
34
|
set :java_stopthread, true # no idea what this does, but it defaults to true on Hardy and should be preset for auto inst
|
30
35
|
|
31
36
|
desc "Install Java"
|
32
37
|
task :install do
|
33
38
|
accept_license
|
39
|
+
# only install if license was accepted
|
34
40
|
if java_dlj_11_license_accepted
|
41
|
+
# preseed questions for debconf, so automatic install works
|
35
42
|
debconf_set_selections_file = "/tmp/java_debconf_set_selections.#{Time.now.strftime("%Y%m%d%H%M%S")}"
|
36
43
|
debconf_set_selections = [
|
37
44
|
"sun-java6-jre sun-java6-jre/stopthread boolean #{java_stopthread}",
|
@@ -41,16 +48,21 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
41
48
|
"sun-java6-bin shared/present-sun-dlj-v1-1 note",
|
42
49
|
"sun-java6-jre shared/present-sun-dlj-v1-1 note"
|
43
50
|
]
|
51
|
+
# select packages to install
|
44
52
|
packages = %w(sun-java6-bin sun-java6-jre)
|
45
53
|
if java_include_jdk
|
46
54
|
packages << "sun-java6-jdk"
|
55
|
+
# add preseed answers if also installing jdk
|
47
56
|
debconf_set_selections += [
|
48
57
|
"sun-java6-jdk shared/accepted-sun-dlj-v1-1 boolean #{java_dlj_11_license_accepted}",
|
49
58
|
"sun-java6-jdk shared/present-sun-dlj-v1-1 note"
|
50
59
|
]
|
51
60
|
end
|
61
|
+
# upload preseeded selections
|
52
62
|
put debconf_set_selections.join("\n"), debconf_set_selections_file, :mode => 0644
|
63
|
+
# insert preseeded selections into debconf db
|
53
64
|
sudo "debconf-set-selections #{debconf_set_selections_file} ; rm -f #{debconf_set_selections_file}"
|
65
|
+
# install java
|
54
66
|
apt.install( {:base => packages}, :stable )
|
55
67
|
end
|
56
68
|
end
|
@@ -2,15 +2,37 @@
|
|
2
2
|
Capistrano::Configuration.instance(:must_exist).load do
|
3
3
|
namespace :deprec do
|
4
4
|
namespace :keepalived do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
set :
|
12
|
-
|
13
|
-
|
5
|
+
|
6
|
+
# :keepalived_scripts should contain a hash where each key is the suffix of the vrrp_script registration,
|
7
|
+
# and the value is again a hash, containing as key => value pairs:
|
8
|
+
# :script => 'killall -0 haproxy' or any script which returns a 0 or 1 exit value
|
9
|
+
# :interval => 1
|
10
|
+
# :weight => 2
|
11
|
+
set :keepalived_scripts, {
|
12
|
+
:haproxy => {
|
13
|
+
:script => 'killall -0 haproxy',
|
14
|
+
:interval => 1,
|
15
|
+
:weight => 2
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
# :keepalived_instances should contain a hash with at least one key => value pair. The key should be a string
|
20
|
+
# with the virtual IP address. The value is again a hash with some settings, containing as key => value pairs:
|
21
|
+
# :virtual_router_id => '51' or any unique integer among all VRRP instances on the same subnet
|
22
|
+
# :interface => 'eth0'
|
23
|
+
# :priority => '101', usually should be one higher for MASTER than for BACKUP
|
24
|
+
# :state => 'MASTER' or 'BACKUP', automatically adds :wanted_state to :scripts below
|
25
|
+
# :scripts => symbol or hash of scripts to execute for this instance, when left undefined all scripts defined above
|
26
|
+
# are added
|
27
|
+
set :keepalived_instances, {
|
28
|
+
"192.168.0.99" => {
|
29
|
+
:virtual_router_id => '51',
|
30
|
+
:interface => 'eth0',
|
31
|
+
:priority => '101',
|
32
|
+
:state => 'MASTER',
|
33
|
+
:scripts => :haproxy
|
34
|
+
}
|
35
|
+
}
|
14
36
|
|
15
37
|
desc "Install keepalived on server"
|
16
38
|
task :install, :roles => :failover do
|
@@ -12,6 +12,10 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
12
12
|
set :nagios_htpasswd_file, '/usr/local/nagios/etc/htpasswd.users'
|
13
13
|
# default :application, 'nagios'
|
14
14
|
set :nagios_ssh_key, nil
|
15
|
+
# all SSH hostnames or IPs that nagios should check
|
16
|
+
set :nagios_known_hosts, [ ]
|
17
|
+
# allow nagios user on check_hosts to do certain commands through sudo
|
18
|
+
set :nagios_sudo_commands, [ ] # i.e.: %w(/usr/bin/killall /bin/kill /sbin/iptables /bin/cat)
|
15
19
|
|
16
20
|
SRC_PACKAGES[:nagios] = {
|
17
21
|
:url => "http://prdownloads.sourceforge.net/sourceforge/nagios/nagios-3.2.0.tar.gz",
|
@@ -52,7 +56,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
52
56
|
|
53
57
|
task :create_nagios_user, :roles => :nagios do
|
54
58
|
deprec2.groupadd(nagios_group)
|
55
|
-
deprec2.useradd(nagios_user, :group => nagios_group
|
59
|
+
deprec2.useradd(nagios_user, :group => nagios_group)
|
56
60
|
# deprec2.add_user_to_group(nagios_user, apache_user)
|
57
61
|
deprec2.groupadd(nagios_cmd_group)
|
58
62
|
deprec2.add_user_to_group(nagios_user, nagios_cmd_group)
|
@@ -145,6 +149,10 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
145
149
|
deprec2.push_configs(:nagios, SYSTEM_CONFIG_FILES[:nagios])
|
146
150
|
config_check
|
147
151
|
restart
|
152
|
+
if nagios_known_hosts.size > 0
|
153
|
+
put nagios_known_hosts.join("\n"), tmp_file = "/tmp/ssh_keyscan_#{Time.now.strftime("%Y%m%d%H%M%S")}.txt", :mode => 0644
|
154
|
+
run "ssh-keyscan -f #{tmp_file} -t rsa > ~nagios/.ssh/known_hosts ; rm -f #{tmp_file} ; chown #{nagios_user}:#{nagios_group} ~nagios/.ssh/known_hosts"
|
155
|
+
end
|
148
156
|
end
|
149
157
|
|
150
158
|
desc "Run Nagios config check"
|
@@ -212,35 +220,45 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
212
220
|
create_nagios_user
|
213
221
|
deprec2.download_src(SRC_PACKAGES[:nagios_plugins], src_dir)
|
214
222
|
deprec2.install_from_src(SRC_PACKAGES[:nagios_plugins], src_dir)
|
223
|
+
config_access
|
224
|
+
install_custom
|
215
225
|
end
|
216
226
|
|
227
|
+
# allow the user to install custom plugins from RAILS_ROOT/config/nagios_plugins/plugins
|
228
|
+
# any file there is uploaded to /usr/local/nagios/libexec and chmodded to 755
|
217
229
|
desc "Install user plugins for nagios from config/nagios_plugins/plugins in user's project"
|
218
230
|
task :install_custom do
|
219
|
-
remote_path = File.join('/', 'usr', 'local', 'nagios', 'libexec')
|
220
231
|
plugins_path = File.join('config', 'nagios_plugins', 'plugins')
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
232
|
+
if File.directory?(plugins_path)
|
233
|
+
remote_path = File.join('/', 'usr', 'local', 'nagios', 'libexec')
|
234
|
+
Dir.new(plugins_path).entries.each do |entry|
|
235
|
+
remote_plugin = File.join(remote_path, entry)
|
236
|
+
plugin = File.join(plugins_path, entry)
|
237
|
+
if File.file?(plugin)
|
238
|
+
std.su_put File.read(plugin), remote_plugin, '/tmp', :mode => 0755
|
239
|
+
end
|
226
240
|
end
|
227
241
|
end
|
228
242
|
end
|
229
243
|
|
244
|
+
# configure ssh + sudo for nagios:
|
245
|
+
# * allow certain commands so nagios can do checks (killall, kill, iptables, cat) as root
|
246
|
+
# * add nagios ssh key to authorized keys on servers to check (if the variable is set)
|
230
247
|
desc "configure ssh + sudo access for nagios_user"
|
231
248
|
task :config_access do
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
249
|
+
nagios_sudo_commands.each do |command|
|
250
|
+
deprec2.append_to_file_if_missing('/etc/sudoers', "#{nagios_user} ALL=(root) NOPASSWD:#{command}")
|
251
|
+
end
|
252
|
+
unless nagios_ssh_key.nil?
|
253
|
+
sudo "mkdir -p /home/#{nagios_user}/.ssh"
|
254
|
+
sudo "chmod 700 /home/#{nagios_user}/.ssh"
|
255
|
+
if nagios_ssh_key
|
256
|
+
sudo "echo '#{nagios_ssh_key}' >> /tmp/authorized_keys_file_for_nagios_user.tmp"
|
257
|
+
end
|
258
|
+
sudo "mv /tmp/authorized_keys_file_for_nagios_user.tmp /home/#{nagios_user}/.ssh/authorized_keys"
|
259
|
+
sudo "chmod 600 /home/#{nagios_user}/.ssh/authorized_keys"
|
260
|
+
sudo "chown -R nagios:nagios /home/#{nagios_user}/.ssh"
|
240
261
|
end
|
241
|
-
sudo "mv /tmp/authorized_keys_file_for_nagios_user.tmp /home/#{nagios_user}/.ssh/authorized_keys"
|
242
|
-
sudo "chmod 600 /home/#{nagios_user}/.ssh/authorized_keys"
|
243
|
-
sudo "chown -R nagios:nagios /home/#{nagios_user}/.ssh"
|
244
262
|
end
|
245
263
|
|
246
264
|
# Install dependencies for nagios plugins
|
@@ -1,44 +1,124 @@
|
|
1
1
|
# Copyright 2009-2010 by le1t0@github. All rights reserved.
|
2
|
+
|
3
|
+
# A little bit about profiles.. What I wanted to achieve was having a list of tasks which I can run,
|
4
|
+
# which install a server from bare install to fully functional with only one deprec call. Before,
|
5
|
+
# I fixed this by writing pages and pages of capistrano tasks performing each step, installing,
|
6
|
+
# configuring and activating all that was needed and for all different configurations. I really
|
7
|
+
# didn't like this as it was a nightmare to maintain. So I knew I wanted something that very compactly
|
8
|
+
# can describe the steps to take to fully install a server.
|
9
|
+
#
|
10
|
+
# I had no idea what to name it, I based my ideas on the notion of tasks in the debian linux distribution, but
|
11
|
+
# since the word task is already taken in capistrano, I just went for the word profile :) So a profile is
|
12
|
+
# basically an ordered list of recipes to run, including which tasks to perform on them; furthermore, a
|
13
|
+
# profile can reference other profiles, so you can extract out generic definitions. I defined
|
14
|
+
# [ :config_gen, :install, :config, :activate, :start ] to be the default list of tasks to perform on a recipe,
|
15
|
+
# but made it possible to override this list per profile (for example when you want to make profiles which can
|
16
|
+
# give you a status overview of the entire server farm or something).. Each profile would mention only the name
|
17
|
+
# of a recipe to run, and possibly (as arguments) the list of tasks to include this recipe for. So when a profile
|
18
|
+
# should run in the 5 mentioned tasks, and you add recipe imagemagick with as an argument :install, then it would
|
19
|
+
# only run install on the imagemagick recipe. Finally, I added the option of calling a custom task (i.e. not one
|
20
|
+
# of the 5 default ones). Some examples:
|
21
|
+
#
|
22
|
+
# profile :tools, :install do |p, r| # only execute the :install task on the include recipes in this profile
|
23
|
+
# r.ubuntu
|
24
|
+
# r.nagios_plugins
|
25
|
+
# r.imagemagick
|
26
|
+
# r.aspell
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# profile :base do |p, r| # the 'p' object allows you to call other profiles, the 'r' object allows you to call recipes
|
30
|
+
# r.iptables
|
31
|
+
# r.postfix
|
32
|
+
# r.ntp
|
33
|
+
# r.ubuntu.utils.bash :config # call :config in utils.bash namespace of ubuntu recipe
|
34
|
+
# p.tools # include another profile in this profile
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# profile :app_base do |p, r| # this profile, as most others, doesn't define a custom set of tasks, so it runs [ :config_gen, :install, :config, :activate, :start ]
|
38
|
+
# r.call.passenger.config_gen_system :config_gen
|
39
|
+
# r.call.rails.install_stack :install # run custom task :install_stack during the :install phase on the rails recipe; use call to define calling a custom task
|
40
|
+
# r.java
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# profile :db_base do |p, r|
|
44
|
+
# r.mysql
|
45
|
+
# r.sphinx :install # only execute :install for sphinx, for the others run the defaults ([ :config_gen, :install, :config, :activate, :start ])
|
46
|
+
# r.java
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# profile :composite_server do |p, r| # make a profile purely based on other profiles
|
50
|
+
# p.base
|
51
|
+
# p.app_base
|
52
|
+
# p.db_base
|
53
|
+
# end
|
54
|
+
|
2
55
|
Capistrano::Configuration.instance(:must_exist).load do
|
3
56
|
class DeprecProfile
|
57
|
+
# list of profiles or recipes to call for this profile
|
4
58
|
attr_accessor :tasks_to_call
|
59
|
+
# keep track the current called profile/recipe. We can't add them directly to :tasks_to_call, because we need to support
|
60
|
+
# (namespace) nested tasks and literal tasks as well.
|
5
61
|
attr_accessor :current_task
|
62
|
+
# boolean which states whether we are in the sub namespace of a recipe or just the base recipe
|
6
63
|
attr_accessor :sub_namespace
|
64
|
+
# if we want to call another task than the default for a certain step (i.e. calling :install_all_my_stuff instead
|
65
|
+
# of just :install for the install step) this variable (boolean) tells us whether this is the case
|
7
66
|
attr_accessor :literal
|
8
67
|
|
9
68
|
def initialize
|
69
|
+
# by default, we have no profiles/recipes defined and we don't start out in a sub_namespace
|
10
70
|
@tasks_to_call = []
|
11
71
|
@sub_namespace = false
|
12
72
|
end
|
13
73
|
|
14
74
|
def call
|
75
|
+
# only support calling literal tasks when we are not in a sub_namespace
|
15
76
|
if !@sub_namespace
|
77
|
+
# since method_missing won't be called now, we need to call finalize, before continuing
|
16
78
|
finalize
|
17
|
-
@literal = true # can't set directly in current_task, since it will run finalize again in method_missing then (wrongly)
|
79
|
+
@literal = true # can't set directly in :current_task, since it will run finalize again in method_missing then (wrongly)
|
18
80
|
end
|
19
81
|
self
|
20
82
|
end
|
21
83
|
|
84
|
+
# the very core of the profiles code, which implements the DSL for defining profiles and recipes to call
|
22
85
|
def method_missing(method_name, *args, &block)
|
23
86
|
obj = nil
|
87
|
+
# if we are not in a sub_namespace, then this is a base recipe or profile to call
|
24
88
|
if !@sub_namespace
|
89
|
+
# call finalize, so any previous current_task gets registered
|
25
90
|
finalize
|
91
|
+
# make sure we start out fresh
|
26
92
|
@current_task ||= {}
|
93
|
+
# if we want to call a literal task, define it now in the :current_task variable, and reset the literal variable
|
94
|
+
# for next tasks to come
|
27
95
|
@current_task[:literal] = true if @literal
|
28
96
|
@literal = false
|
97
|
+
# define the name of the recipe or profile to call
|
29
98
|
@current_task[:name] = method_name.to_s
|
99
|
+
# define any arguments as well, which could be a list of symbols overriding (only_recipe_tasks) below. Use this
|
100
|
+
# when you want a certain recipe or profile to only execute for one or more certain steps. I.e. when the entire
|
101
|
+
# profile executes for :install and :config steps, but you want imagemagick to only execute for the :install step,
|
102
|
+
# you would use this.
|
30
103
|
@current_task[:args] = args.size == 0 ? nil : args.flatten
|
104
|
+
# return a copy of ourselves, since we don't want to contaminate the current one with the sub_namespace setting,
|
105
|
+
# as we can never set it to false anymore (no way to detect it)
|
31
106
|
obj = self.dup
|
32
107
|
obj.sub_namespace = true
|
33
108
|
else
|
109
|
+
# we are in a sub_namespace, so define the sub_namespace name by concatting it to the existing name with a dot
|
34
110
|
@current_task[:name] = [ @current_task[:name], method_name.to_s ].join('.')
|
35
|
-
|
111
|
+
# register any arguments as well
|
112
|
+
@current_task[:args] = args.size == 0 ? nil : args.flatten
|
113
|
+
# return our self, so we can also go into another sub_namespace
|
36
114
|
obj = self
|
37
115
|
end
|
38
116
|
obj
|
39
117
|
end
|
40
118
|
|
41
119
|
def finalize
|
120
|
+
# if a current task is defined, make sure it gets registered in :tasks_to_call and reset :current_task,
|
121
|
+
# for a possible next call
|
42
122
|
if !@current_task.nil?
|
43
123
|
@tasks_to_call << @current_task
|
44
124
|
@current_task = nil
|
@@ -46,11 +126,20 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
46
126
|
end
|
47
127
|
end
|
48
128
|
|
129
|
+
# define a profile, with arguments
|
130
|
+
# - a name for the resulting task which executes the profile
|
131
|
+
# - optionally an array of (or just one) symbol(s) of task names to execute (by default) within the recipes
|
132
|
+
# contained in this profile
|
133
|
+
# - a block containing calls to either other profiles or recipes
|
49
134
|
def profile(profile_task_name, *only_recipe_tasks, &block)
|
50
135
|
only_recipe_tasks = [ :config_gen, :install, :config, :activate, :start ] if only_recipe_tasks.size == 0
|
136
|
+
# Execute the block with two arguments, the first will contain profiles which should be called, the second
|
137
|
+
# will contain recipes which should be called
|
51
138
|
yield(profiles = DeprecProfile.new, recipes = DeprecProfile.new)
|
52
139
|
profiles.finalize
|
53
140
|
recipes.finalize
|
141
|
+
# define a list of distclean tasks which remove all stamps for the respective profile for either all step tasks
|
142
|
+
# (only_recipe_tasks) or one of these
|
54
143
|
([ :all ] + only_recipe_tasks).each do |tsk|
|
55
144
|
cmd = "
|
56
145
|
namespace :deprec do\nnamespace :profiles do\nnamespace :#{profile_task_name} do\nnamespace :distclean do\ndesc '#{profile_task_name}:distclean:#{tsk}'\ntask :#{tsk} do\n
|
@@ -58,7 +147,9 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
58
147
|
"
|
59
148
|
puts cmd if ENV['DEBUG_PROFILES']
|
60
149
|
eval(cmd)
|
61
|
-
end
|
150
|
+
end
|
151
|
+
# define the :all task for the profile, which calls all tasks defined in (only_recipe_tasks) on the profile itself, and
|
152
|
+
# stamps its success. Remove all stamps when successful
|
62
153
|
cmd = "
|
63
154
|
namespace :deprec do\nnamespace :profiles do\nnamespace :#{profile_task_name} do\ndesc '#{profile_task_name}:all'\ntask :all do\n
|
64
155
|
#{only_recipe_tasks.collect do |n|
|
@@ -71,6 +162,9 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
71
162
|
"
|
72
163
|
puts cmd if ENV['DEBUG_PROFILES']
|
73
164
|
eval(cmd)
|
165
|
+
# define the various tasks defined in (only_recipe_tasks) for this profile, they should call their respective step task
|
166
|
+
# on the profiles and recipes contained within this profile, and stamp their success. Remove all stamps if everything
|
167
|
+
# was successful
|
74
168
|
only_recipe_tasks.each do |rt|
|
75
169
|
cmd = "
|
76
170
|
desc '#{profile_task_name}:#{rt}'\nnamespace :deprec do\nnamespace :profiles do\nnamespace :#{profile_task_name} do\ntask :#{rt} do\n
|
@@ -97,11 +191,14 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
97
191
|
end
|
98
192
|
end
|
99
193
|
|
194
|
+
# create a stamp for the currently executed profile, the recipe that has been completed and specifically which task
|
195
|
+
# has been run on it
|
100
196
|
def profile_stamp(profile_name, executing_recipe, executing_task)
|
101
197
|
stamp_name = "stamp-#{profile_name.gsub(/:/, '_')}-#{executing_task_name(executing_recipe, executing_task)}"
|
102
198
|
run "mkdir -p ~/.deprec ; touch ~/.deprec/#{stamp_name}"
|
103
199
|
end
|
104
|
-
|
200
|
+
|
201
|
+
# check whether a stamp already exists
|
105
202
|
def profile_stamp_exists?(profile_name, executing_recipe, executing_task)
|
106
203
|
stamp_name = "stamp-#{profile_name.gsub(/:/, '_')}-#{executing_task_name(executing_recipe, executing_task)}"
|
107
204
|
result = nil
|
@@ -111,10 +208,12 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
111
208
|
result
|
112
209
|
end
|
113
210
|
|
211
|
+
# remove all stamps for a profile
|
114
212
|
def remove_profile_stamps(profile_name)
|
115
213
|
run "mkdir -p ~/.deprec ; rm -f ~/.deprec/stamp-#{profile_name.gsub(/:/, '_')}-*"
|
116
214
|
end
|
117
215
|
|
216
|
+
# helper method for the stamp and stamp_exists? methods above
|
118
217
|
def executing_task_name(executing_recipe, executing_task)
|
119
218
|
if executing_recipe =~ /\./
|
120
219
|
"#{executing_recipe.gsub(/\./, '_')}--#{executing_task}"
|
@@ -122,4 +221,24 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
122
221
|
"#{executing_recipe}_#{executing_task}"
|
123
222
|
end
|
124
223
|
end
|
224
|
+
|
225
|
+
profile :rails_stack, :install do |p, r|
|
226
|
+
r.ruby
|
227
|
+
r.rails
|
228
|
+
r.svn
|
229
|
+
r.git
|
230
|
+
r.web
|
231
|
+
r.app
|
232
|
+
r.monit if use_monit
|
233
|
+
r.logrotate if use_logrotate
|
234
|
+
end
|
235
|
+
|
236
|
+
profile :single_server do |p,r|
|
237
|
+
p.rails_stack
|
238
|
+
r.iptables
|
239
|
+
r.postfix
|
240
|
+
r.ntp
|
241
|
+
r.mysql
|
242
|
+
end
|
243
|
+
|
125
244
|
end
|
@@ -3,6 +3,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
3
3
|
namespace :deprec do
|
4
4
|
namespace :redhat_cluster do
|
5
5
|
|
6
|
+
# check redhat cluster's cluster.conf manpage for explanation of configuring this
|
6
7
|
set :redhat_cluster_name, 'storagecluster'
|
7
8
|
set :redhat_cluster_config_version, '1'
|
8
9
|
set :redhat_cluster_nodes, [
|
@@ -114,11 +115,14 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
114
115
|
end
|
115
116
|
|
116
117
|
task :config_common, :roles => [:storage_client, :storage_server] do
|
118
|
+
# add some modules to load
|
117
119
|
deprec2.append_to_file_if_missing('/etc/modules', 'dm-mod')
|
118
120
|
deprec2.append_to_file_if_missing('/etc/modules', 'gfs')
|
119
121
|
deprec2.append_to_file_if_missing('/etc/modules', 'lock_dlm')
|
120
|
-
deprec2.append_to_file_if_missing('/etc/modules', 'gnbd')
|
122
|
+
deprec2.append_to_file_if_missing('/etc/modules', 'gnbd')
|
123
|
+
# comment out default setting of locking_type
|
121
124
|
deprec2.substitute_in_file('/etc/lvm/lvm.conf', '^(\s*)(locking_type = 1\s*)$', '$1#$2')
|
125
|
+
# uncomment these settings in config file
|
122
126
|
deprec2.substitute_in_file('/etc/lvm/lvm.conf', '^(\s*)#\s*(locking_library = \"liblvm2clusterlock.so\"\s*)$', '$1$2')
|
123
127
|
deprec2.substitute_in_file('/etc/lvm/lvm.conf', '^(\s*)#\s*(locking_type = 2\s*)$', '$1$2')
|
124
128
|
deprec2.substitute_in_file('/etc/lvm/lvm.conf', '^(\s*)#\s*(library_dir = \"/lib/lvm2\"\s*)$', '$1$2', '%')
|
data/lib/deprec/recipes/rvm.rb
CHANGED
@@ -19,9 +19,9 @@ EOF
|
|
19
19
|
task :install_rubies do
|
20
20
|
rvm_rubies.each_with_index do |ruby, i|
|
21
21
|
run "rvm install #{ruby}"
|
22
|
-
run "rvm --default #{ruby}" if i == 0 && (rvm_default_ruby.
|
22
|
+
run "rvm --default #{ruby}" if i == 0 && (rvm_default_ruby.nil? || rvm_default_ruby.empty?)
|
23
23
|
end
|
24
|
-
if !(rvm_default_ruby.
|
24
|
+
if !(rvm_default_ruby.nil? || rvm_default_ruby.empty?)
|
25
25
|
set_default = rvm_default_ruby == "system" ? rvm_default_ruby : "--default #{rvm_default_ruby}"
|
26
26
|
run "rvm #{set_default}"
|
27
27
|
end
|
@@ -4,7 +4,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
4
4
|
namespace :s3utils do
|
5
5
|
|
6
6
|
set :s3utils_bucket_location, 'EU'
|
7
|
-
set :s3utils_calling_format, 'SUBDOMAIN'
|
7
|
+
set :s3utils_calling_format, 'SUBDOMAIN' # used by s3sync in s3config.yml
|
8
8
|
set :s3utils_access_key, "0123456789ABCDEFGHIJ"
|
9
9
|
set :s3utils_secret_key, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcd"
|
10
10
|
set :s3utils_passphrase, "my_passphrase"
|
@@ -19,7 +19,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
19
19
|
:install => 'cp -a S3 s3cmd /usr/local/bin/ ; cp s3cmd.1 /usr/local/share/man/man1/ ;'
|
20
20
|
}
|
21
21
|
|
22
|
-
# setting it as a class variable makes it initialize too early, making 'user' contain the wrong value!
|
22
|
+
# XXX - setting it as a class variable makes it initialize too early, making 'user' contain the wrong value! - le1t0
|
23
23
|
def s3utils_system_config_files
|
24
24
|
[
|
25
25
|
{:template => "s3cfg",
|
data/lib/deprec/recipes/ssh.rb
CHANGED
@@ -3,15 +3,23 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
3
3
|
namespace :deprec do
|
4
4
|
namespace :ssh do
|
5
5
|
|
6
|
-
#
|
7
|
-
# :
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# define SSH keys for each user
|
7
|
+
# :ssh_user_keys should be a hash, with:
|
8
|
+
# * the keys being any identifier for the user
|
9
|
+
# * the values being either:
|
10
|
+
# ** one SSH key in the form of a string
|
11
|
+
# ** multiple SSH keys in the form of an array of strings
|
12
|
+
# Define this variable in the main deploy.rb when using multistage capistrano
|
10
13
|
set :ssh_user_keys, { }
|
11
|
-
|
12
|
-
#
|
14
|
+
# :ssh_users should contain an array of user identifiers as defined in :ssh_user_keys,
|
15
|
+
# use this variable to define which users have access to the all the servers defined.
|
16
|
+
# Specify this variable in a stage deploy file when using multistage capistrano
|
17
|
+
# (so you can have different users have access to different servers)
|
13
18
|
set :ssh_users, [ ]
|
14
|
-
|
19
|
+
# :ssh_known_hosts variable should contain the hostnames or IP addresses (as an array of strings)
|
20
|
+
# of all hosts that should be put in the deploy_user's known_hosts file. This known_hosts file will
|
21
|
+
# be put on all defined servers.
|
22
|
+
set :ssh_known_hosts, [ ]
|
15
23
|
|
16
24
|
SYSTEM_CONFIG_FILES[:ssh] = [
|
17
25
|
|
@@ -43,6 +51,10 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
43
51
|
restart
|
44
52
|
end
|
45
53
|
|
54
|
+
# set access for SSH:
|
55
|
+
# * add keys of users to authorized_keys file of deploy_user
|
56
|
+
# * add host keys to known hosts file of deploy_user
|
57
|
+
desc "create authorized_keys and known_hosts files on servers"
|
46
58
|
task :set_access do
|
47
59
|
if ssh_users.size > 0
|
48
60
|
run "rm -f ~/.ssh/authorized_keys.new"
|
@@ -56,16 +68,9 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
56
68
|
run "mv ~/.ssh/authorized_keys.new ~/.ssh/authorized_keys"
|
57
69
|
end
|
58
70
|
|
59
|
-
if
|
60
|
-
|
61
|
-
|
62
|
-
keys = [ssh_host_keys[ssh_user]].flatten
|
63
|
-
keys.each do |ssh_key|
|
64
|
-
deprec2.append_to_file_if_missing('~/.ssh/known_hosts.new', ssh_key)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
run "cp ~/.ssh/known_hosts ~/.ssh/known_hosts.bak"
|
68
|
-
run "mv ~/.ssh/known_hosts.new ~/.ssh/known_hosts"
|
71
|
+
if ssh_known_hosts.size > 0
|
72
|
+
put ssh_known_hosts.join("\n"), tmp_file = "/tmp/ssh_keyscan_#{Time.now.strftime("%Y%m%d%H%M%S")}.txt", :mode => 0644
|
73
|
+
run "ssh-keyscan -f #{tmp_file} -t rsa > ~/.ssh/known_hosts ; rm -f #{tmp_file}"
|
69
74
|
end
|
70
75
|
end
|
71
76
|
|
@@ -47,7 +47,9 @@ function define_forwards () {
|
|
47
47
|
srcport="$(echo $forward | cut -d '>' -f 1 | cut -d ':' -f 2)"
|
48
48
|
destip="$(echo $forward | cut -d ';' -f 1 | cut -d '>' -f '2' | cut -d ':' -f 1)"
|
49
49
|
destport="$(echo $forward | cut -d ';' -f 1 | cut -d '>' -f '2' | cut -d ':' -f 2)"
|
50
|
+
# define forward in the nat chain, redirect to the destination IP and port
|
50
51
|
$IPTABLES -t nat -A PREROUTING -p $proto -d $localip --dport $srcport -j DNAT --to $destip:$destport
|
52
|
+
# allow access to the destination IP and port in the FORWARD chain
|
51
53
|
$IPTABLES -A FORWARD -p $proto -d $destip --dport $destport -j ACCEPT
|
52
54
|
} ; done
|
53
55
|
}
|
@@ -71,7 +73,9 @@ function set_default_rules () {
|
|
71
73
|
|
72
74
|
# don't call parse_sources directly! It's called by set_allowed_rules
|
73
75
|
function parse_sources () {
|
76
|
+
# parse all sources
|
74
77
|
for sourcedef in $1 ; do {
|
78
|
+
# define targets for each source
|
75
79
|
parse_targets "$2" "-s ${sourcedef}"
|
76
80
|
} ; done
|
77
81
|
}
|
@@ -79,13 +83,18 @@ function parse_sources () {
|
|
79
83
|
# don't call parse_targets directly! It's called by set_allowed_rules
|
80
84
|
function parse_targets () {
|
81
85
|
sourcedef="$2"
|
86
|
+
# parse all targets
|
82
87
|
for targetdef in $1 ; do {
|
88
|
+
# targets should be define as:
|
89
|
+
# protocol[,protocol[,...]][:port[,port[,...]]]
|
83
90
|
protocols="$(echo $targetdef | awk -F ":" '{ print $1; }' | sed 's/,/ /g')"
|
84
91
|
ports="$(echo $targetdef | awk -F ":" '{ print $2; }' | sed 's/,/ /g')"
|
85
92
|
for protocol in ${protocols} ; do {
|
93
|
+
# if no ports are defined, just allow access to the entire protocol (i.e. pptp, vrrp, etc)
|
86
94
|
if [ -z "${ports}" ] ; then
|
87
95
|
$IPTABLES -A INPUT -p ${protocol} ${sourcedef} -m state --state NEW -j ACCEPT ;
|
88
96
|
else
|
97
|
+
# for each defined port, allow access to the defined source or the world (if empty)
|
89
98
|
for port in ${ports} ; do {
|
90
99
|
OPT="--dport"
|
91
100
|
[ "$protocol" = "icmp" ] && OPT="--icmp-type"
|
@@ -97,12 +106,18 @@ function parse_targets () {
|
|
97
106
|
}
|
98
107
|
|
99
108
|
function set_allowed_rules () {
|
109
|
+
# all rules should be defined in one space separated variable called allowed
|
100
110
|
for ruledef in ${allowed} ; do {
|
111
|
+
# everything before the @ sign defines the target specification (i.e. IP + port or protocol to allow access to)
|
112
|
+
# targets should be semi colon (;) separated (which are changed to spaces for easy parsing)
|
101
113
|
target="$(echo $ruledef | awk -F "@" '{ print $1; }' | sed 's/;/ /g')"
|
114
|
+
# everything after the @ sign defines the source specification (i.e. IP, etc from where the request is coming)
|
115
|
+
# sources should be comma (,) separated (which are changed to spaces for easy parsing)
|
102
116
|
source="$(echo $ruledef | awk -F "@" '{ print $2; }' | sed 's/,/ /g')"
|
117
|
+
# if there is no source specification (allow entire world), then only define the target
|
103
118
|
if [ -z "${source}" ] ; then
|
104
119
|
parse_targets "$target"
|
105
|
-
else
|
120
|
+
else # else define the source first
|
106
121
|
parse_sources "$source" "$target"
|
107
122
|
fi
|
108
123
|
} ; done
|
@@ -1,18 +1,38 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# touch /etc/keepalived/MASTER to make a BACKUP keepalived be MASTER. Remove it to make it BACKUP again.
|
2
|
+
vrrp_script chk_wanted_state {
|
3
|
+
script "test -e /etc/keepalived/MASTER"
|
4
|
+
interval 1
|
5
|
+
weight 2
|
5
6
|
}
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
<% keepalived_scripts.each do |name, settings| %>
|
9
|
+
vrrp_script chk_<%= name.to_s %> {
|
10
|
+
script "<%= settings[:script] %>"
|
11
|
+
interval <%= settings[:interval] %>
|
12
|
+
weight <%= settings[:weight] %>
|
13
|
+
}
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<% instance_counter = 1 %>
|
17
|
+
<% keepalived_instances.each do |virtual_ipaddress, settings| %>
|
18
|
+
vrrp_instance VI_<%= instance_counter %> {
|
19
|
+
interface <%= settings[:interface] %>
|
20
|
+
state <%= settings[:state] %>
|
21
|
+
virtual_router_id <%= settings[:virtual_router_id] %>
|
22
|
+
priority <%= settings[:priority] %>
|
12
23
|
virtual_ipaddress {
|
13
|
-
<%=
|
24
|
+
<%= virtual_ipaddress %>
|
14
25
|
}
|
26
|
+
<% if settings[:state] != 'MASTER' %>
|
27
|
+
track_script {
|
28
|
+
chk_wanted_state
|
29
|
+
}
|
30
|
+
<% end %>
|
31
|
+
<% (settings[:scripts].nil? || [settings[:scripts]].flatten.empty? ? keepalived_scripts.keys : [settings[:scripts]].flatten).each do |name| %>
|
15
32
|
track_script {
|
16
|
-
|
33
|
+
chk_<%= name.to_s %>
|
17
34
|
}
|
18
|
-
|
35
|
+
<% end %>
|
36
|
+
}
|
37
|
+
<% instance_counter += 1 %>
|
38
|
+
<% end %>
|
metadata
CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
|
|
6
6
|
- 2
|
7
7
|
- 1
|
8
8
|
- 6
|
9
|
-
-
|
10
|
-
version: 2.1.6.
|
9
|
+
- 7
|
10
|
+
version: 2.1.6.007
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Le1t0
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-06-
|
18
|
+
date: 2010-06-10 00:00:00 +02:00
|
19
19
|
default_executable: depify
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|