LinuxMonitor 1.5.6__tar.gz → 1.5.8__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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: LinuxMonitor
3
- Version: 1.5.6
3
+ Version: 1.5.8
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
@@ -25,6 +25,19 @@ License-File: LICENSE.md
25
25
  Requires-Dist: psutil
26
26
  Requires-Dist: typing
27
27
  Requires-Dist: asyncio
28
+ Dynamic: author
29
+ Dynamic: author-email
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: platform
38
+ Dynamic: requires-dist
39
+ Dynamic: requires-python
40
+ Dynamic: summary
28
41
 
29
42
  # Linux Monitor (Python library)
30
43
  [![PyPI version](https://badge.fury.io/py/LinuxMonitor.svg)](https://pypi.org/project/LinuxMonitor/) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor)](https://pepy.tech/project/LinuxMonitor) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor/month)](https://pepy.tech/project/LinuxMonitor)
@@ -85,6 +98,8 @@ echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /bin/systemctl" >> /etc/sudoers.d/USERNA
85
98
  echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/apache2" >> /etc/sudoers.d/USERNAME_HERE
86
99
  echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/mariadb" >> /etc/sudoers.d/USERNAME_HERE
87
100
  ```
101
+ - Install missing packages :
102
+ - If `last` command not available, install needed package: `sudo apt install wtmpdb util-linux`
88
103
  - Check monitor possibilities (you can find it out by calling `python -m linuxmonitor --help` from the root of this repository)
89
104
  - Load your shell or python script
90
105
 
@@ -162,15 +177,45 @@ python3 -m linuxmonitor --list_commands --config_file config-example.json --node
162
177
  # Execute a custom commands
163
178
  python3 -m linuxmonitor --execute_command CUSTOM_COMMAND_HERE --config_file config-example.json --nodebug
164
179
 
180
+ # Execute a custom command with parameters (only for commands with "accept_parameters": true or a {arg1}/{args} placeholder)
181
+ python3 -m linuxmonitor --execute_command unban_ip --parameters "1.2.3.4" --config_file config-example.json --nodebug
182
+
165
183
  # Execute all custom commands
166
184
  python3 -m linuxmonitor --execute_all_commands --config_file config-example.json --nodebug
167
185
  ```
168
186
 
187
+ ## Custom commands with parameters
188
+
189
+ A custom command (in the `commands` section of the config file) can receive parameters at execution time.
190
+ This is opt-in per command: set `"accept_parameters": true` and/or use a placeholder in the `command` string.
191
+
192
+ Placeholders inside the `command` string:
193
+ - `{arg1}`, `{arg2}`, ... : replaced by the Nth provided parameter
194
+ - `{args}` : replaced by all provided parameters
195
+ - no placeholder : all parameters are appended at the end of the command
196
+
197
+ Every parameter is shell-quoted before substitution, so it is always treated as a single literal
198
+ argument. This prevents command injection through the parameters.
199
+
200
+ Example config entry:
201
+ ```json
202
+ "unban_ip": {
203
+ "display_name": "Unban an IP from Fail2Ban",
204
+ "command": "sudo fail2ban-client unban {arg1}",
205
+ "is_private": true,
206
+ "accept_parameters": true,
207
+ "show_content_if_success": true,
208
+ "show_content_if_issue": true,
209
+ "timeout_in_sec": 60
210
+ }
211
+ ```
212
+
169
213
  ## How to use in python script
170
214
 
171
215
  Example of python script using this library:
172
216
  - [Discord Bot Linux Monitor](https://github.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library)
173
217
 
218
+
174
219
  ## License
175
220
 
176
221
  This project is under MIT license. This means you can use it as you want (just don't delete the library header).
@@ -1,3 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: LinuxMonitor
3
+ Version: 1.5.8
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
+ Home-page: https://github.com/QuentinCG/Linux-Monitor-Python-Library
6
+ Author: Quentin Comte-Gaz
7
+ Author-email: quentin@comte-gaz.com
8
+ License: MIT
9
+ Keywords: linux monitor monitoring server service port ping ssl certificate disk folder cpu ram swap usage ip connection
10
+ Platform: Linux
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Natural Language :: English
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: Topic :: System :: Monitoring
18
+ Classifier: Topic :: System :: Networking :: Monitoring
19
+ Classifier: Topic :: System :: Systems Administration
20
+ Classifier: Development Status :: 5 - Production/Stable
21
+ Classifier: Environment :: Console
22
+ Requires-Python: >=3.3
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE.md
25
+ Requires-Dist: psutil
26
+ Requires-Dist: typing
27
+ Requires-Dist: asyncio
28
+ Dynamic: author
29
+ Dynamic: author-email
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: platform
38
+ Dynamic: requires-dist
39
+ Dynamic: requires-python
40
+ Dynamic: summary
41
+
1
42
  # Linux Monitor (Python library)
2
43
  [![PyPI version](https://badge.fury.io/py/LinuxMonitor.svg)](https://pypi.org/project/LinuxMonitor/) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor)](https://pepy.tech/project/LinuxMonitor) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor/month)](https://pepy.tech/project/LinuxMonitor)
3
44
 
@@ -57,6 +98,8 @@ echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /bin/systemctl" >> /etc/sudoers.d/USERNA
57
98
  echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/apache2" >> /etc/sudoers.d/USERNAME_HERE
58
99
  echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/mariadb" >> /etc/sudoers.d/USERNAME_HERE
59
100
  ```
101
+ - Install missing packages :
102
+ - If `last` command not available, install needed package: `sudo apt install wtmpdb util-linux`
60
103
  - Check monitor possibilities (you can find it out by calling `python -m linuxmonitor --help` from the root of this repository)
61
104
  - Load your shell or python script
62
105
 
@@ -134,15 +177,45 @@ python3 -m linuxmonitor --list_commands --config_file config-example.json --node
134
177
  # Execute a custom commands
135
178
  python3 -m linuxmonitor --execute_command CUSTOM_COMMAND_HERE --config_file config-example.json --nodebug
136
179
 
180
+ # Execute a custom command with parameters (only for commands with "accept_parameters": true or a {arg1}/{args} placeholder)
181
+ python3 -m linuxmonitor --execute_command unban_ip --parameters "1.2.3.4" --config_file config-example.json --nodebug
182
+
137
183
  # Execute all custom commands
138
184
  python3 -m linuxmonitor --execute_all_commands --config_file config-example.json --nodebug
139
185
  ```
140
186
 
187
+ ## Custom commands with parameters
188
+
189
+ A custom command (in the `commands` section of the config file) can receive parameters at execution time.
190
+ This is opt-in per command: set `"accept_parameters": true` and/or use a placeholder in the `command` string.
191
+
192
+ Placeholders inside the `command` string:
193
+ - `{arg1}`, `{arg2}`, ... : replaced by the Nth provided parameter
194
+ - `{args}` : replaced by all provided parameters
195
+ - no placeholder : all parameters are appended at the end of the command
196
+
197
+ Every parameter is shell-quoted before substitution, so it is always treated as a single literal
198
+ argument. This prevents command injection through the parameters.
199
+
200
+ Example config entry:
201
+ ```json
202
+ "unban_ip": {
203
+ "display_name": "Unban an IP from Fail2Ban",
204
+ "command": "sudo fail2ban-client unban {arg1}",
205
+ "is_private": true,
206
+ "accept_parameters": true,
207
+ "show_content_if_success": true,
208
+ "show_content_if_issue": true,
209
+ "timeout_in_sec": 60
210
+ }
211
+ ```
212
+
141
213
  ## How to use in python script
142
214
 
143
215
  Example of python script using this library:
144
216
  - [Discord Bot Linux Monitor](https://github.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library)
145
217
 
218
+
146
219
  ## License
147
220
 
148
221
  This project is under MIT license. This means you can use it as you want (just don't delete the library header).
@@ -1,31 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: LinuxMonitor
3
- Version: 1.5.6
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
- Home-page: https://github.com/QuentinCG/Linux-Monitor-Python-Library
6
- Author: Quentin Comte-Gaz
7
- Author-email: quentin@comte-gaz.com
8
- License: MIT
9
- Keywords: linux monitor monitoring server service port ping ssl certificate disk folder cpu ram swap usage ip connection
10
- Platform: Linux
11
- Classifier: Programming Language :: Python :: 3 :: Only
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Operating System :: POSIX :: Linux
14
- Classifier: Natural Language :: English
15
- Classifier: Intended Audience :: Developers
16
- Classifier: Intended Audience :: System Administrators
17
- Classifier: Topic :: System :: Monitoring
18
- Classifier: Topic :: System :: Networking :: Monitoring
19
- Classifier: Topic :: System :: Systems Administration
20
- Classifier: Development Status :: 5 - Production/Stable
21
- Classifier: Environment :: Console
22
- Requires-Python: >=3.3
23
- Description-Content-Type: text/markdown
24
- License-File: LICENSE.md
25
- Requires-Dist: psutil
26
- Requires-Dist: typing
27
- Requires-Dist: asyncio
28
-
29
1
  # Linux Monitor (Python library)
30
2
  [![PyPI version](https://badge.fury.io/py/LinuxMonitor.svg)](https://pypi.org/project/LinuxMonitor/) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/QuentinCG/Linux-Monitor-Python-Library/blob/master/LICENSE.md) [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/QuentinCG) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor)](https://pepy.tech/project/LinuxMonitor) [![Downloads](https://static.pepy.tech/badge/LinuxMonitor/month)](https://pepy.tech/project/LinuxMonitor)
31
3
 
@@ -85,6 +57,8 @@ echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /bin/systemctl" >> /etc/sudoers.d/USERNA
85
57
  echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/apache2" >> /etc/sudoers.d/USERNAME_HERE
86
58
  echo "USERNAME_HERE ALL=(ALL) NOPASSWD: /etc/init.d/mariadb" >> /etc/sudoers.d/USERNAME_HERE
87
59
  ```
60
+ - Install missing packages :
61
+ - If `last` command not available, install needed package: `sudo apt install wtmpdb util-linux`
88
62
  - Check monitor possibilities (you can find it out by calling `python -m linuxmonitor --help` from the root of this repository)
89
63
  - Load your shell or python script
90
64
 
@@ -162,15 +136,45 @@ python3 -m linuxmonitor --list_commands --config_file config-example.json --node
162
136
  # Execute a custom commands
163
137
  python3 -m linuxmonitor --execute_command CUSTOM_COMMAND_HERE --config_file config-example.json --nodebug
164
138
 
139
+ # Execute a custom command with parameters (only for commands with "accept_parameters": true or a {arg1}/{args} placeholder)
140
+ python3 -m linuxmonitor --execute_command unban_ip --parameters "1.2.3.4" --config_file config-example.json --nodebug
141
+
165
142
  # Execute all custom commands
166
143
  python3 -m linuxmonitor --execute_all_commands --config_file config-example.json --nodebug
167
144
  ```
168
145
 
146
+ ## Custom commands with parameters
147
+
148
+ A custom command (in the `commands` section of the config file) can receive parameters at execution time.
149
+ This is opt-in per command: set `"accept_parameters": true` and/or use a placeholder in the `command` string.
150
+
151
+ Placeholders inside the `command` string:
152
+ - `{arg1}`, `{arg2}`, ... : replaced by the Nth provided parameter
153
+ - `{args}` : replaced by all provided parameters
154
+ - no placeholder : all parameters are appended at the end of the command
155
+
156
+ Every parameter is shell-quoted before substitution, so it is always treated as a single literal
157
+ argument. This prevents command injection through the parameters.
158
+
159
+ Example config entry:
160
+ ```json
161
+ "unban_ip": {
162
+ "display_name": "Unban an IP from Fail2Ban",
163
+ "command": "sudo fail2ban-client unban {arg1}",
164
+ "is_private": true,
165
+ "accept_parameters": true,
166
+ "show_content_if_success": true,
167
+ "show_content_if_issue": true,
168
+ "timeout_in_sec": 60
169
+ }
170
+ ```
171
+
169
172
  ## How to use in python script
170
173
 
171
174
  Example of python script using this library:
172
175
  - [Discord Bot Linux Monitor](https://github.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library)
173
176
 
177
+
174
178
  ## License
175
179
 
176
180
  This project is under MIT license. This means you can use it as you want (just don't delete the library header).
@@ -31,6 +31,7 @@ def main() -> None:
31
31
  parser.add_argument('--list_commands', action='store_true', help='📋 List all available commands 📋')
32
32
  parser.add_argument('execute_command', type=str, help='📋 Execute a command 📋')
33
33
  parser.add_argument('execute_all_commands', type=str, help='📋 Execute all commands 📋')
34
+ parser.add_argument('--parameters', type=str, default='', help='Optional parameters passed to --execute_command (space separated, quoted if needed)')
34
35
 
35
36
  parser.add_argument('--debug', action='store_true', help='Enable debug mode')
36
37
  parser.add_argument('--nodebug', action='store_true', help='Disable all logs')
@@ -163,7 +164,7 @@ def main() -> None:
163
164
  if args.execute_command is not None:
164
165
  handled = True
165
166
  print(f"Executing command: {args.execute_command}...")
166
- out_msg: str = asyncio.run(monitoring.execute_command(is_private=True, command_name=args.execute_command))
167
+ out_msg: str = asyncio.run(monitoring.execute_command(is_private=True, command_name=args.execute_command, parameters=args.parameters))
167
168
  print(out_msg)
168
169
 
169
170
  if args.execute_all_commands is not None:
@@ -31,9 +31,9 @@ Non exhaustive list of features (available by using it in shell or in python scr
31
31
  __author__ = 'Quentin Comte-Gaz'
32
32
  __email__ = "quentin@comte-gaz.com"
33
33
  __license__ = "MIT License"
34
- __copyright__ = "Copyright Quentin Comte-Gaz (2024)"
34
+ __copyright__ = "Copyright Quentin Comte-Gaz (2026)"
35
35
  __python_version__ = "3.+"
36
- __version__ = "1.5.6 (2024/11/03)"
36
+ __version__ = "1.5.8 (2026/08/22)"
37
37
  __status__ = "Usable for any Linux project"
38
38
 
39
39
  import json
@@ -48,6 +48,7 @@ import socket
48
48
  from datetime import datetime, timedelta
49
49
  import platform
50
50
  import re
51
+ import shlex
51
52
  import logging
52
53
  import asyncio
53
54
  from http.client import responses
@@ -1574,11 +1575,11 @@ class LinuxMonitor:
1574
1575
  :return: A dictionary containing the recent user logins.
1575
1576
  """
1576
1577
  # Format the date without microseconds (milliseconds)
1577
- past_date: str = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%dT%H:%M:%S')
1578
+ past_date: str = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d %H:%M:%S')
1578
1579
 
1579
1580
  try:
1580
1581
  # Fetch the logs since the specified date
1581
- command: str = f"sudo last --ip --hostlast --time-format iso --since '{past_date}' | grep 'pts/' || true"
1582
+ command: str = f"sudo /usr/bin/last --ip --hostlast --time-format iso --since '{past_date}' | grep 'pts/' || true"
1582
1583
  last_output: str = subprocess.check_output(
1583
1584
  args=command,
1584
1585
  shell=True,
@@ -2020,40 +2021,166 @@ class LinuxMonitor:
2020
2021
  logging.exception(msg=out_msg)
2021
2022
  return out_msg
2022
2023
 
2023
- async def execute_command(self, is_private: bool, command_name: str) -> str:
2024
+ def _apply_command_parameters(self, command_template: str, parameters: str) -> str:
2025
+ """
2026
+ Substitute user-provided parameters into a command template in an injection-safe way.
2027
+
2028
+ Placeholders supported in the template:
2029
+ - {args} : replaced by all provided parameters (space separated)
2030
+ - {arg1}, {arg2} : replaced by the Nth provided parameter
2031
+ If the template contains no placeholder, all parameters are appended at the end.
2032
+
2033
+ Every parameter is shell-quoted (shlex.quote) so it is always treated as a single
2034
+ literal argument, which prevents command injection through the parameters.
2035
+
2036
+ :param command_template: The command as defined in the configuration file.
2037
+ :param parameters: The raw parameters string provided by the user.
2038
+
2039
+ :return: The final command ready to be executed.
2040
+ """
2041
+ param_list: List[str] = shlex.split(parameters) if parameters else []
2042
+ quoted_params: List[str] = [shlex.quote(param) for param in param_list]
2043
+
2044
+ has_placeholder: bool = ("{args}" in command_template) or bool(re.search(pattern=r"\{arg\d+\}", string=command_template))
2045
+
2046
+ # No placeholder: append every quoted parameter at the end of the command
2047
+ if not has_placeholder:
2048
+ if quoted_params:
2049
+ return command_template + " " + " ".join(quoted_params)
2050
+ return command_template
2051
+
2052
+ final_command: str = command_template
2053
+
2054
+ # Replace positional placeholders {arg1}, {arg2}, ...
2055
+ for index, quoted_param in enumerate(iterable=quoted_params, start=1):
2056
+ final_command = final_command.replace(f"{{arg{index}}}", quoted_param)
2057
+
2058
+ # Replace the catch-all {args} placeholder with every quoted parameter
2059
+ final_command = final_command.replace("{args}", " ".join(quoted_params))
2060
+
2061
+ # Any positional placeholder left unprovided is replaced by an empty string
2062
+ final_command = re.sub(pattern=r"\{arg\d+\}", repl="", string=final_command)
2063
+
2064
+ return final_command
2065
+
2066
+ def _normalize_command_name(self, name: str) -> str:
2067
+ """
2068
+ Normalize a command name for permissive matching: lowercase and ignore spaces, '-' and '_'.
2069
+ """
2070
+ return re.sub(pattern=r"[\s\-_]+", repl="", string=name.strip().lower())
2071
+
2072
+ def _levenshtein_distance(self, a: str, b: str) -> int:
2073
+ """
2074
+ Compute the Levenshtein (edit) distance between two strings (number of single-character
2075
+ insertions, deletions or substitutions needed to turn one into the other).
2076
+ """
2077
+ if a == b:
2078
+ return 0
2079
+ if len(a) == 0:
2080
+ return len(b)
2081
+ if len(b) == 0:
2082
+ return len(a)
2083
+
2084
+ previous_row: List[int] = list(range(len(b) + 1))
2085
+ for i, char_a in enumerate(iterable=a, start=1):
2086
+ current_row: List[int] = [i]
2087
+ for j, char_b in enumerate(iterable=b, start=1):
2088
+ insert_cost: int = current_row[j - 1] + 1
2089
+ delete_cost: int = previous_row[j] + 1
2090
+ replace_cost: int = previous_row[j - 1] + (0 if char_a == char_b else 1)
2091
+ current_row.append(min(insert_cost, delete_cost, replace_cost))
2092
+ previous_row = current_row
2093
+
2094
+ return previous_row[-1]
2095
+
2096
+ def _find_matching_command_key(self, command_name: str) -> Optional[str]:
2097
+ """
2098
+ Find the configured command key matching the requested name in a permissive way:
2099
+ exact match first, then case/'-'/'_'/space-insensitive match, then a small typo tolerance.
2100
+ Ambiguous matches (several equally good candidates) are rejected to avoid running the wrong command.
2101
+
2102
+ :param command_name: The command name requested by the user.
2103
+
2104
+ :return: The matching command key from the configuration, or None if no safe match is found.
2105
+ """
2106
+ command_keys: List[str] = list(self.config['commands'].keys())
2107
+
2108
+ # 1. Exact match
2109
+ if command_name in command_keys:
2110
+ return command_name
2111
+
2112
+ # 2. Normalized match (case-insensitive, '-'/'_'/spaces ignored)
2113
+ target: str = self._normalize_command_name(name=command_name)
2114
+ normalized_matches: List[str] = [key for key in command_keys if self._normalize_command_name(name=key) == target]
2115
+ if len(normalized_matches) == 1:
2116
+ return normalized_matches[0]
2117
+ if len(normalized_matches) > 1:
2118
+ # Several commands normalize to the same name: too ambiguous to guess
2119
+ return None
2120
+
2121
+ # 3. Fuzzy match tolerating small typos (on the normalized names)
2122
+ threshold: int = 1 if len(target) <= 4 else 2
2123
+ best_distance: Optional[int] = None
2124
+ best_keys: List[str] = []
2125
+ for key in command_keys:
2126
+ distance: int = self._levenshtein_distance(a=self._normalize_command_name(name=key), b=target)
2127
+ if best_distance is None or distance < best_distance:
2128
+ best_distance = distance
2129
+ best_keys = [key]
2130
+ elif distance == best_distance:
2131
+ best_keys.append(key)
2132
+
2133
+ # Only accept a typo-based match if it is close enough AND unambiguous
2134
+ if best_distance is not None and best_distance <= threshold and len(best_keys) == 1:
2135
+ return best_keys[0]
2136
+
2137
+ return None
2138
+
2139
+ async def execute_command(self, is_private: bool, command_name: str, parameters: str = "") -> str:
2024
2140
  try:
2025
2141
  out_msg: str = ""
2026
- display_name: str = command_name
2027
- for command_config_key in self.config['commands'].keys():
2028
- if command_config_key == command_name:
2029
- display_name = self.config['commands'][command_config_key]['display_name']
2030
- command: str = self.config['commands'][command_config_key]['command']
2031
- is_private_cmd: bool = self.config['commands'][command_config_key]['is_private']
2032
-
2033
- if is_private or is_private == is_private_cmd:
2034
- show_content_if_success: bool = self.config['commands'][command_config_key].get('show_content_if_success', False)
2035
- show_content_if_issue: bool = self.config['commands'][command_config_key].get('show_content_if_issue', False)
2036
- is_content_json: bool = self.config['commands'][command_config_key].get('is_content_json', False)
2037
- timeout_in_sec: int = self.config['commands'][command_config_key].get('timeout_in_sec', 10)
2038
-
2039
- res: str = await self._execute_command(command=command, display_name=display_name, show_content_if_success=show_content_if_success,
2040
- show_content_if_issue=show_content_if_issue, is_content_json=is_content_json,
2041
- timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
2042
- if res != "":
2043
- if out_msg != "":
2044
- out_msg += "\n"
2045
- out_msg += f"{res}"
2046
- else:
2047
- out_msg = f"⚠️ **Command {display_name} is not allowed**"
2048
- logging.warning(msg=out_msg)
2049
2142
 
2050
- if out_msg != "":
2051
- out_msg = f"# 📜 Command {display_name} 📜\n{out_msg}"
2143
+ command_config_key: Optional[str] = self._find_matching_command_key(command_name=command_name)
2144
+ if command_config_key is None:
2145
+ out_msg = f"⚠️ **Command {command_name} not found**"
2146
+ logging.warning(msg=out_msg)
2147
+ return out_msg
2148
+
2149
+ display_name: str = self.config['commands'][command_config_key]['display_name']
2150
+ command: str = self.config['commands'][command_config_key]['command']
2151
+ is_private_cmd: bool = self.config['commands'][command_config_key]['is_private']
2152
+
2153
+ if is_private or is_private == is_private_cmd:
2154
+ parameters = parameters.strip()
2155
+ # A command accepts parameters only if it exposes a placeholder or explicitly opts in
2156
+ accept_parameters: bool = self.config['commands'][command_config_key].get('accept_parameters', False) \
2157
+ or ("{args}" in command) or bool(re.search(pattern=r"\{arg\d+\}", string=command))
2158
+
2159
+ if parameters != "" and not accept_parameters:
2160
+ out_msg = f"⚠️ **Command {display_name} does not accept parameters**"
2161
+ logging.warning(msg=out_msg)
2162
+ else:
2163
+ final_command: str = self._apply_command_parameters(command_template=command, parameters=parameters)
2052
2164
 
2053
- return out_msg
2165
+ show_content_if_success: bool = self.config['commands'][command_config_key].get('show_content_if_success', False)
2166
+ show_content_if_issue: bool = self.config['commands'][command_config_key].get('show_content_if_issue', False)
2167
+ is_content_json: bool = self.config['commands'][command_config_key].get('is_content_json', False)
2168
+ timeout_in_sec: int = self.config['commands'][command_config_key].get('timeout_in_sec', 10)
2169
+
2170
+ res: str = await self._execute_command(command=final_command, display_name=display_name, show_content_if_success=show_content_if_success,
2171
+ show_content_if_issue=show_content_if_issue, is_content_json=is_content_json,
2172
+ timeout_in_sec=timeout_in_sec, display_only_if_critical=False)
2173
+ if res != "":
2174
+ if out_msg != "":
2175
+ out_msg += "\n"
2176
+ out_msg += f"{res}"
2177
+ else:
2178
+ out_msg = f"⚠️ **Command {display_name} is not allowed**"
2179
+ logging.warning(msg=out_msg)
2180
+
2181
+ if out_msg != "":
2182
+ out_msg = f"# 📜 Command {display_name} 📜\n{out_msg}"
2054
2183
 
2055
- out_msg = f"⚠️ **Command {display_name} not found**"
2056
- logging.warning(msg=out_msg)
2057
2184
  return out_msg
2058
2185
  except Exception as e:
2059
2186
  return f"⚠️ **Error executing command {command_name}**:\n```sh\n{e}\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.5.6",
9
+ version="1.5.8",
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