#!/usr/bin/perl -w

use warnings;
use strict;

use Sys::Hostname;
use Config::Simple;
use Getopt::Long;
use MIME::Lite;
use MIME::Base64 qw(encode_base64);
use Encode qw(encode decode_utf8);
use File::stat;

my $type        = 'temp';
my @notify      = ();
my $witness     = '/tmp/alert_temp';
my $triggers    = 1;
my $shutdown    = 0;
my $time        = 900;
my $verbose     = 0;
my $help        = 0;

GetOptions(
  "type=s"        => \$type,
  "notify=s"      => \@notify,
  "witness=s"     => \$witness,
  "triggers=i"    => \$triggers,
  "time=i"        => \$time,
  "shutdown"      => \$shutdown,
  "verbose"       => \$verbose,
  "help"          => \$help,
) || _usage();

if ($help){
  _usage();
  exit 0;
}

@notify = split(/,/, join(',', @notify));

die "Le nombre de déclencheurs ne peut être inférieur à 1\n"
  if $triggers < 1;

# Lecture de la liste des senseurs utilisés par Zabbix
my $cfg = new Config::Simple;
$cfg->read('/etc/zabbix/sensors.ini');
my %sensors = ();
# Cette boucle permet simplement d'extraire la liste des senseurs. $cfg->vars
# contient toutes les clés indépendantes eg cpu0.threshold_high
foreach my $k (keys %{$cfg->vars}){
  $k =~ s/\..*$//;
  $sensors{$k} = 1 unless $sensors{$k};
}

my $sensors = {};
my $alert = 0;
# Maintenant, pour chaque senseur
foreach my $k (keys %sensors){
  print "Checking sensor $k\n" if $verbose;
  # On récupère la définition dans le fichier de config
  my $sensor = $cfg->get_block($k);
  next if ($type ne 'all' && $type ne $sensor->{type});
  $sensors->{$k} = $sensor;
  # Exécution de la commande pour lire la valeur
  my @val = qx($sensor->{cmd});
  if ($? != 0){
    die "Couldn't check sensor $k\n";
  }
  my $val = join("", @val);
  chomp($val);
  print "Got value $val\n" if $verbose;
  # On stock cette valeur bien au chaud
  $sensors->{$k}->{value} = $val;
  if ($val > $sensor->{threshold_high}){
    # Si on dépasse le seuil, on active un flag global d'alerte
    # et un flag associé à ce senseur (pour la liste envoyée par email)
    print "$val > $sensor->{threshold_high} setting alert flag\n" if $verbose;
    $sensors->{$k}->{alert} = 1;
    $alert++;
  }
  print "\n\n" if $verbose;
}

