aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2020-01-17 01:15:04 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2020-01-17 01:15:04 +0100
commit71a2425ed95120a6de3a41bb233b1066779d4c26 (patch)
tree007f2756abab644604577352da28b1fd4c20df44 /modules
parent981fa80354fd6f00f49446777c38f77bd8a65f65 (diff)
downloadNix-71a2425ed95120a6de3a41bb233b1066779d4c26.tar.gz
Nix-71a2425ed95120a6de3a41bb233b1066779d4c26.tar.zst
Nix-71a2425ed95120a6de3a41bb233b1066779d4c26.zip
Add e-mail checks monitoring
Diffstat (limited to 'modules')
-rw-r--r--modules/private/environment.nix13
-rw-r--r--modules/private/mail/postfix.nix36
-rw-r--r--modules/private/mail/relay.nix6
-rw-r--r--modules/private/monitoring/default.nix54
-rw-r--r--modules/private/monitoring/objects_common.nix2
-rw-r--r--modules/private/monitoring/objects_eldiron.nix5
-rw-r--r--modules/private/monitoring/objects_immae-eu.nix7
-rw-r--r--modules/private/monitoring/objects_phare.nix11
-rw-r--r--modules/private/monitoring/objects_ulminfo-fr.nix11
-rwxr-xr-xmodules/private/monitoring/plugins/check_emails119
-rwxr-xr-xmodules/private/monitoring/send_mails15
11 files changed, 240 insertions, 39 deletions
diff --git a/modules/private/environment.nix b/modules/private/environment.nix
index 81b5df5..7da2480 100644
--- a/modules/private/environment.nix
+++ b/modules/private/environment.nix
@@ -486,6 +486,19 @@ in
486 slack_url = mkOption { type = str; description = "Slack webhook url to push status update"; }; 486 slack_url = mkOption { type = str; description = "Slack webhook url to push status update"; };
487 slack_channel = mkOption { type = str; description = "Slack channel to push status update"; }; 487 slack_channel = mkOption { type = str; description = "Slack channel to push status update"; };
488 contacts = mkOption { type = attrsOf unspecified; description = "Contact dicts to fill naemon objects"; }; 488 contacts = mkOption { type = attrsOf unspecified; description = "Contact dicts to fill naemon objects"; };
489 email_check = mkOption {
490 description = "Emails services to check";
491 type = attrsOf (submodule {
492 options = {
493 local = mkOption { type = bool; default = false; description = "Use local configuration"; };
494 port = mkOption { type = nullOr str; default = null; description = "Port to connect to ssh"; };
495 login = mkOption { type = nullOr str; default = null; description = "Login to connect to ssh"; };
496 targets = mkOption { type = listOf str; description = "Hosts to send E-mails to"; };
497 mail_address = mkOption { type = str; description = "E-mail recipient part to send e-mail to"; };
498 mail_domain = mkOption { type = str; description = "E-mail domain part to send e-mail to"; };
499 };
500 });
501 };
489 }; 502 };
490 }; 503 };
491 }; 504 };
diff --git a/modules/private/mail/postfix.nix b/modules/private/mail/postfix.nix
index 8fe06da..51f4de7 100644
--- a/modules/private/mail/postfix.nix
+++ b/modules/private/mail/postfix.nix
@@ -1,4 +1,4 @@
1{ lib, pkgs, config, nodes, name, ... }: 1{ lib, pkgs, config, nodes, ... }:
2{ 2{
3 config = lib.mkIf config.myServices.mail.enable { 3 config = lib.mkIf config.myServices.mail.enable {
4 services.duplyBackup.profiles.mail.excludeFile = '' 4 services.duplyBackup.profiles.mail.excludeFile = ''
@@ -220,7 +220,13 @@
220 ''; 220 '';
221 scripts = lib.attrsets.mapAttrs (n: v: 221 scripts = lib.attrsets.mapAttrs (n: v:
222 toScript n (pkgs.callPackage (builtins.fetchGit { url = v.src.url; ref = "master"; rev = v.src.rev; }) { scriptEnv = v.env; }) 222 toScript n (pkgs.callPackage (builtins.fetchGit { url = v.src.url; ref = "master"; rev = v.src.rev; }) { scriptEnv = v.env; })
223 ) config.myEnv.mail.scripts; 223 ) config.myEnv.mail.scripts // {
224 testmail = pkgs.writeScript "testmail" ''
225 #! ${pkgs.stdenv.shell}
226 ${pkgs.coreutils}/bin/touch \
227 "/var/lib/naemon/checks/email/$(${pkgs.procmail}/bin/formail -x To: | ${pkgs.coreutils}/bin/tr -d ' <>')"
228 '';
229 };
224 in builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v: ''${n}: "|${v}"'') scripts); 230 in builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v: ''${n}: "|${v}"'') scripts);
225 mapFiles = let 231 mapFiles = let
226 recipient_maps = let 232 recipient_maps = let
@@ -246,8 +252,12 @@
246 ) config.myEnv.mail.postfix.backup_domains 252 ) config.myEnv.mail.postfix.backup_domains
247 ); 253 );
248 virtual_map = { 254 virtual_map = {
249 virtual = pkgs.writeText "postfix-virtual" ( 255 virtual = let
256 cfg = config.myEnv.monitoring.email_check.eldiron;
257 address = "${cfg.mail_address}@${cfg.mail_domain}";
258 in pkgs.writeText "postfix-virtual" (
250 builtins.concatStringsSep "\n" ( 259 builtins.concatStringsSep "\n" (
260 ["${address} testmail@localhost"] ++
251 lib.attrsets.mapAttrsToList ( 261 lib.attrsets.mapAttrsToList (
252 n: v: lib.optionalString v.external '' 262 n: v: lib.optionalString v.external ''
253 script_${n}@mail.immae.eu ${n}@localhost, scripts@mail.immae.eu 263 script_${n}@mail.immae.eu ${n}@localhost, scripts@mail.immae.eu
@@ -425,5 +435,25 @@
425 "smtp.immae.eu" = null; 435 "smtp.immae.eu" = null;
426 }; 436 };
427 }; 437 };
438 system.activationScripts.testmail = {
439 deps = [ "users" ];
440 text = let
441 allCfg = config.myEnv.monitoring.email_check;
442 cfg = allCfg.eldiron;
443 reverseTargets = builtins.attrNames (lib.attrsets.filterAttrs (k: v: builtins.elem "eldiron" v.targets) allCfg);
444 to_email = cfg': host':
445 let sep = if lib.strings.hasInfix "+" cfg'.mail_address then "_" else "+";
446 in "${cfg'.mail_address}${sep}${host'}@${cfg'.mail_domain}";
447 mails_to_receive = builtins.concatStringsSep " " (map (to_email cfg) reverseTargets);
448 in ''
449 install -m 0555 -o nobody -g nogroup -d /var/lib/naemon/checks/email
450 for f in ${mails_to_receive}; do
451 if [ ! -f /var/lib/naemon/checks/email/$f ]; then
452 install -m 0644 -o nobody -g nogroup /dev/null -T /var/lib/naemon/checks/email/$f
453 touch -m -d @0 /var/lib/naemon/checks/email/$f
454 fi
455 done
456 '';
457 };
428 }; 458 };
429} 459}
diff --git a/modules/private/mail/relay.nix b/modules/private/mail/relay.nix
index e0aa387..6ac3df8 100644
--- a/modules/private/mail/relay.nix
+++ b/modules/private/mail/relay.nix
@@ -126,8 +126,12 @@
126 ) config.myEnv.mail.postfix.backup_domains 126 ) config.myEnv.mail.postfix.backup_domains
127 ); 127 );
128 virtual_map = { 128 virtual_map = {
129 virtual = pkgs.writeText "postfix-virtual" ( 129 virtual = let
130 cfg = config.myEnv.monitoring.email_check.eldiron;
131 address = "${cfg.mail_address}@${cfg.mail_domain}";
132 in pkgs.writeText "postfix-virtual" (
130 builtins.concatStringsSep "\n" ( 133 builtins.concatStringsSep "\n" (
134 ["${address} 1"] ++
131 lib.attrsets.mapAttrsToList ( 135 lib.attrsets.mapAttrsToList (
132 n: v: lib.optionalString v.external '' 136 n: v: lib.optionalString v.external ''
133 script_${n}@mail.immae.eu 1 137 script_${n}@mail.immae.eu 1
diff --git a/modules/private/monitoring/default.nix b/modules/private/monitoring/default.nix
index e1357a7..a7013af 100644
--- a/modules/private/monitoring/default.nix
+++ b/modules/private/monitoring/default.nix
@@ -1,6 +1,16 @@
1{ config, pkgs, lib, name, nodes, ... }: 1{ config, pkgs, lib, name, nodes, ... }:
2let 2let
3 cfg = config.myServices.monitoring; 3 cfg = config.myServices.monitoring;
4 send_mails = pkgs.runCommand "send_mails" {
5 buildInputs = [ pkgs.makeWrapper ];
6 } ''
7 mkdir -p $out/bin
8 cp ${./send_mails} $out/bin/send_mails
9 patchShebangs $out
10 wrapProgram $out/bin/send_mails --prefix PATH : ${lib.makeBinPath [
11 pkgs.mailutils
12 ]}
13 '';
4 myplugins = pkgs.runCommand "buildplugins" { 14 myplugins = pkgs.runCommand "buildplugins" {
5 buildInputs = [ pkgs.makeWrapper pkgs.perl ]; 15 buildInputs = [ pkgs.makeWrapper pkgs.perl ];
6 } '' 16 } ''
@@ -29,6 +39,11 @@ let
29 wrapProgram $out/check_openldap_replication --prefix PATH : ${lib.makeBinPath [ 39 wrapProgram $out/check_openldap_replication --prefix PATH : ${lib.makeBinPath [
30 pkgs.gnugrep pkgs.gnused pkgs.coreutils pkgs.openldap 40 pkgs.gnugrep pkgs.gnused pkgs.coreutils pkgs.openldap
31 ]} 41 ]}
42 wrapProgram $out/check_emails --prefix PATH : ${lib.makeBinPath [
43 pkgs.openssh send_mails
44 ]} --prefix PERL5LIB : ${pkgs.perlPackages.makePerlPath [
45 pkgs.perlPackages.TimeDate
46 ]}
32 wrapProgram $out/check_ftp_database --prefix PATH : ${lib.makeBinPath [ 47 wrapProgram $out/check_ftp_database --prefix PATH : ${lib.makeBinPath [
33 pkgs.lftp 48 pkgs.lftp
34 ]} 49 ]}
@@ -67,11 +82,6 @@ let
67 }; 82 };
68 }; 83 };
69 masterPassiveObjects = let 84 masterPassiveObjects = let
70 otherPassiveObjects = map
71 (n: (pkgs.callPackage (./. + "/objects_" + n + ".nix") {}))
72 [ "caldance-1" "ulminfo-fr" "immae-eu" "phare" "tiboqorl-fr" ];
73 otherPassiveServices = lib.flatten (map (h: h.service or []) otherPassiveObjects);
74 otherPassiveHosts = (map (h: h.host)) otherPassiveObjects;
75 passiveNodes = lib.attrsets.filterAttrs (n: _: builtins.elem n ["backup-2" "eldiron"]) nodes; 85 passiveNodes = lib.attrsets.filterAttrs (n: _: builtins.elem n ["backup-2" "eldiron"]) nodes;
76 toPassiveServices = map (s: s.passiveInfo.filter s // s.passiveInfo); 86 toPassiveServices = map (s: s.passiveInfo.filter s // s.passiveInfo);
77 passiveServices = lib.flatten (lib.attrsets.mapAttrsToList 87 passiveServices = lib.flatten (lib.attrsets.mapAttrsToList
@@ -79,12 +89,37 @@ let
79 passiveNodes 89 passiveNodes
80 ); 90 );
81 in { 91 in {
82 service = passiveServices ++ otherPassiveServices; 92 service = passiveServices;
83 host = lib.lists.foldr 93 host = lib.lists.foldr
84 (a: b: a//b) 94 (a: b: a//b)
85 {} 95 {}
86 (otherPassiveHosts ++ lib.attrsets.mapAttrsToList (_: h: h.config.myServices.monitoring.hosts) passiveNodes); 96 (lib.attrsets.mapAttrsToList (_: h: h.config.myServices.monitoring.hosts) passiveNodes);
87 }; 97 };
98 emailCheck = host: hostFQDN: let
99 allCfg = config.myEnv.monitoring.email_check;
100 cfg = allCfg."${host}";
101 reverseTargets = builtins.attrNames (lib.attrsets.filterAttrs (k: v: builtins.elem host v.targets) allCfg);
102 to_email = cfg': host':
103 let sep = if lib.strings.hasInfix "+" cfg'.mail_address then "_" else "+";
104 in "${cfg'.mail_address}${sep}${host'}@${cfg'.mail_domain}";
105 mails_to_send = builtins.concatStringsSep "," (map (n: to_email allCfg."${n}" host) cfg.targets);
106 mails_to_receive = builtins.concatStringsSep "," (map (n: "${to_email cfg n}:${n}") reverseTargets);
107 command = if cfg.local
108 then
109 [ "check_emails_local" "/var/lib/naemon/checks/email" mails_to_send mails_to_receive ]
110 else
111 [ "check_emails" cfg.login cfg.port mails_to_send mails_to_receive ];
112 in
113 {
114 service_description = "${hostFQDN} email service is active";
115 use = "mail-service";
116 host_name = hostFQDN;
117 servicegroups = "webstatus-email";
118 check_command = command;
119 };
120 otherObjects = map
121 (n: (pkgs.callPackage (./. + "/objects_" + n + ".nix") { inherit emailCheck; }))
122 [ "caldance-1" "ulminfo-fr" "immae-eu" "phare" "tiboqorl-fr" ];
88 masterObjects = pkgs.callPackage ./objects_master.nix { inherit config; }; 123 masterObjects = pkgs.callPackage ./objects_master.nix { inherit config; };
89 commonObjects = pkgs.callPackage ./objects_common.nix ({ 124 commonObjects = pkgs.callPackage ./objects_common.nix ({
90 master = cfg.master; 125 master = cfg.master;
@@ -99,7 +134,7 @@ let
99 lib.attrsets.optionalAttrs 134 lib.attrsets.optionalAttrs
100 (builtins.pathExists specific_file) 135 (builtins.pathExists specific_file)
101 (pkgs.callPackage specific_file { 136 (pkgs.callPackage specific_file {
102 inherit config; 137 inherit config emailCheck;
103 hostFQDN = config.hostEnv.fqdn; 138 hostFQDN = config.hostEnv.fqdn;
104 hostName = name; 139 hostName = name;
105 }); 140 });
@@ -228,7 +263,8 @@ in
228 objectDefs = toObjects commonObjects 263 objectDefs = toObjects commonObjects
229 + toObjects hostObjects 264 + toObjects hostObjects
230 + lib.optionalString cfg.master (toObjects masterObjects) 265 + lib.optionalString cfg.master (toObjects masterObjects)
231 + lib.optionalString cfg.master (toObjects masterPassiveObjects); 266 + lib.optionalString cfg.master (toObjects masterPassiveObjects)
267 + lib.optionalString cfg.master (builtins.concatStringsSep "\n" (map toObjects otherObjects));
232 }; 268 };
233 }; 269 };
234} 270}
diff --git a/modules/private/monitoring/objects_common.nix b/modules/private/monitoring/objects_common.nix
index 15eee97..a9d6da4 100644
--- a/modules/private/monitoring/objects_common.nix
+++ b/modules/private/monitoring/objects_common.nix
@@ -91,6 +91,8 @@ in
91 ]; 91 ];
92 command = { 92 command = {
93 check_dns = "$USER1$/check_dns -H $ARG1$ -s $HOSTADDRESS$ $ARG2$"; 93 check_dns = "$USER1$/check_dns -H $ARG1$ -s $HOSTADDRESS$ $ARG2$";
94 check_emails = "$USER2$/check_emails -H $HOSTADDRESS$ -i $USER203$ -l $ARG1$ -p $ARG2$ -s $ARG3$ -f $ARG4$";
95 check_emails_local = "$USER2$/check_emails -H $HOSTADDRESS$ -n $ARG1$ -r $ADMINEMAIL$ -s $ARG2$ -f $ARG3$";
94 check_eriomem = "$USER2$/check_eriomem $USER208$"; 96 check_eriomem = "$USER2$/check_eriomem $USER208$";
95 check_external_dns = "$USER1$/check_dns -H $ARG2$ -s $ARG1$ $ARG3$"; 97 check_external_dns = "$USER1$/check_dns -H $ARG2$ -s $ARG1$ $ARG3$";
96 check_ftp_database = "$USER2$/check_ftp_database"; 98 check_ftp_database = "$USER2$/check_ftp_database";
diff --git a/modules/private/monitoring/objects_eldiron.nix b/modules/private/monitoring/objects_eldiron.nix
index bee4645..92f997f 100644
--- a/modules/private/monitoring/objects_eldiron.nix
+++ b/modules/private/monitoring/objects_eldiron.nix
@@ -1,4 +1,4 @@
1{ lib, hostFQDN, ... }: 1{ lib, hostFQDN, emailCheck, ... }:
2let 2let
3 defaultPassiveInfo = { 3 defaultPassiveInfo = {
4 filter = lib.attrsets.filterAttrs 4 filter = lib.attrsets.filterAttrs
@@ -24,5 +24,8 @@ in
24 use = "local-service"; 24 use = "local-service";
25 check_command = ["check_mailq"]; 25 check_command = ["check_mailq"];
26 } 26 }
27 (emailCheck "eldiron" hostFQDN // {
28 passiveInfo = defaultPassiveInfo // { servicegroups = "webstatus-email"; freshness_threshold = "1350"; };
29 })
27 ]; 30 ];
28} 31}
diff --git a/modules/private/monitoring/objects_immae-eu.nix b/modules/private/monitoring/objects_immae-eu.nix
index a6337e9..cffb180 100644
--- a/modules/private/monitoring/objects_immae-eu.nix
+++ b/modules/private/monitoring/objects_immae-eu.nix
@@ -84,12 +84,5 @@ in
84 service_description = "mailq is empty"; 84 service_description = "mailq is empty";
85 servicegroups = "webstatus-email"; 85 servicegroups = "webstatus-email";
86 } 86 }
87
88 ## Sending e-mail
89 {
90 service_description = "immae.eu email service is active";
91 servicegroups = "webstatus-email";
92 freshness_threshold = "1350";
93 }
94 ]; 87 ];
95} 88}
diff --git a/modules/private/monitoring/objects_phare.nix b/modules/private/monitoring/objects_phare.nix
index ab46436..a00e5e8 100644
--- a/modules/private/monitoring/objects_phare.nix
+++ b/modules/private/monitoring/objects_phare.nix
@@ -1,4 +1,4 @@
1{ ... }: 1{ emailCheck, ... }:
2{ 2{
3 host = { 3 host = {
4 "phare.normalesup.org" = { 4 "phare.normalesup.org" = {
@@ -10,13 +10,6 @@
10 }; 10 };
11 }; 11 };
12 service = [ 12 service = [
13 { 13 (emailCheck "phare" "phare.normalesup.org")
14 service_description = "phare.normalesup.org email service is active";
15 use = "external-passive-service";
16 host_name = "phare.normalesup.org";
17 freshness_threshold = "1350";
18 retry_interval = "1";
19 servicegroups = "webstatus-email";
20 }
21 ]; 14 ];
22} 15}
diff --git a/modules/private/monitoring/objects_ulminfo-fr.nix b/modules/private/monitoring/objects_ulminfo-fr.nix
index 87a3e05..b970ecd 100644
--- a/modules/private/monitoring/objects_ulminfo-fr.nix
+++ b/modules/private/monitoring/objects_ulminfo-fr.nix
@@ -1,4 +1,4 @@
1{ ... }: 1{ emailCheck, ... }:
2{ 2{
3 host = { 3 host = {
4 "ulminfo.fr" = { 4 "ulminfo.fr" = {
@@ -10,13 +10,6 @@
10 }; 10 };
11 }; 11 };
12 service = [ 12 service = [
13 { 13 (emailCheck "ulminfo" "ulminfo.fr")
14 service_description = "ulminfo.fr email service is active";
15 use = "external-passive-service";
16 host_name = "ulminfo.fr";
17 freshness_threshold = "1350";
18 retry_interval = "1";
19 servicegroups = "webstatus-email";
20 }
21 ]; 14 ];
22} 15}
diff --git a/modules/private/monitoring/plugins/check_emails b/modules/private/monitoring/plugins/check_emails
new file mode 100755
index 0000000..0ee3e4e
--- /dev/null
+++ b/modules/private/monitoring/plugins/check_emails
@@ -0,0 +1,119 @@
1#!/usr/bin/env perl
2
3use strict;
4use Getopt::Std;
5use File::Basename;
6use Date::Parse;
7use POSIX qw(strftime);
8
9$| = 1;
10
11my %opts;
12getopts('hH:l:s:p:f:i:n:r:', \%opts);
13
14my $STATE_OK = 0;
15my $STATE_WARNING = 1;
16my $STATE_CRITICAL = 2;
17my $STATE_UNKNOWN = 3;
18
19if ($opts{'h'} || scalar(%opts) == 0) {
20 &print_help();
21 exit($STATE_OK);
22}
23
24my $port = $opts{'p'};
25my $host = $opts{'H'};
26my $login = $opts{'l'};
27if ($login ne '') {
28 $login = "$login@";
29}
30
31my $identity = $opts{'i'};
32my $local_directory = $opts{'n'};
33my $return_path = $opts{'r'};
34
35my @emails_to_send = split(/,/, $opts{'s'});
36my @emails_to_expect = split(/,/, $opts{'f'});
37
38my $cmd_result;
39if ($local_directory ne '') {
40 if (! -d $local_directory) {
41 print "Emails $host UNKNOWN - Could not find local directory";
42 exit($STATE_UNKNOWN);
43 }
44 $cmd_result = `send_mails $local_directory $return_path @emails_to_send 2>&1`;
45} else {
46 $cmd_result = `ssh -o BatchMode=yes -o UserKnownHostsFile=/dev/null -o CheckHostIP=no -o StrictHostKeyChecking=no -p $port -i $identity $login$host send_mails @emails_to_send 2>&1`;
47
48 if ($cmd_result =~ /Host key verification failed./) {
49 print "Emails $host UNKNOWN - Could not connect to host with ssh key\n";
50 exit($STATE_UNKNOWN);
51 }
52}
53
54my @lines = split(/\n/, $cmd_result);
55
56my %found_emails;
57
58foreach my $line (@lines) {
59 my @split_line = split(/;/, $line, 2);
60 $found_emails{$split_line[0]} = $split_line[1];
61}
62
63my $output = "";
64foreach my $email_from (@emails_to_expect) {
65 my @email_split = split(/:/, $email_from);
66 my $email = $email_split[0];
67 my $from = $email_split[1];
68
69 if ( exists $found_emails{$email} ) {
70 my $email_date = str2time($found_emails{$email});
71 my $current_date = strftime "%s", localtime;
72
73 if ($current_date - $email_date > 60*30) {
74 $output = "$output$email ($found_emails{$email} from $from) ";
75 }
76 } else {
77 $output = "$output$email (missing) "
78 }
79}
80
81if ($output ne '') {
82 print "Emails $host CRITICAL - expecting emails: $output\n";
83 exit($STATE_CRITICAL);
84} else {
85 print "Emails $host OK\n";
86 exit($STATE_OK);
87}
88
89sub print_help() {
90 print << "EOF";
91Check sent emails
92
93Options:
94-h
95 Print detailed help screen
96
97-H
98 Host to check
99
100-l
101 Login
102
103-i
104 Identity file
105
106-n
107 Don’t use ssh, pass that directory to script
108
109-r
110 Return path for local e-mails
111
112-s
113 Comma separated list of emails to send from the host.
114
115-f
116 Comma separated list of emails to expect on the host.
117EOF
118}
119
diff --git a/modules/private/monitoring/send_mails b/modules/private/monitoring/send_mails
new file mode 100755
index 0000000..105c505
--- /dev/null
+++ b/modules/private/monitoring/send_mails
@@ -0,0 +1,15 @@
1#!/usr/bin/env bash
2
3CHECK_DIR=$1
4shift
5RETURN_PATH=$1
6shift
7
8for mail in "$@"; do
9 echo "Test Mail" | MAILRC=/dev/null mail -n -r "$RETURN_PATH" -s "TestMailImmae " "$mail"
10done
11
12if [ -d "$CHECK_DIR" ]; then
13 cd $CHECK_DIR
14 stat -c '%n;%y' *
15fi