#!/usr/bin/perl
#
# Usage stage.pl [ACTION] [backup=path] [master=IPv4] [ntp=dns.name] 
#
# Recommended Activation ACTION:
#   M  - activate Master system 
#   Msf - activate Master system with sos and format storage
#
# Recover backup (master installation only)
#   backup=find
#   backup=/path/to/backup
#
# Notes: 
#   1. an interactive menu will be presented if ACTION is not provided
#   2. disk group 'vg0' is expected for 'Evaluation' type of installation
#   3. NTP manipulation:
#       - Master is 'self-synchronized' if NTP server is not provided
#       - Node is synchronized with master if NTP server is not provided
#       if DHCP is used for servers then NTP manipulation is not needed
#   4. predefined locations:
#       - $APL_VAR=/var/sarch
#       - $APL_VAR/log/activate   - location for activation logs
#       - $APL_VAR/vctl/activated - information about activation
#       - $APL_VAR/vctl/verinfo   - information about current version
#       - $APL_VAR/installed      - fresh installation (is not activated/upgraded)


use strict;
use Socket;
use CGI qw/escape unescape/;
use XML::Simple;
use Data::Dumper;
use LWP::UserAgent;
use HTTP::Request::Common;
use User::pwent;

# Set safe umask? 
umask 0027;

my $APL='/opt/sarch';                     # should be automatically set in future
my $APL_VAR='/var/sarch';
my $APL_USR='apl';
my $APL_GID=(getpwnam($APL_USR))->gid;    # group ID for APL user (numeric)
my $ACTIVATE_LOGS="$APL_VAR/log/activate";
my $LOG="$ACTIVATE_LOGS/stage.log";
my $ACTIVATED="$APL_VAR/vctl/activated";  # information about activation
my $RESULT="$APL_VAR/vctl/result";        # result of activation and update
my $FINDBACKUP=". $APL/base/etc/env.conf && $APL/conf/bin/find_backup"; #show backup list
my $VERINFO="$APL_VAR/vctl/verinfo";      # information about current version
my $INSTALLED="$APL_VAR/installed";       # fresh installation (is not activated/upgraded)
my $IPADDRSHOW="$APL/base/bin/ip_addr_show";#prints IPs
my $LIST='/sbin/fdisk -l';                # look for USB-flash
my $NTPCONF='/etc/ntp.conf';
my $NTPDCONF='/etc/sysconfig/ntpd';
my $EASYRSA='/etc/openvpn/easy-rsa';
my $ACTIVATE="$APL/base/bin/activate";
my $ACTIVATE_LEGACY="$APL/bin/activate-legacy";
my $UPDATE="$APL/base/bin/update";
my $SOS_PROFILE='/home/sos/.bash_profile';
my $VER=`$APL/vpatch/bin/vctl ver`; chomp $VER;
my $VERID=`$APL/vpatch/bin/vctl verid`; chomp $VERID;
my $TICKER='/etc/ntp/step-tickers';
my $MOUNT_POINT='/tmp/stage';             # custom scripts will be mounted
my $ACTION=($ARGV[0]=~/=/)?'':$ARGV[0];   # ignore argv[0] if key=val
my $INTERACTIVE=($ACTION)?0:1; # use interactive installation if no ACTION
my %ARGS = map{/(^\w+)=(.+)/} grep {/(^\w+)=(.+)/} @ARGV; # collect named args

my %types  =(M=>'Master',N=>'Node',E=>'Evaluation',S=>'StandAlone' );
my %options=(m=>'MUMS',s=>'SOS',u=>'Custom scripts');

# set PATH (work around for running with wrong env)
$ENV{PATH}='/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin';

#-------------------------------------------------------------------------------
sub Log { # print message to log
#-------------------------------------------------------------------------------
   my $msg=shift;
   open  LOG, ">>$LOG";
   print LOG scalar localtime().'| '.$msg;
   close LOG;
}


#-------------------------------------------------------------------------------
sub prlog { # print message to screen and place in a log
#-------------------------------------------------------------------------------
   my $msg=shift;
   print $msg;
   Log $msg;
}

#-------------------------------------------------------------------------------
sub Errlog { # print error to the log and screen then die
#-------------------------------------------------------------------------------
   Log "\nERROR: @_\n";
   die "\nERROR: @_\n";
}

#-------------------------------------------------------------------------------
sub mk_default_storage {
#-------------------------------------------------------------------------------
    my $vgs=`/usr/sbin/vgs | grep vg0`;
    Errlog("Disk group vg0 is missing") if not $vgs; # this error will not happen
    `/usr/sbin/lvdisplay /dev/vg0/default >/dev/null 2>&1`; # check if default is already present
    if($?) {   # default is not present. let's create it
       # ex: vg0    1   5   0 wz--n- 279.25G 237.31G
       Errlog("Cannot parse $vgs") if not $vgs=~/\s*vg0\s+\d+\s+\d+\s+\d+\s+\S+\s+\d+(\.\d+)?[GT]\s+(\d+(\.\d+)?)([GT])/i;
       my ($size,$unit)=($2,$4);
       Errlog("Disk size is too small (free only $2$4, need >2G)") if $size <2 and $unit eq 'G';
       $size*=1024 if $unit eq 'T';  # have the size in GB
       $size=int($size);             # drop fractional part
       $size=100 if $size>100;       # default volume cannot be bigger then 100G
       my $lvcreate=`lvcreate --name default --size ${size}G vg0`;
       Errlog("Cannot create default storage: $lvcreate") if $?;
    } else {
       prlog("default volume is present, reformatting is required")
    }
    system("mkfs.xfs -f -L va-default /dev/mapper/vg0-default");
    Errlog("Cannot format default storage") if $?;
}

