LinuxMonitor 1.2.7__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.2.7
3
+ Version: 1.3.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
@@ -61,6 +61,7 @@ Additionnal functionalities:
61
61
  - Get processes list (PID and name)
62
62
  - Kill a process by PID
63
63
  - Reboot server
64
+ - Restart/stop a service
64
65
 
65
66
  ## How to install (python script and shell)
66
67
 
@@ -84,7 +85,7 @@ echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/mariadb" >> /etc/sudoers.d/U
84
85
 
85
86
  ## How to use in shell
86
87
 
87
- ```shell
88
+ ```sh
88
89
  # Get help
89
90
  python3 -m linuxmonitor --help
90
91
  # Use "--debug" to show more information during command
@@ -135,6 +136,9 @@ python3 -m linuxmonitor --restart_all --config_file config-example.json --nodebu
135
136
  # Restart a service
136
137
  python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
137
138
 
139
+ # Stop a service
140
+ python3 -m linuxmonitor --stop_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
141
+
138
142
  # Check ports
139
143
  python3 -m linuxmonitor --ports --config_file config-example.json --nodebug
140
144
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: LinuxMonitor
3
- Version: 1.2.7
3
+ Version: 1.3.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
@@ -61,6 +61,7 @@ Additionnal functionalities:
61
61
  - Get processes list (PID and name)
62
62
  - Kill a process by PID
63
63
  - Reboot server
64
+ - Restart/stop a service
64
65
 
65
66
  ## How to install (python script and shell)
66
67
 
@@ -84,7 +85,7 @@ echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/mariadb" >> /etc/sudoers.d/U
84
85
 
85
86
  ## How to use in shell
86
87
 
87
- ```shell
88
+ ```sh
88
89
  # Get help
89
90
  python3 -m linuxmonitor --help
90
91
  # Use "--debug" to show more information during command
@@ -135,6 +136,9 @@ python3 -m linuxmonitor --restart_all --config_file config-example.json --nodebu
135
136
  # Restart a service
136
137
  python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
137
138
 
139
+ # Stop a service
140
+ python3 -m linuxmonitor --stop_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
141
+
138
142
  # Check ports
139
143
  python3 -m linuxmonitor --ports --config_file config-example.json --nodebug
140
144
 
@@ -33,6 +33,7 @@ Additionnal functionalities:
33
33
  - Get processes list (PID and name)
34
34
  - Kill a process by PID
35
35
  - Reboot server
36
+ - Restart/stop a service
36
37
 
37
38
  ## How to install (python script and shell)
38
39
 
@@ -56,7 +57,7 @@ echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/mariadb" >> /etc/sudoers.d/U
56
57
 
57
58
  ## How to use in shell
58
59
 
59
- ```shell
60
+ ```sh
60
61
  # Get help
61
62
  python3 -m linuxmonitor --help
62
63
  # Use "--debug" to show more information during command
@@ -107,6 +108,9 @@ python3 -m linuxmonitor --restart_all --config_file config-example.json --nodebu
107
108
  # Restart a service
108
109
  python3 -m linuxmonitor --restart_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
109
110
 
111
+ # Stop a service
112
+ python3 -m linuxmonitor --stop_service SERVICE_NAME_HERE --config_file config-example.json --nodebug
113
+
110
114
  # Check ports
111
115
  python3 -m linuxmonitor --ports --config_file config-example.json --nodebug
112
116
 
@@ -23,6 +23,7 @@ def main() -> None:
23
23
  parser.add_argument('--services_status', action='store_true', help='🩺 Check services are running 🩺')
24
24
  parser.add_argument('--restart_all', action='store_true', help='🚀 Restart all services 🚀')
25
25
  parser.add_argument('--restart_service', type=str, help='🚀 Restart a service 🚀')
26
+ parser.add_argument('--stop_service', type=str, help='🚫 Stop a service 🚫')
26
27
  parser.add_argument('--list_services', action='store_true', help='📋 List all available services 📋')
27
28
  parser.add_argument('--ports', action='store_true', help='🔒 Check ports 🔒')
28
29
  parser.add_argument('--list_processes', action='store_true', help='📋 List active processes 📋')
