LinuxMonitor 1.1.1__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)
5
5
  Home-page: https://github.com/QuentinCG/Linux-Monitor-Python-Library
6
6
  Author: Quentin Comte-Gaz
@@ -49,6 +49,7 @@ List of 'check' functionalities:
49
49
  - Check disk usage
50
50
  - Check folder usage
51
51
  - Check websites basic availability (ping)
52
+ - Check websites access with optional authentication (GET request)
52
53
  - Check services status and restart them if needed
53
54
  - Check certificates expiration and validity
54
55
  - Check last user connections IPs
@@ -102,46 +103,49 @@ python3 -m linuxmonitor --start_scheduled_task_show_info --config_file config-ex
102
103
 
103
104
 
104
105
  # View disk space, CPU, RAM, ...
105
- python3 -m linuxmonitor --usage --config_file config-example.json
106
+ python3 -m linuxmonitor --usage --config_file config-example.json --nodebug
106
107
 
107
108
  # View basic system information
108
- python3 -m linuxmonitor --os_infos --config_file config-example.json
109
+ python3 -m linuxmonitor --os_infos --config_file config-example.json --nodebug
109
110
 
110
111
  # View connected users
111
- python3 -m linuxmonitor --users --config_file config-example.json
112
+ python3 -m linuxmonitor --users --config_file config-example.json --nodebug
112
113
 
113
114
  # View last user connections
114
- python3 -m linuxmonitor --user_logins --config_file config-example.json
115
+ python3 -m linuxmonitor --user_logins --config_file config-example.json --nodebug
115
116
 
116
117
  # Check websites (ping)
117
- python3 -m linuxmonitor --ping --config_file config-example.json
118
+ python3 -m linuxmonitor --ping --config_file config-example.json --nodebug
119
+
120
+ # Check websites access with optional authentication (GET request)
121
+ python3 -m linuxmonitor --websites --config_file config-example.json --nodebug
118
122
 
119
123
  # Check SSL certificates
120
- python3 -m linuxmonitor --certificates --config_file config-example.json
124
+ python3 -m linuxmonitor --certificates --config_file config-example.json --nodebug
121
125
 
122
126
  # Check if services are running and restart if down
123
- python3 -m linuxmonitor --services_status --config_file config-example.json
127
+ python3 -m linuxmonitor --services_status --config_file config-example.json --nodebug
124
128
 
125
129
  # List all available services
126
- python3 -m linuxmonitor --list_services --config_file config-example.json
130
+ python3 -m linuxmonitor --list_services --config_file config-example.json --nodebug
127
131
 
128
132
  # Restart all services
129
- python3 -m linuxmonitor --restart_all --config_file config-example.json
133
+ python3 -m linuxmonitor --restart_all --config_file config-example.json --nodebug
130
134
 
131
135
  # Restart a service
132
- python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json
136
+ python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
133
137
 
134
138
  # Check ports
135
- python3 -m linuxmonitor --ports --config_file config-example.json
139
+ python3 -m linuxmonitor --ports --config_file config-example.json --nodebug
136
140
 
137
141
  # List active processes
138
- python3 -m linuxmonitor --list_processes --config_file config-example.json
142
+ python3 -m linuxmonitor --list_processes --config_file config-example.json --nodebug
139
143
 
140
144
  # Stop a process by PID
141
- python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.json
145
+ python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.json --nodebug
142
146
 
143
147
  # Restart the entire server
144
- python3 -m linuxmonitor --reboot_server --config_file config-example.json
148
+ python3 -m linuxmonitor --reboot_server --config_file config-example.json --nodebug
145
149
  ```
146
150
 
147
151
  ## How to use in python script
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)
5
5
  Home-page: https://github.com/QuentinCG/Linux-Monitor-Python-Library
6
6
  Author: Quentin Comte-Gaz
@@ -49,6 +49,7 @@ List of 'check' functionalities:
49
49
  - Check disk usage
50
50
  - Check folder usage
51
51
  - Check websites basic availability (ping)
52
+ - Check websites access with optional authentication (GET request)
52
53
  - Check services status and restart them if needed
53
54
  - Check certificates expiration and validity
54
55
  - Check last user connections IPs
@@ -102,46 +103,49 @@ python3 -m linuxmonitor --start_scheduled_task_show_info --config_file config-ex
102
103
 
103
104
 
104
105
  # View disk space, CPU, RAM, ...
105
- python3 -m linuxmonitor --usage --config_file config-example.json
106
+ python3 -m linuxmonitor --usage --config_file config-example.json --nodebug
106
107
 
107
108
  # View basic system information
108
- python3 -m linuxmonitor --os_infos --config_file config-example.json
109
+ python3 -m linuxmonitor --os_infos --config_file config-example.json --nodebug
109
110
 
110
111
  # View connected users
111
- python3 -m linuxmonitor --users --config_file config-example.json
112
+ python3 -m linuxmonitor --users --config_file config-example.json --nodebug
112
113
 
113
114
  # View last user connections
114
- python3 -m linuxmonitor --user_logins --config_file config-example.json
115
+ python3 -m linuxmonitor --user_logins --config_file config-example.json --nodebug
115
116
 
116
117
  # Check websites (ping)
117
- python3 -m linuxmonitor --ping --config_file config-example.json
118
+ python3 -m linuxmonitor --ping --config_file config-example.json --nodebug
119
+
120
+ # Check websites access with optional authentication (GET request)
121
+ python3 -m linuxmonitor --websites --config_file config-example.json --nodebug
118
122
 
119
123
  # Check SSL certificates
120
- python3 -m linuxmonitor --certificates --config_file config-example.json
124
+ python3 -m linuxmonitor --certificates --config_file config-example.json --nodebug
121
125
 
122
126
  # Check if services are running and restart if down
123
- python3 -m linuxmonitor --services_status --config_file config-example.json
127
+ python3 -m linuxmonitor --services_status --config_file config-example.json --nodebug
124
128
 
125
129
  # List all available services
126
- python3 -m linuxmonitor --list_services --config_file config-example.json
130
+ python3 -m linuxmonitor --list_services --config_file config-example.json --nodebug
127
131
 
128
132
  # Restart all services
129
- python3 -m linuxmonitor --restart_all --config_file config-example.json
133
+ python3 -m linuxmonitor --restart_all --config_file config-example.json --nodebug
130
134
 
131
135
  # Restart a service
132
- python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json
136
+ python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
133
137
 
134
138
  # Check ports
135
- python3 -m linuxmonitor --ports --config_file config-example.json
139
+ python3 -m linuxmonitor --ports --config_file config-example.json --nodebug
136
140
 
137
141
  # List active processes
138
- python3 -m linuxmonitor --list_processes --config_file config-example.json
142
+ python3 -m linuxmonitor --list_processes --config_file config-example.json --nodebug
139
143
 
140
144
  # Stop a process by PID
141
- python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.json
145
+ python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.json --nodebug
142
146
 
143
147
  # Restart the entire server
144
- python3 -m linuxmonitor --reboot_server --config_file config-example.json
148
+ python3 -m linuxmonitor --reboot_server --config_file config-example.json --nodebug
145
149
  ```
146
150
 
