I have several websites that I host on my server. Sometimes I make configuration changes and sometimes it breaks things. I'd like to have a basic safeguard against breaking a website. Ensuring that the home page is available is enough for me.
I decided to re-learn CHICKEN Scheme for a change and made a script that solves my problem. It checks for a list of websites and sends me a Telegram notification: silent one if everything is okay and a normal one if something is broken.
Telegram setup
You should have a Telegram account. Then follow instructions on how to create a Telegram bot. You should obtain a token and a link to your bot.
Next open the chat with a bot and send it any message. We need it to get the chat ID. Send an HTTP GET request to get updates (replace AAAAAAAAA
with your token):
curl 'https://api.telegram.org/botAAAAAAAAA/getUpdates'
The result is something like this:
{
"ok": true,
"result": [
{
"update_id": 969242700,
"message": {
"message_id": 1,
"from": {
"id": 1111111,
"is_bot": false,
"first_name": "Yourname",
"last_name": "Yoursurname",
"username": "yourusername",
"language_code": "en",
"is_premium": true
},
"chat": {
"id": 2222222,
"first_name": "Yourname",
"last_name": "Yoursurname",
"username": "yourusername",
"type": "private"
},
"date": 1717751320,
"text": "/start",
"entities": [
{
"offset": 0,
"length": 6,
"type": "bot_command"
}
]
}
}
]
}
Find the chat ID in result[0]["chat"]["id"]
, here it is 2222222
.
Now save them both to a file, I have it in TelegramSecrets.rakumod
for loading later in a Raku script:
unit module TelegramSecrets;
our $secret-chat-id = 270617458;
our $secret-token = "7421804383:AAHs9x3aRKMCTRmI7Ubs-Q6PPNvw36UupMo";
Also a good idea is to set stricter permissions for this file:
chmod 600 TelegramSecrets.rakumod
The Script
This is a script in Raku. I have tried writing it in several other languages, with comments:
-
CHICKEN Scheme – I got it too verbose with too much abstraction. This is in part because it was my first try of a Scheme in a while, and partly because of weird functional APIs Scheme libraries provide. Scheme is functional after all, I might have not had enough time to adjust my thinking.
-
Common Lisp – this was my first program ever, but it was pretty concise and pretty easy to develop. I can attribute this easiness to the lack of the design stage, I previously had already explored the code with Scheme. Also the programming is less alien than in Scheme. It also turned out that "scripting" in Common Lisp is a somewhat controversial topic. There's Roswell, an attempt at improvement, but it is controversial too. It seems that people don't really do "scripting" as usual: running little script programs, instead they load the whole Lisp file and do the scripting in a REPL. Also running a script in Roswell feels the same as running with
sbcl --script
and Roswell creates an additional abstraction layer. I think I need some more time to evaluate the whole situation, it is really complicated in the Common Lisp land. -
Raku – this is my last and current attempt at scripting. Raku seems as a language that should be ideal for this situation, it is literally made for scripting. Since I have already spent about a month learning it, copying a program from Common Lisp seemed easy. As for the code expressiveness, I still think that the Common Lisp version is better because it is more functional.
I think I want to explore the Gauche Scheme implementation because it is made specifically for scripting, like Raku. And I want to dive more into Common Lisp to better understand its viability for scripting.
Here is the code in Raku:
#!/bin/env raku
# Monitor several websites for availability sending them GET requests to the
# home page, not tolerating redirects. Send a silent message via Telegram bot
# if everything is okay. Send a message with a notification if something
# broke.
use v6.d;
need IO::Socket::SSL;
use HTTP::Tiny;
use JSON::Fast;
# Contains secret data, like this:
# unit module TelegramSecrets;
# our $secret-chat-id = 111111111;
# our $secret-token = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
use lib $*PROGRAM.dirname;
use TelegramSecrets;
my @websites =
"https://greenfork.me",
"https://garden.greenfork.me",
"https://links.greenfork.me",
"https://fossil.greenfork.me",
"https://dmitryxsabina.ru",
"https://sabinahelp.ru",
;
my $http = HTTP::Tiny.new(:max-redirect(0));
sub check-website($website) {
say "Checking $website...";
my $response = $http.get($website);
if $response<success> {
return "$website: ok", True;
} else {
return "$website: code $response<status>, {$response<content>.decode}", False;
}
}
constant base-uri = "https://api.telegram.org/bot";
my $chat-id = $TelegramSecrets::secret-chat-id; # my private chat-id, got with /getUpdates
my $token = $TelegramSecrets::secret-token; # see Telegram Bot API docs to get it
sub send-message($message, :$notify) {
my %msg = chat_id => $chat-id, text => $message,
disable_notification => not $notify,
link_preview_options => {is_disabled => True};
$http.post: "{base-uri}$token/sendMessage",
:headers(content-type => "application/json")
:content(to-json %msg);
}
my $text = "";
my $was-error = False;
for @websites -> $website {
(my $status, my $ok) = check-website($website);
$text ~= "[ERROR] " if not $ok;
$text ~= $status ~ "\n";
$was-error or= not $ok;
}
$text = "ERROR!\n" ~ $text if $was-error;
send-message($text, :notify($was-error));
Schedule
I will use a standard facility on Arch Linux (same as Server Backup Of OpenBSD) – systemd's timers! I created a directory at /var/check-websites
and put there both files, the script and the secret file:
sudo mkdir /var/check-websites
sudo chown grfork:grfork /var/check-websites
cd /var/check-websites
# copy files here
Then I have a timer that schedules the service to start twice a day – at 9am and at 6pm /etc/systemd/system/backup.timer
:
[Unit]
Description=Check websites availability
[Timer]
OnCalendar=*-*-* 09:00:00
OnCalendar=*-*-* 18:00:00
Persistent=true
RandomizedDelaySec=1m
[Install]
WantedBy=timers.target
And a service that starts the script /etc/systemd/system/backup.service
:
[Unit]
Description=Check websites availability
After=network.target
[Service]
Type=oneshot
ExecStart=/var/check-websites/check-websites.scm
WorkingDirectory=/var/check-websites
User=grfork
Group=grfork
Start and enable the timer:
systemctl start backup.timer
systemctl enable backup.timer
Check that it is present in the list:
systemctl list-timers
Summary
Now I have more confidence in the availability of my websites. I get a notification twice a day that everything is okay, this is important so that I could also react if the script stops working for some reason. And I also get a loud notification if something is broken so I can quickly fix it.
In addition to the initially stated goal this experience opened some doors for me:
-
I can now write Scheme for useful tasks. I initially preferred Raku for such tasks but there's a question of portability and maturity. Raku is a young language with a long history, so I'm a little bit cautious writing scripts that I want to forget about.
-
Now I have a Telegram bot for notifications and a working API part for sending messages in Scheme, I can integrate more notifications there. I also liked the idea of sending silent notifications if everything is fine, to be sure that the scripts are getting run.