@@ -123,7 +124,7 @@ def main() -> None:
123
124
  if args.services_status:
124
125
  handled = True
125
126
  print("Checking if services are running and restart if down...")
126
- out_msg: str = asyncio.run(monitoring.check_all_services_status_and_restart_if_down(is_private=True))
127
+ out_msg: str = asyncio.run(monitoring.check_all_services_status(is_private=True, display_only_if_critical=False))
127
128
  print(out_msg)
128
129
 
129
130
  if args.restart_all:
@@ -134,19 +135,25 @@ def main() -> None:
134
135
  if args.restart_service is not None:
135
136
  handled = True
136
137
  print(f"Restarting service: {args.restart_service}...")
137
- out_msg: str = asyncio.run(monitoring.restart_service(is_private=True, service_name=args.restart_service))
138
+ out_msg: str = asyncio.run(monitoring.restart_service(is_private=True, service_name=args.restart_service, force_restart=True))
139
+ print(out_msg)
140
+
141
+ if args.stop_service is not None:
142
+ handled = True
143
+ print(f"Stopping service: {args.stop_service}...")
144
+ out_msg: str = asyncio.run(monitoring.stop_service(is_private=True, service_name=args.stop_service))
138
145
  print(out_msg)
139
146
 
140
147
  if args.list_services:
141
148
  handled = True
142
149
  print("Listing all available services...")
143
- out_msg: str = monitoring.get_all_services_allowed_to_restart(is_private=True)
150
+ out_msg: str = monitoring.get_all_services(is_private=True)
144
151
  print(out_msg)
145
152
 
146
153
  if args.ports:
147
154
  handled = True
148
155
  print("Checking ports...")
149
- out_msg: str = asyncio.run(monitoring.check_all_ports(is_private=True, display_only_if_critical=False, restart_if_down=False))
156
+ out_msg: str = asyncio.run(monitoring.check_all_ports(is_private=True, display_only_if_critical=False))
150
157
  print(out_msg)
151
158
 
152
159
  if args.list_processes:
@@ -16,6 +16,9 @@ Non exhaustive list of features (available by using it in shell or in python scr
16
16
  - Check last user connections IPs
17
17
  - Check uptime (to inform if the server has been rebooted)
18
18
 
19
+ - Restart/stop a service
20
+ - List all services
21
+
19
22
  - Get hostname, OS details, kernel version, server datetime, uptime
20
23
  - Get connected users
21
24
 
@@ -30,7 +33,7 @@ __email__ = "quentin@comte-gaz.com"
30
33
  __license__ = "MIT License"
31
34
  __copyright__ = "Copyright Quentin Comte-Gaz (2024)"
32
35
  __python_version__ = "3.+"
33
- __version__ = "1.2.7 (2024/09/16)"
36
+ __version__ = "1.3.0 (2024/09/19)"
34
37
  __status__ = "Usable for any Linux project"
35
38
 
36
39
  import json
@@ -179,7 +182,7 @@ class LinuxMonitor:
179
182
 
180
183
  #region Execute Command
181
184
 
182
- async def execute_and_verify(self, command: List[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
+ 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]:
183
186
  """
184
187
  Execute a shell command asynchronously and verify its correct execution.
185
188
 
@@ -195,11 +198,11 @@ class LinuxMonitor:
195
198
 
196
199
  try:
197
200
  logging.debug(msg=f"Executing command {display_name} (command: {command})...")
198
- process = await asyncio.create_subprocess_exec(
199
- *command,
201
+ process = await asyncio.create_subprocess_shell(
202
+ command,
200
203
  stdout=asyncio.subprocess.PIPE,
201
204
  stderr=asyncio.subprocess.PIPE,
202
- shell=False
205
+ shell=True
203
206
  )
204
207
 
205
208
  try:
@@ -270,7 +273,7 @@ class LinuxMonitor:
270
273
  IMPORTANT: To be usable, the user must have the right to execute `sudo /sbin/reboot` command without password.
271
274
  """
272
275
  logging.info(msg="Rebooting the server...")
273
- _, 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)
276
+ _, 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)
274
277
  return out_msg
