LinuxMonitor 1.1.2__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.2
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.2
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 🩺')
@@ -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.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
@@ -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,87 @@ 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
+ 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
+
780
871
  #region Services
781
872
 
782
873
  def get_all_services_allowed_to_restart(self, is_private: bool) -> str:
@@ -816,40 +907,45 @@ class LinuxMonitor:
816
907
 
817
908
  :return: A string containing the result message.
818
909
  """
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
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
823
915
 
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
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
829
921
 
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
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
835
927
 
836
- timeout_in_sec: int = service.get('timeout_in_sec', 90)
928
+ timeout_in_sec: int = service.get('timeout_in_sec', 90)
837
929
 
838
- service_call: List[str] = service['restart_command']
839
- out_msg: str = ""
930
+ service_call: List[str] = service['restart_command']
931
+ out_msg: str = ""
840
932
 
841
- 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...")
842
934
 
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()
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()
846
938
 
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)
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)
851
943
 
852
- 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
853
949
 
854
950
  async def restart_all_services(self, is_private: bool) -> str:
855
951
  """
@@ -859,19 +955,24 @@ class LinuxMonitor:
859
955
 
860
956
  :return: A string containing the result message.
861
957
  """
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}"
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}"
870
967
 
871
- if out_msg:
872
- out_msg = f"# 📱 Restart services 📱\n{out_msg}"
968
+ if out_msg:
969
+ out_msg = f"# 📱 Restart services 📱\n{out_msg}"
873
970
 
874
- 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
875
976
 
876
977
  async def _get_service_status(self, is_private: bool, service_name: str) -> Tuple[Optional[bool], str]:
877
978
  """
@@ -882,23 +983,28 @@ class LinuxMonitor:
882
983
 
883
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.
884
985
  """
885
- if service_name not in self.config["services"].keys():
886
- logging.error(msg=f"Service {service_name} not found")
887
- 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, ""
888
990
 
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)
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)
892
994
 
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, ""
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, ""
896
998
 
897
- status_command = service['status_command']
898
- 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)
899
1001
 
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
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
902
1008
 
903
1009
  async def check_all_services_status(self, is_private: bool) -> str:
904
1010
  """
@@ -1433,62 +1539,67 @@ class LinuxMonitor:
1433
1539
 
1434
1540
  :return: A string containing the result message.
1435
1541
  """
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")
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")
1440
1547
 
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
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
1446
1553
 
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
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
1458
1565
 
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
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
1461
1568
 
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
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
1466
1573
 
1467
- res = f"- PID **{proc['pid']}**: {proc['name']} ("
1574
+ res = f"- PID **{proc['pid']}**: {proc['name']} ("
1468
1575
 
1469
- if proc['cpu_percent'] > 0:
1470
- res += f"CPU {proc['cpu_percent']}%, "
1576
+ if proc['cpu_percent'] > 0:
1577
+ res += f"CPU {proc['cpu_percent']}%, "
1471
1578
 
1472
- if proc['memory'] > 0:
1473
- res += f"RAM {proc['memory'] // (1024 * 1024)} MB, "
1579
+ if proc['memory'] > 0:
1580
+ res += f"RAM {proc['memory'] // (1024 * 1024)} MB, "
1474
1581
 
1475
- res += f"👤 {proc['username']}, "
1476
- res += f"⏰ {proc['create_time']}"
1582
+ res += f"👤 {proc['username']}, "
1583
+ res += f"⏰ {proc['create_time']}"
1477
1584
 
1478
- if proc['cmdline'] != "":
1479
- res += f", 📄 `{proc['cmdline']}`"
1585
+ if proc['cmdline'] != "":
1586
+ res += f", 📄 `{proc['cmdline']}`"
1480
1587
 
1481
- res += ")"
1588
+ res += ")"
1482
1589
 
1483
- if full_res != "":
1484
- full_res += "\n"
1485
- full_res += res
1590
+ if full_res != "":
1591
+ full_res += "\n"
1592
+ full_res += res
1486
1593
 
1487
- if full_res != "":
1488
- full_res = f"# 🔄 Processes 🔄\n{full_res}"
1594
+ if full_res != "":
1595
+ full_res = f"# 🔄 Processes 🔄\n{full_res}"
1489
1596
 
1490
- logging.info(msg=full_res)
1491
- 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
1492
1603
 
1493
1604
  async def kill_process(self, pid: int, timeout_in_sec: int = 10) -> str:
1494
1605
  """
