LinuxMonitor 1.1.2__tar.gz → 1.2.1__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.2
3
+ Version: 1.2.1
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.2
3
+ Version: 1.2.1
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 🩺')
@@ -101,6 +102,12 @@ def main() -> None:
101
102
  out_msg: str = asyncio.run(monitoring.ping_all_websites(is_private=True, display_only_if_critical=False))
102
103
  print(out_msg)
103
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))
109
+ print(out_msg)
110
+
104
111
  if args.certificates:
105
112
  handled = True
106
113
  print("Checking SSL certificates...")
@@ -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.2 (2024/09/14)"
33
+ __version__ = "1.2.1 (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
@@ -737,19 +742,24 @@ class LinuxMonitor:
737
742
 
738
743
  :return: A string containing the result message.
739
744
  """
740
- ping_command: list[str] = ["ping", "-c", "1", website]
741
- 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})"
742
748
 
743
- start_time: float = time.time()
744
- 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)
745
- 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()
746
752
 
747
- if res == True and not display_only_if_critical:
748
- res_ping_sec: str = "{:.2f}sec".format(end_time - start_time)
749
- out_msg: str = f"✅ **{display_name} answered in {res_ping_sec}**."
750
- 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)
751
757
 
752
- 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
753
763
 
754
764
  async def ping_all_websites(self, is_private: bool, display_only_if_critical: bool=False) -> str:
755
765
  """
@@ -777,6 +787,89 @@ class LinuxMonitor:
777
787
 
778
788
  #endregion
779
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
+ start_time: float = time.time()
810
+ async with session.get(url, auth=auth, headers=headers, timeout=timeout_in_sec) as response:
811
+ end_time: float = time.time()
812
+ # Default allowed status codes (200-299)
813
+ allowed_statuses = list(range(200, 300))
814
+
815
+ # Include any additional allowed status codes if provided
816
+ if additional_allowed_statuses:
817
+ allowed_statuses += additional_allowed_statuses
818
+
819
+ # Check if the status code is within the allowed list
820
+ if response.status in allowed_statuses:
821
+ out_msg: str = f"✅ **{display_name} answered with valid status code {response.status}** in {end_time - start_time:.2f}sec."
822
+ logging.info(msg=out_msg)
823
+ return True, out_msg
824
+ else:
825
+ # Get the reason phrase (e.g., "Unauthorized" for 401)
826
+ status_reason: str = responses.get(response.status, "Unknown status")
827
+ out_msg = f"❌ **{display_name} answered with invalid status code {response.status} - {status_reason}**."
828
+ logging.warning(msg=out_msg)
829
+ return False, out_msg
830
+
831
+ except asyncio.TimeoutError:
832
+ out_msg = f"⚠️ **Error checking {display_name}: Timeout**"
833
+ return False, out_msg
834
+ except Exception as e:
835
+ out_msg = f"⚠️ **Error checking {display_name}**:\n```sh\n{e}\n```"
836
+ return False, out_msg
837
+
838
+ async def check_all_websites(self, is_private: bool, display_only_if_critical: bool=False) -> str:
839
+ try:
840
+ out_msg: str = ""
841
+ for website_config in self.config['websites']:
842
+ if is_private or is_private == website_config['is_private']:
843
+ timeout_in_sec: int = website_config.get('timeout_in_sec', 5)
844
+ url: str = website_config['url']
845
+ display_name: str = website_config.get('display_name', url)
846
+ auth_type: Optional[str] = website_config.get('auth_type', None)
847
+ username: Optional[str] = website_config.get('username', None)
848
+ password: Optional[str] = website_config.get('password', None)
849
+ token: Optional[str] = website_config.get('token', None)
850
+ additional_allowed_statuses: List[int] = website_config.get('additional_allowed_statuses', [])
851
+
852
+ _, msg = await self._check_website(url=url, display_name=display_name, timeout_in_sec=timeout_in_sec,
853
+ auth_type=auth_type, username=username, password=password, token=token,
854
+ additional_allowed_statuses=additional_allowed_statuses,
855
+ display_only_if_critical=display_only_if_critical)
856
+
857
+ if msg != "":
858
+ if out_msg:
859
+ out_msg += "\n"
860
+ out_msg += f"- {msg}"
861
+
862
+ if out_msg != "":
863
+ out_msg = f"# 🌐 Website access state 🌐\n{out_msg}"
864
+
865
+ return out_msg
866
+ except Exception as e:
867
+ out_msg = f"⚠️ **Error checking website access states**:\n```sh\n{e}\n```"
868
+ logging.exception(msg=out_msg)
869
+ return out_msg
870
+
871
+ #endregion
872
+
780
873
  #region Services