275
278
 
276
279
  #endregion
@@ -749,7 +752,7 @@ class LinuxMonitor:
749
752
  :return: A string containing the result message.
750
753
  """
751
754
  try:
752
- ping_command: list[str] = ["ping", "-c", "1", website]
755
+ ping_command: str = f"ping -c 1 {website}"
753
756
  display_name = f"[{display_name}](https://{website})"
754
757
 
755
758
  start_time: float = time.time()
@@ -795,7 +798,10 @@ class LinuxMonitor:
795
798
 
796
799
  #region Check website response
797
800
 
798
- 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]:
801
+ async def _check_website(self, url: str, display_name: str, show_content_if_issue: bool, service_name: str = "",
802
+ timeout_in_sec: int = 5, auth_type: Optional[str] = None, username: Optional[str] = None,
803
+ password: Optional[str] = None, token: Optional[str] = None, additional_allowed_statuses: List[int] = [],
804
+ display_only_if_critical: bool=False) -> Tuple[bool, str]:
799
805
  try:
800
806
  display_name = f"[{display_name}]({url})"
801
807
 
@@ -834,9 +840,11 @@ class LinuxMonitor:
834
840
  # Get the reason phrase (e.g., "Unauthorized" for 401)
835
841
  status_reason: str = responses.get(response.status, "Unknown status")
836
842
  out_msg = f"❌ **{display_name} answered with invalid status code {response.status} - {status_reason}**."
843
+ if show_content_if_issue:
844
+ content: str = (await response.text()).replace('`', '"').strip()
845
+ out_msg += f"\n```sh\n{content}\n```"
837
846
  logging.warning(msg=out_msg)
838
847
  return False, out_msg
839
-
840
848
  except asyncio.TimeoutError:
841
849
  out_msg = f"⚠️ **Error checking {display_name}: Timeout**"
842
850
  return False, out_msg
@@ -852,22 +860,28 @@ class LinuxMonitor:
852
860
  timeout_in_sec: int = website_config.get('timeout_in_sec', 5)
853
861
  url: str = website_config['url']
854
862
  display_name: str = website_config.get('display_name', url)
863
+ show_content_if_issue: bool = website_config.get('show_content_if_issue', False)
864
+ service_name_to_restart: str = website_config.get('service_name_to_restart', "")
855
865
  auth_type: Optional[str] = website_config.get('auth_type', None)
856
866
  username: Optional[str] = website_config.get('username', None)
857
867
  password: Optional[str] = website_config.get('password', None)
858
868
  token: Optional[str] = website_config.get('token', None)
859
869
  additional_allowed_statuses: List[int] = website_config.get('additional_allowed_statuses', [])
860
870
 
861
- _, msg = await self._check_website(url=url, display_name=display_name, timeout_in_sec=timeout_in_sec,
862
- auth_type=auth_type, username=username, password=password, token=token,
863
- additional_allowed_statuses=additional_allowed_statuses,
864
- display_only_if_critical=display_only_if_critical)
865
-
871
+ res, msg = await self._check_website(url=url, display_name=display_name, timeout_in_sec=timeout_in_sec,
872
+ show_content_if_issue=show_content_if_issue,
873
+ auth_type=auth_type, username=username, password=password, token=token,
874
+ additional_allowed_statuses=additional_allowed_statuses,
875
+ display_only_if_critical=display_only_if_critical)
866
876
  if msg != "":
867
877
  if out_msg:
868
878
  out_msg += "\n"
869
879
  out_msg += f"- {msg}"
870
880
 
881
+ if not res and service_name_to_restart != "":
882
+ restart_msg = await self.restart_service(is_private=is_private, service_name=service_name_to_restart, force_restart=False)
883
+ out_msg += f"\n - {restart_msg}"
884
+
871
885
  if out_msg != "":
872
886
  out_msg = f"# 🌐 Website access state 🌐\n{out_msg}"
873
887
 
@@ -881,7 +895,7 @@ class LinuxMonitor:
881
895
 
882
896
  #region Services
883
897
 
884
- def get_all_services_allowed_to_restart(self, is_private: bool) -> str:
898
+ def get_all_services(self, is_private: bool) -> str:
885
899
  """