@@ -1576,42 +1687,47 @@ class LinuxMonitor:
1576
1687
 
1577
1688
  :return: A string containing the result message.
1578
1689
  """
1579
- interfaces = psutil.net_if_addrs()
1580
- stats = psutil.net_if_stats()
1581
- network_usage: str = ""
1690
+ try:
1691
+ interfaces = psutil.net_if_addrs()
1692
+ stats = psutil.net_if_stats()
1693
+ network_usage: str = ""
1582
1694
 
1583
- # List of interfaces to exclude (e.g., local interface)
1584
- excluded_interfaces = {"lo"}
1695
+ # List of interfaces to exclude (e.g., local interface)
1696
+ excluded_interfaces = {"lo"}
1585
1697
 
1586
- for interface in interfaces:
1587
- if interface in excluded_interfaces or interface not in stats:
1588
- continue
1698
+ for interface in interfaces:
1699
+ if interface in excluded_interfaces or interface not in stats:
1700
+ continue
1589
1701
 
1590
- ip_addresses = []
1591
- for snic in interfaces[interface]:
1592
- if snic.family == socket.AF_INET:
1593
- 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
1594
1706
 
1595
- # Get network stats
1596
- net_stats = psutil.net_io_counters(pernic=True).get(interface, None)
1597
- if net_stats is None:
1598
- 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
1599
1711
 
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)
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)
1603
1715
 
1604
- 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
1605
1717
 
1606
- if network_usage != "":
1607
- network_usage += "\n"
1608
- 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"
1609
1721
 
1610
- if network_usage != "":
1611
- network_usage = f"# 🌐 Network usage 🌐\n{network_usage}"
1722
+ if network_usage != "":
1723
+ network_usage = f"# 🌐 Network usage 🌐\n{network_usage}"
1612
1724
 
1613
- logging.info(msg=network_usage)
1614
- 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
1615
1731
 
1616
1732
  #endregion
1617
1733
 
@@ -1640,6 +1756,7 @@ class LinuxMonitor:
1640
1756
  datetime_last_swap_usage_error_displayed: Optional[datetime] = None
1641
1757
  datetime_last_cpu_temperature_error_displayed: Optional[datetime] = None
1642
1758
  datetime_last_ping_error_displayed: Optional[datetime] = None
1759
+ datetime_last_websites_error_displayed: Optional[datetime] = None
1643
1760
  datetime_last_certificates_error_displayed: Optional[datetime] = None
1644
1761
  datetime_last_user_logins_error_displayed: Optional[datetime] = None
1645
1762
  datetime_last_port_error_displayed: Optional[datetime] = None
@@ -1760,6 +1877,27 @@ class LinuxMonitor:
1760
1877
  else:
1761
1878
  logging.info(msg="- ✅ Ping of all websites are OK.")
1762
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
+
1763
1901
  # Ports
1764
1902
  msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=True, restart_if_down=True)
1765
1903
  if msg != "":
@@ -1976,6 +2114,13 @@ class LinuxMonitor:
1976
2114
  out_msg += "\n"
1977
2115
  out_msg += msg
1978
2116
 
2117
+ # Websites availability (GET request)
2118
+ msg = await self.check_all_websites(is_private=is_private, display_only_if_critical=False)
2119
+ if msg != "":
2120
+ if out_msg != "":
2121
+ out_msg += "\n"
2122
+ out_msg += msg
2123
+
1979
2124
  # Ports
1980
2125
  msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=False, restart_if_down=False)
1981
2126
  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.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