#-------------------------------------------------------------------------------
sub set_uni { # make uni prior running activation
#-------------------------------------------------------------------------------
   my $uni=shift;
   return if -f "$APL_VAR/conf/node/conf";             # UNI already present
   $uni=`$APL/conf/bin/uni-gen` if $uni eq 'NEW';      # make new UNI
   system("mkdir -p  $APL_VAR/conf/node");             # TBD: check
   open(NCONF,">$APL_VAR/conf/node/conf") || Errlog("Cannot create $APL_VAR/conf/node/conf");
   print NCONF "UNI=$uni\n";
   close NCONF; 
}


sub read_uni { # read existing UNI
   my $uni='NONE';
   if(open(NCONF,"$APL_VAR/conf/node/conf")) {
     my %ninfo=map{/(^\w+)=(.+)/} grep {/(^\w+)=(.+)/} <NCONF>;
     $uni=$ninfo{UNI} if exists $ninfo{UNI};
     close NCONF;
   }
   return $uni;
}

#-------------------------------------------------------------------------------
sub make_keys { # in multinode installation sos & apl have to have key generated
                # TBD function can be move to rpm scripts
#-------------------------------------------------------------------------------
 system qq(su sos -c "ssh-keygen -q -t rsa -f /home/sos/.ssh/id_rsa  -N ''")       unless -f "/home/sos/.ssh/id_rsa";
 system qq(su apl -c "ssh-keygen -q -t rsa -f /var/sarch/home/.ssh/id_rsa  -N ''") unless -f "/var/sarch/home/.ssh/id_rsa";
 system qq(/opt/sarch/smix/bin/keymanager gen);
}

#-------------------------------------------------------------------------------
sub set_ntp_server { # place NTP server into configuration files
#-------------------------------------------------------------------------------
   # TBD: error handling
   my $server=shift;
   # clean ntp.conf
   `/usr/bin/perl -ni -e 'print if not /^(server|fudge)/' $NTPCONF`;
   # append with server
   open NTPCONF,">>$NTPCONF";
   print NTPCONF "server 127.127.1.0\n"
                ."fudge  127.127.1.0 stratum 10\n"
                ."server $server\n";
   close NTPCONF;
   # create step-ticker
   open TICKER,">$TICKER";
   print TICKER "$server\n";
   close TICKER;
   # SYNC_HWCLOCK=yes
   `/usr/bin/perl -pi -e 's|^SYNC_HWCLOCK.*|SYNC_HWCLOCK=yes|' $NTPDCONF`;

   system("/sbin/service ntpd restart");
}



#-------------------------------------------------------------------------------
sub ask_master { # node need to know it's master
#-------------------------------------------------------------------------------
  my $master;
  if(not $INTERACTIVE) {
      Errlog("Master server IP is not provided") if not exists $ARGS{master};
      $master=$ARGS{master};
  }else {
     print "\nAttention: Master system should be already running\n"
           ."Please specify Master IP Address (IPv4):";
     $master = read_choice();
  }
  $master=~s/\s//g; # remove spaces from IP address if any
  Errlog "IP address $master is invalid\n" if not $master=~/^(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)$/;
  Errlog "IP address $master is invalid\n" if $1>255 ||  $2>255 || $3>255 && $4>255;
  return $master;
}


#-------------------------------------------------------------------------------
sub setup_keys { # node need to set up keys
#-------------------------------------------------------------------------------
  set_uni('NEW');               # UNI should be present prior setting up master
  print "Connecting to Master with SOS account..\n";
  my $ret=`$APL/base/bin/node2master s_master`;
  Errlog "$ret" if $?;
}

#-------------------------------------------------------------------------------
sub find_node4replacement { # node need to ask master about replacement slots
#-------------------------------------------------------------------------------
  my $master=shift;
  print "Connecting to Master with SOS account..\n";
  my $ret=`ssh sos\@$master '/usr/bin/sudo /opt/sos/key4node MISSING_NODES'`;
  Errlog "Cannot access to the Master" if $?;
  my @rows=grep {/^MASTER|UNI/} split(/\n/,$ret);
  my %minfo=map{/(^\w+)=(.+)/} grep {/(^\w+)=(.+)/} split (/\s+/,$rows[0]);
  my $nodes=$minfo{MISSINGNODES};
  Errlog "Master is not detected\n" if $minfo{MASTER} eq 'NOTDETECTED';
  Errlog "Master version does not match $minfo{MASTER} != $VER\n" if $minfo{MASTER} ne $VER;
  Errlog "Master is not running (currently $minfo{STATUS})\n" if $minfo{STATUS} ne 'run';
  Errlog "Master has UNKNOWN missing nodes\n" if $nodes eq 'UNKNOWN';
  Errlog "Master does not have missing nodes, Note Replacement cannot be completed\n" if $nodes < 1;
  print "\nReplacement is pending for following nodes:\n";
  for(my $i=1;$i<=$nodes;$i++) {
    print "$i:\t$rows[$i]\n";
  }
  my $uni; 
  for(;;) {
     print "\nChose the node to be replaced (specify number) ";
     my $choice = read_choice();
     if($choice > 0 and $choice<=$nodes) {
        ($uni)= $rows[$choice]=~/UNI=(\w{22})/;
        Errlog "Cannot find Node Id\n" if not $uni;
        prlog  "\nConfiguration of UNI=$uni will be used in activation\n\n";
        last;
     }
  }
  set_uni($uni);    # place uni in configuration
}