781
874
 
782
875
  def get_all_services_allowed_to_restart(self, is_private: bool) -> str:
@@ -816,40 +909,45 @@ class LinuxMonitor:
816
909
 
817
910
  :return: A string containing the result message.
818
911
  """
819
- if service_name not in self.config['services'].keys():
820
- out_msg = f"❌ **Service {service_name} not found**."
821
- logging.error(msg=out_msg)
822
- return out_msg
912
+ try:
913
+ if service_name not in self.config['services'].keys():
914
+ out_msg = f"❌ **Service {service_name} not found**."
915
+ logging.error(msg=out_msg)
916
+ return out_msg
823
917
 
824
- service = self.config['services'][service_name]
825
- if not is_private and is_private != service['is_private']:
826
- out_msg = f"❌ **Service {service_name} not authorized to restart in public**."
827
- logging.error(msg=out_msg)
828
- return out_msg
918
+ service = self.config['services'][service_name]
919
+ if not is_private and is_private != service['is_private']:
920
+ out_msg = f"❌ **Service {service_name} not authorized to restart in public**."
921
+ logging.error(msg=out_msg)
922
+ return out_msg
829
923
 
830
- display_name: str = service.get('display_name', service_name)
831
- if 'restart_command' not in service:
832
- out_msg = f"❌ **Restart command not found for {display_name}**."
833
- logging.error(msg=out_msg)
834
- return out_msg
924
+ display_name: str = service.get('display_name', service_name)
925
+ if 'restart_command' not in service:
926
+ out_msg = f"❌ **Restart command not found for {display_name}**."
927
+ logging.error(msg=out_msg)
928
+ return out_msg
835
929
 
836
- timeout_in_sec: int = service.get('timeout_in_sec', 90)
930
+ timeout_in_sec: int = service.get('timeout_in_sec', 90)
837
931
 
838
- service_call: List[str] = service['restart_command']
839
- out_msg: str = ""
932
+ service_call: List[str] = service['restart_command']
933
+ out_msg: str = ""
840
934
 
841
- logging.info(f"Trying to restart {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
935
+ logging.info(f"Trying to restart {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
842
936
 
843
- start_time: float = time.time()
844
- 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)
845
- end_time: float = time.time()
937
+ start_time: float = time.time()
938
+ 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)
939
+ end_time: float = time.time()
846
940
 
847
- if res == True:
848
- readable_duration: str = "{:.2f}".format(end_time - start_time)
849
- out_msg = f"✅ **{display_name} restarted with success** in {readable_duration}sec."
850
- logging.info(msg=out_msg)
941
+ if res == True:
942
+ readable_duration: str = "{:.2f}".format(end_time - start_time)
943
+ out_msg = f"✅ **{display_name} restarted with success** in {readable_duration}sec."
944
+ logging.info(msg=out_msg)
851
945
 
852
- return out_msg
946
+ return out_msg
947
+ except Exception as e:
948
+ out_msg = f"⚠️ **Error restarting service {service_name}**:\n```sh\n{e}\n```"
949
+ logging.exception(msg=out_msg)
950
+ return out_msg
853
951
 
854
952
  async def restart_all_services(self, is_private: bool) -> str:
855
953
  """
@@ -859,19 +957,24 @@ class LinuxMonitor:
859
957
 
860
958
  :return: A string containing the result message.
861
959
  """
862
- out_msg: str = ""
863
- for service_name in self.config["services"].keys():
864
- if is_private or self.config["services"][service_name]['is_private'] == is_private:
865
- res: str = await self.restart_service(is_private=is_private, service_name=service_name)
866
- if res != "":
867
- if out_msg:
868
- out_msg += "\n"
869
- out_msg += f"- {res}"
960
+ try:
961
+ out_msg: str = ""
962
+ for service_name in self.config["services"].keys():
963
+ if is_private or self.config["services"][service_name]['is_private'] == is_private:
964
+ res: str = await self.restart_service(is_private=is_private, service_name=service_name)
965
+ if res != "":
966
+ if out_msg:
967
+ out_msg += "\n"
968
+ out_msg += f"- {res}"
870
969
 
871
- if out_msg:
872
- out_msg = f"# 📱 Restart services 📱\n{out_msg}"
970
+ if out_msg:
971
+ out_msg = f"# 📱 Restart services 📱\n{out_msg}"
873
972
 
874
- return out_msg
973
+ return out_msg
974
+ except Exception as e:
975
+ out_msg = f"⚠️ **Error restarting services**:\n```sh\n{e}\n```"
976
+ logging.exception(msg=out_msg)
977
+ return out_msg
875
978
 
876
979
  async def _get_service_status(self, is_private: bool, service_name: str) -> Tuple[Optional[bool], str]:
877
980
  """