# Si on a rencontré assez d'alertes
if ($alert >= $triggers){
  my $msg=<<_EOF;

$alert senseur(s) dépasse(nt) le seuil d'alerte:

_EOF

  foreach my $k (keys %sensors){
    # Liste des senseurs en alerte
    $msg .= "$k (" . $sensors->{$k}->{description} . "): " . $sensors->{$k}->{value} .
            " (seuil à " . $sensors->{$k}->{threshold_high} . ")\n" if ($sensors->{$k}->{alert}); 
  }

  $msg .=<<_EOF;

Valeurs des autres senseurs (qui n'ont pas déclenché d'alerte):

_EOF
  foreach my $k (keys %sensors){
    # Liste des senseurs qui n'ont pas dépassé leur seuil d'alerte
    $msg .= "$k (" . $sensors->{$k}->{description} . "): " . $sensors->{$k}->{value} .
      " (seuil à " . $sensors->{$k}->{threshold_high} . ")\n" if (!$sensors->{$k}->{alert});
  }

  if (!-e $witness){
    # Si le fichier témoin n'existe pas encore: c'est la première occurrence de l'alerte
    print "$witness doesn't exist yet, first time the alert is triggerred, creating it\n" if $verbose;
    open ( WIT, ">$witness" ) || die $!;
    print WIT "";
    close WIT;
    $msg .=<<_EOF;

Première occurrence de l'alerte, le fichier témoin $witness a été créé. Si l'alerte persiste pendant $shutdown secondes, la machine sera coupée par mesure de sécurité. Pour interrompre ce processus, vous pouvez supprimer le fichier $witness

_EOF
  }
  else{
    # Le fichier existait déjà ? C'est donc une novuelle occurrence
    my $mtime = stat($witness)->mtime;
    # Combien de temps avant extinction ?
    my $left = $time-(time-$mtime);
    if ($left > 0){
      print "$witness already exist, $left second before shuting down\n" if $verbose;
      $msg .=<<_EOF;

$left secondes avant extinction du serveur. Pour annuler l'extinction, supprimez le fichier $witness (ou assurez-vous que les alertes retombent)

_EOF
    }
    elsif ($shutdown){
      print "shuting down now\n" if $verbose;
      $msg .=<<_EOF;

Extinction du serveur pour éviter d'endommager ses composants

_EOF
      # Un dernier mail et on éteint tout
      _email("[Extinction] " . hostname, $msg);
      system('/sbin/poweroff');
      exit 0;
    }
    else{
      print "We should shut down, but --shutdown not enabled\n" if $verbose;
      $msg .=<<_EOF;

Le serveur devrait être éteint, mais l'extinction d'urgence n'est pas activée, il faut ajouter l'argument --shutdown. Il est conseillé d'éteindre manuellement ce serveur.

_EOF

    _email("[Extinction recommandée] " . hostname, $msg);
    exit 0;
    }
  }
  _email("[ALERTE Température] sur " . hostname, $msg);
}
else{
  print "No (or not enough) alert were found\n" if $verbose;
  # Toutes les alertes sont retombées, on peut supprimer le témoin
  if (-e $witness){
    unlink $witness;
    print "Clearing alert status\n" if $verbose;
    my $msg =<<_EOF;

Les alertes sont retombées, la procédure d'extinction d'urgence est annulée

_EOF
    _email("[Fin d'alerte température] sur " . hostname, $msg);
  }
}


# Send an email, takes subject and body as arg
sub _email {
  my $subject = shift;
  my $body    = shift;

  print "Sending notification to the following addresses: " . join(',', @notify) . "\n" if $verbose;

  my $mail = MIME::Lite->new(
    From     => 'sensor-monitor',
    To       => join(',', @notify),
    Type     => 'text/plain; charset=UTF-8',
    Encoding => 'quoted-printable',
    Subject  => encode("MIME-B", decode_utf8($subject)),
    Data     => $body
  );
  $mail->send;
}

# Print help
sub _usage {
  print <<_EOF;

Ce script permet de contrôler la valeur des senseurs (généralement des températures) utilisés par Zabbix.
Pour chaque senseur définie dans /etc/zabbix/sensors.ini, la valeur actuelle est relevée et comparée au seuil haut.
Si la valeur est dépassée, un fichier témoin est créé.
À la prochaine exécution, si au moins une alerte est toujours active, l'âge du fichier témoin est vérifié, et si cet âge dépasse une valeur prédéfinie, alors le serveur est éteint.

Les options disponibles sont:

  * --help: permet d'afficher cette aide
  * --notify: liste d'adresses email qui seront notifiées en cas d'alerte. Les adresses peuvent être séparées par des virgules, ou vous pouvez donner plusieurs fois l'argument --notify
  * --type: type de senseurs à vérifier. 'temp' par défaut (et n'aura probablement jamais besoin d'être changé)
  * --witness: chemin vers le ficheir témoin. /tmp/alert_temp par défaut
  * --triggers: nombre minimum de senseur en alerte. La valeur par défaut est 1. On peut mettre un nombre plus élevée pour éviter les faux positifs du à une erreur de configuration des senseurs
  * --time: durée en seconde après laquelle le serveur est éteint. 900 par défaut (à ajuster aussi en fonction de la fréquence de lancement de ce script)
  * --shutdown: si présent, éteint le serveur si l'alerte persiste, sinon, ne fait qu'envoyer des alertes par email
  * --verbose: mode verbeux (pour débug)

_EOF
}