886
900
  Get all services allowed to restart which are configured in the JSON configuration file.
887
901
 
@@ -894,12 +908,24 @@ class LinuxMonitor:
894
908
  service = self.config['services'][service_name]
895
909
  if is_private or service['is_private'] == is_private:
896
910
  is_allowed_to_restart: bool = 'restart_command' in service
911
+ is_allowed_to_stop: bool = 'stop_command' in service
912
+ auto_restart: bool = service.get('force_restart', False)
897
913
 
898
914
  if out_msg != "":
899
915
  out_msg += "\n"
900
- out_msg += f"- `{service_name}`: {service['display_name']}"
916
+ out_msg += f"- `{service_name}`: {service['display_name']}:\n"
901
917
  if not is_allowed_to_restart:
902
- out_msg += " (❌ Not authorized to restart)"
918
+ out_msg += " - ❌ Not authorized to restart (no 'restart_command')\n"
919
+ else:
920
+ if auto_restart:
921
+ out_msg += " - ✅ Can be restarted (automatically & manually)\n"
922
+ else:
923
+ out_msg += " - ✅ Can be restarted (manually only because 'force_restart' = false)\n"
924
+
925
+ if is_allowed_to_stop:
926
+ out_msg += " - ✅ Can be stopped manually"
927
+ else:
928
+ out_msg += " - ❌ Not authorized to stop manually (no 'stop_command')"
903
929
 
904
930
  if out_msg != "":
905
931
  out_msg = f"# 🔄 Services list 🔄\n{out_msg}"
@@ -909,7 +935,7 @@ class LinuxMonitor:
909
935
  logging.info(msg=out_msg)
910
936
  return out_msg
911
937
 
912
- async def restart_service(self, is_private: bool, service_name: str) -> str:
938
+ async def restart_service(self, is_private: bool, service_name: str, force_restart: bool) -> str:
913
939
  """
914
940
  Restart a specific service.
915
941
 
@@ -936,9 +962,14 @@ class LinuxMonitor:
936
962
  logging.error(msg=out_msg)
937
963
  return out_msg
938
964
 
965
+ if not force_restart and not service.get('force_restart', False):
966
+ out_msg = f"⚠️ **Service {service_name} can be restarted only manually** (change configuration if needed)."
967
+ logging.error(msg=out_msg)
968
+ return out_msg
969
+
939
970
  timeout_in_sec: int = service.get('timeout_in_sec', 90)
940
971
 
941
- service_call: List[str] = service['restart_command']
972
+ service_call: str = service['restart_command']
942
973
  out_msg: str = ""
943
974
 
944
975
  logging.info(f"Trying to restart {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
@@ -975,7 +1006,7 @@ class LinuxMonitor:
975
1006
  out_msg: str = ""
976
1007
  for service_name in self.config["services"].keys():
977
1008
  if is_private or self.config["services"][service_name]['is_private'] == is_private:
978
- res: str = await self.restart_service(is_private=is_private, service_name=service_name)
1009
+ res: str = await self.restart_service(is_private=is_private, service_name=service_name, force_restart=True)
979
1010
  if res != "":
980
1011
  if out_msg:
981
1012
  out_msg += "\n"
@@ -1012,7 +1043,7 @@ class LinuxMonitor:
1012
1043
  logging.error(msg=f"Status command (status_command) not found for service {service_name}")
1013
1044
  return None, ""
1014
1045
 
1015
- status_command = service['status_command']
1046
+ status_command: str = service['status_command']
1016
1047
  check_also_stdout_not_containing = service.get('check_also_stdout_not_containing', None)
1017
1048
 
1018
1049
  res, out_msg = await self.execute_and_verify(command=status_command, display_name=f"état de {display_name}", show_only_std_and_exception=True, timeout_in_sec=timeout_in_sec, display_only_if_critical=False, check_also_stdout_not_containing=check_also_stdout_not_containing)
@@ -1022,7 +1053,7 @@ class LinuxMonitor:
1022
1053
  logging.exception(msg=out_msg)
1023
1054
  return None, out_msg
1024
1055
 
1025
- async def check_all_services_status(self, is_private: bool) -> str:
1056
+ async def check_all_services_status(self, is_private: bool, display_only_if_critical: bool=False) -> str:
1026
1057
  """
