summaryrefslogtreecommitdiffstats
path: root/bin/generate-mirror-mail.pl
blob: 24e714ae1ce1eb6d853fa3a2bd5a7553dd00b8c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#!/usr/bin/perl
use warnings;
use strict;

use Config::Tiny;
use Date::Format;
use Date::Parse;
use File::Basename;
use HTTP::Cookies;
use JSON;
use Text::Template;
use Try::Tiny;
use WWW::Mechanize;

use Data::Dumper;

=head1 NAME

generate-mirror-mail.pl - Generate notification mails for broken mirrors

=head1 DESCRIPTION

Run the script and pass it URLs to the archweb mirror page (e.g.
<https://www.archlinux.org/mirrors/melbourneitmirror.net/>) via STDIN. If the
mirror has a problem the script will generate an appropriate mail and run
`compose-mail-from-stdin` which should be a script that starts your favourite
mail client with the mail. The mail is currently not complete so it cannot be
sent automatically.

=cut

# TODO: put this in a config file

#$ENV{HTTPS_CA_FILE}    = '/etc/ssl/certs/ca-certificates.crt';

my %templates = (
	'out-of-sync' => {
		'subject' => '[{$mirror_name}] Arch Linux mirror out of sync',
		'template' => 'Hi,

Your mirror seems to be out of sync since {$last_sync}, could you please
investigate?

{$mirror_urls}

Thanks,
Florian
',
	},
	'connection-failed' => {
		'subject' => '[{$mirror_name}] Arch Linux mirror not accessible{$OUT = ", ".join("/", @affected_protocols) if @affected_protocols > 0;}',
		'template' => 'Hi,

We\'re having trouble connecting to your mirror{$OUT = " via ".join(", ", @affected_protocols) if @affected_protocols > 0;}, could you
please check what\'s going on?

{$mirror_urls}

Thanks,
Florian
',
	},
);

my $Config = Config::Tiny->new();
$Config = Config::Tiny->read(dirname($0) . "/../settings.conf");

my $cookie_jar = HTTP::Cookies->new(file => dirname($0) . "/../cookie_jar", autosave => 1);
my $mech = WWW::Mechanize->new(agent => "arch-mirror-tools", cookie_jar => $cookie_jar);

sub login {
	$mech->get("https://www.archlinux.org/login/");
	my $res = $mech->submit_form(
		form_id => "dev-login-form",
		fields => {
			username => $Config->{account}->{username},
			password => $Config->{account}->{password}
		}
	);
}

sub send_mail {
	my $to = shift;
	my $subject = shift;
	my $body = shift;

	open my $fh, "|compose-mail-from-stdin" or die "Failed to run mailer: $!";
	print $fh "To: $to\n";
	printf $fh "From: %s\n", $Config->{misc}->{email_from};
	print $fh "Subject: $subject\n";
	print $fh "\n";
	print $fh "$body";
	close $fh;
}

sub send_template_mail {
	my $to = shift;
	my $subject = shift;
	my $body = shift;
	my $values = shift;

	send_mail($to, fill_template($subject, $values), fill_template($body, $values));
}

sub fill_template {
	my $template = shift;
	my $values = shift;
	my $result = Text::Template::fill_in_string($template, HASH => $values)
		or die "Failed to fill in template: $Text::Template::ERROR";

	return $result;
}

sub get_mirror_json {
	my ($url) = @_;

	$mech->get($url."/json/");
	return JSON::decode_json($mech->content());
}

while (<STDIN>) {
	try {
		my $url = $_;
		chomp($url);
		die "Skipping non-mirror detail URL" if $url =~ m/\/[0-9]+(\/|$)/;
		die "Skipping non-mirror detail URL" if $url eq "https://www.archlinux.org/mirrors/status/";

		my ($mirror_name) = ($url =~ m#/([^/]+)/?$#);
		my $json = get_mirror_json($url);

		if (not defined $json->{admin_email}) {
			login();
			$json = get_mirror_json($url);
			die "Admin email not set in mirror json. Login problem?" unless defined $json->{admin_email};
		}

		my @out_of_sync;
		my @connection_failed;

		for my $mirror (@{$json->{urls}}) {
			if ($mirror->{last_sync}) {
				my $time = str2time($mirror->{last_sync});
				if ($time < time() - 60*60*24*3) {
					push @out_of_sync, {
						time => $time,
						url => $mirror->{url},
						details_link => $mirror->{details},
					};
				}
			} else {
			#if ($mirror->{last_sync} and $mirror->{completion_pct} < 0.9 and $mirror->{completion_pct} > 0) {
				push @connection_failed, {
					url => $mirror->{url},
					details_link => $mirror->{details},
					protocol => $mirror->{protocol},
				};
			}
		}

		# extract and deduplicate sync times
		my @last_sync = keys %{{ map { ${$_}{time} => 1 } @out_of_sync }};
		my $sent_mail = 0;

		my $to = $json->{admin_email};
		$to .= ",".$json->{alternate_email} if $json->{alternate_email} ne "";

		if (@out_of_sync) {
			my %values = (
					last_sync => join(", ", map {time2str("%Y-%m-%d", $_)} @last_sync),
					mirror_urls => join("\n", $url, map {${$_}{details_link}} @out_of_sync),
					mirror_name => $mirror_name,
				);
			send_template_mail($to, $templates{"out-of-sync"}{"subject"}, $templates{"out-of-sync"}{"template"}, \%values);
			$sent_mail = 1;
		}

		if (@connection_failed) {
			my %values = (
					mirror_urls => join("\n", $url, map {${$_}{details_link}} @connection_failed),
					mirror_name => $mirror_name,
				);

			my @protocols = map {${$_}{protocol}} @connection_failed;
			if (scalar(@protocols) != scalar(@{$json->{urls}})) {
				$values{affected_protocols} = \@protocols;
			}

			send_template_mail($to, $templates{"connection-failed"}{"subject"}, $templates{"connection-failed"}{"template"}, \%values);
			$sent_mail = 1;
		}

		if (!$sent_mail) {
			say STDERR "No issue detected for mirror $mirror_name";
		}

	} catch {
		warn "ignoring error: $_";
	}
}