@@ -882,23 +985,28 @@ class LinuxMonitor:
882
985
 
883
986
  :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.
884
987
  """
885
- if service_name not in self.config["services"].keys():
886
- logging.error(msg=f"Service {service_name} not found")
887
- return None, ""
988
+ try:
989
+ if service_name not in self.config["services"].keys():
990
+ logging.error(msg=f"Service {service_name} not found")
991
+ return None, ""
888
992
 
889
- service = self.config["services"][service_name]
890
- display_name = service.get('display_name', service_name)
891
- timeout_in_sec = service.get('timeout_in_sec', 30)
993
+ service = self.config["services"][service_name]
994
+ display_name = service.get('display_name', service_name)
995
+ timeout_in_sec = service.get('timeout_in_sec', 30)
892
996
 
893
- if 'status_command' not in service:
894
- logging.error(msg=f"Status command (status_command) not found for service {service_name}")
895
- return None, ""
997
+ if 'status_command' not in service:
998
+ logging.error(msg=f"Status command (status_command) not found for service {service_name}")
999
+ return None, ""
896
1000
 
897
- status_command = service['status_command']
898
- check_also_stdout_not_containing = service.get('check_also_stdout_not_containing', None)
1001
+ status_command = service['status_command']
1002
+ check_also_stdout_not_containing = service.get('check_also_stdout_not_containing', None)
899
1003
 
900
- 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)
901
- return res, out_msg
1004
+ 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)
1005
+ return res, out_msg
1006
+ except Exception as e:
1007
+ out_msg = f"⚠️ **Error checking status of service {service_name}**:\n```sh\n{e}\n```"
1008
+ logging.exception(msg=out_msg)
1009
+ return None, out_msg
902
1010
 
903
1011
  async def check_all_services_status(self, is_private: bool) -> str:
904
1012
  """
@@ -1433,62 +1541,67 @@ class LinuxMonitor:
1433
1541
 
1434
1542
  :return: A string containing the result message.