1027
1058
  Check the status of all services configured in the JSON configuration file.
1028
1059
 
@@ -1047,58 +1078,85 @@ class LinuxMonitor:
1047
1078
  "❌"
1048
1079
  )
1049
1080
 
1050
- out_msg = "# 📱 Services status 📱\n"
1051
-
1052
1081
  for service_name in self.config["services"].keys():
1053
1082
  if is_private or self.config["services"][service_name]['is_private'] == is_private:
1054
1083
  status, status_msg = await self._get_service_status(is_private=is_private, service_name=service_name)
1055
1084
  service_status: str = status_to_string(res=status)
1056
1085
  service_icon: str = status_to_icon(res=status)
1057
- if out_msg != "":
1058
- out_msg += "\n"
1059
- out_msg += f"- {service_icon} {self.config['services'][service_name]['display_name']}: **{service_status}**"
1060
- if status != True and status_msg != "":
1061
- out_msg += f"\n{status_msg}"
1086
+
1087
+ if status is not True or not display_only_if_critical:
1088
+ if out_msg != "":
1089
+ out_msg += "\n"
1090
+ out_msg += f"- {service_icon} {self.config['services'][service_name]['display_name']}: **{service_status}**"
1091
+ if status != True and status_msg != "":
1092
+ out_msg += f"\n{status_msg}"
1093
+
1094
+ if status is False:
1095
+ out_msg += f"\n - " + await self.restart_service(is_private=is_private, service_name=service_name, force_restart=False)
1096
+
1062
1097
  except Exception as e:
1063
1098
  out_msg = f"**Internal error checking services status**:\n```sh\n{e}\n```"
1064
1099
  logging.exception(msg=out_msg)
1065
1100
 
1101
+
1102
+ if out_msg != "":
1103
+ out_msg = f"# 📱 Services status 📱\n{out_msg}"
1104
+
1066
1105
  return out_msg
1067
1106
 
1068
- async def check_all_services_status_and_restart_if_down(self, is_private: bool) -> str:
1107
+ async def stop_service(self, is_private: bool, service_name: str) -> str:
1069
1108
  """
1070
- Check the status of all services configured in the JSON configuration file and restart them if they are inactive.
1109
+ Stop a specific service.
1071
1110
 
1072
- :param is_private: User permission to check private or public services (True for private, False for public).
1111
+ :param is_private: User permission to stop private or public services (True for private, False for public).
1112
+ :param service_name: The name of the service name to stop (as configured in the JSON configuration file).
1073
1113
 
1074
1114
  :return: A string containing the result message.
1075
1115
  """
1076
- out_msg_full: str = ""
1116
+ out_msg: str = ""
1077
1117
  try:
1078
- for service_name in self.config["services"].keys():
1079
- out_msg: str = ""
1080
- if is_private or self.config["services"][service_name]['is_private'] == is_private:
1081
- status, status_msg = await self._get_service_status(is_private=is_private, service_name=service_name)
1082
- if status is False:
1083
- out_msg = f"- ❌ **{self.config['services'][service_name]['display_name']} inactive**. Restarting the service is necessary."
1084
- logging.warning(msg=out_msg)
1085
- if status_msg != "":
1086
- out_msg += f"\n{status_msg}"
1118
+ if service_name not in self.config['services'].keys():
1119
+ out_msg = f"❌ **Service {service_name} not found**."
1120
+ logging.error(msg=out_msg)
1121
+ return out_msg
1087
1122
 
