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.
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0/LinuxMonitor.egg-info}/PKG-INFO +19 -15
- {linuxmonitor-1.1.2/LinuxMonitor.egg-info → linuxmonitor-1.2.0}/PKG-INFO +19 -15
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/README.md +18 -14
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/linuxmonitor/__main__.py +7 -0
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/linuxmonitor/linuxmonitor.py +276 -131
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/setup.py +1 -1
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/LICENSE.md +0 -0
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/LinuxMonitor.egg-info/SOURCES.txt +0 -0
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/LinuxMonitor.egg-info/dependency_links.txt +0 -0
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/LinuxMonitor.egg-info/requires.txt +0 -0
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/LinuxMonitor.egg-info/top_level.txt +0 -0
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/linuxmonitor/__init__.py +0 -0
- {linuxmonitor-1.1.2 → linuxmonitor-1.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: LinuxMonitor
|
|
3
|
-
Version: 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.
|
|
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.
|
|
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
|
-
|
|
741
|
-
|
|
745
|
+
try:
|
|
746
|
+
ping_command: list[str] = ["ping", "-c", "1", website]
|
|
747
|
+
display_name = f"[{display_name}](https://{website})"
|
|
742
748
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
928
|
+
timeout_in_sec: int = service.get('timeout_in_sec', 90)
|
|
837
929
|
|
|
838
|
-
|
|
839
|
-
|
|
930
|
+
service_call: List[str] = service['restart_command']
|
|
931
|
+
out_msg: str = ""
|
|
840
932
|
|
|
841
|
-
|
|
933
|
+
logging.info(f"Trying to restart {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
|
|
842
934
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
if
|
|
868
|
-
out_msg
|
|
869
|
-
|
|
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
|
-
|
|
872
|
-
|
|
968
|
+
if out_msg:
|
|
969
|
+
out_msg = f"# 📱 Restart services 📱\n{out_msg}"
|
|
873
970
|
|
|
874
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
-
|
|
898
|
-
|
|
999
|
+
status_command = service['status_command']
|
|
1000
|
+
check_also_stdout_not_containing = service.get('check_also_stdout_not_containing', None)
|
|
899
1001
|
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
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
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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
|
-
|
|
1460
|
-
|
|
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
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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
|
-
|
|
1574
|
+
res = f"- PID **{proc['pid']}**: {proc['name']} ("
|
|
1468
1575
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1576
|
+
if proc['cpu_percent'] > 0:
|
|
1577
|
+
res += f"CPU {proc['cpu_percent']}%, "
|
|
1471
1578
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1579
|
+
if proc['memory'] > 0:
|
|
1580
|
+
res += f"RAM {proc['memory'] // (1024 * 1024)} MB, "
|
|
1474
1581
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1582
|
+
res += f"👤 {proc['username']}, "
|
|
1583
|
+
res += f"⏰ {proc['create_time']}"
|
|
1477
1584
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1585
|
+
if proc['cmdline'] != "":
|
|
1586
|
+
res += f", 📄 `{proc['cmdline']}`"
|
|
1480
1587
|
|
|
1481
|
-
|
|
1588
|
+
res += ")"
|
|
1482
1589
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1590
|
+
if full_res != "":
|
|
1591
|
+
full_res += "\n"
|
|
1592
|
+
full_res += res
|
|
1486
1593
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1594
|
+
if full_res != "":
|
|
1595
|
+
full_res = f"# 🔄 Processes 🔄\n{full_res}"
|
|
1489
1596
|
|
|
1490
|
-
|
|
1491
|
-
|
|
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
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1690
|
+
try:
|
|
1691
|
+
interfaces = psutil.net_if_addrs()
|
|
1692
|
+
stats = psutil.net_if_stats()
|
|
1693
|
+
network_usage: str = ""
|
|
1582
1694
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1695
|
+
# List of interfaces to exclude (e.g., local interface)
|
|
1696
|
+
excluded_interfaces = {"lo"}
|
|
1585
1697
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1698
|
+
for interface in interfaces:
|
|
1699
|
+
if interface in excluded_interfaces or interface not in stats:
|
|
1700
|
+
continue
|
|
1589
1701
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
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
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
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
|
-
|
|
1716
|
+
ip_str: str = ", ".join(ip_addresses) if ip_addresses else "N/A" # type: ignore
|
|
1605
1717
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
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
|
-
|
|
1611
|
-
|
|
1722
|
+
if network_usage != "":
|
|
1723
|
+
network_usage = f"# 🌐 Network usage 🌐\n{network_usage}"
|
|
1612
1724
|
|
|
1613
|
-
|
|
1614
|
-
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|