File Coverage

lib/Ubic/Admin/Setup.pm
Criterion Covered Total %
statement 33 244 13.5
branch 0 116 0.0
condition 0 21 0.0
subroutine 11 21 52.3
pod 7 7 100.0
total 51 409 12.4


line stmt bran cond sub pod time code
1             package Ubic::Admin::Setup;
2             $Ubic::Admin::Setup::VERSION = '1.60';
3             # ABSTRACT: this module handles ubic setup: asks user some questions and configures your system
4              
5              
6 1     1   672 use strict;
  1         2  
  1         23  
7 1     1   3 use warnings;
  1         1  
  1         22  
8              
9 1     1   579 use Getopt::Long 2.33;
  1         7084  
  1         17  
10 1     1   97 use Carp;
  1         1  
  1         43  
11 1     1   402 use IPC::Open3;
  1         1880  
  1         42  
12 1     1   5 use File::Path;
  1         1  
  1         38  
13 1     1   375 use File::Which;
  1         624  
  1         58  
14 1     1   7 use File::Spec;
  1         1  
  1         29  
15              
16 1     1   9 use Ubic::AtomicFile;
  1         1  
  1         18  
17 1     1   3 use Ubic::Settings;
  1         1  
  1         16  
18 1     1   3 use Ubic::Settings::ConfigFile;
  1         1  
  1         1791  