1088
- out_msg_full += out_msg + "\n"
1089
- out_msg_full += " - " + await self.restart_service(is_private=is_private, service_name=service_name) + "\n"
1090
- elif status is None:
1091
- out_msg: str = f"- ⚠️ **Error checking status of {self.config['services'][service_name]['display_name']}** (not restarting it)."
1092
- logging.error(msg=out_msg)
1093
- out_msg_full += out_msg + "\n"
1094
- except Exception as e:
1095
- out_msg_full = f"- **Internal error during service status check.**:\n```sh\n{e}\n```"
1096
- logging.exception(msg=out_msg_full)
1123
+ service = self.config['services'][service_name]
1124
+ if not is_private and is_private != service['is_private']:
1125
+ out_msg = f"❌ **Service {service_name} not authorized to stop in public**."
1126
+ logging.error(msg=out_msg)
1127
+ return out_msg
1128
+
1129
+ display_name: str = service.get('display_name', service_name)
1130
+ if 'stop_command' not in service:
1131
+ out_msg = f"❌ **Stop command not found for {display_name}**."
1132
+ logging.error(msg=out_msg)
1133
+ return out_msg
1134
+
1135
+ timeout_in_sec: int = service.get('timeout_in_sec', 90)
1097
1136
 
1098
- if out_msg_full != "":
1099
- out_msg_full = f"# 📱 Services status 📱\n{out_msg_full}"
1137
+ service_call: str = service['stop_command']
1138
+ out_msg: str = ""
1100
1139
 
