LinuxMonitor 1.3.4__tar.gz → 1.3.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.3.4
3
+ Version: 1.3.7
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
@@ -54,6 +54,7 @@ List of 'check' functionalities:
54
54
  - Check certificates expiration and validity
55
55
  - Check last user connections IPs
56
56
  - Check uptime (to inform if the server has been rebooted)
57
+ - Check custom commands
57
58
 
58
59
  Additionnal functionalities:
59
60
  - Get hostname, OS details, kernel version, server datetime, uptime
@@ -62,6 +63,7 @@ Additionnal functionalities:
62
63
  - Kill a process by PID
63
64
  - Reboot server
64
65
  - Restart/stop a service
66
+ - Execute custom commands
65
67
 
66
68
  ## How to install (python script and shell)
67
69
 
@@ -150,6 +152,15 @@ python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.jso
150
152
 
151
153
  # Restart the entire server
152
154
  python3 -m linuxmonitor --reboot_server --config_file config-example.json --nodebug
155
+
156
+ # List all custom commands
157
+ python3 -m linuxmonitor --list_commands --config_file config-example.json --nodebug
158
+
159
+ # Execute a custom commands
160
+ python3 -m linuxmonitor --execute_command CUSTOM_COMMAND_HERE --config_file config-example.json --nodebug
161
+
162
+ # Execute all custom commands
163
+ python3 -m linuxmonitor --execute_all_commands --config_file config-example.json --nodebug
153
164
  ```
154
165
 
155
166
  ## How to use in python script
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.3.4
3
+ Version: 1.3.7
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
@@ -54,6 +54,7 @@ List of 'check' functionalities:
54
54
  - Check certificates expiration and validity
55
55
  - Check last user connections IPs
56
56
  - Check uptime (to inform if the server has been rebooted)
57
+ - Check custom commands
57
58
 
58
59
  Additionnal functionalities:
59
60
  - Get hostname, OS details, kernel version, server datetime, uptime
@@ -62,6 +63,7 @@ Additionnal functionalities:
62
63
  - Kill a process by PID
63
64
  - Reboot server
64
65
  - Restart/stop a service
66
+ - Execute custom commands
65
67
 
66
68
  ## How to install (python script and shell)
67
69
 
@@ -150,6 +152,15 @@ python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.jso
150
152
 
151
153
  # Restart the entire server
152
154
  python3 -m linuxmonitor --reboot_server --config_file config-example.json --nodebug
155
+
156
+ # List all custom commands
157
+ python3 -m linuxmonitor --list_commands --config_file config-example.json --nodebug
158
+
159
+ # Execute a custom commands
160
+ python3 -m linuxmonitor --execute_command CUSTOM_COMMAND_HERE --config_file config-example.json --nodebug
161
+
162
+ # Execute all custom commands
163
+ python3 -m linuxmonitor --execute_all_commands --config_file config-example.json --nodebug
153
164
  ```
154
165
 
155
166
  ## How to use in python script
@@ -26,6 +26,7 @@ List of 'check' functionalities:
26
26
  - Check certificates expiration and validity
27
27
  - Check last user connections IPs
28
28
  - Check uptime (to inform if the server has been rebooted)
29
+ - Check custom commands
29
30
 
30
31
  Additionnal functionalities:
31
32
  - Get hostname, OS details, kernel version, server datetime, uptime
@@ -34,6 +35,7 @@ Additionnal functionalities:
34
35
  - Kill a process by PID
35
36
  - Reboot server
36
37
  - Restart/stop a service
38
+ - Execute custom commands
37
39
 
38
40
  ## How to install (python script and shell)
39
41
 
@@ -122,6 +124,15 @@ python3 -m linuxmonitor --kill_process PID_HERE --config_file config-example.jso
122
124
 
123
125
  # Restart the entire server
124
126
  python3 -m linuxmonitor --reboot_server --config_file config-example.json --nodebug
