dust-deploy 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/dust +7 -7
- data/changelog.md +9 -0
- data/lib/dust.rb +1 -0
- data/lib/dust/examples/nodes/db-staging.yaml +9 -10
- data/lib/dust/examples/nodes/mail.yaml +2 -1
- data/lib/dust/examples/nodes/mysql-production.yaml +4 -1
- data/lib/dust/examples/nodes/proxy-staging.yaml +4 -12
- data/lib/dust/examples/templates/motd/motd.erb +2 -2
- data/lib/dust/examples/templates/postgres/pacemaker.sh.erb +6 -6
- data/lib/dust/examples/templates/postgres/postgresql.conf.erb +8 -8
- data/lib/dust/examples/templates/postgres/recovery.conf.erb +4 -4
- data/lib/dust/examples/templates/zabbix_agent/zabbix_agentd.conf.erb +13 -13
- data/lib/dust/recipe.rb +15 -0
- data/lib/dust/recipes/aliases.rb +5 -7
- data/lib/dust/recipes/basic_setup.rb +13 -15
- data/lib/dust/recipes/debsecan.rb +7 -7
- data/lib/dust/recipes/duplicity.rb +22 -27
- data/lib/dust/recipes/etc_hosts.rb +6 -8
- data/lib/dust/recipes/iptables.rb +6 -11
- data/lib/dust/recipes/locale.rb +8 -8
- data/lib/dust/recipes/memory_limit.rb +6 -8
- data/lib/dust/recipes/motd.rb +4 -6
- data/lib/dust/recipes/mysql.rb +20 -22
- data/lib/dust/recipes/newrelic.rb +8 -8
- data/lib/dust/recipes/nginx.rb +12 -14
- data/lib/dust/recipes/packages.rb +4 -4
- data/lib/dust/recipes/postgres.rb +53 -61
- data/lib/dust/recipes/rc_local.rb +7 -7
- data/lib/dust/recipes/remove_packages.rb +4 -4
- data/lib/dust/recipes/repositories.rb +18 -18
- data/lib/dust/recipes/resolv_conf.rb +15 -15
- data/lib/dust/recipes/ssh_authorized_keys.rb +12 -14
- data/lib/dust/recipes/unattended_upgrades.rb +16 -18
- data/lib/dust/recipes/zabbix_agent.rb +29 -31
- data/lib/dust/version.rb +1 -1
- metadata +4 -3
data/bin/dust
CHANGED
@@ -127,9 +127,9 @@ module Dust
|
|
127
127
|
|
128
128
|
# runs the method with the recipe name, defined and included in recipe/*.rb
|
129
129
|
# call recipes for each recipe that is defined for this node
|
130
|
-
recipes.each do |recipe,
|
130
|
+
recipes.each do |recipe, config|
|
131
131
|
::Dust.print_recipe recipe
|
132
|
-
send recipe,
|
132
|
+
send recipe, 'prepare', server, recipe, context, config, options
|
133
133
|
puts
|
134
134
|
end
|
135
135
|
|
@@ -140,20 +140,20 @@ module Dust
|
|
140
140
|
# generate list of recipes for this node
|
141
141
|
def generate_recipes node, context
|
142
142
|
recipes = {}
|
143
|
-
node['recipes'].each do |recipe,
|
143
|
+
node['recipes'].each do |recipe, config|
|
144
144
|
|
145
145
|
# in case --recipes was set, skip unwanted recipes
|
146
146
|
next unless options[:recipes].include?(recipe) if options[:recipes]
|
147
147
|
|
148
148
|
# skip disabled recipes
|
149
|
-
next if
|
149
|
+
next if config == 'disabled'
|
150
150
|
|
151
151
|
# check if method and thor task actually exist
|
152
152
|
k = Thor::Util.find_by_namespace recipe
|
153
153
|
next unless k
|
154
154
|
next unless k.method_defined? context
|
155
155
|
|
156
|
-
recipes[recipe] =
|
156
|
+
recipes[recipe] = config
|
157
157
|
end
|
158
158
|
recipes
|
159
159
|
end
|
@@ -220,8 +220,8 @@ module Dust
|
|
220
220
|
|
221
221
|
# if hostname is a valid ip address, don't add domain
|
222
222
|
# so we can connect via ip address only
|
223
|
-
unless IPAddress.valid? hostname
|
224
|
-
n['fqdn'] += '.' + n['domain']
|
223
|
+
unless IPAddress.valid? hostname
|
224
|
+
n['fqdn'] += '.' + n['domain'] if n['domain']
|
225
225
|
end
|
226
226
|
|
227
227
|
# pass command line proxy option
|
data/changelog.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
Changelog
|
2
2
|
=============
|
3
3
|
|
4
|
+
0.4.0
|
5
|
+
------------
|
6
|
+
|
7
|
+
switches to the new recipe superclass. please upgrade your recipes and templates
|
8
|
+
- quick hint: change node -> @node and config/ingredients -> @config, options -> @options
|
9
|
+
- the new system makes it easier to write recipes, and you can also use methods more easily, as configuration and node are class-wide private variables now.
|
10
|
+
- if you need more information, best have a look at the iptables recipe at https://github.com/kechagia/dust-deploy/blob/master/lib/dust/recipes/iptables.rb (or one of the others)
|
11
|
+
|
12
|
+
|
4
13
|
0.3.3
|
5
14
|
------------
|
6
15
|
|
data/lib/dust.rb
CHANGED
@@ -8,14 +8,13 @@ recipes:
|
|
8
8
|
dbuser: 'postgres:postgres'
|
9
9
|
|
10
10
|
iptables:
|
11
|
-
|
12
|
-
- 22
|
13
|
-
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
input:
|
12
|
+
- ssh: { dport: 22, match: state, state: NEW }
|
13
|
+
- postgres:
|
14
|
+
dport: 5432
|
15
|
+
match: state
|
16
|
+
state: new
|
17
|
+
in-interface: eth1
|
18
|
+
source: 10.0.0.0/8
|
19
|
+
|
21
20
|
rc_local: blockdev --setra 8192 /dev/vda
|
@@ -7,15 +7,7 @@ recipes:
|
|
7
7
|
sites-enabled: [ proxy ]
|
8
8
|
|
9
9
|
iptables:
|
10
|
-
|
11
|
-
-
|
12
|
-
-
|
13
|
-
|
14
|
-
source: 10.13.37.0/24
|
15
|
-
interface: eth1
|
16
|
-
ip-version: 4
|
17
|
-
- port: 53
|
18
|
-
protocol: udp
|
19
|
-
source: 10.13.37.0/24
|
20
|
-
interface: eth1
|
21
|
-
ip-version: 4
|
10
|
+
input:
|
11
|
+
- ssh: { dport: 22, match: state, state: NEW }
|
12
|
+
- http: { dport: [80, 443], match: state, state: NEW, source: 192.168.1.0/24 }
|
13
|
+
- dns: { dport: 53, protocol: udp }
|
@@ -1,6 +1,6 @@
|
|
1
|
-
this is <%= Dust.blue %><%= node['hostname'] %><%= Dust.none %>, a <%= node['domain'] %> <%= node['environment'] %> server
|
1
|
+
this is <%= Dust.blue %><%= @node['hostname'] %><%= Dust.none %>, a <%= @node['domain'] %> <%= @node['environment'] %> server
|
2
2
|
|
3
|
-
% if node['environment'] == 'production'
|
3
|
+
% if @node['environment'] == 'production'
|
4
4
|
just in case you didn't notice the line above, maybe this cow helps:
|
5
5
|
|
6
6
|
___________________________________
|
@@ -1,25 +1,25 @@
|
|
1
1
|
#!/bin/bash
|
2
2
|
|
3
3
|
# user as which postgres runs
|
4
|
-
PG_USER=<%= config['dbuser'] %>
|
4
|
+
PG_USER=<%= @config['dbuser'] %>
|
5
5
|
|
6
6
|
# path to postgres directory (data and archives)
|
7
|
-
PG_DATA=<%= config['data-dir'] %>
|
8
|
-
PG_ARCHIVE=<%= config['archive-dir'] %>
|
7
|
+
PG_DATA=<%= @config['data-dir'] %>
|
8
|
+
PG_ARCHIVE=<%= @config['archive-dir'] %>
|
9
9
|
|
10
10
|
# path to recovery.conf (on slaves)
|
11
11
|
RECOVERY=$PG_DATA/recovery.conf
|
12
12
|
RECOVERY_DONE=$PG_DATA/recovery.done
|
13
13
|
|
14
14
|
# path to postgresql init script
|
15
|
-
% if node.is_gentoo?
|
16
|
-
PG_INIT=/etc/init.d/postgresql-<%= config['version'] %>
|
15
|
+
% if @node.is_gentoo?
|
16
|
+
PG_INIT=/etc/init.d/postgresql-<%= @config['version'] %>
|
17
17
|
% else
|
18
18
|
PG_INIT=/etc/init.d/postgresql
|
19
19
|
% end
|
20
20
|
|
21
21
|
# the clustered IP
|
22
|
-
DB_MASTER=db-<%= node['environment'] %>-master.<%= node['domain'] %>
|
22
|
+
DB_MASTER=db-<%= @node['environment'] %>-master.<%= @node['domain'] %>
|
23
23
|
|
24
24
|
|
25
25
|
start() {
|
@@ -1,19 +1,19 @@
|
|
1
|
-
data_directory = '<%= config['data-dir'] %>'
|
2
|
-
hba_file = '<%= config['conf-dir'] %>/pg_hba.conf'
|
3
|
-
ident_file = '<%= config['conf-dir'] %>/pg_ident.conf'
|
1
|
+
data_directory = '<%= @config['data-dir'] %>'
|
2
|
+
hba_file = '<%= @config['conf-dir'] %>/pg_hba.conf'
|
3
|
+
ident_file = '<%= @config['conf-dir'] %>/pg_ident.conf'
|
4
4
|
|
5
5
|
listen_addresses = '*'
|
6
6
|
port = 5432
|
7
7
|
ssl = on
|
8
8
|
|
9
|
-
% if node['environment'] == 'production'
|
9
|
+
% if @node['environment'] == 'production'
|
10
10
|
max_connections = 200
|
11
11
|
% else
|
12
12
|
max_connections = 100
|
13
13
|
% end
|
14
14
|
|
15
15
|
|
16
|
-
% if node['environment'] == 'production'
|
16
|
+
% if @node['environment'] == 'production'
|
17
17
|
shared_buffers = 1152MB # min 128kB
|
18
18
|
work_mem = 12MB # min 64kB
|
19
19
|
maintenance_work_mem = 288MB # min 1MB
|
@@ -27,7 +27,7 @@ full_page_writes = yes # make xfs usage safe
|
|
27
27
|
|
28
28
|
wal_level = hot_standby # minimal, archive, or hot_standby
|
29
29
|
|
30
|
-
% if node['environment'] == 'production'
|
30
|
+
% if @node['environment'] == 'production'
|
31
31
|
wal_buffers = 8MB # min 32kB
|
32
32
|
checkpoint_segments = 16 # in logfile segments, min 1, 16MB each
|
33
33
|
checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0
|
@@ -39,13 +39,13 @@ checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0
|
|
39
39
|
|
40
40
|
|
41
41
|
archive_mode = yes
|
42
|
-
archive_command = 'cp -i %p <%= config['archive-dir'] %>/%f < /dev/null'
|
42
|
+
archive_command = 'cp -i %p <%= @config['archive-dir'] %>/%f < /dev/null'
|
43
43
|
|
44
44
|
max_wal_senders = 5
|
45
45
|
wal_keep_segments = 32
|
46
46
|
hot_standby = on
|
47
47
|
|
48
|
-
% if node['environment'] == 'production'
|
48
|
+
% if @node['environment'] == 'production'
|
49
49
|
effective_cache_size = 3584MB
|
50
50
|
% else
|
51
51
|
#effective_cache_size = 128MB
|
@@ -43,7 +43,7 @@
|
|
43
43
|
# NOTE that the basename of %p will be different from %f; do not
|
44
44
|
# expect them to be interchangeable.
|
45
45
|
#
|
46
|
-
restore_command = 'cp -i <%= config['archive-dir'] %>/%f %p < /dev/null'
|
46
|
+
restore_command = 'cp -i <%= @config['archive-dir'] %>/%f %p < /dev/null'
|
47
47
|
#
|
48
48
|
#
|
49
49
|
# archive_cleanup_command
|
@@ -97,9 +97,9 @@ restore_command = 'cp -i <%= config['archive-dir'] %>/%f %p < /dev/null'
|
|
97
97
|
#
|
98
98
|
standby_mode = 'on'
|
99
99
|
#
|
100
|
-
% if node['environment'] == 'production'
|
100
|
+
% if @node['environment'] == 'production'
|
101
101
|
primary_conninfo = '<your pg connection string here>'
|
102
|
-
% elsif node['environment'] == 'staging'
|
102
|
+
% elsif @node['environment'] == 'staging'
|
103
103
|
primary_conninfo = '<your pg connection string here>'
|
104
104
|
% end
|
105
105
|
|
@@ -111,7 +111,7 @@ primary_conninfo = '<your pg connection string here>'
|
|
111
111
|
# Server will poll the trigger file path periodically and stop streaming
|
112
112
|
# when it's found.
|
113
113
|
#
|
114
|
-
trigger_file = '/var/lib/postgresql/<%= config['version'] %>/master_trigger'
|
114
|
+
trigger_file = '/var/lib/postgresql/<%= @config['version'] %>/master_trigger'
|
115
115
|
#
|
116
116
|
#---------------------------------------------------------------------------
|
117
117
|
# HOT STANDBY PARAMETERS
|
@@ -10,7 +10,7 @@
|
|
10
10
|
# Note that hostnames must resolve hostname->IP address and
|
11
11
|
# IP address->hostname.
|
12
12
|
|
13
|
-
Server=zabbix.<%= node['domain'] %>
|
13
|
+
Server=zabbix.<%= @node['domain'] %>
|
14
14
|
|
15
15
|
# Server port for sending active checks
|
16
16
|
|
@@ -18,7 +18,7 @@ Server=zabbix.<%= node['domain'] %>
|
|
18
18
|
|
19
19
|
# Unique hostname. Required for active checks.
|
20
20
|
|
21
|
-
Hostname=<%= node['fqdn'] %>
|
21
|
+
Hostname=<%= @node['fqdn'] %>
|
22
22
|
|
23
23
|
# Listen port. Default is 10050
|
24
24
|
|
@@ -59,22 +59,22 @@ DebugLevel=3
|
|
59
59
|
|
60
60
|
# Name of PID file
|
61
61
|
|
62
|
-
% if node.uses_apt?
|
62
|
+
% if @node.uses_apt?
|
63
63
|
PidFile=/var/run/zabbix-agent/zabbix_agentd.pid
|
64
|
-
% elsif node.uses_emerge?
|
64
|
+
% elsif @node.uses_emerge?
|
65
65
|
PidFile=/var/run/zabbix/zabbix_agentd.pid
|
66
|
-
% elsif node.uses_rpm?
|
66
|
+
% elsif @node.uses_rpm?
|
67
67
|
PidFile=/var/run/zabbix/zabbix_agentd.pid
|
68
68
|
% end
|
69
69
|
|
70
70
|
# Name of log file.
|
71
71
|
# If not set, syslog will be used
|
72
72
|
|
73
|
-
% if node.uses_apt?
|
73
|
+
% if @node.uses_apt?
|
74
74
|
LogFile=/var/log/zabbix-agent/zabbix_agentd.log
|
75
|
-
% elsif node.uses_emerge?
|
75
|
+
% elsif @node.uses_emerge?
|
76
76
|
LogFile=/var/log/zabbix/zabbix_agentd.log
|
77
|
-
% elsif node.uses_emerge?
|
77
|
+
% elsif @node.uses_emerge?
|
78
78
|
LogFile=/var/log/zabbix/zabbix_agentd.log
|
79
79
|
% end
|
80
80
|
|
@@ -91,21 +91,21 @@ Timeout=30
|
|
91
91
|
# Note that shell command must not return empty string or EOL only
|
92
92
|
|
93
93
|
# system updates
|
94
|
-
% if node.uses_apt?
|
94
|
+
% if @node.uses_apt?
|
95
95
|
UserParameter=debian.updates,aptitude search '~U' |wc -l
|
96
96
|
UserParameter=debian.security,debsecan --suite squeeze --only-fixed --format packages |wc -l
|
97
97
|
|
98
|
-
% elsif node.uses_emerge?
|
98
|
+
% elsif @node.uses_emerge?
|
99
99
|
UserParameter=gentoo.security,glsa-check -t all 2>/dev/null | wc -l
|
100
100
|
UserParameter=gentoo.updates,emerge -uNDp @world | grep ebuild|wc -l
|
101
101
|
UserParameter=gentoo.portage,emerge --info| grep 'Timestamp of tree' | sed -e s/'Timestamp of tree':// -e 's/\n//' | xargs -I {} date --date={} +%s |xargs -I {} expr $(date +%s) - {}
|
102
102
|
UserParameter=gentoo.config,find /etc/ -name '._cfg*' 2>/dev/null|wc -l
|
103
103
|
|
104
|
-
% elsif node.uses_rpm?
|
104
|
+
% elsif @node.uses_rpm?
|
105
105
|
UserParameter=centos.updates,yum check-update -q |wc -l
|
106
106
|
% end
|
107
107
|
|
108
|
-
% if node.package_installed?( [ 'postgresql-server', 'postgresql' ], true )
|
108
|
+
% if @node.package_installed?( [ 'postgresql-server', 'postgresql' ], true )
|
109
109
|
# postgres
|
110
110
|
UserParameter=psql.version,psql --version|head -n1
|
111
111
|
UserParameter=psql.server_processes,psql -U zabbix -t -c "select sum(numbackends) from pg_stat_database" postgres
|
@@ -121,7 +121,7 @@ UserParameter=psql.blks_hit,psql -U zabbix -t -c "select sum(blks_hit) from pg_s
|
|
121
121
|
UserParameter=psql.blks_read,psql -U zabbix -t -c "select sum(blks_read) from pg_stat_database" postgres
|
122
122
|
% end
|
123
123
|
|
124
|
-
% if node.package_installed?('arcconf', true)
|
124
|
+
% if @node.package_installed?('arcconf', true)
|
125
125
|
# adaptec raid
|
126
126
|
UserParameter=raid.smart_warnings,/sbin/arcconf getconfig 1 pd |grep "S.M.A.R.T. warnings" | awk '{SMART += $4} END {print SMART}'
|
127
127
|
UserParameter=raid.disk_rpm,/sbin/arcconf getconfig 1 pd |grep "Power State" |grep -v "Full rpm" |wc -l
|
data/lib/dust/recipe.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class Recipe < Thor
|
2
|
+
|
3
|
+
desc 'prepare', 'prepare recipe (do not use manually)'
|
4
|
+
def prepare node, recipe, context, config, options
|
5
|
+
|
6
|
+
# prepare class variables
|
7
|
+
@template_path = "./templates/#{recipe}"
|
8
|
+
@node = node
|
9
|
+
@config = config
|
10
|
+
@options = options
|
11
|
+
|
12
|
+
# run task
|
13
|
+
send context
|
14
|
+
end
|
15
|
+
end
|
data/lib/dust/recipes/aliases.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
-
class Aliases <
|
1
|
+
class Aliases < Recipe
|
2
2
|
desc 'aliases:deploy', 'installs email aliases'
|
3
|
-
def deploy
|
4
|
-
|
5
|
-
|
6
|
-
return unless node.package_installed? 'postfix'
|
7
|
-
node.scp "#{template_path}/aliases", '/etc/aliases'
|
3
|
+
def deploy
|
4
|
+
return unless @node.package_installed? 'postfix'
|
5
|
+
@node.scp "#{@template_path}/aliases", '/etc/aliases'
|
8
6
|
|
9
7
|
::Dust.print_msg 'running newaliases'
|
10
|
-
::Dust.print_result node.exec('newaliases')[:exit_code]
|
8
|
+
::Dust.print_result @node.exec('newaliases')[:exit_code]
|
11
9
|
end
|
12
10
|
end
|
13
11
|
|
@@ -1,33 +1,31 @@
|
|
1
|
-
class BasicSetup <
|
1
|
+
class BasicSetup < Recipe
|
2
2
|
desc 'basic_setup:deploy', 'installs basic packages and config files'
|
3
|
-
def deploy
|
4
|
-
template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
|
5
|
-
|
3
|
+
def deploy
|
6
4
|
# install some basic packages
|
7
5
|
::Dust.print_msg "installing basic packages\n"
|
8
6
|
|
9
|
-
node.install_package 'screen', :indent => 2
|
10
|
-
node.install_package 'rsync', :indent => 2
|
11
|
-
node.install_package 'psmisc', :indent => 2 if node.uses_apt?
|
7
|
+
@node.install_package 'screen', :indent => 2
|
8
|
+
@node.install_package 'rsync', :indent => 2
|
9
|
+
@node.install_package 'psmisc', :indent => 2 if @node.uses_apt?
|
12
10
|
|
13
|
-
if node.uses_rpm?
|
14
|
-
node.install_package 'vim-enhanced', :indent => 2
|
11
|
+
if @node.uses_rpm?
|
12
|
+
@node.install_package 'vim-enhanced', :indent => 2
|
15
13
|
else
|
16
|
-
node.install_package 'vim', :indent => 2
|
14
|
+
@node.install_package 'vim', :indent => 2
|
17
15
|
end
|
18
16
|
|
19
|
-
if node.uses_apt?
|
20
|
-
node.install_package 'git-core', :indent => 2
|
17
|
+
if @node.uses_apt?
|
18
|
+
@node.install_package 'git-core', :indent => 2
|
21
19
|
else
|
22
|
-
node.install_package 'git', :indent => 2
|
20
|
+
@node.install_package 'git', :indent => 2
|
23
21
|
end
|
24
22
|
puts
|
25
23
|
|
26
24
|
# deploy basic configuration for root user
|
27
25
|
::Dust.print_msg "deploying configuration files for root\n"
|
28
|
-
Dir["#{template_path}/.*"].each do |file|
|
26
|
+
Dir["#{@template_path}/.*"].each do |file|
|
29
27
|
next unless File.file? file
|
30
|
-
node.scp file, "/root/#{File.basename file}", :indent => 2
|
28
|
+
@node.scp file, "/root/#{File.basename file}", :indent => 2
|
31
29
|
end
|
32
30
|
|
33
31
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
class Debsecan <
|
1
|
+
class Debsecan < Recipe
|
2
2
|
desc 'debsecan:deploy', 'installs and configures debian security package "debsecan"'
|
3
|
-
def deploy
|
4
|
-
node.collect_facts
|
3
|
+
def deploy
|
4
|
+
@node.collect_facts
|
5
5
|
|
6
|
-
if node.is_os? ['ubuntu', 'debian']
|
7
|
-
node.install_package 'debsecan'
|
6
|
+
if @node.is_os? ['ubuntu', 'debian']
|
7
|
+
@node.install_package 'debsecan'
|
8
8
|
|
9
9
|
::Dust.print_msg 'configuring debsecan'
|
10
10
|
|
@@ -25,7 +25,7 @@ class Debsecan < Thor
|
|
25
25
|
# configures the suite
|
26
26
|
config_file += "# For better reporting, specify the correct suite here, using the code\n" +
|
27
27
|
"# name (that is, \"sid\" instead of \"unstable\").\n" +
|
28
|
-
"SUITE=#{node['lsbdistcodename']}\n\n"
|
28
|
+
"SUITE=#{@node['lsbdistcodename']}\n\n"
|
29
29
|
|
30
30
|
# which user gets the reports?
|
31
31
|
config_file += "# Mail address to which reports are sent.\n" +
|
@@ -36,7 +36,7 @@ class Debsecan < Thor
|
|
36
36
|
"# built-in default.\n" +
|
37
37
|
"SOURCE=#{config['source']}\n\n"
|
38
38
|
|
39
|
-
node.write '/etc/default/debsecan', config_file, :quiet => true
|
39
|
+
@node.write '/etc/default/debsecan', config_file, :quiet => true
|
40
40
|
::Dust.print_ok
|
41
41
|
else
|
42
42
|
::Dust.print_failed 'os not supported'
|