1101
- return out_msg_full
1140
+ logging.info(f"Trying to stop {display_name} (command: {service_call}) in less than {timeout_in_sec}sec...")
1141
+
1142
+ start_time: float = time.time()
1143
+ res, res_msg = await self.execute_and_verify(command=service_call, display_name=f"stop {display_name}", show_only_std_and_exception=True, timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1144
+ end_time: float = time.time()
1145
+
1146
+ if res == True:
1147
+ readable_duration: str = "{:.2f}".format(end_time - start_time)
1148
+ out_msg = f"✅ **{display_name} stopped with success** in {readable_duration}sec."
1149
+ logging.info(msg=out_msg)
1150
+ else:
1151
+ out_msg = f"❌ **Error stopping service {display_name}**"
1152
+ if res_msg and res_msg != "":
1153
+ out_msg += f"\n{res_msg}"
1154
+ logging.warning(msg=out_msg)
1155
+ except Exception as e:
1156
+ out_msg = f"⚠️ **Error stopping service {service_name}**:\n```sh\n{e}\n```"
1157
+ logging.exception(msg=out_msg)
1158
+
1159
+ return out_msg
1102
1160
 
1103
1161
  #endregion
1104
1162
 
@@ -1372,7 +1430,8 @@ class LinuxMonitor:
1372
1430
  command: str = f"last --ip --hostlast --time-format iso --since '{past_date}' | grep 'pts/'"
1373
1431
  last_output: str = subprocess.check_output(
1374
1432
  args=command,
1375
- shell=True, text=True
1433
+ shell=True,
1434
+ text=True
1376
1435
  ).strip()
1377
1436
  except subprocess.CalledProcessError as e:
1378
1437
  logging.error(msg=f"Error getting recent user logins:\n{e}")
@@ -1503,13 +1562,12 @@ class LinuxMonitor:
1503
1562
 
1504
1563
  return res, out_msg
1505
1564
 
1506
- async def check_all_ports(self, is_private: bool, display_only_if_critical: bool=False, restart_if_down: bool=False) -> str:
1565
+ async def check_all_ports(self, is_private: bool, display_only_if_critical: bool=False) -> str:
1507
1566
  """
1508
1567
  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).
1509
1568
 
1510
1569
  :param is_private: User permission to check private or public ports (True for private, False for public).
1511
1570
  :param display_only_if_critical: If True, the string result will only be returned if there is an error during execution.
1512
- :param restart_if_down: If True, restart the service if the port is down.
1513
1571
 
1514
1572
  :return: A string containing the result message.
1515
1573
  """
@@ -1524,7 +1582,7 @@ class LinuxMonitor:
1524
1582
  want_port_to_be_open: bool = port_config.get('want_port_to_be_open', True)
1525
1583
 
1526
1584
  service_name_to_restart: str = ""
1527
- if want_port_to_be_open and restart_if_down:
1585
+ if want_port_to_be_open:
1528
1586
  service_name_to_restart = port_config.get('service_name_to_restart', "")
1529
1587
 
1530
1588
  result, result_msg = self._is_port_in_required_state(port=port, host=host, timeout_in_sec=timeout_in_sec, want_port_to_be_open=want_port_to_be_open, display_name=display_name)
@@ -1533,11 +1591,11 @@ class LinuxMonitor:
1533
1591
  out_msg += "\n"
1534
1592
  out_msg += f"- {result_msg}"
1535
1593
 
1536
- if (not result) and restart_if_down and service_name_to_restart != "":
1594
+ if (not result) and service_name_to_restart != "":
1537
1595
  if out_msg != "":
1538
1596
  out_msg += "\n"
1539
1597
 
1540
- restart_res: str = await self.restart_service(is_private=is_private, service_name=service_name_to_restart)
1598
+ restart_res: str = await self.restart_service(is_private=is_private, service_name=service_name_to_restart, force_restart=False)
1541
1599
  out_msg += f" - {restart_res}"
1542
1600
 
1543
1601
  if out_msg != "":
@@ -1658,7 +1716,7 @@ class LinuxMonitor:
1658
1716
  process_cmdline = process_cmdline[:60] + '...'
1659
1717
 
1660
1718
  # Attempt to terminate the process
1661
- terminate_command: List[str] = ['sudo', '/bin/kill', '-TERM', str(pid)]
1719
+ terminate_command: str = f"sudo /bin/kill -TERM {pid}"
1662
1720
  result_terminate, _ = await self.execute_and_verify(command=terminate_command, display_name=f"stop process {pid} ({process_name})", show_only_std_and_exception=False, timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1663
1721
 
1664
1722
  if result_terminate == True:
@@ -1670,7 +1728,7 @@ class LinuxMonitor:
1670
1728
  out_msg = f"⚠️ **Stopping nicely {pid} ({process_name}) expired**.\n"
1671
1729
 
1672
1730
  # Attempt to kill the process
1673
- kill_command = ['sudo', '/bin/kill', '-KILL', str(pid)]
1731
+ kill_command: str = f"sudo /bin/kill -KILL {pid}"
1674
1732
  _, strerror_kill = await self.execute_and_verify(command=kill_command, display_name=f"kill process {pid} ({process_name})", show_only_std_and_exception=True, timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
1675
1733
  out_msg += strerror_kill
1676
1734
 
@@ -1793,7 +1851,7 @@ class LinuxMonitor:
1793
1851
  logging.info(msg="Checking services status and all disk usage, CPU, RAM, Swap, CPU temperature and ping of websites periodically...")
1794
1852
  try:
1795
1853
  # Services status
1796
- msg: str = await self.check_all_services_status_and_restart_if_down(is_private=is_private)
1854
+ msg: str = await self.check_all_services_status(is_private=is_private, display_only_if_critical=True)
1797
1855
  if msg != "":
1798
1856
  logging.warning(msg=msg)
1799
1857
  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):
@@ -1919,7 +1977,7 @@ class LinuxMonitor:
1919
1977
  logging.info(msg="- ✅ All websites availability (GET requests) are OK.")
1920
1978
 
1921
1979
  # Ports
1922
- msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=True, restart_if_down=True)
1980
+ msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=True)
1923
1981
  if msg != "":
1924
1982
  logging.warning(msg=msg)
1925
1983
  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):
@@ -2100,7 +2158,7 @@ class LinuxMonitor:
2100
2158
  logging.info(msg="-----------------------------------------------")
2101
2159
 
2102
2160
  # Services status
2103
- msg: str = await self.check_all_services_status_and_restart_if_down(is_private=is_private)
2161
+ msg: str = await self.check_all_services_status(is_private=is_private, display_only_if_critical=False)
2104
2162
  if msg != "":
2105
2163
  if out_msg != "":
2106
2164
  out_msg += "\n"
@@ -2142,7 +2200,7 @@ class LinuxMonitor:
2142
2200
  out_msg += msg
2143
2201
 
2144
2202
  # Ports
2145
- msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=False, restart_if_down=False)
2203
+ msg = await self.check_all_ports(is_private=is_private, display_only_if_critical=False)
2146
2204
  if msg != "":
2147
2205
  if out_msg != "":
2148
2206
  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.2.7",
9
+ version="1.3.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