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