147
151
  ## How to use in python script
@@ -21,6 +21,7 @@ List of 'check' functionalities:
21
21
  - Check disk usage
22
22
  - Check folder usage
23
23
  - Check websites basic availability (ping)
24
+ - Check websites access with optional authentication (GET request)
24
25
  - Check services status and restart them if needed
25
26
  - Check certificates expiration and validity
26
27
  - Check last user connections IPs
@@ -74,46 +75,49 @@ python3 -m linuxmonitor --start_scheduled_task_show_info --config_file config-ex
74
75
 
75
76
 
76
77
  # View disk space, CPU, RAM, ...
77
- python3 -m linuxmonitor --usage --config_file config-example.json
78
+ python3 -m linuxmonitor --usage --config_file config-example.json --nodebug
78
79
 
79
80
  # View basic system information
80
- python3 -m linuxmonitor --os_infos --config_file config-example.json
81
+ python3 -m linuxmonitor --os_infos --config_file config-example.json --nodebug
81
82
 
82
83
  # View connected users
83
- python3 -m linuxmonitor --users --config_file config-example.json
84
+ python3 -m linuxmonitor --users --config_file config-example.json --nodebug
84
85
 
85
86
  # View last user connections
86
- python3 -m linuxmonitor --user_logins --config_file config-example.json
87
+ python3 -m linuxmonitor --user_logins --config_file config-example.json --nodebug
87
88
 
88
89
  # Check websites (ping)
89
- python3 -m linuxmonitor --ping --config_file config-example.json
90
+ python3 -m linuxmonitor --ping --config_file config-example.json --nodebug
91
+
92
+ # Check websites access with optional authentication (GET request)
93
+ python3 -m linuxmonitor --websites --config_file config-example.json --nodebug
90
94
 
91
95
  # Check SSL certificates
92
- python3 -m linuxmonitor --certificates --config_file config-example.json
96
+ python3 -m linuxmonitor --certificates --config_file config-example.json --nodebug
93
97
 
94
98
  # Check if services are running and restart if down
95
- python3 -m linuxmonitor --services_status --config_file config-example.json
99
+ python3 -m linuxmonitor --services_status --config_file config-example.json --nodebug
96
100
 
97
101
  # List all available services
98
- python3 -m linuxmonitor --list_services --config_file config-example.json
102
+ python3 -m linuxmonitor --list_services --config_file config-example.json --nodebug
99
103
 
100
104
  # Restart all services
101
- python3 -m linuxmonitor --restart_all --config_file config-example.json
105
+ python3 -m linuxmonitor --restart_all --config_file config-example.json --nodebug
102
106
 
103
107
  # Restart a service
104
- python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json
108
+ python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
105
109
 
106
110
  # Check ports
107
- python3 -m linuxmonitor --ports --config_file config-example.json
111
+ python3 -m linuxmonitor --ports --config_file config-example.json --nodebug
108
112
 
109
113
  # List active processes
110
- python3 -m linuxmonitor --list_processes --config_file config-example.json
114
+ python3 -m linuxmonitor --list_processes --config_file config-example.json --nodebug
111
115
 
112
116
  # Stop a process by PID
113
- python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.json
117
+ python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.json --nodebug
114
118
 
115
119
  # Restart the entire server