1435
1543
  """
1436
- processes = []
1437
- for process in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent', 'memory_info', 'create_time', 'cmdline']):
1438
- try:
1439
- create_time = datetime.fromtimestamp(process.info['create_time']).strftime("%Y-%m-%d %H:%M:%S")
1544
+ try:
1545
+ processes = []
1546
+ for process in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent', 'memory_info', 'create_time', 'cmdline']):
1547
+ try:
1548
+ create_time = datetime.fromtimestamp(process.info['create_time']).strftime("%Y-%m-%d %H:%M:%S")
1440
1549
 
1441
- cmdline = ' '.join(process.info['cmdline']) # Join the command line arguments
1442
- # Remove extra spaces
1443
- cmdline = re.sub(r'\s+', ' ', cmdline).strip()
1444
- if len(cmdline) > 60:
1445
- cmdline = cmdline[:60] + '...' # Truncate and add ellipsis
1550
+ cmdline = ' '.join(process.info['cmdline']) # Join the command line arguments
1551
+ # Remove extra spaces
1552
+ cmdline = re.sub(r'\s+', ' ', cmdline).strip()
1553
+ if len(cmdline) > 60:
1554
+ cmdline = cmdline[:60] + '...' # Truncate and add ellipsis
1446
1555
 
1447
- processes.append({ # type: ignore
1448
- 'pid': process.info['pid'],
1449
- 'name': process.info['name'],
1450
- 'username': process.info['username'],
1451
- 'cpu_percent': process.info['cpu_percent'],
1452
- 'memory': process.info['memory_info'].rss, # Resident Set Size (RSS) memory
1453
- 'create_time': create_time,
1454
- 'cmdline': cmdline
1455
- })
1456
- except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
1457
- pass
1556
+ processes.append({ # type: ignore
1557
+ 'pid': process.info['pid'],
1558
+ 'name': process.info['name'],
1559
+ 'username': process.info['username'],
1560
+ 'cpu_percent': process.info['cpu_percent'],
1561
+ 'memory': process.info['memory_info'].rss, # Resident Set Size (RSS) memory
1562
+ 'create_time': create_time,
1563
+ 'cmdline': cmdline
1564
+ })
1565
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
1566
+ pass
1458
1567
 
1459
- # Sort processes by memory usage, then by CPU usage, both in descending order
1460
- processes.sort(key=lambda proc: (proc['memory'], proc['cpu_percent'], proc['create_time']), reverse=True) # type: ignore
1568
+ # Sort processes by memory usage, then by CPU usage, both in descending order
1569
+ processes.sort(key=lambda proc: (proc['memory'], proc['cpu_percent'], proc['create_time']), reverse=True) # type: ignore
1461
1570
 
1462
- full_res: str = ""
1463
- for proc in processes: # type: ignore
1464
- if not get_non_consuming_processes and proc['cpu_percent'] == 0 and proc['memory'] == 0:
1465
- continue
1571
+ full_res: str = ""
1572
+ for proc in processes: # type: ignore
1573
+ if not get_non_consuming_processes and proc['cpu_percent'] == 0 and proc['memory'] == 0:
1574
+ continue
1466
1575
 
1467
- res = f"- PID **{proc['pid']}**: {proc['name']} ("
1576
+ res = f"- PID **{proc['pid']}**: {proc['name']} ("
1468
1577
 
1469
- if proc['cpu_percent'] > 0:
1470
- res += f"CPU {proc['cpu_percent']}%, "
1578
+ if proc['cpu_percent'] > 0:
1579
+ res += f"CPU {proc['cpu_percent']}%, "
1471
1580
 
1472
- if proc['memory'] > 0:
1473
- res += f"RAM {proc['memory'] // (1024 * 1024)} MB, "
1581
+ if proc['memory'] > 0:
1582
+ res += f"RAM {proc['memory'] // (1024 * 1024)} MB, "
1474
1583
 
1475
- res += f"👤 {proc['username']}, "
1476
- res += f"⏰ {proc['create_time']}"
1584
+ res += f"👤 {proc['username']}, "
1585
+ res += f"⏰ {proc['create_time']}"
1477
1586
 
1478
- if proc['cmdline'] != "":
1479
- res += f", 📄 `{proc['cmdline']}`"
1587
+ if proc['cmdline'] != "":
1588
+ res += f", 📄 `{proc['cmdline']}`"
1480
1589
 
1481
- res += ")"
1590
+ res += ")"
1482
1591
 
1483
- if full_res != "":
1484
- full_res += "\n"
1485
- full_res += res
1592
+ if full_res != "":
1593
+ full_res += "\n"
1594
+ full_res += res
1486
1595
 
1487
- if full_res != "":
1488
- full_res = f"# 🔄 Processes 🔄\n{full_res}"
1596
+ if full_res != "":
1597
+ full_res = f"# 🔄 Processes 🔄\n{full_res}"
1489
1598
 
1490
- logging.info(msg=full_res)
1491
- return full_res
1599
+ logging.info(msg=full_res)
1600
+ return full_res
1601
+ except Exception as e:
1602
+ out_msg = f"⚠️ **Error getting ordered processes**:\n```sh\n{e}\n```"
1603
+ logging.exception(msg=out_msg)
1604
+ return out_msg
1492
1605
 
1493
1606
  async def kill_process(self, pid: int, timeout_in_sec: int = 10) -> str:
1494
1607
  """
@@ -1576,42 +1689,47 @@ class LinuxMonitor:
1576
1689
 
1577
1690
  :return: A string containing the result message.
