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