127
+
128
+ # List all custom commands
129
+ python3 -m linuxmonitor --list_commands --config_file config-example.json --nodebug
130
+
131
+ # Execute a custom commands
132
+ python3 -m linuxmonitor --execute_command CUSTOM_COMMAND_HERE --config_file config-example.json --nodebug
133
+
134
+ # Execute all custom commands
135
+ python3 -m linuxmonitor --execute_all_commands --config_file config-example.json --nodebug
125
136
  ```
126
137
 
127
138
  ## How to use in python script
@@ -28,6 +28,9 @@ def main() -> None:
28
28
  parser.add_argument('--ports', action='store_true', help='🔒 Check ports 🔒')
29
29
  parser.add_argument('--list_processes', action='store_true', help='📋 List active processes 📋')
30
30
  parser.add_argument('--kill_process', type=int, help='🚫 Stop a process by PID 🚫')
31
+ parser.add_argument('--list_commands', action='store_true', help='📋 List all available commands 📋')
32
+ parser.add_argument('execute_command', type=str, help='📋 Execute a command 📋')
33
+ parser.add_argument('execute_all_commands', type=str, help='📋 Execute all commands 📋')
31
34
 
32
35
  parser.add_argument('--debug', action='store_true', help='Enable debug mode')
33
36
  parser.add_argument('--nodebug', action='store_true', help='Disable all logs')
@@ -150,6 +153,24 @@ def main() -> None:
150
153
  out_msg: str = monitoring.get_all_services(is_private=True)
151
154
  print(out_msg)
152
155
 
156
+ if args.list_commands:
157
+ handled = True
158
+ print("Listing all available commands...")
159
+ out_msg: str = asyncio.run(monitoring.list_commands(is_private=True))
160
+ print(out_msg)
161
+
162
+ if args.execute_command is not None:
163
+ handled = True
164
+ print(f"Executing command: {args.execute_command}...")
165
+ out_msg: str = asyncio.run(monitoring.execute_command(is_private=True, command_name=args.execute_command))
166
+ print(out_msg)
167
+
168
+ if args.execute_all_commands is not None:
169
+ handled = True
170
+ print(f"Executing all commands...")
171
+ out_msg: str = asyncio.run(monitoring.execute_all_commands(is_private=True))
172
+ print(out_msg)
173
+
153
174
  if args.ports:
154
175
  handled = True
155
176
  print("Checking ports...")
@@ -33,7 +33,7 @@ __email__ = "quentin@comte-gaz.com"
33
33
  __license__ = "MIT License"
34
34
  __copyright__ = "Copyright Quentin Comte-Gaz (2024)"
35
35
  __python_version__ = "3.+"
36
- __version__ = "1.3.4 (2024/09/19)"
36
+ __version__ = "1.3.7 (2024/09/19)"
37
37
  __status__ = "Usable for any Linux project"
38
38
 
39
39
  import json
@@ -182,7 +182,47 @@ class LinuxMonitor:
182
182
 
183
183
  #region Execute Command
184
184
 
185
- async def execute_and_verify(self, command: str, display_name: str, show_only_std_and_exception: bool, timeout_in_sec: Optional[int] = None, display_only_if_critical: bool = False, check_also_stdout_not_containing: Optional[str] = None) -> Tuple[Optional[bool], str]:
185
+ def _json_to_md(self, json_string: str, level: int = 0) -> str:
186
+ """
187
+ Convert a JSON string into a Markdown representation.
188
+
189
+ Parameters:
190
+ json_string (str): The input JSON string.
191
+ level (int): The current indentation level (default is 0).
192
+
193
+ Returns:
194
+ str: The resulting Markdown formatted string.
195
+ """
196
+ md: str = ""
197
+ indent: str = " " * level # type: ignore # Indentation for markdown headings
198
+
199
+ # Parse the JSON string to a Python object
200
+ try:
201
+ data = json.loads(json_string)
202
+ except json.JSONDecodeError as e:
203
+ return f"Error parsing JSON: {str(e)}"
204
+
205
+ # Helper function to convert the parsed JSON to markdown
206
+ def process_data(data, level: int) -> None: # type: ignore
207
+ nonlocal md # Declare md as nonlocal so it can be modified
208
+ indent = " " * level
209
+
210
+ if isinstance(data, dict):
211
+ for key, value in data.items(): # type: ignore
212
+ md += f"{indent}- {key}\n"
213
+ process_data(value, level + 1) # type: ignore
214
+ elif isinstance(data, list):
215
+ for index, item in enumerate(data): # type: ignore
216
+ md += f"{indent}- Item {index + 1}\n"
217
+ process_data(item, level + 1) # type: ignore
218
+ else:
219
+ md += f"{indent} - {data}\n"
220
+
221
+ process_data(data, level)
222
+
223
+ return md
224
+
225
+ async def execute_and_verify(self, command: str, display_name: str, show_only_std_and_exception: bool, timeout_in_sec: Optional[int] = None, display_only_if_critical: bool = False, check_also_stdout_not_containing: Optional[str] = None, is_stdout_json: bool=False) -> Tuple[Optional[bool], str]:
186
226
  """
187
227
  Execute a shell command asynchronously and verify its correct execution.
188
228
 
@@ -211,11 +251,11 @@ class LinuxMonitor:
211
251
  except asyncio.TimeoutError:
212
252
  logging.warning(msg=f"Timeout expired for {display_name} (command: {command})")
213
253
  process.kill()
214
- return None, f"⚠️ **Error {display_name}**:\n- Failed to execute the command in less than {timeout_in_sec} seconds)"
254
+ return None, f" - ⚠️ **Error {display_name}**:\n - Failed to execute the command in less than {timeout_in_sec} seconds)"
215
255
  except Exception as e:
216
256
  # Failed to execute the command
217
257
  if not show_only_std_and_exception:
218
- out_msg = f"⚠️ **Error {display_name}**:\n```sh\n{e}\n```"
258
+ out_msg = f" - ⚠️ **Error {display_name}**:\n```sh\n{e}\n```"
219
259
  else:
220
260
  out_msg = f"```sh\n{e}\n```"
221
261
  logging.exception(msg=out_msg)
@@ -234,24 +274,27 @@ class LinuxMonitor:
234
274
  if res == False:
235
275
  # Command returned an error code
236
276
  if not show_only_std_and_exception:
237
- out_msg = f"❌ **Error {display_name}**\n"
277
+ out_msg = f" - ❌ **Error {display_name}**\n"
238
278
  out_msg += f" - Error code: `{returncode}`"
239
279
 
240
280
  if stderr_str != "":
241
281
  out_msg += f"\n - Error log:\n```sh\n{stderr_str}\n```"
242
282
 
243
283
  if stdout_str != "":
244
- out_msg += f"\n - Info:\n```sh\n{stdout_str}\n```"
284
+ msg_stout: str = stdout_str
285
+ if is_stdout_json:
286
+ msg_stout = self._json_to_md(stdout_str)
287
+ out_msg += f"\n - Info:\n```sh\n{msg_stout}\n```"
245
288
  elif res == None:
246
289
  # Command timed out
247
290
  if not show_only_std_and_exception:
248
- out_msg = f"⚠️ **Error {display_name}**\n"
249
- out_msg += f"- Failed to wait for the command to finish (due to timeout of {timeout_in_sec} seconds)"
291
+ out_msg = f" - ⚠️ **Error {display_name}**\n"
292
+ out_msg += f" - Failed to wait for the command to finish (due to timeout of {timeout_in_sec} seconds)"
250
293
  else:
251
294
  # Command executed successfully
252
295
  if not display_only_if_critical:
253
296
  if not show_only_std_and_exception:
254
- out_msg = f"✅ **{display_name} executed successfully**"
297
+ out_msg = f" - ✅ **{display_name} executed successfully**"
255
298
 
256
299
  if res == True:
257
300
  logging.info(msg=f"Command {display_name} (command {command}) executed successfully")
@@ -274,6 +317,10 @@ class LinuxMonitor:
274
317
  """
275
318
  logging.info(msg="Rebooting the server...")
276
319
  _, out_msg = await self.execute_and_verify(command="sudo /sbin/reboot", display_name="Server reboot", show_only_std_and_exception=False, timeout_in_sec=None, display_only_if_critical=False)
320
+
321
+ if out_msg != "":
322
+ out_msg = f"# 🔄 Reboot 🔄\n{out_msg}"
323
+
277
324
  return out_msg
278
325
 
279
326
  #endregion
@@ -963,7 +1010,7 @@ class LinuxMonitor:
963
1010
  return out_msg
964
1011
 
965
1012
  if not force_restart and not service.get('auto_restart', False):
966
- out_msg = f"⚠️ **Service {service_name} can be restarted only manually** (change configuration if needed)."
1013
+ out_msg = f"⚠️ **Service {display_name} can be restarted only manually** (change configuration if needed)."
967
1014
  logging.error(msg=out_msg)
968
1015
  return out_msg
969
1016
 
@@ -1809,6 +1856,126 @@ class LinuxMonitor:
1809
1856
 
1810
1857
  #endregion
1811
1858
 
1859
+ #region Commands
1860
+
1861
+ async def _execute_command(self, command: str, display_name: str, show_content_if_success: bool, show_content_if_issue: bool, is_content_json: bool, timeout_in_sec: int, display_only_if_critical: bool) -> str:
1862
+ try:
1863
+ out_msg: str = ""
1864
+
1865
+ # Execute the command
1866
+ start_time: float = time.time()
1867
+ res, res_msg = await self.execute_and_verify(command=command, display_name=display_name, show_only_std_and_exception=True, timeout_in_sec=timeout_in_sec, display_only_if_critical=display_only_if_critical, is_stdout_json=is_content_json)
1868
+ end_time: float = time.time()
1869
+
1870
+ if res == True:
1871
+ readable_duration: str = "{:.2f}".format(end_time - start_time)
1872
+ out_msg = f"✅ **{display_name} executed with success** in {readable_duration}sec."
1873
+ logging.info(msg=out_msg)
1874
+
1875
+ if show_content_if_success:
1876
+ out_msg += f"\n{res_msg}"
1877
+
1878
+ elif res == False:
1879
+ out_msg = f"⚠️ **{display_name} failed to execute**."
1880
+ logging.warning(msg=out_msg)
1881
+
1882
+ if show_content_if_issue:
1883
+ out_msg += f"\n{res_msg}"
1884
+
1885
+ else:
1886
+ out_msg = f"⚠️ **{display_name} expired**."
1887
+ logging.warning(msg=out_msg)
1888
+
1889
+ return out_msg
1890
+ except Exception as e:
1891
+ out_msg = f"⚠️ **Error while executing {display_name}**:\n```sh\n{e}\n```"
1892
+ logging.exception(msg=out_msg)
1893
+ return out_msg
1894
+
1895
+ async def execute_command(self, is_private: bool, command_name: str) -> str:
1896
+ try:
1897
+ out_msg: str = ""
1898
+ display_name: str = command_name
1899
+ for command_config_key in self.config['commands'].keys():
1900
+ if self.config['commands'][command_config_key]['command'] == command_name:
1901
+ display_name = self.config['commands'][command_config_key]['display_name']
1902
+ command: str = self.config['commands'][command_config_key]['command']
1903
+ is_private_cmd: bool = self.config['commands'][command_config_key]['is_private']
1904
+
1905
+ if is_private or is_private == is_private_cmd:
1906
+ show_content_if_success: bool = self.config['commands'][command_config_key].get('show_content_if_success', False)
1907
+ show_content_if_issue: bool = self.config['commands'][command_config_key].get('show_content_if_issue', False)
1908
+ is_content_json: bool = self.config['commands'][command_config_key].get('is_content_json', False)
1909
+ timeout_in_sec: int = self.config['commands'][command_config_key].get('timeout_in_sec', 10)
1910
+
1911
+ res: str = await self._execute_command(command=command, display_name=display_name, show_content_if_success=show_content_if_success, show_content_if_issue=show_content_if_issue, is_content_json=is_content_json, timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1912
+ if res != "":
1913
+ if out_msg != "":
1914
+ out_msg += "\n"
1915
+ out_msg += f"{res}"
1916
+ else:
1917
+ out_msg = f"⚠️ **Command {display_name} is not allowed**"
1918
+ logging.warning(msg=out_msg)
1919
+
1920
+ if out_msg != "":
1921
+ out_msg = f"# 📜 Command {display_name} 📜\n{out_msg}"
1922
+
1923
+ return out_msg
1924
+
1925
+ out_msg = f"⚠️ **Command {display_name} not found**"
1926
+ logging.warning(msg=out_msg)
1927
+ return out_msg
1928
+ except Exception as e:
1929
+ return f"⚠️ **Error executing command {command_name}**:\n```sh\n{e}\n```"
1930
+
1931
+ async def execute_all_commands(self, is_private: bool, is_scheduled_tasks_for_issues: bool=False, is_scheduled_tasks_for_infos: bool=False, display_only_if_critical: bool=False) -> str:
1932
+ try:
1933
+ out_msg: str = ""
1934
+
1935
+ for command_config_key in self.config['commands'].keys():
1936
+ if is_private or is_private == self.config['commands'][command_config_key]['is_private']:
1937
+ display_name: str = self.config['commands'][command_config_key]['display_name']
1938
+ command: str = self.config['commands'][command_config_key]['command']
1939
+ show_content_if_success: bool = self.config['commands'][command_config_key].get('show_content_if_success', False)
1940
+ show_content_if_issue: bool = self.config['commands'][command_config_key].get('show_content_if_issue', False)
1941
+ is_content_json: bool = self.config['commands'][command_config_key].get('is_content_json', False)
1942
+ timeout_in_sec: int = self.config['commands'][command_config_key].get('timeout_in_sec', 10)
1943
+
1944
+ if is_scheduled_tasks_for_issues and not self.config['commands'][command_config_key].get('execute_in_scheduled_tasks_for_issues', False):
1945
+ continue
1946
+ if is_scheduled_tasks_for_infos and not self.config['commands'][command_config_key].get('execute_in_scheduled_tasks_for_infos', False):
1947
+ continue
1948
+
1949
+ res: str = await self._execute_command(command=command, display_name=display_name, show_content_if_success=show_content_if_success, show_content_if_issue=show_content_if_issue, is_content_json=is_content_json, timeout_in_sec=timeout_in_sec, display_only_if_critical=display_only_if_critical)
1950
+ if res != "":
1951
+ if out_msg != "":
1952
+ out_msg += "\n"
1953
+ out_msg += f"{res}"
1954
+
1955
+ if out_msg != "":
1956
+ out_msg = f"# 📜 Commands 📜\n{out_msg}"
1957
+
1958
+ return out_msg
1959
+ except Exception as e:
1960
+ return f"⚠️ **Error executing commands**:\n```sh\n{e}\n```"
1961
+
1962
+ async def list_commands(self, is_private: bool) -> str:
1963
+ try:
1964
+ out_msg: str = "# 📜 Commands 📜\n"
1965
+ for command_config_key in self.config['commands'].keys():
1966
+ if is_private or is_private == self.config['commands'][command_config_key]['is_private']:
1967
+ display_name: str = self.config['commands'][command_config_key]['display_name']
1968
+ out_msg += f"- {display_name}\n"
1969
+
1970
+ logging.info(msg=out_msg)
1971
+ return out_msg
1972
+ except Exception as e:
1973
+ out_msg = f"⚠️ **Error listing commands**:\n```sh\n{e}\n```"
1974
+ logging.exception(msg=out_msg)
1975
+ return out_msg
1976
+
1977
+ #endregion
1978
+
1812
1979
  #region Scheduled Tasks
1813
1980
 
1814
1981
  async def schedule_task(self, handle_error_message: Callable[[str], Awaitable[None]], is_private: bool) -> None:
@@ -1839,6 +2006,7 @@ class LinuxMonitor:
1839
2006
  datetime_last_user_logins_error_displayed: Optional[datetime] = None
1840
2007
  datetime_last_port_error_displayed: Optional[datetime] = None
1841
2008
  datetime_last_services_error_displayed: Optional[datetime] = None
2009
+ datetime_last_commands_error_displayed: Optional[datetime] = None
1842
2010
  need_to_check_uptime: bool = True # No need to check uptime every time (since once ok, it can't be wrong)
1843
2011
 
1844
2012
  if not self.start_scheduled_tasks_immediately:
@@ -1997,6 +2165,27 @@ class LinuxMonitor:
1997
2165
  else:
1998
2166
  logging.info(msg="- ✅ All ports are OK.")
1999
2167
 
2168
+ # Commands
2169
+ msg = await self.execute_all_commands(is_private=is_private, is_scheduled_tasks_for_issues=True, is_scheduled_tasks_for_infos=False, display_only_if_critical=True)
2170
+ if msg != "":
2171
+ logging.warning(msg=msg)
2172
+ if datetime_last_commands_error_displayed is None or ((datetime.now() - datetime_last_commands_error_displayed).total_seconds() > self.max_duration_seconds_showing_same_error_again_in_scheduled_tasks):
2173
+ if out_msg != "":
2174
+ out_msg += "\n"
2175
+ out_msg += msg
2176
+ datetime_last_commands_error_displayed = datetime.now()
2177
+ else:
2178
+ logging.warning(msg="Commands critical but already notified less than 12 hours ago, not notifying...")
2179
+ elif datetime_last_commands_error_displayed is not None:
2180
+ msg="✅ **Commands returned to normal state**"
2181
+ logging.info(msg=msg)
2182
+ if out_msg != "":
2183
+ out_msg += "\n"
2184
+ out_msg += msg
2185
+ datetime_last_commands_error_displayed = None
2186
+ else:
2187
+ logging.info(msg="- ✅ All commands are OK.")
2188
+
2000
2189
  # User logins
2001
2190
  if is_private:
2002
2191
  msg = self.check_all_recent_user_logins(display_only_if_critical=True)
@@ -2206,6 +2395,13 @@ class LinuxMonitor:
2206
2395
  out_msg += "\n"
2207
2396
  out_msg += msg
2208
2397
 
2398
+ # Commands
2399
+ msg = await self.execute_all_commands(is_private=is_private, is_scheduled_tasks_for_issues=False, is_scheduled_tasks_for_infos=True, display_only_if_critical=False)
2400
+ if msg != "":
2401
+ if out_msg != "":
2402
+ out_msg += "\n"
2403
+ out_msg += msg
2404
+
2209
2405
  # User logins
2210
2406
  if is_private:
2211
2407
  msg = self.check_all_recent_user_logins(display_only_if_critical=False)
@@ -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.3.4",
9
+ version="1.3.7",
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