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