116
- python3 -m linuxmonitor --reboot_server --config_file config-example.json
120
+ python3 -m linuxmonitor --reboot_server --config_file config-example.json --nodebug
117
121
  ```
118
122
 
119
123
  ## How to use in python script
@@ -17,6 +17,7 @@ def main() -> None:
17
17
  parser.add_argument('--users', action='store_true', help='👥 View connected users 👥')
18
18
  parser.add_argument('--user_logins', action='store_true', help='👥 View last user connections 👥')
19
19
  parser.add_argument('--ping', action='store_true', help='🌐 Ping websites 🌐')
20
+ parser.add_argument('--websites', action='store_true', help='🌐 Check websites access (GET requests) 🌐')
20
21
  parser.add_argument('--certificates', action='store_true', help='🔒 Check SSL certificates 🔒')
21
22
  parser.add_argument('--reboot_server', action='store_true', help='🔄 Restart the entire server 🔄')
22
23
  parser.add_argument('--services_status', action='store_true', help='🩺 Check services are running 🩺')
@@ -98,7 +99,13 @@ def main() -> None:
98
99
  if args.ping:
99
100
  handled = True
100
101
  print("Pinging websites...")
101
- out_msg: str = monitoring.ping_all_websites(is_private=True, display_only_if_critical=False)
102
+ out_msg: str = asyncio.run(monitoring.ping_all_websites(is_private=True, display_only_if_critical=False))
103
+ print(out_msg)
104
+
105
+ if args.websites:
106
+ handled = True
107
+ print("Checking websites availability...")
108
+ out_msg: str = asyncio.run(monitoring.check_all_websites(is_private=True, display_only_if_critical=False))
102
109
  print(out_msg)
103
110
 
104
111
  if args.certificates:
@@ -110,24 +117,24 @@ def main() -> None:
110
117
  if args.reboot_server:
111
118
  handled = True
112
119
  print("Restarting the entire server...")
113
- out_msg: str = monitoring.reboot_server()
120
+ out_msg: str = asyncio.run(monitoring.reboot_server())
114
121
  print(out_msg)
115
122
 
116
123
  if args.services_status:
117
124
  handled = True
118
125
  print("Checking if services are running and restart if down...")
119
- out_msg: str = monitoring.check_all_services_status_and_restart_if_down(is_private=True)
126
+ out_msg: str = asyncio.run(monitoring.check_all_services_status_and_restart_if_down(is_private=True))
120
127
  print(out_msg)
121
128
 
122
129
  if args.restart_all:
123
130
  handled = True
124
131
  print("Restarting all services...")
125
- out_msg: str = monitoring.restart_all_services(is_private=True)
132
+ out_msg: str = asyncio.run(monitoring.restart_all_services(is_private=True))
126
133
 
127
134
  if args.restart_service is not None:
128
135
  handled = True
129
136
  print(f"Restarting service: {args.restart_service}...")
130
- out_msg: str = monitoring.restart_service(is_private=True, service_name=args.restart_service)
137
+ out_msg: str = asyncio.run(monitoring.restart_service(is_private=True, service_name=args.restart_service))
131
138
  print(out_msg)
132
139
 
133
140
  if args.list_services:
@@ -139,7 +146,7 @@ def main() -> None:
139
146
  if args.ports:
140
147
  handled = True
141
148
  print("Checking ports...")
142
- out_msg: str = monitoring.check_all_ports(is_private=True, display_only_if_critical=False, restart_if_down=False)
149
+ out_msg: str = asyncio.run(monitoring.check_all_ports(is_private=True, display_only_if_critical=False, restart_if_down=False))
143
150
  print(out_msg)
144
151
 
145
152
  if args.list_processes:
@@ -151,7 +158,7 @@ def main() -> None:
151
158
  if args.kill_process is not None:
152
159
  handled = True
153
160
  print(f"Stopping process with PID: {args.kill_process}...")
154
- out_msg: str = monitoring.kill_process(pid=args.kill_process)
161
+ out_msg: str = asyncio.run(monitoring.kill_process(pid=args.kill_process))
155
162
  print(out_msg)
156
163
 
157
164
  if args.start_scheduled_task_check_for_issues:
@@ -10,6 +10,7 @@ Non exhaustive list of features (available by using it in shell or in python scr
10
10
  - Check disk usage
11
11
  - Check folder usage
12
12
  - Check websites basic availability (ping)
13
+ - Check websites access with optional authentication (GET request)
13
14
  - Check services status and restart them if needed
14
15
  - Check certificates expiration and validity
15
16
  - Check last user connections IPs
@@ -29,7 +30,7 @@ __email__ = "quentin@comte-gaz.com"
29
30
  __license__ = "MIT License"
30
31
  __copyright__ = "Copyright Quentin Comte-Gaz (2024)"
31
32
  __python_version__ = "3.+"
32
- __version__ = "1.1.1 (2024/09/14)"
33
+ __version__ = "1.2.0 (2024/09/16)"
33
34
  __status__ = "Usable for any Linux project"
34
35
 
35
36
  import json
@@ -46,6 +47,8 @@ import platform
46
47
  import re
47
48
  import logging
48
49
  import asyncio
50
+ from http.client import responses
51
+ import aiohttp
49
52
 
50
53
  class LinuxMonitor:
51
54
  def __init__(self, config_file: str, allow_scheduled_tasks_check_for_issues: bool, allow_scheduled_task_show_info: bool) -> None:
@@ -92,6 +95,8 @@ class LinuxMonitor:
92
95
  raise ValueError("The configuration must contain the 'services' key")
93
96
  if 'certificates' not in self.config: # type: ignore
94
97
  raise ValueError("The configuration must contain the 'certificates' key")
98
+ if 'websites' not in self.config: # type: ignore
99
+ raise ValueError("The configuration must contain the 'websites' key")
95
100
 
96
101
  # Get the basic configuration (check if it is a dictionary)
97
102
  basic_config: Dict[str, Any] = self.config.get('basic_config', {}) # type: ignore
@@ -174,9 +179,9 @@ class LinuxMonitor:
174
179
 
175
180
  #region Execute Command
176
181
 
177
- def execute_and_verify(self, command: List[str], display_name: str, timeout_in_sec: Optional[int] = None, display_only_if_critical: bool = False, check_also_stdout_not_containing: Optional[str] = None) -> Tuple[Optional[bool], str]:
182
+ async def execute_and_verify(self, command: List[str], display_name: str, timeout_in_sec: Optional[int] = None, display_only_if_critical: bool = False, check_also_stdout_not_containing: Optional[str] = None) -> Tuple[Optional[bool], str]:
178
183
  """
179
- Execute a shell command and verify its correct execution.
184
+ Execute a shell command asynchronously and verify its correct execution.
180
185
 
181
186
  :param command: A list of strings representing the command and its arguments.
182
187
  :param display_name: The name of the command to display in the output message.
@@ -186,18 +191,24 @@ class LinuxMonitor:
186
191
 
187
192
  :return: A tuple containing a boolean indicating if the command was executed successfully and a string containing the result message.
188
193
  """
189
- returncode: int = -1
194
+ returncode: Optional[int] = None
190
195
 
191
196
  try:
192
197
  logging.debug(msg=f"Executing command {display_name} (command: {command})...")
193
- pipe = subprocess.Popen(args=command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
198
+ process = await asyncio.create_subprocess_exec(
199
+ *command,
200
+ stdout=asyncio.subprocess.PIPE,
201
+ stderr=asyncio.subprocess.PIPE,
202
+ shell=False
203
+ )
204
+
194
205
  try:
195
- stdout, stderr = pipe.communicate(timeout=timeout_in_sec)
196
- returncode = pipe.returncode
197
- except subprocess.TimeoutExpired:
206
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout_in_sec)
207
+ returncode = process.returncode
208
+ except asyncio.TimeoutError:
198
209
  logging.warning(msg=f"Timeout expired for {display_name} (command: {command})")
199
- pipe.kill()
200
- stdout, stderr = pipe.communicate()
210
+ process.kill()
211
+ return None, f"⚠️ **Error {display_name}**:\n- Failed to execute the command in less than {timeout_in_sec} seconds)"
201
212
  except Exception as e:
202
213
  # Failed to execute the command
203
214
  out_msg = f"⚠️ **Error {display_name}**:\n```sh\n{e}\n```"
@@ -209,7 +220,7 @@ class LinuxMonitor:
209
220
  stderr_str: str = stderr.decode(encoding='utf-8').replace('`', '"').strip()
210
221
 
211
222
  # Check if the command executed successfully
212
- res: Optional[bool] = (returncode == 0) if (returncode != -1) else None
223
+ res: Optional[bool] = (returncode == 0) if (returncode != None) else None
213
224
  if res and check_also_stdout_not_containing:
214
225
  res = check_also_stdout_not_containing not in stdout_str
215
226
 
@@ -244,7 +255,7 @@ class LinuxMonitor:
244
255
 
245
256
  #region Reboot
246
257
 
247
- def reboot_server(self) -> str:
258
+ async def reboot_server(self) -> str:
248
259
  """
249
260
  Reboot the server.
250
261
 
@@ -253,7 +264,7 @@ class LinuxMonitor:
253
264
  IMPORTANT: To be usable, the user must have the right to execute `sudo /sbin/reboot` command without password.
254
265
  """
255
266
  logging.info(msg="Rebooting the server...")
256
- _, out_msg = self.execute_and_verify(command=["sudo", "/sbin/reboot"], display_name="Server reboot", timeout_in_sec=None, display_only_if_critical=False)
267
+ _, out_msg = await self.execute_and_verify(command=["sudo", "/sbin/reboot"], display_name="Server reboot", timeout_in_sec=None, display_only_if_critical=False)
257
268
  return out_msg
258
269
 
259
270
  #endregion
@@ -721,7 +732,7 @@ class LinuxMonitor:
721
732
 
722
733
  #region Ping Websites
723
734
 
724
- def _ping_website(self, website: str, display_name: str, timeout_in_sec: int, display_only_if_critical: bool=False) -> str:
735
+ async def _ping_website(self, website: str, display_name: str, timeout_in_sec: int, display_only_if_critical: bool=False) -> str:
725
736
  """
726
737
  Ping a website.
727
738
 
@@ -731,21 +742,26 @@ class LinuxMonitor:
731
742
 
732
743
  :return: A string containing the result message.
733
744
  """
734
- ping_command: list[str] = ["ping", "-c", "1", website]
735
- display_name = f"[{display_name}](https://{website})"
745
+ try:
746
+ ping_command: list[str] = ["ping", "-c", "1", website]
747
+ display_name = f"[{display_name}](https://{website})"
736
748
 