19              
20             my $batch_mode;
21             my $quiet;
22              
23             sub _defaults {
24 0 0   0     if ($^O eq 'freebsd') {
25             return (
26 0           config => '/usr/local/etc/ubic/ubic.cfg',
27             data_dir => '/var/db/ubic',
28             service_dir => '/usr/local/etc/ubic/service',
29             log_dir => '/var/log/ubic',
30             example => '/usr/local/etc, /var',
31             );
32             }
33             else {
34             # fhs
35             return (
36 0           config => '/etc/ubic/ubic.cfg',
37             data_dir => '/var/lib/ubic',
38             service_dir => '/etc/ubic/service',
39             log_dir => '/var/log/ubic',
40             example => '/etc, /var',
41             );
42             }
43             };
44              
45             sub print_tty {
46 0 0 0 0 1   print @_ unless $quiet or $batch_mode;
47             }
48              
49             sub prompt ($;$) {
50 0     0 1   my($mess, $def) = @_;
51 0 0         Carp::confess("prompt function called without an argument")
52             unless defined $mess;
53              
54 0 0         return $def if $batch_mode;
55              
56 0   0       my $isa_tty = -t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT));
57 0 0         Carp::confess("tty not found") if not $isa_tty;
58              
59 0 0         $def = defined $def ? $def : "";
60              
61 0           local $| = 1;
62 0           local $\ = undef;
63 0           print "$mess ";
64              
65 0           my $ans;
66 0 0 0       if (not $isa_tty and eof STDIN) {
67 0           print "$def\n";
68             }
69             else {
70 0           $ans = ;
71 0 0         if( defined $ans ) {
72 0           chomp $ans;
73             }
74             else { # user hit ctrl-D
75 0           print "\n";
76             }
77             }
78              
79 0 0 0       return (!defined $ans || $ans eq '') ? $def : $ans;
80             }
81              
82             sub prompt_str {
83 0     0 1   my ($description, $default) = @_;
84 0           return prompt("$description [$default]", $default);
85             }
86              
87             sub prompt_bool {
88 0     0 1   my ($description, $default) = @_;
89 0 0         my $yn = ($default ? 'y' : 'n');
90 0 0         my $yn_hint = ($default ? 'Y/n' : 'y/N');
91 0           my $result = prompt("$description [$yn_hint]", $yn);
92 0 0         if ($result =~ /^y/i) {
93 0           return 1;
94             }
95 0           return;
96             }
97              
98             sub xsystem {
99 0     0 1   local $! = local $? = 0;
100 0 0         return if system(@_) == 0;
101              
102 0           my @msg;
103 0 0         if ($!) {
104 0           push @msg, "error ".int($!)." '$!'";
105             }
106 0 0         if ($? > 0) {
107 0 0         push @msg, "kill by signal ".($? & 127) if ($? & 127);
108 0 0         push @msg, "core dumped" if ($? & 128);
109 0 0         push @msg, "exit code ".($? >> 8) if $? >> 8;
110             }
111 0           die join ", ", @msg;
112             }
113              
114             sub slurp {
115 0     0 1   my ($file) = @_;
116 0 0         open my $fh, '<', $file or die "Can't open $file: $!";
117 0           my $result = join '', <$fh>;
118 0           close $fh;
119 0           return $result;
120             }
121              
122             sub setup {
123              
124 0     0 1   my $opt_reconfigure;
125             my $opt_service_dir;
126 0           my $opt_data_dir;
127 0           my $opt_log_dir;
128 0           my $opt_default_user = 'root';
129 0           my $opt_sticky_777 = 1;
130 0           my $opt_install_services = 1;
131 0           my $opt_crontab = 1;
132 0           my $opt_local;
133              
134             # These options are documented in ubic-admin script POD.
135             # Don't forget to update their description if you change them.
136 0 0         GetOptions(
137             'local!' => \$opt_local,
138             'batch-mode' => \$batch_mode,
139             'quiet' => \$quiet,
140             'reconfigure!' => \$opt_reconfigure,
141             'service-dir=s' => \$opt_service_dir,
142             'data-dir=s' => \$opt_data_dir,
143             'log-dir=s' => \$opt_log_dir,
144             'default-user=s' => \$opt_default_user,
145             'sticky-777!' => \$opt_sticky_777,
146             'install-services!' => \$opt_install_services,
147             'crontab!' => \$opt_crontab,
148             ) or die "Getopt failed";
149              
150 0 0         die "Unexpected arguments '@ARGV'" if @ARGV;
151              
152 0           my %defaults = _defaults();
153              
154 0           eval { Ubic::Settings->check_settings };
  0            
155 0 0         unless ($@) {
156 0           my $go = prompt_bool("Looks like ubic is already configured, do you want to reconfigure?", $opt_reconfigure);
157 0 0         return unless $go;
158 0           print_tty "\n";
159             }
160              
161 0           print_tty "Ubic can be installed either in your home dir or into standard system paths ($defaults{example}).\n";
162 0           print_tty "You need to be root to install it into system.\n";
163              
164 0 0         unless ($batch_mode) {
165 0           $batch_mode = prompt_bool("Would you like to configure as much as possible automatically?", 1);
166             }
167              
168             # ideally, we want is_root option and local option to be orthogonal
169             # it's not completely true by now, though
170 0 0         my $is_root = ( $> ? 0 : 1 );
171 0           my $local = $opt_local;
172 0 0         unless (defined $local) {
173 0 0         if ($is_root) {
174 0           my $ok = prompt_bool("You are root, install into system?", 1);
175 0 0         $local = 1 unless $ok; # root can install locally
176             }
177             else {
178 0           my $ok = prompt_bool("You are not root, install locally?", 1);
179 0 0         return unless $ok; # non-root user can't install into system
180 0           $local = 1;
181             }
182             }
183              
184 0           my $local_dir;
185 0 0         if ($local) {
186 0           $local_dir = $ENV{HOME};
187 0 0         unless (defined $local_dir) {
188 0           die "Can't find your home!";
189             }
190 0 0         unless (-d $local_dir) {
191 0           die "Can't find your home dir '$local_dir'!";
192             }
193             }
194              
195 0           print_tty "\nService dir is a directory with descriptions of your services.\n";
196             my $default_service_dir = (
197             defined($local_dir)
198             ? "$local_dir/ubic/service"
199             : $defaults{service_dir}
200 0 0         );
201 0 0         $default_service_dir = $opt_service_dir if defined $opt_service_dir;
202 0           my $service_dir = prompt_str("Service dir?", $default_service_dir);
203              
204 0           print_tty "\nData dir is where ubic stores all of its data: locks,\n";
205 0           print_tty "status files, tmp files.\n";
206             my $default_data_dir = (
207             defined($local_dir)
208             ? "$local_dir/ubic/data"
209             : $defaults{data_dir}
210 0 0         );
211 0 0         $default_data_dir = $opt_data_dir if defined $opt_data_dir;
212 0           my $data_dir = prompt_str("Data dir?", $default_data_dir);
213              
214 0           print_tty "\nLog dir is where ubic.watchdog will write its logs.\n";
215 0           print_tty "(Your own services are free to write logs wherever they want.)\n";
216             my $default_log_dir = (
217             defined($local_dir)
218             ? "$local_dir/ubic/log"
219             : $defaults{log_dir}
220 0 0         );
221 0 0         $default_log_dir = $opt_log_dir if defined $opt_log_dir;
222 0           my $log_dir = prompt_str("Log dir?", $default_log_dir);
223              
224             # TODO - sanity checks?
225              
226 0           my $default_user;
227 0 0         if ($is_root) {
228 0           print_tty "\nUbic services can be started from any user.\n";
229 0           print_tty "Some services don't specify the user from which they must be started.\n";
230 0           print_tty "Default user will be used in this case.\n";
231 0           $default_user = prompt_str("Default user?", $opt_default_user);
232             }
233             else {
234 0           print_tty "\n";
235 0           $default_user = getpwuid($>);
236 0 0         unless (defined $default_user) {
237 0           die "Can't get login (uid '$>')";
238             }
239 0           print_tty "You're using local installation, so default service user will be set to '$default_user'.\n";
240             }
241              
242 0           my $enable_1777;
243 0 0         if ($is_root) {
244 0           print_tty "\nSystem-wide installations usually need to store service-related data\n";
245 0           print_tty "into data dir for different users. For non-root services to work\n";
246 0           print_tty "1777 grants for some data dir subdirectories is required.\n";
247 0           print_tty "(1777 grants means that everyone is able to write to the dir,\n";
248 0           print_tty "but only file owners are able to modify and remove their files.)\n";
249 0           print_tty "There are no known security issues with this approach, but you have\n";
250 0           print_tty "to decide for yourself if that's ok for you.\n";
251              
252 0           $enable_1777 = prompt_bool("Enable 1777 grants for data dir?", $opt_sticky_777);
253             }
254              
255 0           my $install_services;
256             {
257 0           print_tty "There are three standard services in ubic service tree:\n";
  0            
258 0           print_tty " - ubic.watchdog (universal watchdog)\n";
259 0           print_tty " - ubic.ping (http service status reporter)\n";
260 0           print_tty " - ubic.update (helper process which updates service portmap, used by ubic.ping service)\n";
261 0           print_tty "If you'll choose to install them, ubic.watchdog will be started automatically\n";
262 0           print_tty "and two other services will be initially disabled.\n";
263 0           $install_services = prompt_bool("Do you want to install standard services?", $opt_install_services);
264             }
265              
266 0           my $enable_crontab;
267             {
268 0           print_tty "\n'ubic.watchdog' is a service which checks all services and restarts them if\n";
  0            
269 0           print_tty "there are any problems with their statuses.\n";
270 0           print_tty "It is very simple and robust, but since it's important that watchdog never\n";
271 0           print_tty "goes down, we recommended to install the cron job which checks watchdog itself.\n";
272 0           print_tty "Also, this cron job will bring watchdog and all other services online on host reboots.\n";
273 0           $enable_crontab = prompt_bool("Install watchdog's watchdog as a cron job?", $opt_crontab);
274             }
275              
276 0           my $crontab_env_fix = '';
277 0           my $crontab_wrap_bash;
278 0 0         my $ubic_watchdog_full_name = which('ubic-watchdog') or die "ubic-watchdog script not found in your current PATH";
279             {
280 0           my @path = split /:/, $ENV{PATH};
  0            
281 0           my @perls = grep { -x $_ } map { "$_/perl" } @path;
  0            
  0            
282 0 0         if ($perls[0] =~ /perlbrew/) {
    0          
283 0           print_tty "\nYou're using perlbrew.\n";
284              
285 0   0       my $HOME = $ENV{ORIGINAL_HOME} || $ENV{HOME}; # ORIGINAL_HOME is set in ubic tests
286 0 0         unless ($HOME) {
287 0           die "HOME env variable not defined";
288             }
289 0   0       my $perlbrew_config = File::Spec->catfile($ENV{PERLBREW_ROOT} || "$HOME/perl5/perlbrew", "etc/bashrc");
290 0 0         if (not -e $perlbrew_config) {
291 0           die "Can't find perlbrew config (assumed $perlbrew_config)";
292             }
293 0           print_tty "I'll source your perlbrew config in ubic crontab entry to start the watchdog in the correct environment.\n";
294 0           $crontab_env_fix .= "source $perlbrew_config && ";
295 0           $crontab_wrap_bash = 1;
296             }
297             elsif (@perls > 1) {
298 0           print_tty "\nYou're using custom perl and it's not from perlbrew.\n";
299 0           print_tty "I'll add your current PATH to ubic crontab entry.\n";
300              
301             # TODO - what if PATH contains " quotes? hopefully nobody is that crazy...
302 0           $crontab_env_fix .= qq[PATH="$ENV{PATH}" ];
303             }
304              
305 0 0         if ($ENV{PERL5LIB}) {
306 0           print_tty "\nYou're using custom PERL5LIB.\n";
307 0           print_tty "I'll add your current PERL5LIB to ubic crontab entry.\n";
308 0           print_tty "Feel free to edit your crontab manually after installation if necessary.\n";
309 0           $crontab_env_fix .= qq[PERL5LIB="$ENV{PERL5LIB}" ];
310             }
311             }
312              
313             my $config_file = (
314             defined($local_dir)
315             ? "$local_dir/.ubic.cfg"
316             : $defaults{config}
317 0 0         );
318              
319             {
320 0           print_tty "\nThat's all I need to know.\n";
  0            
321 0           print_tty "If you proceed, all necessary directories will be created,\n";
322 0           print_tty "and configuration file will be stored into $config_file.\n";
323 0           my $run = prompt_bool("Complete setup?", 1);
324 0 0         return unless $run;
325             }
326              
327 0           print "Installing dirs...\n";
328              
329 0           mkpath($_) for ($service_dir, $data_dir, $log_dir);
330              
331 0           for my $subdir (qw[
332             status simple-daemon/pid lock ubic-daemon tmp watchdog/lock watchdog/status
333             ]) {
334 0           mkpath("$data_dir/$subdir");
335 0 0         chmod(01777, "$data_dir/$subdir") or die "chmod failed: $!";
336             }
337              
338 0           mkpath("$service_dir/ubic");
339              
340 0 0         if ($install_services) {
341             my $add_service = sub {
342 0     0     my ($name, $content) = @_;
343 0           print "Installing ubic.$name service...\n";
344              
345 0           my $file = "$service_dir/ubic/$name";
346 0           Ubic::AtomicFile::store($content => $file);
347 0           };
348              
349 0           $add_service->(
350             'ping',
351             "use Ubic::Ping::Service;\n"
352             ."Ubic::Ping::Service->new;\n"
353             );
354              
355 0           $add_service->(
356             'watchdog',
357             "use Ubic::Service::SimpleDaemon;\n"
358             ."Ubic::Service::SimpleDaemon->new(\n"
359             ."bin => [ 'ubic-periodic', '--rotate-logs', '--period=60', '--stdout=$log_dir/watchdog.log', '--stderr=$log_dir/watchdog.err.log', 'ubic-watchdog' ],\n"
360             .");\n"
361             );
362              
363 0           $add_service->(
364             'update',
365             "use Ubic::Service::SimpleDaemon;\n"
366             ."Ubic::Service::SimpleDaemon->new(\n"
367             ."bin => [ 'ubic-periodic', '--rotate-logs', '--period=60', '--stdout=$log_dir/update.log', '--stderr=$log_dir/update.err.log', 'ubic-update' ],\n"
368             .");\n"
369             );
370             }
371              
372 0 0         if ($enable_crontab) {
373 0           print "Installing cron jobs...\n";
374              
375 0           system("crontab -l >$data_dir/tmp/crontab.stdout 2>$data_dir/tmp/crontab.stderr");
376 0           my $old_crontab = slurp("$data_dir/tmp/crontab.stdout");
377 0           my $stderr = slurp("$data_dir/tmp/crontab.stderr");
378 0           unlink "$data_dir/tmp/crontab.stdout";
379 0           unlink "$data_dir/tmp/crontab.stderr";
380 0 0 0       if ($stderr and $stderr !~ /no crontab/) {
381 0           die "crontab -l failed";
382             }
383              
384 0           my @crontab_lines = split /\n/, $old_crontab;
385 0           @crontab_lines = grep { $_ !~ /\Qubic-watchdog ubic.watchdog\E/ } @crontab_lines;
  0            
386              
387 0 0         open my $fh, '|-', 'crontab -' or die "Can't run 'crontab -': $!";
388             my $printc = sub {
389 0 0   0     print {$fh} @_ or die "Can't write to pipe: $!";
  0            
390 0           };
391              
392 0           my $crontab_command = "$crontab_env_fix$ubic_watchdog_full_name ubic.watchdog";
393 0 0         if ($crontab_wrap_bash) {
394 0           $crontab_command = "bash -c '$crontab_command'";
395             }
396 0           push @crontab_lines, "* * * * * $crontab_command >>$log_dir/watchdog.log 2>>$log_dir/watchdog.err.log";
397 0           $printc->("$_\n") for @crontab_lines;
398 0 0         close $fh or die "Can't close pipe: $!";
399             }
400              
401 0           print "Installing $config_file...\n";
402 0           Ubic::Settings::ConfigFile->write($config_file, {
403             service_dir => $service_dir,
404             data_dir => $data_dir,
405             default_user => $default_user,
406             });
407              
408 0 0         if ($install_services) {
409 0           print "Starting ubic.watchdog...\n";
410 0           xsystem('ubic start ubic.watchdog');
411             }
412              
413 0           print "Installation complete.\n";
414             }
415              
416              
417             1;
418              
419             __END__