1578
1691
  """
1579
- interfaces = psutil.net_if_addrs()
1580
- stats = psutil.net_if_stats()
1581
- network_usage: str = ""
1692
+ try:
1693
+ interfaces = psutil.net_if_addrs()
1694
+ stats = psutil.net_if_stats()
1695
+ network_usage: str = ""
1582
1696
 
1583
- # List of interfaces to exclude (e.g., local interface)
1584
- excluded_interfaces = {"lo"}
1697
+ # List of interfaces to exclude (e.g., local interface)
1698
+ excluded_interfaces = {"lo"}
1585
1699
 
1586
- for interface in interfaces:
1587
- if interface in excluded_interfaces or interface not in stats:
1588
- continue
1700
+ for interface in interfaces:
1701
+ if interface in excluded_interfaces or interface not in stats:
1702
+ continue
1589
1703
 
1590
- ip_addresses = []
1591
- for snic in interfaces[interface]:
1592
- if snic.family == socket.AF_INET:
1593
- ip_addresses.append(snic.address) # type: ignore
1704
+ ip_addresses = []
1705
+ for snic in interfaces[interface]:
1706
+ if snic.family == socket.AF_INET:
1707
+ ip_addresses.append(snic.address) # type: ignore
1594
1708
 
1595
- # Get network stats
1596
- net_stats = psutil.net_io_counters(pernic=True).get(interface, None)
1597
- if net_stats is None:
1598
- continue
1709
+ # Get network stats
1710
+ net_stats = psutil.net_io_counters(pernic=True).get(interface, None)
1711
+ if net_stats is None:
1712
+ continue
1599
1713
 
1600
- # Convert bytes to GB for readability
1601
- receive_bytes: float = net_stats.bytes_recv / (1024 ** 3)
1602
- transmit_bytes: float = net_stats.bytes_sent / (1024 ** 3)
1714
+ # Convert bytes to GB for readability
1715
+ receive_bytes: float = net_stats.bytes_recv / (1024 ** 3)
1716
+ transmit_bytes: float = net_stats.bytes_sent / (1024 ** 3)
1603
1717
 
1604
- ip_str: str = ", ".join(ip_addresses) if ip_addresses else "N/A" # type: ignore
1718
+ ip_str: str = ", ".join(ip_addresses) if ip_addresses else "N/A" # type: ignore
1605
1719
 
1606
- if network_usage != "":
1607
- network_usage += "\n"
1608
- network_usage += f"- {interface} ({ip_str}): ⬇️ {receive_bytes:,.2f} GB, ⬆️ {transmit_bytes:,.2f} GB"
1720
+ if network_usage != "":
1721
+ network_usage += "\n"
1722
+ network_usage += f"- {interface} ({ip_str}): ⬇️ {receive_bytes:,.2f} GB, ⬆️ {transmit_bytes:,.2f} GB"
1609
1723
 
1610
- if network_usage != "":
1611
- network_usage = f"# 🌐 Network usage 🌐\n{network_usage}"
1724
+ if network_usage != "":
1725
+ network_usage = f"# 🌐 Network usage 🌐\n{network_usage}"
1612
1726
 
1613
- logging.info(msg=network_usage)
1614
- return network_usage
1727
+ logging.info(msg=network_usage)
1728
+ return network_usage
1729
+ except Exception as e:
1730
+ out_msg = f"⚠️ **Error getting network information**:\n```sh\n{e}\n```"
1731
+ logging.exception(msg=out_msg)
1732
+ return out_msg
1615
1733
 
1616
1734
  #endregion
1617
1735
 
@@ -1640,6 +1758,7 @@ class LinuxMonitor:
1640
1758
  datetime_last_swap_usage_error_displayed: Optional[datetime] = None
1641
1759
  datetime_last_cpu_temperature_error_displayed: Optional[datetime] = None
1642
1760
  datetime_last_ping_error_displayed: Optional[datetime] = None
1761
+ datetime_last_websites_error_displayed: Optional[datetime] = None
1643
1762
  datetime_last_certificates_error_displayed: Optional[datetime] = None
1644
1763
  datetime_last_user_logins_error_displayed: Optional[datetime] = None
1645
1764
  datetime_last_port_error_displayed: Optional[datetime] = None
@@ -1760,6 +1879,27 @@ class LinuxMonitor:
1760
1879
  else:
1761
1880
  logging.info(msg="- ✅ Ping of all websites are OK.")
1762
1881
 
1882
+ # Websites availability (GET request)
1883
+ msg = await self.check_all_websites(is_private=is_private, display_only_if_critical=True)
1884
+ if msg != "":
1885
+ logging.warning(msg=msg)
1886
+ 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):
1887
+ if out_msg != "":
1888
+ out_msg += "\n"
1889
+ out_msg += msg
1890
+ datetime_last_websites_error_displayed = datetime.now()
1891
+ else:
1892
+ logging.warning(msg="Websites critical but already notified less than 12 hours ago, not notifying...")
1893
+ elif datetime_last_websites_error_displayed is not None:
1894
+ msg="✅ **Websites returned to normal state**"
1895
+ logging.info(msg=msg)
1896
+ if out_msg != "":
1897
+ out_msg += "\n"
1898
+ out_msg += msg
1899
+ datetime_last_websites_error_displayed = None
1900
+ else:
1901
+ logging.info(msg="- ✅ All websites availability (GET requests) are OK.")
1902
+
1763
1903
  # Ports
1764
1904
  msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=True, restart_if_down=True)
1765
1905
  if msg != "":
@@ -1976,6 +2116,13 @@ class LinuxMonitor:
1976
2116
  out_msg += "\n"
1977
2117
  out_msg += msg
1978
2118
 
2119
+ # Websites availability (GET request)
2120
+ msg = await self.check_all_websites(is_private=is_private, display_only_if_critical=False)
2121
+ if msg != "":
2122
+ if out_msg != "":
2123
+ out_msg += "\n"
2124
+ out_msg += msg
2125
+
1979
2126
  # Ports
1980
2127
  msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=False, restart_if_down=False)
1981
2128
  if msg != "":
@@ -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.2",
9
+ version="1.2.1",
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