737
- start_time: float = time.time()
738
- res, out_msg = self.execute_and_verify(command=ping_command, display_name=f"ping {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=display_only_if_critical)
739
- end_time: float = time.time()
749
+ start_time: float = time.time()
750
+ res, out_msg = await self.execute_and_verify(command=ping_command, display_name=f"ping {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=display_only_if_critical)
751
+ end_time: float = time.time()
740
752
 
741
- if res == True and not display_only_if_critical:
742
- res_ping_sec: str = "{:.2f}sec".format(end_time - start_time)
743
- out_msg: str = f"✅ **{display_name} answered in {res_ping_sec}**."
744
- logging.info(msg=out_msg)
753
+ if res == True and not display_only_if_critical:
754
+ res_ping_sec: str = "{:.2f}sec".format(end_time - start_time)
755
+ out_msg: str = f"✅ **{display_name} answered in {res_ping_sec}**."
756
+ logging.info(msg=out_msg)
745
757
 
746
- return out_msg
758
+ return out_msg
759
+ except Exception as e:
760
+ out_msg = f"⚠️ **Error pinging {display_name}**:\n```sh\n{e}\n```"
761
+ logging.exception(msg=out_msg)
762
+ return out_msg
747
763
 
748
- def ping_all_websites(self, is_private: bool, display_only_if_critical: bool=False) -> str:
764
+ async def ping_all_websites(self, is_private: bool, display_only_if_critical: bool=False) -> str:
749
765
  """
750
766
  Ping all websites configured in the JSON configuration file.
751
767
 
@@ -758,7 +774,7 @@ class LinuxMonitor:
758
774
  for ping_config in self.config['pings']:
759
775
  if is_private or is_private == ping_config['is_private']:
760
776
  timeout_in_sec: int = ping_config.get('timeout_in_sec', 5)
761
- result: str = self._ping_website(website=ping_config['website'], display_name=ping_config['display_name'], timeout_in_sec=timeout_in_sec, display_only_if_critical=display_only_if_critical)
777
+ result: str = await self._ping_website(website=ping_config['website'], display_name=ping_config['display_name'], timeout_in_sec=timeout_in_sec, display_only_if_critical=display_only_if_critical)
762
778
  if result:
763
779
  if out_msg:
764
780
  out_msg += "\n"
@@ -771,6 +787,87 @@ class LinuxMonitor:
771
787
 
772
788
  #endregion
773
789
 
790
+ #region Check website response
791
+
792
+ async def _check_website(self, url: str, display_name: str, timeout_in_sec: int = 5, auth_type: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None, token: Optional[str] = None, additional_allowed_statuses: List[int] = [], display_only_if_critical: bool=False) -> Tuple[bool, str]:
793
+ try:
794
+ display_name = f"[{display_name}]({url})"
795
+
796
+ auth = None
797
+ headers: Dict[str, str] = {}
798
+
799
+ # Select the appropriate authentication method
800
+ if auth_type == 'basic' and username and password:
801
+ auth = aiohttp.BasicAuth(username, password) # Use aiohttp.BasicAuth for basic authentication
802
+ elif auth_type == 'digest' and username and password:
803
+ return False, f"❌ **Digest authentication not supported, cannot check {display_name}**."
804
+ elif auth_type == 'bearer' and token:
805
+ headers = {'Authorization': f'Bearer {token}'}
806
+
807
+ # Create an aiohttp client session to make the request
808
+ async with aiohttp.ClientSession() as session:
809
+ async with session.get(url, auth=auth, headers=headers, timeout=timeout_in_sec) as response:
810
+ # Default allowed status codes (200-299)
811
+ allowed_statuses = list(range(200, 300))
812
+
813
+ # Include any additional allowed status codes if provided
814
+ if additional_allowed_statuses:
815
+ allowed_statuses += additional_allowed_statuses
816
+
817
+ # Check if the status code is within the allowed list
818
+ if response.status in allowed_statuses:
819
+ out_msg: str = f"✅ **{display_name} answered with valid status code {response.status}**."
820
+ logging.info(msg=out_msg)
821
+ return True, out_msg
822
+ else:
823
+ # Get the reason phrase (e.g., "Unauthorized" for 401)
824
+ status_reason: str = responses.get(response.status, "Unknown status")
825
+ out_msg = f"❌ **{display_name} answered with invalid status code {response.status} - {status_reason}**."
826
+ logging.warning(msg=out_msg)
827
+ return False, out_msg
828
+
829
+ except asyncio.TimeoutError:
830
+ out_msg = f"⚠️ **Error checking {display_name}: Timeout**"
831
+ return False, out_msg
832
+ except Exception as e:
833
+ out_msg = f"⚠️ **Error checking {display_name}**:\n```sh\n{e}\n```"
834
+ return False, out_msg
835
+
836
+ async def check_all_websites(self, is_private: bool, display_only_if_critical: bool=False) -> str:
837
+ try:
838
+ out_msg: str = ""
839
+ for website_config in self.config['websites']:
840
+ if is_private or is_private == website_config['is_private']:
841
+ timeout_in_sec: int = website_config.get('timeout_in_sec', 5)
842
+ url: str = website_config['url']
843
+ display_name: str = website_config.get('display_name', url)
844
+ auth_type: Optional[str] = website_config.get('auth_type', None)
845
+ username: Optional[str] = website_config.get('username', None)
846
+ password: Optional[str] = website_config.get('password', None)
847
+ token: Optional[str] = website_config.get('token', None)
848
+ additional_allowed_statuses: List[int] = website_config.get('additional_allowed_statuses', [])
849
+
850
+ _, msg = await self._check_website(url=url, display_name=display_name, timeout_in_sec=timeout_in_sec,
851
+ auth_type=auth_type, username=username, password=password, token=token,
852
+ additional_allowed_statuses=additional_allowed_statuses,
853
+ display_only_if_critical=display_only_if_critical)
854
+
855
+ if msg != "":
856
+ if out_msg:
857
+ out_msg += "\n"
858
+ out_msg += f"- {msg}"
859
+
860
+ if out_msg != "":
861
+ out_msg = f"# 🌐 Website access state 🌐\n{out_msg}"
862
+
863
+ return out_msg
864
+ except Exception as e:
865
+ out_msg = f"⚠️ **Error checking website access states**:\n```sh\n{e}\n```"
866
+ logging.exception(msg=out_msg)
867
+ return out_msg
868
+
869
+ #endregion
870
+
774
871
  #region Services
775
872
 
776
873
  def get_all_services_allowed_to_restart(self, is_private: bool) -> str:
@@ -801,7 +898,7 @@ class LinuxMonitor:
801
898
  logging.info(msg=out_msg)
802
899
  return out_msg
803
900
 
804
- def restart_service(self, is_private: bool, service_name: str) -> str:
901
+ async def restart_service(self, is_private: bool, service_name: str) -> str:
805
902
  """
806
903
  Restart a specific service.
807
904
 
@@ -810,42 +907,47 @@ class LinuxMonitor:
810
907
 
811
908
  :return: A string containing the result message.
812
909
  """
813
- if service_name not in self.config['services'].keys():
814
- out_msg = f"❌ **Service {service_name} not found**."
815
- logging.error(msg=out_msg)
816
- return out_msg
910
+ try:
911
+ if service_name not in self.config['services'].keys():
912
+ out_msg = f"❌ **Service {service_name} not found**."
913
+ logging.error(msg=out_msg)
914
+ return out_msg
817
915
 
818
- service = self.config['services'][service_name]
819
- if not is_private and is_private != service['is_private']:
820
- out_msg = f"❌ **Service {service_name} not authorized to restart in public**."
821
- logging.error(msg=out_msg)
822
- return out_msg
916
+ service = self.config['services'][service_name]
917
+ if not is_private and is_private != service['is_private']:
918
+ out_msg = f"❌ **Service {service_name} not authorized to restart in public**."
919
+ logging.error(msg=out_msg)
920
+ return out_msg
823
921
 
824
- display_name: str = service.get('display_name', service_name)
825
- if 'restart_command' not in service:
826
- out_msg = f"❌ **Restart command not found for {display_name}**."
827
- logging.error(msg=out_msg)
828
- return out_msg
922
+ display_name: str = service.get('display_name', service_name)
923
+ if 'restart_command' not in service:
924
+ out_msg = f"❌ **Restart command not found for {display_name}**."
925
+ logging.error(msg=out_msg)
926
+ return out_msg
829
927
 
830
- timeout_in_sec: int = service.get('timeout_in_sec', 90)
928
+ timeout_in_sec: int = service.get('timeout_in_sec', 90)
831
929
 
832
- service_call: List[str] = service['restart_command']
833
- out_msg: str = ""
930
+ service_call: List[str] = service['restart_command']
931
+ out_msg: str = ""
834
932
 
835
- logging.info(f"Trying to restart {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
933
+ logging.info(f"Trying to restart {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
836
934
 
837
- start_time: float = time.time()
838
- res, out_msg = self.execute_and_verify(command=service_call, display_name=f"restart {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
839
- end_time: float = time.time()
935
+ start_time: float = time.time()
936
+ res, out_msg = await self.execute_and_verify(command=service_call, display_name=f"restart {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
937
+ end_time: float = time.time()
840
938
 
841
- if res == True:
842
- readable_duration: str = "{:.2f}".format(end_time - start_time)
843
- out_msg = f"✅ **{display_name} restarted with success** in {readable_duration}sec."
844
- logging.info(msg=out_msg)
939
+ if res == True:
940
+ readable_duration: str = "{:.2f}".format(end_time - start_time)
941
+ out_msg = f"✅ **{display_name} restarted with success** in {readable_duration}sec."
942
+ logging.info(msg=out_msg)
845
943
 
846
- return out_msg
944
+ return out_msg
945
+ except Exception as e:
946
+ out_msg = f"⚠️ **Error restarting service {service_name}**:\n```sh\n{e}\n```"
947
+ logging.exception(msg=out_msg)
948
+ return out_msg
847
949
 
848
- def restart_all_services(self, is_private: bool) -> str:
950
+ async def restart_all_services(self, is_private: bool) -> str:
849
951
  """
850
952
  Restart all services allowed to restart which are configured in the JSON configuration file.
851
953
 
@@ -853,21 +955,26 @@ class LinuxMonitor:
853
955
 
854
956
  :return: A string containing the result message.
855
957
  """
856
- out_msg: str = ""
857
- for service_name in self.config["services"].keys():
858
- if is_private or self.config["services"][service_name]['is_private'] == is_private:
859
- res: str = self.restart_service(is_private=is_private, service_name=service_name)
860
- if res != "":
861
- if out_msg:
862
- out_msg += "\n"
863
- out_msg += f"- {res}"
958
+ try:
959
+ out_msg: str = ""
960
+ for service_name in self.config["services"].keys():
961
+ if is_private or self.config["services"][service_name]['is_private'] == is_private:
962
+ res: str = await self.restart_service(is_private=is_private, service_name=service_name)
963
+ if res != "":
964
+ if out_msg:
965
+ out_msg += "\n"
966
+ out_msg += f"- {res}"
864
967
 
865
- if out_msg:
866
- out_msg = f"# 📱 Restart services 📱\n{out_msg}"
968
+ if out_msg:
969
+ out_msg = f"# 📱 Restart services 📱\n{out_msg}"
867
970
 
868
- return out_msg
971
+ return out_msg
972
+ except Exception as e:
973
+ out_msg = f"⚠️ **Error restarting services**:\n```sh\n{e}\n```"
974
+ logging.exception(msg=out_msg)
975
+ return out_msg
869
976
 
870
- def _get_service_status(self, is_private: bool, service_name: str) -> Tuple[Optional[bool], str]:
977
+ async def _get_service_status(self, is_private: bool, service_name: str) -> Tuple[Optional[bool], str]:
871
978
  """
872
979
  Get the status of a specific service.
873
980
 
@@ -876,25 +983,30 @@ class LinuxMonitor:
876
983
 
877
984
  :return: A tuple containing the status of the service (True for active, False for inactive, None for error) and a string containing the result message.
878
985
  """
879
- if service_name not in self.config["services"].keys():
880
- logging.error(msg=f"Service {service_name} not found")
881
- return None, ""
986
+ try:
987
+ if service_name not in self.config["services"].keys():
988
+ logging.error(msg=f"Service {service_name} not found")
989
+ return None, ""
882
990
 
883
- service = self.config["services"][service_name]
884
- display_name = service.get('display_name', service_name)
885
- timeout_in_sec = service.get('timeout_in_sec', 30)
991
+ service = self.config["services"][service_name]
992
+ display_name = service.get('display_name', service_name)
993
+ timeout_in_sec = service.get('timeout_in_sec', 30)
886
994
 
887
- if 'status_command' not in service:
888
- logging.error(msg=f"Status command (status_command) not found for service {service_name}")
889
- return None, ""
995
+ if 'status_command' not in service:
996
+ logging.error(msg=f"Status command (status_command) not found for service {service_name}")
997
+ return None, ""
890
998
 
891
- status_command = service['status_command']
892
- check_also_stdout_not_containing = service.get('check_also_stdout_not_containing', None)
999
+ status_command = service['status_command']
1000
+ check_also_stdout_not_containing = service.get('check_also_stdout_not_containing', None)
893
1001
 
894
- res, out_msg = self.execute_and_verify(command=status_command, display_name=f"état de {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=False, check_also_stdout_not_containing=check_also_stdout_not_containing)
895
- return res, out_msg
1002
+ res, out_msg = await self.execute_and_verify(command=status_command, display_name=f"état de {display_name}", timeout_in_sec=timeout_in_sec, display_only_if_critical=False, check_also_stdout_not_containing=check_also_stdout_not_containing)
1003
+ return res, out_msg
1004
+ except Exception as e:
1005
+ out_msg = f"⚠️ **Error checking status of service {service_name}**:\n```sh\n{e}\n```"
1006
+ logging.exception(msg=out_msg)
1007
+ return None, out_msg
896
1008
 
897
- def check_all_services_status(self, is_private: bool) -> str:
1009
+ async def check_all_services_status(self, is_private: bool) -> str:
898
1010
  """
899
1011
  Check the status of all services configured in the JSON configuration file.
900
1012
 
@@ -923,7 +1035,7 @@ class LinuxMonitor:
923
1035
 
924
1036
  for service_name in self.config["services"].keys():
925
1037
  if is_private or self.config["services"][service_name]['is_private'] == is_private:
926
- status, status_msg = self._get_service_status(is_private=is_private, service_name=service_name)
1038
+ status, status_msg = await self._get_service_status(is_private=is_private, service_name=service_name)
927
1039
  service_status: str = status_to_string(res=status)
928
1040
  service_icon: str = status_to_icon(res=status)
929
1041
  if out_msg != "":
@@ -937,7 +1049,7 @@ class LinuxMonitor:
937
1049
 
938
1050
  return out_msg
939
1051
 
940
- def check_all_services_status_and_restart_if_down(self, is_private: bool) -> str:
1052
+ async def check_all_services_status_and_restart_if_down(self, is_private: bool) -> str:
941
1053
  """
942
1054
  Check the status of all services configured in the JSON configuration file and restart them if they are inactive.
943
1055
 
@@ -949,7 +1061,7 @@ class LinuxMonitor:
949
1061
  try:
950
1062
  for service_name in self.config["services"].keys():
951
1063
  if is_private or self.config["services"][service_name]['is_private'] == is_private:
952
- status, status_msg = self._get_service_status(is_private=is_private, service_name=service_name)
1064
+ status, status_msg = await self._get_service_status(is_private=is_private, service_name=service_name)
953
1065
  if status is False:
954
1066
  out_msg: str = f"❌ **{self.config['services'][service_name]['display_name']} inactive**. Restarting the service is necessary."
955
1067
  logging.warning(msg=out_msg)
@@ -957,7 +1069,7 @@ class LinuxMonitor:
957
1069
  out_msg += f"\n{status_msg}"
958
1070
 
959
1071
  out_msg_full += out_msg + "\n"
960
- out_msg_full += self.restart_service(is_private=is_private, service_name=service_name) + "\n"
1072
+ out_msg_full += await self.restart_service(is_private=is_private, service_name=service_name) + "\n"
961
1073
  elif status is None:
962
1074
  out_msg: str = f"⚠️ **Error checking status of {self.config['services'][service_name]['display_name']}** (not restarting it)."
963
1075
  logging.error(msg=out_msg)
@@ -1371,7 +1483,7 @@ class LinuxMonitor:
1371
1483
 
1372
1484
  return res, out_msg
1373
1485
 
1374
- def check_all_ports(self, is_private: bool, display_only_if_critical: bool=False, restart_if_down: bool=False) -> str:
1486
+ async def check_all_ports(self, is_private: bool, display_only_if_critical: bool=False, restart_if_down: bool=False) -> str:
1375
1487
  """
1376
1488
  Check all ports configured in the JSON configuration file (and restart the service if the port is down and service_name_to_restart added in config file).
1377
1489
 
@@ -1405,7 +1517,7 @@ class LinuxMonitor:
1405
1517
  if out_msg != "":
1406
1518
  out_msg += "\n"
1407
1519
 
1408
- restart_res: str = self.restart_service(is_private=is_private, service_name=service_name_to_restart)
1520
+ restart_res: str = await self.restart_service(is_private=is_private, service_name=service_name_to_restart)
1409
1521
  out_msg += f" - {restart_res}"
1410
1522
 
1411
1523
  if out_msg != "":
@@ -1427,64 +1539,69 @@ class LinuxMonitor:
1427
1539
 
1428
1540
  :return: A string containing the result message.
1429
1541
  """
1430
- processes = []
1431
- for process in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent', 'memory_info', 'create_time', 'cmdline']):
1432
- try:
1433
- create_time = datetime.fromtimestamp(process.info['create_time']).strftime("%Y-%m-%d %H:%M:%S")
1542
+ try:
1543
+ processes = []
1544
+ for process in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent', 'memory_info', 'create_time', 'cmdline']):
1545
+ try:
1546
+ create_time = datetime.fromtimestamp(process.info['create_time']).strftime("%Y-%m-%d %H:%M:%S")
1434
1547
 
1435
- cmdline = ' '.join(process.info['cmdline']) # Join the command line arguments
1436
- # Remove extra spaces
1437
- cmdline = re.sub(r'\s+', ' ', cmdline).strip()
1438
- if len(cmdline) > 60:
1439
- cmdline = cmdline[:60] + '...' # Truncate and add ellipsis
1548
+ cmdline = ' '.join(process.info['cmdline']) # Join the command line arguments
1549
+ # Remove extra spaces
1550
+ cmdline = re.sub(r'\s+', ' ', cmdline).strip()
1551
+ if len(cmdline) > 60:
1552
+ cmdline = cmdline[:60] + '...' # Truncate and add ellipsis
1440
1553
 
1441
- processes.append({ # type: ignore
1442
- 'pid': process.info['pid'],
1443
- 'name': process.info['name'],
1444
- 'username': process.info['username'],
1445
- 'cpu_percent': process.info['cpu_percent'],
1446
- 'memory': process.info['memory_info'].rss, # Resident Set Size (RSS) memory
1447
- 'create_time': create_time,
1448
- 'cmdline': cmdline
1449
- })
1450
- except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
1451
- pass
1554
+ processes.append({ # type: ignore
1555
+ 'pid': process.info['pid'],
1556
+ 'name': process.info['name'],
1557
+ 'username': process.info['username'],
1558
+ 'cpu_percent': process.info['cpu_percent'],
1559
+ 'memory': process.info['memory_info'].rss, # Resident Set Size (RSS) memory
1560
+ 'create_time': create_time,
1561
+ 'cmdline': cmdline
1562
+ })
1563
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
1564
+ pass
1452
1565
 
1453
- # Sort processes by memory usage, then by CPU usage, both in descending order
1454
- processes.sort(key=lambda proc: (proc['memory'], proc['cpu_percent'], proc['create_time']), reverse=True) # type: ignore
1566
+ # Sort processes by memory usage, then by CPU usage, both in descending order
1567
+ processes.sort(key=lambda proc: (proc['memory'], proc['cpu_percent'], proc['create_time']), reverse=True) # type: ignore
1455
1568
 
1456
- full_res: str = ""
1457
- for proc in processes: # type: ignore
1458
- if not get_non_consuming_processes and proc['cpu_percent'] == 0 and proc['memory'] == 0:
1459
- continue
1569
+ full_res: str = ""
1570
+ for proc in processes: # type: ignore
1571
+ if not get_non_consuming_processes and proc['cpu_percent'] == 0 and proc['memory'] == 0:
1572
+ continue
1460
1573
 
1461
- res = f"- PID **{proc['pid']}**: {proc['name']} ("
1574
+ res = f"- PID **{proc['pid']}**: {proc['name']} ("
1462
1575
 
1463
- if proc['cpu_percent'] > 0:
1464
- res += f"CPU {proc['cpu_percent']}%, "
1576
+ if proc['cpu_percent'] > 0:
1577
+ res += f"CPU {proc['cpu_percent']}%, "
1465
1578
 
1466
- if proc['memory'] > 0:
1467
- res += f"RAM {proc['memory'] // (1024 * 1024)} MB, "
1579
+ if proc['memory'] > 0:
1580
+ res += f"RAM {proc['memory'] // (1024 * 1024)} MB, "
1468
1581
 
1469
- res += f"👤 {proc['username']}, "
1470
- res += f"⏰ {proc['create_time']}"
1582
+ res += f"👤 {proc['username']}, "
1583
+ res += f"⏰ {proc['create_time']}"
1471
1584
 
1472
- if proc['cmdline'] != "":
1473
- res += f", 📄 `{proc['cmdline']}`"
1585
+ if proc['cmdline'] != "":
1586
+ res += f", 📄 `{proc['cmdline']}`"
1474
1587
 
1475
- res += ")"
1588
+ res += ")"
1476
1589
 
1477
- if full_res != "":
1478
- full_res += "\n"
1479
- full_res += res
1590
+ if full_res != "":
1591
+ full_res += "\n"
1592
+ full_res += res
1480
1593
 
1481
- if full_res != "":
1482
- full_res = f"# 🔄 Processes 🔄\n{full_res}"
1594
+ if full_res != "":
1595
+ full_res = f"# 🔄 Processes 🔄\n{full_res}"
1483
1596
 
1484
- logging.info(msg=full_res)
1485
- return full_res
1597
+ logging.info(msg=full_res)
1598
+ return full_res
1599
+ except Exception as e:
1600
+ out_msg = f"⚠️ **Error getting ordered processes**:\n```sh\n{e}\n```"
1601
+ logging.exception(msg=out_msg)
1602
+ return out_msg
1486
1603
 
1487
- def kill_process(self, pid: int, timeout_in_sec: int = 10) -> str:
1604
+ async def kill_process(self, pid: int, timeout_in_sec: int = 10) -> str:
1488
1605
  """
1489
1606
  Kills a process with the specified PID.
1490
1607
  1. Tries to terminate the process gracefully
@@ -1522,7 +1639,7 @@ class LinuxMonitor:
1522
1639
 
1523
1640
  # Attempt to terminate the process
1524
1641
  terminate_command: List[str] = ['sudo', '/bin/kill', '-TERM', str(pid)]
1525
- result_terminate, _ = self.execute_and_verify(command=terminate_command, display_name=f"stop process {pid} ({process_name})", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1642
+ result_terminate, _ = await self.execute_and_verify(command=terminate_command, display_name=f"stop process {pid} ({process_name})", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1526
1643
 
1527
1644
  if result_terminate == True:
1528
1645
  out_msg = f"✅ **Process {pid} ({process_name}) stopped with success** (nicely)).\n"
@@ -1534,7 +1651,7 @@ class LinuxMonitor:
1534
1651
 
1535
1652
  # Attempt to kill the process
1536
1653
  kill_command = ['sudo', '/bin/kill', '-KILL', str(pid)]
1537
- _, strerror_kill = self.execute_and_verify(command=kill_command, display_name=f"kill process {pid} ({process_name})", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1654
+ _, strerror_kill = await self.execute_and_verify(command=kill_command, display_name=f"kill process {pid} ({process_name})", timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1538
1655
  out_msg += strerror_kill
1539
1656
 
1540
1657
  if process_cpu_percent > 0:
@@ -1570,42 +1687,47 @@ class LinuxMonitor:
1570
1687
 
1571
1688
  :return: A string containing the result message.
1572
1689
  """
1573
- interfaces = psutil.net_if_addrs()
1574
- stats = psutil.net_if_stats()
1575
- network_usage: str = ""
1690
+ try:
1691
+ interfaces = psutil.net_if_addrs()
1692
+ stats = psutil.net_if_stats()
1693
+ network_usage: str = ""
1576
1694
 
1577
- # List of interfaces to exclude (e.g., local interface)
1578
- excluded_interfaces = {"lo"}
1695
+ # List of interfaces to exclude (e.g., local interface)
1696
+ excluded_interfaces = {"lo"}
1579
1697
 
1580
- for interface in interfaces:
1581
- if interface in excluded_interfaces or interface not in stats:
1582
- continue
1698
+ for interface in interfaces:
1699
+ if interface in excluded_interfaces or interface not in stats:
1700
+ continue
1583
1701
 
1584
- ip_addresses = []
1585
- for snic in interfaces[interface]:
1586
- if snic.family == socket.AF_INET:
1587
- ip_addresses.append(snic.address) # type: ignore
1702
+ ip_addresses = []
1703
+ for snic in interfaces[interface]:
1704
+ if snic.family == socket.AF_INET:
1705
+ ip_addresses.append(snic.address) # type: ignore
1588
1706
 
1589
- # Get network stats
1590
- net_stats = psutil.net_io_counters(pernic=True).get(interface, None)
1591
- if net_stats is None:
1592
- continue
1707
+ # Get network stats
1708
+ net_stats = psutil.net_io_counters(pernic=True).get(interface, None)
1709
+ if net_stats is None:
1710
+ continue
1593
1711
 
1594
- # Convert bytes to GB for readability
1595
- receive_bytes: float = net_stats.bytes_recv / (1024 ** 3)
1596
- transmit_bytes: float = net_stats.bytes_sent / (1024 ** 3)
1712
+ # Convert bytes to GB for readability
1713
+ receive_bytes: float = net_stats.bytes_recv / (1024 ** 3)
1714
+ transmit_bytes: float = net_stats.bytes_sent / (1024 ** 3)
1597
1715
 
1598
- ip_str: str = ", ".join(ip_addresses) if ip_addresses else "N/A" # type: ignore
1716
+ ip_str: str = ", ".join(ip_addresses) if ip_addresses else "N/A" # type: ignore
1599
1717
 
1600
- if network_usage != "":
1601
- network_usage += "\n"
1602
- network_usage += f"- {interface} ({ip_str}): ⬇️ {receive_bytes:,.2f} GB, ⬆️ {transmit_bytes:,.2f} GB"
1718
+ if network_usage != "":
1719
+ network_usage += "\n"
1720
+ network_usage += f"- {interface} ({ip_str}): ⬇️ {receive_bytes:,.2f} GB, ⬆️ {transmit_bytes:,.2f} GB"
1603
1721
 
1604
- if network_usage != "":
1605
- network_usage = f"# 🌐 Network usage 🌐\n{network_usage}"
1722
+ if network_usage != "":
1723
+ network_usage = f"# 🌐 Network usage 🌐\n{network_usage}"
1606
1724
 
1607
- logging.info(msg=network_usage)
1608
- return network_usage
1725
+ logging.info(msg=network_usage)
1726
+ return network_usage
1727
+ except Exception as e:
1728
+ out_msg = f"⚠️ **Error getting network information**:\n```sh\n{e}\n```"
1729
+ logging.exception(msg=out_msg)
1730
+ return out_msg
1609
1731
 
1610
1732
  #endregion
1611
1733
 
@@ -1624,7 +1746,8 @@ class LinuxMonitor:
1624
1746
  if not self.allow_scheduled_tasks_check_for_issues:
1625
1747
  raise Exception("Scheduled tasks are not allowed")
1626
1748
 
1627
- await asyncio.sleep(delay=10) # Sleep for 10 sec before lauching the scheduled tasks (to allow the lib to be ready)
1749
+ logging.info(msg=f"Waiting for 45 seconds before starting the execution of {'private' if is_private else 'public'} scheduled tasks to allow the discord bot and linux server to be ready...")
1750
+ await asyncio.sleep(delay=45) # Sleep for 45 sec before lauching the scheduled tasks (to allow the lib to be ready)
1628
1751
 
1629
1752
  datetime_last_disk_usage_error_displayed: Optional[datetime] = None
1630
1753
  datetime_last_folder_usage_error_displayed: Optional[datetime] = None
@@ -1633,6 +1756,7 @@ class LinuxMonitor:
1633
1756
  datetime_last_swap_usage_error_displayed: Optional[datetime] = None
1634
1757
  datetime_last_cpu_temperature_error_displayed: Optional[datetime] = None
1635
1758
  datetime_last_ping_error_displayed: Optional[datetime] = None
1759
+ datetime_last_websites_error_displayed: Optional[datetime] = None
1636
1760
  datetime_last_certificates_error_displayed: Optional[datetime] = None
1637
1761
  datetime_last_user_logins_error_displayed: Optional[datetime] = None
1638
1762
  datetime_last_port_error_displayed: Optional[datetime] = None
@@ -1649,7 +1773,7 @@ class LinuxMonitor:
1649
1773
  logging.info(msg="Checking services status and all disk usage, CPU, RAM, Swap, CPU temperature and ping of websites periodically...")
1650
1774
  try:
1651
1775
  # Services status
1652
- msg: str = self.check_all_services_status_and_restart_if_down(is_private=is_private)
1776
+ msg: str = await self.check_all_services_status_and_restart_if_down(is_private=is_private)
1653
1777
  if msg != "":
1654
1778
  logging.warning(msg=msg)
1655
1779
  if datetime_last_services_error_displayed is None or ((datetime.now() - datetime_last_services_error_displayed).total_seconds() > self.max_duration_seconds_showing_same_error_again_in_scheduled_tasks):
@@ -1733,7 +1857,7 @@ class LinuxMonitor:
1733
1857
  logging.info(msg="- ✅ Certificates are OK.")
1734
1858
 
1735
1859
  # Ping
1736
- msg = self.ping_all_websites(is_private=is_private, display_only_if_critical=True)
1860
+ msg = await self.ping_all_websites(is_private=is_private, display_only_if_critical=True)
1737
1861
  if msg != "":
1738
1862
  logging.warning(msg=msg)
1739
1863
  if datetime_last_ping_error_displayed is None or ((datetime.now() - datetime_last_ping_error_displayed).total_seconds() > self.max_duration_seconds_showing_same_error_again_in_scheduled_tasks):
@@ -1753,8 +1877,29 @@ class LinuxMonitor:
1753
1877
  else:
1754
1878
  logging.info(msg="- ✅ Ping of all websites are OK.")
1755
1879
 
1880
+ # Websites availability (GET request)
1881
+ msg = await self.check_all_websites(is_private=is_private, display_only_if_critical=True)
1882
+ if msg != "":
1883
+ logging.warning(msg=msg)
1884
+ if datetime_last_websites_error_displayed is None or ((datetime.now() - datetime_last_websites_error_displayed).total_seconds() > self.max_duration_seconds_showing_same_error_again_in_scheduled_tasks):
1885
+ if out_msg != "":
1886
+ out_msg += "\n"
1887
+ out_msg += msg
1888
+ datetime_last_websites_error_displayed = datetime.now()
1889
+ else:
1890
+ logging.warning(msg="Websites critical but already notified less than 12 hours ago, not notifying...")
1891
+ elif datetime_last_websites_error_displayed is not None:
1892
+ msg="✅ **Websites returned to normal state**"
1893
+ logging.info(msg=msg)
1894
+ if out_msg != "":
1895
+ out_msg += "\n"
1896
+ out_msg += msg
1897
+ datetime_last_websites_error_displayed = None
1898
+ else:
1899
+ logging.info(msg="- ✅ All websites availability (GET requests) are OK.")
1900
+
1756
1901
  # Ports
1757
- msg = self.check_all_ports(is_private=is_private, display_only_if_critical=True, restart_if_down=True)
1902
+ msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=True, restart_if_down=True)
1758
1903
  if msg != "":
1759
1904
  logging.warning(msg=msg)
1760
1905
  if datetime_last_port_error_displayed is None or ((datetime.now() - datetime_last_port_error_displayed).total_seconds() > self.max_duration_seconds_showing_same_error_again_in_scheduled_tasks):
@@ -1922,7 +2067,8 @@ class LinuxMonitor:
1922
2067
  if not self.allow_scheduled_task_show_info:
1923
2068
  raise Exception("Scheduled show info tasks are not allowed")
1924
2069
 
1925
- await asyncio.sleep(delay=10)
2070
+ logging.info(msg=f"Waiting for 45 seconds before starting the execution of {'private' if is_private else 'public'} show info scheduled tasks to allow the discord bot and linux server to be ready...")
2071
+ await asyncio.sleep(delay=45)
1926
2072
 
1927
2073
  if not self.start_scheduled_task_show_info_immediately:
1928
2074
  logging.info(msg=f"Waiting for {self.duration_in_sec_wait_between_each_schedule_task_show_info_execution} seconds before starting the execution of {'private' if is_private else 'public'} show info scheduled tasks...")
@@ -1934,7 +2080,7 @@ class LinuxMonitor:
1934
2080
  logging.info(msg="-----------------------------------------------")
1935
2081
 
1936
2082
  # Services status
1937
- msg: str = self.check_all_services_status_and_restart_if_down(is_private=is_private)
2083
+ msg: str = await self.check_all_services_status_and_restart_if_down(is_private=is_private)
1938
2084
  if msg != "":
1939
2085
  if out_msg != "":
1940
2086
  out_msg += "\n"
@@ -1962,14 +2108,21 @@ class LinuxMonitor:
1962
2108
  out_msg += msg
1963
2109
 
1964
2110
  # Ping
1965
- msg = self.ping_all_websites(is_private=is_private, display_only_if_critical=False)
2111
+ msg = await self.ping_all_websites(is_private=is_private, display_only_if_critical=False)
2112
+ if msg != "":
2113
+ if out_msg != "":
2114
+ out_msg += "\n"
2115
+ out_msg += msg
2116
+
2117
+ # Websites availability (GET request)
2118
+ msg = await self.check_all_websites(is_private=is_private, display_only_if_critical=False)
1966
2119
  if msg != "":
1967
2120
  if out_msg != "":
1968
2121
  out_msg += "\n"
1969
2122
  out_msg += msg
1970
2123
 
1971
2124
  # Ports
1972
- msg = self.check_all_ports(is_private=is_private, display_only_if_critical=False, restart_if_down=False)
2125
+ msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=False, restart_if_down=False)
1973
2126
  if msg != "":
1974
2127
  if out_msg != "":
1975
2128
  out_msg += "\n"
@@ -6,7 +6,7 @@ with io.open(file='README.md', mode='r', encoding='utf-8') as readme_file:
6
6
 
7
7
  setup(
8
8
  name="LinuxMonitor",
9
- version="1.1.1",
9
+ version="1.2.0",
10
10
  description="Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)",
11
11
  long_description=readme,
12
12
  long_description_content_type="text/markdown",
File without changes
File without changes