zscams 2.0.6__tar.gz → 2.0.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.
- {zscams-2.0.6 → zscams-2.0.8}/PKG-INFO +1 -1
- {zscams-2.0.6 → zscams-2.0.8}/pyproject.toml +1 -1
- {zscams-2.0.6 → zscams-2.0.8}/zscams/__main__.py +12 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/__init__.py +6 -1
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/configuration/config.j2 +2 -2
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/backend/bootstrap.py +3 -1
- zscams-2.0.8/zscams/agent/src/core/backend/unbootstrap.py +49 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/prerequisites.py +4 -1
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/services/reverse_ssh.py +4 -3
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/services/ssh_forwarder.py +4 -3
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/services/system_monitor.py +3 -4
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/filesystem.py +8 -3
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/os.py +51 -0
- {zscams-2.0.6 → zscams-2.0.8}/README.md +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/__init__.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/certificates/.gitkeep +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/config.yaml +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/configuration/freebsd_service.j2 +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/configuration/linux_service.j2 +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/keys/autoport.key +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/__init__.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/__init__.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/backend/client.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/backend/exceptions.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/backend/update_machine_info.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/service_health_check.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/services.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/tunnel/__init__.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/tunnel/tls.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/core/tunnels.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/services/__init__.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/__init__.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/cli.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/configuration.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/logger.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/mac.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/network.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/openssl.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/ssh.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/agent/src/support/yaml.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/deps.py +0 -0
- {zscams-2.0.6 → zscams-2.0.8}/zscams/lib/.gitkeep +0 -0
|
@@ -3,7 +3,7 @@ name = "zscams_agent"
|
|
|
3
3
|
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "zscams"
|
|
6
|
-
version = "2.0.
|
|
6
|
+
version = "2.0.8"
|
|
7
7
|
description = "Async TLS tunnel client with SNI routing, auto-reconnect, and health checks"
|
|
8
8
|
authors = ["OCD - Cairo Software Team"]
|
|
9
9
|
maintainers = ["OCD - Cairo Software Team"]
|
|
@@ -6,6 +6,7 @@ import asyncio
|
|
|
6
6
|
import sys
|
|
7
7
|
import os
|
|
8
8
|
from zscams.agent.src.core.backend.bootstrap import bootstrap
|
|
9
|
+
from zscams.agent.src.core.backend.unbootstrap import unbootstrap
|
|
9
10
|
from zscams.agent.src.core.backend.update_machine_info import update_machine_info
|
|
10
11
|
from zscams.agent.src.support.logger import get_logger
|
|
11
12
|
from zscams.agent import init_parser, ensure_bootstrapped, run
|
|
@@ -32,6 +33,17 @@ def main():
|
|
|
32
33
|
update_machine_info()
|
|
33
34
|
sys.exit(0)
|
|
34
35
|
|
|
36
|
+
if args.unbootstrap:
|
|
37
|
+
try:
|
|
38
|
+
if os.geteuid() != 0:
|
|
39
|
+
logger.error("You are NOT running as root.")
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
unbootstrap()
|
|
42
|
+
sys.exit(0)
|
|
43
|
+
except Exception as exception:
|
|
44
|
+
logger.error(exception)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
35
47
|
try:
|
|
36
48
|
ensure_bootstrapped()
|
|
37
49
|
asyncio.run(run())
|
|
@@ -22,10 +22,15 @@ def init_parser():
|
|
|
22
22
|
action="store_true",
|
|
23
23
|
help="Run bootstrap process and exit",
|
|
24
24
|
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--unbootstrap",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="Run unbootstrap process and exit",
|
|
29
|
+
)
|
|
25
30
|
parser.add_argument(
|
|
26
31
|
"--update-machine-info",
|
|
27
32
|
action="store_true",
|
|
28
|
-
help="
|
|
33
|
+
help="Reinitialize machine info",
|
|
29
34
|
)
|
|
30
35
|
return parser
|
|
31
36
|
|
|
@@ -39,7 +39,7 @@ services:
|
|
|
39
39
|
custom_port_name: "reverse_port"
|
|
40
40
|
params:
|
|
41
41
|
local_port: 4422
|
|
42
|
-
private_key:
|
|
42
|
+
private_key: keys/autoport.key
|
|
43
43
|
remote_host: localhost
|
|
44
44
|
reverse_port: "{reverse_port}"
|
|
45
45
|
server_ssh_user: ssh_user
|
|
@@ -52,7 +52,7 @@ services:
|
|
|
52
52
|
port: 22
|
|
53
53
|
prerequisites:
|
|
54
54
|
files:
|
|
55
|
-
-
|
|
55
|
+
- keys/autoport.key
|
|
56
56
|
ports:
|
|
57
57
|
- 22
|
|
58
58
|
services: []
|
|
@@ -4,7 +4,7 @@ import sysconfig
|
|
|
4
4
|
import sys
|
|
5
5
|
from typing import cast
|
|
6
6
|
from zscams.agent.src.core.backend.client import backend_client
|
|
7
|
-
from zscams.agent.src.support.configuration import reinitialize
|
|
7
|
+
from zscams.agent.src.support.configuration import reinitialize, CONFIG_PATH
|
|
8
8
|
from zscams.agent.src.support.logger import get_logger
|
|
9
9
|
from zscams.agent.src.support.os import create_system_user, install_service, is_freebsd
|
|
10
10
|
from zscams.agent.src.support.ssh import add_to_authorized_keys
|
|
@@ -47,6 +47,8 @@ def bootstrap():
|
|
|
47
47
|
f"{equipment_type.lower()}-{customer_name.lower()}-{connector_name.lower()}"
|
|
48
48
|
)
|
|
49
49
|
enforced_id = prompt("Enforced ID", "Enforced Agent ID")
|
|
50
|
+
if not os.path.exists(CONFIG_PATH):
|
|
51
|
+
os.remove(CONFIG_PATH)
|
|
50
52
|
reinitialize(equipment_name=equipment_name, equipment_type=equipment_type)
|
|
51
53
|
cm_info = backend_client.bootstrap(equipment_name, enforced_id or None)
|
|
52
54
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from zscams.agent.src.support.filesystem import resolve_path
|
|
3
|
+
from zscams.agent.src.support.configuration import get_config, CONFIG_PATH, ROOT_PATH
|
|
4
|
+
from zscams.agent.src.core.backend.client import BackendClient
|
|
5
|
+
from zscams.agent.src.support.logger import get_logger
|
|
6
|
+
from zscams.agent.src.support.os import remove_service, is_freebsd
|
|
7
|
+
|
|
8
|
+
logger = get_logger("Unbootstrap")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def unbootstrap():
|
|
12
|
+
remote_configs = get_config().get("remote", {})
|
|
13
|
+
backend_config = get_config().get("backend", {})
|
|
14
|
+
private_key_path = resolve_path(
|
|
15
|
+
remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
cert_path = resolve_path(
|
|
19
|
+
remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
ca_chain_path = resolve_path(
|
|
23
|
+
remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
cache_path = os.path.join(
|
|
27
|
+
ROOT_PATH.parent,
|
|
28
|
+
backend_config.get("cache_dir"),
|
|
29
|
+
BackendClient.MACHINE_INFO_FILE_NAME,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if os.path.exists(private_key_path):
|
|
33
|
+
os.remove(private_key_path)
|
|
34
|
+
logger.debug("Removed private key")
|
|
35
|
+
|
|
36
|
+
if os.path.exists(cert_path):
|
|
37
|
+
os.remove(cert_path)
|
|
38
|
+
logger.debug("Removed certificate")
|
|
39
|
+
|
|
40
|
+
if os.path.exists(ca_chain_path):
|
|
41
|
+
os.remove(ca_chain_path)
|
|
42
|
+
logger.debug("Removed CA Chain")
|
|
43
|
+
|
|
44
|
+
if os.path.exists(cache_path):
|
|
45
|
+
os.remove(cache_path)
|
|
46
|
+
logger.debug("Removed Machine info")
|
|
47
|
+
|
|
48
|
+
remove_service("zscams" if is_freebsd() else "zscams.service")
|
|
49
|
+
logger.debug("Removed ZSCAMs service")
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from zscams.agent.src.support.logger import get_logger
|
|
2
3
|
from zscams.agent.src.support.filesystem import is_file_exists
|
|
3
4
|
from zscams.agent.src.core import running_services
|
|
4
5
|
from zscams.agent.src.support.network import is_port_open, is_service_running
|
|
6
|
+
from zscams.agent.src.support.configuration import CONFIG_PATH
|
|
5
7
|
|
|
6
8
|
logger = get_logger("service_prerequisites")
|
|
9
|
+
config_dir = os.path.dirname(CONFIG_PATH)
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
def check_prerequisites(prereqs: dict) -> bool:
|
|
@@ -18,7 +21,7 @@ def check_prerequisites(prereqs: dict) -> bool:
|
|
|
18
21
|
|
|
19
22
|
# Check file prerequisites
|
|
20
23
|
for path in prereqs.get("files", []):
|
|
21
|
-
results.append(is_file_exists(path, logger))
|
|
24
|
+
results.append(is_file_exists(path, logger, config_dir))
|
|
22
25
|
|
|
23
26
|
# Check port prerequisites
|
|
24
27
|
for port in prereqs.get("ports", []):
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
from zscams.agent.src.support.logger import get_logger
|
|
6
6
|
from zscams.agent.src.support.filesystem import resolve_path
|
|
7
|
+
from zscams.agent.src.support.configuration import CONFIG_PATH
|
|
7
8
|
|
|
8
9
|
logger = get_logger("autossh_service")
|
|
9
10
|
|
|
@@ -14,11 +15,11 @@ if not params_env:
|
|
|
14
15
|
sys.exit(1)
|
|
15
16
|
|
|
16
17
|
params = json.loads(params_env)
|
|
17
|
-
|
|
18
|
+
config_dir = os.path.dirname(CONFIG_PATH)
|
|
18
19
|
LOCAL_PORT = params.get("local_port", 4422)
|
|
19
20
|
SERVER_SSH_USER = params.get("server_ssh_user", "ssh_user")
|
|
20
21
|
REVERSE_PORT = params.get("reverse_port", 2222)
|
|
21
|
-
PRIVATE_KEY = resolve_path(params.get("private_key")) # Path to RSA key
|
|
22
|
+
PRIVATE_KEY = resolve_path(params.get("private_key"), config_dir) # Path to RSA key
|
|
22
23
|
SSH_OPTIONS = params.get("ssh_options", [])
|
|
23
24
|
CHECK_INTERVAL = 120 #
|
|
24
25
|
|
|
@@ -42,7 +43,7 @@ async def run():
|
|
|
42
43
|
|
|
43
44
|
# Add additional SSH options
|
|
44
45
|
ssh_cmd += SSH_OPTIONS
|
|
45
|
-
|
|
46
|
+
os.chmod(PRIVATE_KEY, 0o700)
|
|
46
47
|
logger.info(f"Starting reverse SSH tunnel: {' '.join(ssh_cmd)}")
|
|
47
48
|
|
|
48
49
|
while True:
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
from zscams.agent.src.support.logger import get_logger
|
|
6
6
|
from zscams.agent.src.support.filesystem import resolve_path
|
|
7
|
+
from zscams.agent.src.support.configuration import CONFIG_PATH
|
|
7
8
|
|
|
8
9
|
logger = get_logger("autossh_service")
|
|
9
10
|
|
|
@@ -14,13 +15,13 @@ if not params_env:
|
|
|
14
15
|
sys.exit(1)
|
|
15
16
|
|
|
16
17
|
params = json.loads(params_env)
|
|
17
|
-
|
|
18
|
+
config_dir = os.path.dirname(CONFIG_PATH)
|
|
18
19
|
FORWARDER_PORT = params.get("forwarder_port", 4422)
|
|
19
20
|
LOCAL_PORT = params.get("local_port", 4422)
|
|
20
21
|
REMOTE_PORT = params.get("remote_port", 4422)
|
|
21
22
|
REMOTE_HOST = params.get("remote_host", "localhost")
|
|
22
23
|
SERVER_SSH_USER = params.get("server_ssh_user", "ssh_user")
|
|
23
|
-
PRIVATE_KEY = resolve_path(params.get("private_key")) # Path to RSA key
|
|
24
|
+
PRIVATE_KEY = resolve_path(params.get("private_key"), config_dir) # Path to RSA key
|
|
24
25
|
SSH_OPTIONS = params.get("ssh_options", [])
|
|
25
26
|
CHECK_INTERVAL = 120 #
|
|
26
27
|
|
|
@@ -44,7 +45,7 @@ async def run():
|
|
|
44
45
|
|
|
45
46
|
# Add additional SSH options
|
|
46
47
|
ssh_cmd += SSH_OPTIONS
|
|
47
|
-
|
|
48
|
+
os.chmod(PRIVATE_KEY, 0o700)
|
|
48
49
|
logger.info(f"Starting reverse SSH tunnel: {' '.join(ssh_cmd)}")
|
|
49
50
|
|
|
50
51
|
while True:
|
|
@@ -10,6 +10,7 @@ import socket
|
|
|
10
10
|
import platform
|
|
11
11
|
from zscams.agent.src.support.logger import get_logger
|
|
12
12
|
from http.client import BadStatusLine, HTTPConnection, HTTPException
|
|
13
|
+
|
|
13
14
|
logger = get_logger("system_monitor")
|
|
14
15
|
|
|
15
16
|
# Load service-specific params from environment
|
|
@@ -30,8 +31,6 @@ SERVICE_NAME = params.get("service_name", "Zscaler-AppConnector")
|
|
|
30
31
|
HOSTNAME = platform.node()
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
34
|
def network():
|
|
36
35
|
"""
|
|
37
36
|
Collect network interfaces and their statistics.
|
|
@@ -232,7 +231,6 @@ def log():
|
|
|
232
231
|
}
|
|
233
232
|
|
|
234
233
|
|
|
235
|
-
|
|
236
234
|
# -----------------------------
|
|
237
235
|
# Helper function to log JSON
|
|
238
236
|
# -----------------------------
|
|
@@ -249,13 +247,14 @@ async def send_json_log(payload: dict):
|
|
|
249
247
|
|
|
250
248
|
|
|
251
249
|
async def schedule_task(interval):
|
|
252
|
-
while
|
|
250
|
+
while True:
|
|
253
251
|
try:
|
|
254
252
|
await send_json_log(log())
|
|
255
253
|
except Exception as exception:
|
|
256
254
|
print(exception)
|
|
257
255
|
await asyncio.sleep(interval)
|
|
258
256
|
|
|
257
|
+
|
|
259
258
|
if __name__ == "__main__":
|
|
260
259
|
try:
|
|
261
260
|
asyncio.run(schedule_task(30))
|
|
@@ -9,6 +9,7 @@ from zscams.agent.src.support.logger import get_logger
|
|
|
9
9
|
|
|
10
10
|
logger = get_logger("FileSystem")
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
def resolve_path(path: Optional[str], base_dir: Optional[str] = None) -> Optional[str]:
|
|
13
14
|
"""
|
|
14
15
|
Resolve a file path relative to a base directory.
|
|
@@ -34,9 +35,12 @@ def ensure_dir(path: str):
|
|
|
34
35
|
os.makedirs(path, exist_ok=True)
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
def is_file_exists(path, logger):
|
|
38
|
+
def is_file_exists(path, logger, base_dir=None):
|
|
38
39
|
"""Check if file exists."""
|
|
39
|
-
|
|
40
|
+
absolute_path = resolve_path(path, base_dir)
|
|
41
|
+
if os.path.exists(path) or os.path.exists(
|
|
42
|
+
Path(absolute_path if absolute_path else __file__)
|
|
43
|
+
):
|
|
40
44
|
logger.debug(f"File exists: {path}")
|
|
41
45
|
return True
|
|
42
46
|
|
|
@@ -50,7 +54,7 @@ def append_to_file(path: str | Path, content: str):
|
|
|
50
54
|
path = Path(path)
|
|
51
55
|
|
|
52
56
|
if not path.parent.exists():
|
|
53
|
-
os.mkdir(path.parent,0o700)
|
|
57
|
+
os.mkdir(path.parent, 0o700)
|
|
54
58
|
|
|
55
59
|
with open(path, "a", encoding="utf-8") as file:
|
|
56
60
|
file.write(content)
|
|
@@ -58,6 +62,7 @@ def append_to_file(path: str | Path, content: str):
|
|
|
58
62
|
logger.error(exception)
|
|
59
63
|
raise exception
|
|
60
64
|
|
|
65
|
+
|
|
61
66
|
def write_to_file(path: str | Path, content: str):
|
|
62
67
|
with open(path, "w", encoding="utf-8") as f:
|
|
63
68
|
f.write(content)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import sys
|
|
2
3
|
import subprocess
|
|
3
4
|
import platform
|
|
@@ -136,3 +137,53 @@ def install_systemd_service(service_name: str, content: str):
|
|
|
136
137
|
service_name,
|
|
137
138
|
service_name,
|
|
138
139
|
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def remove_service(service_name: str):
|
|
143
|
+
"""
|
|
144
|
+
Stops, disables, and removes service configurations for Linux and FreeBSD.
|
|
145
|
+
"""
|
|
146
|
+
# Standardize name (remove .service suffix if present for consistency)
|
|
147
|
+
clean_name = service_name.replace(".service", "")
|
|
148
|
+
|
|
149
|
+
if is_linux():
|
|
150
|
+
_remove_systemd_service(clean_name)
|
|
151
|
+
elif is_freebsd():
|
|
152
|
+
_remove_rc_service(clean_name)
|
|
153
|
+
else:
|
|
154
|
+
logger.error("Unsupported OS for service removal.")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _remove_systemd_service(name: str):
|
|
158
|
+
service_file = f"/etc/systemd/system/{name}.service"
|
|
159
|
+
try:
|
|
160
|
+
logger.info("Stopping and disabling systemd service: %s", name)
|
|
161
|
+
subprocess.run(["sudo", "systemctl", "stop", name], check=False)
|
|
162
|
+
subprocess.run(["sudo", "systemctl", "disable", name], check=False)
|
|
163
|
+
|
|
164
|
+
if os.path.exists(service_file):
|
|
165
|
+
subprocess.run(["sudo", "rm", service_file], check=True)
|
|
166
|
+
subprocess.run(["sudo", "systemctl", "daemon-reload"], check=True)
|
|
167
|
+
logger.info("Systemd service %s removed.", name)
|
|
168
|
+
except subprocess.CalledProcessError as e:
|
|
169
|
+
logger.error("Failed to remove systemd service: %s", e)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _remove_rc_service(name: str):
|
|
173
|
+
rc_file = f"/usr/local/etc/rc.d/{name}"
|
|
174
|
+
try:
|
|
175
|
+
logger.info("Stopping and disabling FreeBSD service: %s", name)
|
|
176
|
+
# 1. Stop the service
|
|
177
|
+
subprocess.run(["sudo", "service", name, "stop"], check=False)
|
|
178
|
+
|
|
179
|
+
# 2. Disable in /etc/rc.conf
|
|
180
|
+
# Using sysrc -x removes the variable entirely from rc.conf
|
|
181
|
+
subprocess.run(["sudo", "sysrc", "-x", f"{name}_enable"], check=False)
|
|
182
|
+
|
|
183
|
+
# 3. Remove the rc.d script
|
|
184
|
+
if os.path.exists(rc_file):
|
|
185
|
+
subprocess.run(["sudo", "rm", rc_file], check=True)
|
|
186
|
+
logger.info("FreeBSD rc script %s removed.", name)
|
|
187
|
+
|
|
188
|
+
except subprocess.CalledProcessError as e:
|
|
189
|
+
logger.error("Failed to remove FreeBSD service: %s", e)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|