diff options
| author | user <user@node5.net> | 2024-01-02 05:16:27 +0100 |
|---|---|---|
| committer | user <user@node5.net> | 2024-01-02 05:16:27 +0100 |
| commit | 937a5571a158115060f062b066543132d8c66752 (patch) | |
| tree | 9ade99f4118dab905197d467b82796beaf44b6c8 | |
initial commit. Ping and http status works + state change
| -rw-r--r-- | .profile | 1 | ||||
| -rw-r--r-- | Makefile | 22 | ||||
| -rw-r--r-- | requirements.txt | 4 | ||||
| -rwxr-xr-x | src/check_server_status.sh | 2 | ||||
| -rw-r--r-- | src/config.template.yml | 29 | ||||
| -rw-r--r-- | src/server_status.py | 164 | ||||
| -rw-r--r-- | unit_files/check-server-status.service | 11 | ||||
| -rw-r--r-- | unit_files/check-server-status.timer | 11 | ||||
| -rw-r--r-- | unit_files/server-status.service | 14 |
9 files changed, 258 insertions, 0 deletions
diff --git a/.profile b/.profile new file mode 100644 index 0000000..f864f82 --- /dev/null +++ b/.profile @@ -0,0 +1 @@ +source /opt/server_status/venv/bin/activate diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..732f06a --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +.PHONY : add_user install_dependencies unit_files setup + +add_user: + sudo useradd server_status -d /opt/server_status -s /bin/bash + +copy_software: + sudo cp -r ./* /opt/server_status + +install_dependencies: + sudo apt install -y python3 python3-pip + sudo -u server_status python3 -m venv venv + sudo -u server_status venv/bin/pip install -r requirements.txt + +unit_files: + sudo ln -s /opt/server_status/unit_files/server-status.service /etc/systemd/system/server-status.service + sudo ln -s /opt/server_status/unit_files/check-server-status.service /etc/systemd/system/check-server-status.service + sudo ln -s /opt/server_status/unit_files/check-server-status.timer /etc/systemd/system/check-server-status.timer + sudo systemctl daemon-reload + sudo systemctl enable --now server-status + sudo systemctl enable --now check-server-status + +setup: add_user install_dependencies unit_files diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..018a8f8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +APScheduler~=3.10 +python-telegram-bot~=20.7 +requests~=2.31 +PyYAML~=6.0 diff --git a/src/check_server_status.sh b/src/check_server_status.sh new file mode 100755 index 0000000..4edbb8d --- /dev/null +++ b/src/check_server_status.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "test" | socat - UNIX-CONNECT:/tmp/server_status.sock diff --git a/src/config.template.yml b/src/config.template.yml new file mode 100644 index 0000000..dbb75da --- /dev/null +++ b/src/config.template.yml @@ -0,0 +1,29 @@ +telegram: + token: "" + chat_id: +hosts: + node5.net: + urls: + - https://node5.net + - https://blog.node5.net/ + - https://git.node5.net/ + - https://ip.node5.net/ + - https://os.node5.net/ + - https://www.node5.net + - https://www.blog.node5.net/ + - https://www.git.node5.net/ + # - https://www.ip.node5.net/ + - https://www.os.node5.net/ + - http://node5.net + - http://blog.node5.net/ + - http://git.node5.net/ + - http://ip.node5.net/ + - http://os.node5.net/ + - http://www.node5.net + - http://www.blog.node5.net/ + - http://www.git.node5.net/ + # - http://www.ip.node5.net/ + - http://www.os.node5.net/ + localhost: + desired_online: False + diff --git a/src/server_status.py b/src/server_status.py new file mode 100644 index 0000000..cba9bdd --- /dev/null +++ b/src/server_status.py @@ -0,0 +1,164 @@ +import telegram # Notifications +import asyncio # Telegram library is async +import requests # HTTP +import os # Unix socket file handling +import subprocess # Ping +import socket # Hostname, unix socket +import yaml # Config file +import logging # Log messages + +logger = logging.getLogger('server_status') +logger.setLevel(level=logging.DEBUG) + +with open("config.yml", "r") as file: + config = yaml.safe_load(file) + +# Set the path for the Unix socket +# without reinitialising entire script +# Used for SystemD timer to invoke status check, +socket_path = "/tmp/server_status.sock" +bot = telegram.Bot(config["telegram"]["token"]) +hostname = socket.gethostname() +# Used to only notify on state change +# Initialise data stucture based on targets in config file +state = {} +for host, host_config in config["hosts"].items(): + state[host] = {"online": None,"open_ports": []} + if 'urls' in host_config: + state[host]["http_status"] = {} + for url in host_config['urls']: + state[host]['http_status'][url] = None + +# Send message from non async function +def send_message(text): + logger.debug(f"Sending message: {text}") + asyncio.run(async_send_message(text)) + +async def async_send_message(text): + async with bot: + await bot.send_message(text=text, chat_id=config["telegram"]["chat_id"], + parse_mode=telegram.constants.ParseMode.MARKDOWN) + +def ping(host: str) -> bool: + response = subprocess.run(["ping", "-c", "1", host]) + return response.returncode == 0 + +def check_target(host: str, host_config: dict): + # Ping + online = ping(host) + # Default to desire host online, if not specified in config file + desired_online = host_config['desired_online'] if 'desired_online' in host_config else True + host_is_desired_state = desired_online == online + first_test = state[host]['online'] == None + changed_state = not first_test and state[host]['online'] != online + logger.debug(f''' + host_is_desired_state: {host_is_desired_state} + first_test: {first_test} + changed_state: {changed_state} + ''') + # notify if the host online state changed, or on fresh boot, if it isn't the desired online state + if changed_state or (first_test and not host_is_desired_state): + message = f"`{host}` is {'online' if online else 'offline'}" + logger.warning(message) + send_message(message) + state[host]['online'] = online # Save current state + + if not online: + return + + messages = [] + for url in host_config['urls']: + http_state = None + # HTTP Code + try: + #print(f''' + #---=== Getting: {url} ===--- + #''') + r = requests.get(url) + if r.status_code != 200: + http_state = r.status_code + except requests.exceptions.SSLError as exception: + try: + reason = exception.args[0].reason.args[0].verify_message + except Exception as exception: + logger.warning("Unable to get reason") + raise + http_state = "SSL error" + except Exception as exception: + raise + if http_state: + if state[host]['http_status'][url] is None or state[host]['http_status'][url] != http_state: + messages.append(f'[{url}]({url}) {http_state}') + state[host]['http_status'][url] = http_state + + + if len(messages) > 0: + send_message('\n'.join(messages)) # NTEINRIETNOIW NOIE#NOIE#N#IEN##I @NUY UYN@YUN@UY@NYU@N YUN@ UYN@YUN@YUN@YU@NUY@NU@NYU@N@UY@UNYUN@ + + +def check_status(): + try: + logger.info("Checking status") + for host, host_config in config["hosts"].items(): + check_target(host, host_config) + except Exception as ex: + send_message("Error getting status") + raise ex + + +def listen(): + # remove the socket file if it already exists + try: + os.unlink(socket_path) + except OSError: + if os.path.exists(socket_path): + raise + + try: + # Create the Unix socket server + server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + + # Bind the socket to the path + server.bind(socket_path) + + # Listen for incoming connections + server.listen(1) + + while True: + try: + # accept connections + logger.info("Server is listening for incoming connections...") + connection, client_address = server.accept() + + logger.debug("Connection from", str(connection).split(", ")[0][-4:]) + + # receive data from the client + while True: + data = connection.recv(1024) + if not data: + break + logger.debug("Received data:", data.decode()) + + # Send a response back to the client + response = "OK\n" + connection.sendall(response.encode()) + + check_status() + finally: + # Always close the connection if it"s been initialised + if "connection" in locals(): + connection.close() + finally: + # remove the socket file + os.unlink(socket_path) + + + +def main(): + # send_message(f"`{hostname}` online") + check_status() + listen() + +if __name__ == "__main__": + asyncio.run(main()) + diff --git a/unit_files/check-server-status.service b/unit_files/check-server-status.service new file mode 100644 index 0000000..5157cd8 --- /dev/null +++ b/unit_files/check-server-status.service @@ -0,0 +1,11 @@ +[Unit] +Description=Check server status +Wants=check-server-status.timer + +[Service] +Type=oneshot +User=server_status +ExecStart=/opt/server_status/src/check_server_status.sh + +[Install] +WantedBy=multi-user.target diff --git a/unit_files/check-server-status.timer b/unit_files/check-server-status.timer new file mode 100644 index 0000000..d7f2c78 --- /dev/null +++ b/unit_files/check-server-status.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Check servers status timer +Requires=check-server-status.service + +[Timer] +User=server_status +Unit=check-server-status.service +OnCalendar=*:0/15 + +[Install] +WantedBy=timers.target
\ No newline at end of file diff --git a/unit_files/server-status.service b/unit_files/server-status.service new file mode 100644 index 0000000..b9402dc --- /dev/null +++ b/unit_files/server-status.service @@ -0,0 +1,14 @@ +[Unit] +Description=Check if servers are online +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +User=server_status +WorkingDirectory=/opt/server_status/src +ExecStart=/opt/server_status/venv/bin/python3 /opt/server_status/src/server_status.py + +[Install] +WantedBy=multi-user.target |