#-------------------------------------------------------------------------------
sub ask_ntp_server {     # dialog for ntp_server
#-------------------------------------------------------------------------------
  my $ntp_server;
  if(not $INTERACTIVE) {
     Errlog("NTP server is not provided") if not exists $ARGS{ntp};
     $ntp_server=$ARGS{ntp};
     $_=`/usr/sbin/ntpdate -q $ntp_server 2>&1`; 
     if($?) {
        Errlog("NTP server '$ntp_server' does not respond");
     }else {
        prlog("NTP server '$ntp_server' is accepted\n");
     }
     return $ntp_server;
  }
  #------------------------  INTERACTIVE NTP configuration
  for(;;) {
    print "\nPlease specify NTP server:";
    my $ntp = <STDIN>;
    chomp $ntp;
    if($ntp eq 'NONE') {
       prlog "using self-synchronized NTP (it is not advisable)\n";
       return '';
    }
    prlog "querying NTP server '$ntp'. Please wait..\n";
 
    $_=`/usr/sbin/ntpdate -q $ntp 2>&1`;
    if($?) {                     # query fails
      prlog "Query fails: $_\n";
      print "Server '$ntp' is rejected";
      print "(type NONE for self-synchronization)\n";
      next;
    }else {
      $ntp_server=$ntp;              # set a global variable for use later
      prlog "query returns: $_";
      prlog "NTP server '$ntp' is accepted\n";
      last;
    }
  }
  return $ntp_server;
}

#-------------------------------------------------------------------------------
sub configure_openvpn { # Configure openvpn server
#-------------------------------------------------------------------------------
    if (not -d $EASYRSA) { # OpenVPN server isn't configured
        prlog "Configuring OpenVPN server...\n";
        `cp $APL/base/etc/openvpn/server.conf /etc/openvpn`;
        mkdir $EASYRSA;
        `cp -rf /usr/share/easy-rsa/2.0/* $EASYRSA`;
        `cp -f $APL/base/etc/openvpn/vars $EASYRSA`;
        # Create ca, keys and certs
        system("cd $EASYRSA && source ./vars && ./clean-all && ./pkitool --initca && ./pkitool --server server && ./build-dh");
        if ($?) {
            Errlog("Failed to create keys and certificates");
        }
        else {
            `cp $EASYRSA/keys/{dh2048.pem,ca.crt,server.crt,server.key} /etc/openvpn`;
        }
    }
    # Patch certificate database settings. Allow non-unique subjects
    `perl -pi -e 's/^unique_subject\\s*=\\s*yes\$/unique_subject = no/' $EASYRSA/keys/index.txt.attr`;
    # Enable openvpn server if disabled
    `systemctl is-enabled openvpn\@server.service`;
    if ($?) { # openvpn is disabled
        prlog "OpenVPN is disabled. Enabling...\n";
        system("ln -s /lib/systemd/system/openvpn\@.service /etc/systemd/system/multi-user.target.wants/openvpn\@server.service");
        system("systemctl -f enable openvpn\@server.service");;
    }
    # Enable and start openvpn server if not running
    `systemctl is-active openvpn\@server.service`;
    if ($?) { # openvpn isn't active
        prlog "OpenVPN is not running. Starting...\n";
        system("systemctl start openvpn\@server.service");
    }
}

#-------------------------------------------------------------------------------

sub determine_smaster {
# 1. read IPv4 for s_master from /etc/hosts
# 2. if s_master present and correspond to one of interfaces then do nothing
# 3. if s_master not present or not correspond to any interfaces
#        then set s_master as the first if from the list
  open(IF,"$IPADDRSHOW|") || die "ERROR: Cannot run $IPADDRSHOW\n";
  my @ips= <IF>;
  close IF;
  my ($first)=$ips[0]; chomp $first;     # first IP from the list
  die("ERROR: Cannot find network interface on the server\n") if not $first;
  my $iaddr=gethostbyname('s_master');
  if($iaddr) {   # s_master present
    my $ipaddr=inet_ntoa($iaddr);
    my %ips=map {chomp;($_,1)} @ips;
    return $ipaddr if exists $ips{$ipaddr};
  } 
  return $first;
}

#-------------------------------------------------------------------------------
sub change_master{
#
# NOTE1: only IP4 is supported at this time
# NOTE2: function will correct incorrect s_master entries
# NOTE3: function is taken from va-sos as is
#-------------------------------------------------------------------------------
  my $master=shift;
 eval {
  my $new = "/etc/hosts.$$";
  open(OLD,"</etc/hosts") or die "Couldn't open /etc/hosts file\n" ;
  my @hosts=<OLD> or die "cannot read /etc/hosts\n";
  close OLD;
  open(NEW,">$new") or die "Couldn't create new /etc/hosts file\n" ;
  my $corrected=0;
  foreach(@hosts) {
    chomp;
    if (not (/^\s*#/ || /^\s*$/)) {  # string is not commented or empty line
      if(/^\s*([\d\.:]+)(\s+[^#]+)(#.*)*$/) { # looks like a valid entry
        my $ip=$1;
        my $names=$2;
        if(!$corrected) { # s_master entry is not corrected yet
          if($ip eq $master) {
             $names=~s/\ss_master\b([^\s#])*//g; # remove s_master if any
             $names=~s/\sva-master\.localdomain\b([^\s#])*//g;      # remove va-master.localdomain if any
             $names=~s/^\s+//;                # remove leading spaces
             $_="$master\ts_master va-master.localdomain $names";   # a new version is created
             $corrected=1;
          } elsif ($names=~/\ss_master\b/ || $names=~/\sva-master\.localdomain\b/) {
             $names=~s/\ss_master\b([^\s#])*//g; # remove s_master
             $names=~s/\sva-master\.localdomain\b([^\s#])*//g;      # remove va-master.localdomain
             if($names=~/\w/) {             # else names are present still
               s/\ss_master\b([^\s#])*//g;  # remove s_master from string
               s/\sva-master\.localdomain\b([^\s#])*//g;            # remove va-master.localdomain from string
             } else {                        # no name except master or remove va-master.localdomain was in names
               $_="$master\ts_master va-master.localdomain";        # new version is created
               $corrected=1;
             }
          }
        } else  {          # already corrected. any mention of s_master has to be removed
          if($ip eq $master) {
            $_="#$_";      # comment out doubled IP
          }elsif ($names=~/\ss_master\b/ || $names=~/\sva-master\.localdomain\b/) {
             $names=~s/\ss_master\b([^\s#])*//g;# remove s_master
             $names=~s/\sva-master\.localdomain\b([^\s#])*//g;  # remove va-master.localdomain
             if($names=~/\w/) {                            # else names are present still
               s/\ss_master\b([^\s#])*//g;     # remove s_master from string
               s/\sva-master\.localdomain\b([^\s#])*//g;        # remove va-master.localdomain from string
             }else {                # no name except master was in names
               $_="#$_";            # comment out (this should never be!)
             }
          }
        }
      }
      else {              # comment out invalid entry
        $_="#$_";
      }
    }
    print(NEW "$_\n") or die("Cannot write a $new file\n");
  }
  if(!$corrected) {
    print(NEW "$master\ts_master va-master.localdomain\n") or die("Cannot write a $new file\n");
  }
  close(NEW) or die "Couldn't close new hosts file\n";
  unlink("/etc/hosts.old");
  rename("/etc/hosts","/etc/hosts.old");
  rename($new,"/etc/hosts");
  chmod 0644, "/etc/hosts";
  print "matching...\n";
#  matching();
 };
 if($@) { # exception
    return "ERROR:$@\ns_master is not changed";
 }
 return 'SUCCESS';
}

#-------------------------------------------------------------------------------
sub check_for_custom_scripts {  # mount  USB
                                # return path/to/scripts
#-------------------------------------------------------------------------------
  my ($pre,$post);  # empty script slots
  `umount $MOUNT_POINT 2>/dev/null; rm -rf $MOUNT_POINT`;
  mkdir( $MOUNT_POINT,0700);
  my @list;
  open(LIST,"$LIST 2>/dev/null|") || die "Cannot do $LIST: $@";
  foreach(<LIST>) {
      push(@list,$1) if m|^(/dev/sd\w?\d)\s.+FAT\d?\d?|;
      push(@list,$1) if m|^Disk\s(/dev/sd\w?):\s+(\d\d\d\d?)\sMB|;
  }
  close $LIST;
  #print "Candidates: ". join(' ',@list). "\n";
  # ------------------------------------------try to mount
  foreach my $drive (@list) {
    `umount $MOUNT_POINT 2>/dev/null; mount -r $drive $MOUNT_POINT 2>/dev/null`;
     next if $?;                       # mount fails
     prlog "mounted: $drive\n";
     opendir(DIR,$MOUNT_POINT);
     my ($dir)= grep { /^activate$/ } readdir(DIR);
     closedir DIR;
     print "dir=$dir\n";
     next if not $dir;
     ($pre,$post)=("$MOUNT_POINT/activate/pre","$MOUNT_POINT/activate/post");
     $pre=''  if ! -x $pre;      # clean if not executable exists
     $post='' if ! -x $post;     # clean if not executable exists
     last;  
  }
  return ($pre,$post);
}

#-------------------------------------------------------------------------------
sub activate {
#-------------------------------------------------------------------------------
 my ($role,$list)=@_;
 die "\n" if not $list=~/^([MNER])([msfctxv]*)$/;
 my ($type,$option)=($1,$2);
 my ($master,$ntp);
 my ($custom_pre,$custom_post);
 chown(0, $APL_GID, "$APL_VAR");                     # explicitly owner and group for directory root:apache
 if($option=~/c/) {#-------------------- check for custom-scripts
   ($custom_pre,$custom_post)=check_for_custom_scripts();
   if($custom_pre.$custom_post eq '') {
     Errlog("Custom scripts are not found (USB:/activate/pre or USB:/activate/post)");
   }
 }
 if($custom_pre)  {#-------------------- execute custom pre script
   prlog "Executing $custom_pre\n";
   system($custom_pre);
 }
 if($type eq 'N') {#-------------------- ask for master IPv4 address
    prlog "\nActivating New Node\n";
    $master=ask_master();
    change_master($master);
    set_uni('NEW');               
    make_keys();
    setup_keys();
 }
 if($type eq 'R') {#-------------------- ask for master IPv4 address and find UNI for replacement
    prlog "\nReplacing existing non-active Node\n";
    $master=ask_master();
    find_node4replacement($master);
    change_master($master);
    make_keys();
    setup_keys();
 }
 if($type eq 'E') {#-------------------- create small default storage
    mk_default_storage;
 }
 if($option=~/t/) {#-------------------- set NTP server
    $ntp=ask_ntp_server;   # ask and check for correct NTP server name or IP
 }
 if($role eq 'MASTER') {   #------------ set s_master prior installation 
    $master=determine_smaster;
    change_master($master);
    set_uni('NEW');               
    make_keys();           # TBD: single master installation should not make keys
    configure_openvpn() if $option=~/v/;
 }
 #-------------------------------------- Activate an application
 activate_application($role,$list);
 register_node($role,$list);
 if($role eq 'NODE') {#-------------------- make Node specific changes  TBD!!! prior installation
   set_ntp_server('s_master') if not $ntp; # point to master if ntp_server was not defined 
 }
 if($option=~/s/) {#-------------------- sos   TBD!!! should be included inside STRATUS
      prlog "Installing va-sos..\n";
      `perl -ni -e 'print if not /sos.pl/' $SOS_PROFILE`;# remove sos.pl if already present
      open PROFILE,">>/home/sos/.bash_profile";
      print PROFILE "[ -e /opt/sos/sos.pl ] && sudo /opt/sos/sos.pl\n";
      close PROFILE;
      `cp $APL/base/etc/sos-sudo /etc/sudoers.d/ && chmod 440 /etc/sudoers.d/sos-sudo`;
 } 
 if($option=~/m/) {#-------------------- mums
      prlog "Installing va-mums..\n";
 } 
# if($option=~/4/) {#-------------------- ext4 filesystem
#      prlog "Configuring ext4 as default filesystem\n";
#      if( -f "$APL_VAR/conf/sm/options") {
#        system("cp $APL/sm/etc/options $APL_VAR/conf/sm/options");
#      }
#      system("perl -ni -e 'print if not /FSTYPE/' $APL_VAR/conf/sm/options");
#      open  OPTION,">>$APL_VAR/conf/sm/options";
#      print OPTION "FSTYPE=ext4\n";
#      close OPTION;  
# }
 if($option=~/f/) {#-------------------- format
    prlog "Formatting storage. For results see $ACTIVATE_LOGS/formatstorage.log\n";
    system("/bin/bash $APL/sm/sbin/formatstorage '<all>'|/usr/bin/tee $ACTIVATE_LOGS/formatstorage.log");
 }
 if($ntp) {        #-------------------- if NTP server defined
    set_ntp_server($ntp);
 }
 if($role eq 'MASTER' and -f $ACTIVATE_LEGACY) { # legacy API if presented
    system("$ACTIVATE_LEGACY > $ACTIVATE_LOGS/activate-legacy.log 2>&1");
 } 
 if($custom_post) {#-------------------- execute custom post script
   prlog "Executing $custom_post\n";
   system($custom_post);
 }
}

#-------------------------------------------------------------------------------
sub register_node { #
#-------------------------------------------------------------------------------
  my ($role,$list)=@_;
  if( not $list=~/^H/) {   # do not register node for H - MasterHead
    `su $ENV{APL_USR} -c ". $APL/base/etc/env.conf && $APL/conf/bin/conf_kowtow register"`;
     if( $list=~/^R/ ) {   # node restore
         my $uni=read_uni();
         ErrLog("Cannot find UNI") if $uni eq 'NONE';
         system qq(echo "update sm_nodes set cmd='RESTORE' where nodeid='$uni'"| su apl -c 'psql -h s_master');
     }
  }
}
#-------------------------------------------------------------------------------
sub start_application { #
#-------------------------------------------------------------------------------
  prlog "Starting video archive application\n";
  system("/sbin/service patrol start"); 
}

#-------------------------------------------------------------------------------
sub activate_application { #
#-------------------------------------------------------------------------------
  my ($role,$choice)=@_; # role=MASTER|NODE
  my %result=(ACTIVATE=>'UNKNOWN',UPDATE=>'UNKNOWN');
  mkdir $ACTIVATE_LOGS if not -d $ACTIVATE_LOGS;
  prlog "Activating application $VER\n";
  open FH, "export APL_ROLE=$role ACTIVATE_OPTIONS=$choice; $ACTIVATE 2>&1 |";
  prlog $_ while <FH>;
  close FH;
  $result{ACTIVATE}=($?)?'FAIL':'SUCCESS';
  mkdir("$APL_VAR/vctl",0750) if not -d "$APL_VAR/vctl";
  chown(0, $APL_GID, "$APL_VAR/vctl");                    # explicitly owner and group for directory root:apache
  open  VERINFO, ">$VERINFO";
  print VERINFO   "VER=$VER\n";   # write version information (needs for upgrade)
  close VERINFO;
  chown(0, $APL_GID,$VERINFO);                            # explicitly owner and group
  sleep 10; # let shepherd start
  prlog "Performing update steps\n";
  system("export APL_ROLE=$role ACTIVATE_OPTIONS=$choice; $UPDATE");
  if($?) {
     prlog "UPDATE FINISHED EXECUTING. RESULT:\t\t\t\tFAIL\n";
     $result{UPDATE}='FAIL';
  }else {
     prlog "UPDATE FINISHED EXECUTING. RESULT:\t\t\t\tSUCCESS\n";
     $result{UPDATE}='SUCCESS';
  }
  open  RESULT, ">$RESULT";
  print RESULT "ACTIVATE=$result{ACTIVATE}\nUPDATE=$result{UPDATE}\n";
  close RESULT;
  chown(0, $APL_GID,$RESULT);                            # explicitly owner and group
  if(! -f $ACTIVATED) {            #this is initial activation, write activation info
    open  ACTIVATED,">$ACTIVATED";
    print ACTIVATED "ACTIVATED=$choice\nROLE=$role\nVER=$VER\n";
    close ACTIVATED;
  }
  chown(0, $APL_GID,$ACTIVATED);                         # explicitly owner and group
  unlink $INSTALLED;   # Cannot be activated again
  if ( -e "/etc/puppet/skm-post-activation/post-activation.sh" ) { # execute post-activation puppet scripts for hardened installations 
    prlog "Running post-activation puppet scripts (hardened install)\n";
    system("/etc/puppet/skm-post-activation/post-activation.sh");
  }
}


#-------------------------------------------------------------------------------
sub check_master_access {
    # Check SSH connectivity
    `su - apl -c '/bin/ssh s_master -i ~/.ssh/id_rsa -oBatchMode=yes -oConnectTimeout=10 echo ok' 2>&1`;
    Errlog "SSH connection to master failed.\n".
           "If your master server has changed or reinstalled with 'Clean' option, ".
           "run the command '/opt/sarch/bin/activate C' to forget the old master" if $?;
}

#-------------------------------------------------------------------------------
sub upgrade { #
#-------------------------------------------------------------------------------
  open(ACTIVATED,$ACTIVATED);               # sure file exists
  my %conf=map{/(^\w+)=(.+)/} grep {/(^\w+)=(.+)/} <ACTIVATED>;
  close ACTIVATED;
  # If node, check if master is still accessible
  check_master_access if ! -f "$APL_VAR/conf/master/s_master";
  system qq(/opt/sarch/smix/bin/keymanager gen);     # this is workaround if keys are lost 
  activate_application($conf{ROLE},$conf{ACTIVATED});
  start_application();
}


#-------------------------------------------------------------------------------
sub de_name {
  my $str=shift;
  my %info=map{/(^\w+)=(.*)/} grep {/(^\w+)=(.*)/} split (/\s+/, $str);
  return \%info;
}

#-------------------------------------------------------------------------------
sub recover {
#-------------------------------------------------------------------------------
  my $location=shift;
  my %info;
  if (uc($location) eq 'FIND') {
    my %backup= map {($1,de_name($_)) if /ID=(\d+.\d+)/} split(/\n/,`$FINDBACKUP -v`) ;
    #print Dumper \%backup; 
    my %pool;
    my $choice;
    for(;;)  {
      my $count=1;
      print "\nFolowing backups are avaiable:\n";
      foreach (sort {$b<=>$a} keys %backup) { # 10 latest backups in reverse order
        $pool{$count}=$_;                     # remember in pool with order number
        print "\t$count:\t$_ $backup{$_}->{TYPE} $backup{$_}->{COMMENT}\n";
        last if ++$count > 9;
      }
      print "\nChose backup for recovery (specify number) :";
      $choice=read_choice();
      last if $choice>0 and $choice<$count;
      print "'$choice' is incorrect\n";
    }
    %info=%{$backup{$pool{$choice}}};   # find backup from the choice in the pool
  } 
  else { # /path/to/backup or USB
    my $ret=`$FINDBACKUP -vn $location 2>&1`;
    chomp $ret;
    die("ERROR: $ret") if $?;
    die("ERROR: backup not found") if $ret eq 'NOT FOUND';
    %info=%{de_name($ret)};
  }
  die('ERROR: backup info missing ACTIVATED') if not defined $info{ACTIVATED};
  die('ERROR: backup info missing UNI')       if not defined $info{UNI};
  die('ERROR: backup info missing ID')        if not defined $info{ID};
  my ($sver) = $VER=~/^(\d+\.\d+)\./;
  my ($sver_bck) = $info{VER}=~/^(\d+\.\d+)\./;
  my $migrate = "-m" if $sver and $sver_bck and $sver ne $sver_bck;
  set_uni($info{UNI});
  mkdir("$APL_VAR/backup");        
  mkdir("$APL_VAR/backup/master");
  if (uc($location) eq 'FIND') {
  	system("$FINDBACKUP -v $info{ID}"); # copy last backup 
  }
  else {
  	system("$FINDBACKUP -v $location"); # copy specified backup
  }
  activate('MASTER',$info{ACTIVATED});
  start_application();
  prlog "\nRecovering backup: $info{ID}\n";
  prlog "Migrate configuration from $info{VER}\n" if $migrate;
  `su $ENV{APL_USR} -c ". $APL/base/etc/env.conf && $APL/conf/bin/master_restore $migrate $info{ID}"`; 
}

#-------------------------------------------------------------------------------
sub deactivate { #
#-------------------------------------------------------------------------------

  print "deactivating ..\n";
  # remove $APL_VAR except home and installed
  system "cd /var/sarch && ls | egrep -v 'home|installed' |xargs rm -rf";
  `rm -rf /var/sarch/home/.ssh`;
  `rm -rf /home/sos/.ssh/authorized_keys`;
  `ssh-keygen -R s_master -f /root/.ssh/known_hosts`;
  # remove database/configuration etc ...
  `su $ENV{APL_DB_USR} -lc "/usr/bin/pg_ctl stop -m immediate"`;
  `rm -rf $ENV{APL_DB_DATA}`   if -e $ENV{APL_DB_DATA} and $ENV{APL_DB_DATA}=~m|^/var/|;
  unlink "/etc/sudoers.d/sos-sudo" if -f "/etc/sudoers.d/sos-sudo";
  `perl -ni -e 'print if not /sos.pl/' $SOS_PROFILE`;# remove sos.pl if present 
  `/bin/umount /vasm/store && /bin/mount /dev/mapper/vg0-store /vasm/stage/store`;
  print "\ndeactivation is completed\n";
}


#-------------------------------------------------------------------------------
sub status { #  returns: C if 'clean' or deactivated
             #           L if activated but activation is lost
             #           A if system is activated/upgraded already 
             #           or activation type with options (Msf etc)
#-------------------------------------------------------------------------------
  return 'A' if not -f $INSTALLED;          # INSTALLED present till activated
  return 'C' if not -d $ACTIVATE_LOGS;
  open(ACTIVATED,$ACTIVATED) or return 'L'; # type and options are lost
  my %conf=map{/(^\w+)=(.+)/} grep {/(^\w+)=(.+)/} <ACTIVATED>;
  close ACTIVATED;
  my @ver= map { (" $_=>$conf{$_} ")} sort keys %conf;
  print "\nConfiguration from the previous installation is found:\n\t",@ver,"\n";
  my $prev_choice=$conf{ACTIVATED};
  return 'L' if not $prev_choice;           # type and options are lost
  return $prev_choice;
}


#-------------------------------------------------------------------------------
sub read_choice {
  my $choice= <STDIN>;
  chomp($choice);
  exit(1) if $choice=~/^(exit|quit|q)$/i;
  return $choice;
}
#-------------------------------------------------------------------------------
my $MENU_master_or_node='
Define the role for the system
-------------------------------------------------------------------------------
M - Master     Master system 
N - Node       Node for existing Master system
-------------------------------------------------------------------------------

? ';

sub choice_master_or_node {    # return M|N
  my $choice = $ACTION;
  $choice='M' if $ACTION=~s/^M//; # extract Master, keep other actions
  $choice='N' if $ACTION=~s/^N//; # extract Note,   keep other actions
  for(;;) {
    if ($INTERACTIVE) {
      print $MENU_master_or_node;
      $choice = read_choice();
    }
    last if $choice=~/^[MN]$/; 
    prlog "\nERROR: Your choice '$choice' is not valid.\n\n";
    Errlog('invalid choice') if not $INTERACTIVE; # ErrLog does exit
  }
  return $choice; # M|N only
}

#-------------------------------------------------------------------------------
my $MENU_master_restore='
The following actions are possible:
-------------------------------------------------------------------------------
I - Ignore backup and proceed with new activation
R - Recover backup and upgrade to a new version
-------------------------------------------------------------------------------

? ';

sub choice_master_restore {    # return R|I
  my $backup=shift;
  my $choice = $ACTION;
  $choice='I' if $ACTION=~s/I//; # extract Ignore, keep other actions
  for(;;) {
    if ($INTERACTIVE) {
      print "BACKUP is found $backup\n\n";
      print $MENU_master_restore;
      $choice = read_choice();
    }
    last if $choice=~/^[IR]$/;
    prlog "\nERROR: Your choice '$choice' is not valid.\n\n";
    Errlog('invalid choice') if not $INTERACTIVE; # ErrLog does exit
  }
  return $choice; # I|R only
}

#-------------------------------------------------------------------------------
my $MENU_master_activation='
Define a Master activation scenario by combining the TYPE with multiple options
-TYPE -------------------------------------------------------------------------
M - MultiNode    Master system with capabilities to handle multiple nodes 
E - Evaluation   SingleMaster system with a small storage on the system disk
S - SingleMaster (future) Master system. Nodes cannot be added
H - MasterHead   (future) Dedicated Master without storage and cameras
-options ----------------------------------------------------------------------
f - Format Storage (Find and format potential storage drives)
s - Activate SOS menu (system operation set)
t - Setup NTP
v - Setup OpenVPN server
x - Exclude legacy API
c - Custom scripts (run USB:/activate/pre & USB:/activate/post from USB-flash)
-------------------------------------------------------------------------------

? ';

sub choice_master_activation {
  for(;;) {
    my $choice = $ACTION;
    if ($INTERACTIVE) {
      print $MENU_master_activation;
      $choice = read_choice();
    }
    if (not $choice=~/^([MER])([fsctxv]*)$/) {
      prlog "\nERROR: Your choice '$choice' is not valid.\n\n";
      last if not $INTERACTIVE;
      next;
    }
    activate('MASTER',$choice);
    start_application();
    return 0;            # SUCCESS if we in this point
  }
  return 1; # ! SUCCESS
}
#-------------------------------------------------------------------------------
my $MENU_node_activation='
Define a Node activation scenario by combining the TYPE with multiple options
-TYPE -------------------------------------------------------------------------
N - New Node     Activate an additional new Node for existing  Master system
R - Replace      Replace existing non-active node  
-options ----------------------------------------------------------------------
f - Format Storage (Find and format potential storage drives)
s - Activate SOS menu (system operation set)
t - Setup NTP
c - Custom scripts (run USB:/activate/pre & USB:/activate/post from USB-flash)
-------------------------------------------------------------------------------

? ';

sub choice_node_activation {
  for(;;) {
    my $choice = $ACTION;
    if ($INTERACTIVE) {
      print $MENU_node_activation;
      $choice = read_choice();
    }
    if (not $choice=~/^([NR])([fsct]*)$/) {
      prlog "\nERROR: Your choice '$choice' is not valid.\n\n";
      last if not $INTERACTIVE;
      next;
    }
    activate('NODE',$choice);
    start_application();
    return 0;            # SUCCESS if we in this point
  }
  return 1; # ! SUCCESS
}
#-------------------------------------------------------------------------------
my $MENU_upgrade_or_clean='
The following actions are possible:
-------------------------------------------------------------------------------
C - Clean       Deactivate/wipe-out existing configuration
U - Upgrade     Upgrade early activated system
-------------------------------------------------------------------------------

? ';

my $MENU_cleanonly='
System was activated but activation string is lost
Only one action is possible:
-------------------------------------------------------------------------------
C - Clean       Deactivate/wipe-out existing configuration
-------------------------------------------------------------------------------

? ';

sub choice_upgrade_or_clean {
   my $previous=shift;   # previously was activated with "$previous" 
   for(;;) {
      my $choice = $ACTION;
      if ($INTERACTIVE) {
        if( -f $ACTIVATED) {
           print $MENU_upgrade_or_clean;
        } else {
           print $MENU_cleanonly;
        }
        $choice = read_choice();
      }
      if (not $choice=~/^[CU]$/) {
        prlog "\nERROR: Your choice '$choice' is not valid.\n\n";
        last if not $INTERACTIVE;
        next;
      }
      upgrade if $choice eq 'U';
      deactivate if $choice eq 'C';   # clean or deactivate
      return 0;                       # SUCCESS if we in this point
   }
   return 1; # ! SUCCESS
}

sub load_env {
  open (ICF,"$APL/base/etc/install.conf") || die("Cannot read install.conf");
  open (ECF,"$APL/base/etc/env.conf")     || die("Cannot read env.conf");
  map {$ENV{$1}=$2 if /^(\w+)=(\S+)/} grep {/^\w+=\S+/} (<ICF>,<ECF>);
  close ICF;
  close ECF;
}

# MAIN -------------------------------------------------------------------------
my $ret=1;   # exit code is ERROR by default
chdir($APL); 

Log "======================= START ======================\n";
eval {
  die("Should be run by root\n") if $>!=0;
  load_env;
  my $status=status(); # posible C|L|A| or activation type with options (Msf etc)
  if($status eq 'A')     {
     Log "system already activated\n";
     die "\nsystem already activated\n";
  }
  if($status eq 'C')     { # system is clean, have to chose M/N
     my $choice=choice_master_or_node();
     if($choice eq 'M') {  # MASTER
       if(exists  $ARGS{backup}) {         
          recover($ARGS{backup});
       }else {
          choice_master_activation()
       }
     }else {               # NODE
         choice_node_activation();
     }
  }else {                  # UPGRADE
     Log "system was activated with $status\n";
     $ret=choice_upgrade_or_clean($status);
  }
};

Log "======================== END =======================\n";
print "$@\n" if $@; 

exit $ret;

# END =========================================================================
END {
  chown(0, $APL_GID,$LOG);
  chown(0, $APL_GID,"$ACTIVATE_LOGS/formatstorage.log");
  chown(0, $APL_GID,"$ACTIVATE_LOGS/activate-legacy.log") if -f "$ACTIVATE_LOGS/activate-legacy.log";
  `umount $MOUNT_POINT 2>/dev/null; rm -rf $MOUNT_POINT`;
}
