From 1b82a7e9134e7330d42989d794bdb67e6ccece0c Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 01:28:34 +0600 Subject: [PATCH 1/9] 502 nginx error handled --- node_cli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node_cli.py b/node_cli.py index 7230f4dc..9c6376f3 100755 --- a/node_cli.py +++ b/node_cli.py @@ -66,7 +66,7 @@ def adjust_python_path(): ) from Framework.Utilities import ConfigModule # noqa: E402 from Framework.Utilities import live_log_service # noqa: E402 -from Framework.node_server_state import STATE # noqa: E402 +from Framework.node_server_state import STATE, LoginCredentials # noqa: E402 from server import main as node_server # noqa: E402 @@ -279,6 +279,10 @@ async def Login( elif status_code == 502: print(Fore.YELLOW + "Server offline. Retrying after 60s") await asyncio.sleep(60) + STATE.reconnect_with_credentials = LoginCredentials( + server=ConfigModule.get_config_value(AUTHENTICATION_TAG, "server_address").strip('"').strip(), + api_key=ConfigModule.get_config_value(AUTHENTICATION_TAG, "api-key").strip('"').strip(), + ) return else: line_color = Fore.RED From 547e2a009468edf4183abce46fdfbe504ff6fc45 Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 01:29:02 +0600 Subject: [PATCH 2/9] node_id underscore bug fix --- Framework/Utilities/RequestFormatter.py | 10 +++------- server/status.py | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Framework/Utilities/RequestFormatter.py b/Framework/Utilities/RequestFormatter.py index 10a9d0a1..d67e984c 100644 --- a/Framework/Utilities/RequestFormatter.py +++ b/Framework/Utilities/RequestFormatter.py @@ -210,16 +210,12 @@ def Get(resource_path, payload=None, **kwargs): **kwargs ).json() - except requests.exceptions.RequestException: - print( - "Exception in UpdateGet: Authentication Failed. Please check your server, username and password. " - "Please include full server name. Example: https://zeuz.zeuz.ai.\n" - "If you are using IP Address: Type in just the IP without http. Example: 12.15.10.6" - ) + except requests.exceptions.RequestException as e: + print(e) return "" except Exception as e: - print("Get Exception: {}".format(e)) + print(e) return {} diff --git a/server/status.py b/server/status.py index f86fd925..d59a239e 100644 --- a/server/status.py +++ b/server/status.py @@ -37,7 +37,7 @@ class StatusResponse(BaseModel): def status(): try: node_id = CommonUtil.MachineInfo().getLocalUser().lower() - username, id = node_id.split("_") + username, id = node_id.split("_", 1) if len(username) == 0: node_id = id except Exception: From a18f2f1c0873b586f24de085c2923a12bbcc748b Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 04:00:05 +0600 Subject: [PATCH 3/9] Nginx down, VM down, network issue, docker-compose stopped, UnAuthorized handled --- Framework/deploy_handler/long_poll_handler.py | 135 +++++++++--------- node_cli.py | 2 +- 2 files changed, 66 insertions(+), 71 deletions(-) diff --git a/Framework/deploy_handler/long_poll_handler.py b/Framework/deploy_handler/long_poll_handler.py index 5e5bda7d..1d19becf 100644 --- a/Framework/deploy_handler/long_poll_handler.py +++ b/Framework/deploy_handler/long_poll_handler.py @@ -9,6 +9,7 @@ from colorama import Fore from pathlib import Path from urllib.parse import urlparse +import requests from Framework.Utilities import RequestFormatter, ConfigModule, CommonUtil from Framework.Utilities.RequestFormatter import REQUEST_TIMEOUT @@ -253,80 +254,74 @@ def respond_to_key_request(self, request_id: str, private_key_pem: str) -> None: async def run(self, host: str) -> None: reconnect = False - server_online = False - async with httpx.AsyncClient(timeout=httpx.Timeout(70.0), verify=False) as client: - while True: - if STATE.reconnect_with_credentials is not None: + print_online = False + while True: + if STATE.reconnect_with_credentials is not None: + break + + if reconnect: + await asyncio.sleep(random.randint(1, 3)) + + await self.on_connect_callback(reconnect) + + try: + reconnect = True + resp = RequestFormatter.request("get", host, verify=False, timeout=70) + if resp is None: break - if reconnect: - if server_online: - await asyncio.sleep(0.1) - else: - await asyncio.sleep(random.randint(1, 3)) + if resp.content.startswith(self.ERROR_PREFIX): + self.on_error(resp.content) + continue - await self.on_connect_callback(reconnect) + if resp.ok and print_online: + print_online = False + node_id = CommonUtil.MachineInfo().getLocalUser().lower() + print(f"🟢 {node_id} back to online") - try: - reconnect = True - resp = await self.fetch(host, client) - if resp is None: - break - - if resp.content.startswith(self.ERROR_PREFIX): - server_online = False - self.on_error(resp.content) - continue - - if resp.status_code == httpx.codes.NO_CONTENT: - server_online = False - continue - - if not resp.is_success: - server_online = False - print( - "[deploy] facing difficulty communicating with the server, status code:", - resp.status_code, - " | reconnecting", - ) - try: - print(Fore.YELLOW + str(resp.content)) - except Exception: - pass - - # Encountered a server error, retry. - await asyncio.sleep(random.randint(1, 3)) - return - - should_quit = await self.on_message(resp.content) - if should_quit: - break - - reconnect = False - server_online = True - except httpx.ReadTimeout: - pass - except Exception: - traceback.print_exc() - print("[deploy] RETRYING...") - - async def fetch(self, host: str, client: httpx.AsyncClient) -> httpx.Response | None: - try: - api_key = ConfigModule.get_config_value("Authentication", "api-key") - headers = {"X-API-KEY": api_key} - - while True: + if resp.status_code == httpx.codes.NO_CONTENT: + continue + + if resp.status_code == httpx.codes.BAD_GATEWAY: + print_online = True + print(Fore.YELLOW + "Server offline. Retrying after 20s") + await asyncio.sleep(20) + continue + + if not resp.ok: + print( + "[deploy] facing difficulty communicating with the server, status code:", + resp.status_code, + " | reconnecting", + ) + + # Encountered a server error, retry. + await asyncio.sleep(random.randint(1, 3)) + continue + + should_quit = await self.on_message(resp.content) + if should_quit: + break + + reconnect = False + except ( + requests.exceptions.ConnectTimeout, + requests.exceptions.ReadTimeout, + requests.exceptions.ConnectionError, + ) as e: + # Nginx down, VM down, network issue, docker-compose stopped if STATE.reconnect_with_credentials is not None: return None - - try: - resp = await client.get(host, headers=headers) - return resp - except asyncio.CancelledError: + print_online = True + print(e) + print(Fore.YELLOW + "Retrying after 30s") + await asyncio.sleep(30) + + except Exception as e: + if STATE.reconnect_with_credentials is not None: return None - except Exception: - if STATE.reconnect_with_credentials is not None: - return None - await asyncio.sleep(0.1) - except Exception: - return None + print_online = True + print(e) + print(Fore.YELLOW + "Retrying after 30s") + await asyncio.sleep(30) + diff --git a/node_cli.py b/node_cli.py index 9c6376f3..62f6a898 100755 --- a/node_cli.py +++ b/node_cli.py @@ -565,7 +565,7 @@ def update_machine(dependency, should_print=True): resp = RequestFormatter.request("post", url, json=update_object) if resp.status_code != 200: - CommonUtil.ExecLog("", "Machine is not registered as online", 4) + # CommonUtil.ExecLog("", "Machine is not registered as online", 4) return data = resp.json() From 680eb536966070759d66d548517275d4f7ba5785 Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 04:11:51 +0600 Subject: [PATCH 4/9] all wait is set to 30 sec --- Framework/deploy_handler/long_poll_handler.py | 4 ++-- node_cli.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Framework/deploy_handler/long_poll_handler.py b/Framework/deploy_handler/long_poll_handler.py index 1d19becf..de22be2d 100644 --- a/Framework/deploy_handler/long_poll_handler.py +++ b/Framework/deploy_handler/long_poll_handler.py @@ -284,8 +284,8 @@ async def run(self, host: str) -> None: if resp.status_code == httpx.codes.BAD_GATEWAY: print_online = True - print(Fore.YELLOW + "Server offline. Retrying after 20s") - await asyncio.sleep(20) + print(Fore.YELLOW + "Server offline. Retrying after 30s") + await asyncio.sleep(30) continue if not resp.ok: diff --git a/node_cli.py b/node_cli.py index 62f6a898..1a1e04a5 100755 --- a/node_cli.py +++ b/node_cli.py @@ -277,8 +277,8 @@ async def Login( console.print(table) elif status_code == 502: - print(Fore.YELLOW + "Server offline. Retrying after 60s") - await asyncio.sleep(60) + print(Fore.YELLOW + "Server offline. Retrying after 30s") + await asyncio.sleep(30) STATE.reconnect_with_credentials = LoginCredentials( server=ConfigModule.get_config_value(AUTHENTICATION_TAG, "server_address").strip('"').strip(), api_key=ConfigModule.get_config_value(AUTHENTICATION_TAG, "api-key").strip('"').strip(), From 02e21b3637cc65dc99d9f16d7863404c72d9d1e2 Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 13:24:16 +0600 Subject: [PATCH 5/9] default timeout for server communication is set to `70 sec` for all api --- Framework/Utilities/RequestFormatter.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Framework/Utilities/RequestFormatter.py b/Framework/Utilities/RequestFormatter.py index d67e984c..fd681f1b 100644 --- a/Framework/Utilities/RequestFormatter.py +++ b/Framework/Utilities/RequestFormatter.py @@ -169,10 +169,16 @@ def request(*args, **kwargs): """ request() is a wrapper for requests.request which handles automatic session management. + Default values: + verify = False + timeout = 70 sec """ renew_token_with_expiry_check() if "verify" not in kwargs: kwargs["verify"] = False + if "timeout" not in kwargs: + kwargs["timeout"] = 70 + return session.request(*args, **kwargs) From cd3cba817f557b3475e41d4d10d062b28c6b1996 Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 13:29:04 +0600 Subject: [PATCH 6/9] report upload, node-ai-content upload, attachment download request timeout is set to `10 min` --- Framework/MainDriverApi.py | 44 +++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Framework/MainDriverApi.py b/Framework/MainDriverApi.py index f4dca8d3..f7734a5f 100644 --- a/Framework/MainDriverApi.py +++ b/Framework/MainDriverApi.py @@ -1246,7 +1246,7 @@ def send_dom_variables(): res = RequestFormatter.request("post", RequestFormatter.form_uri("node_ai_contents/"), data=json.dumps(data), - verify=False + timeout=600 ) if res.status_code == 500: CommonUtil.ExecLog(sModuleInfo, res.json()["info"], 2) @@ -1465,7 +1465,7 @@ def upload_step_report(run_id: str, tc_id: str, step_seq: int, step_id: int, exe "execution_detail": execution_detail, }) }, - verify=False + timeout=600 ) duration = round(res.elapsed.total_seconds(), 2) # if res.status_code == 200: @@ -1506,17 +1506,18 @@ def upload_reports_and_zips(temp_ini_file, run_id): "post", RequestFormatter.form_uri("create_report_log_api/"), data={"execution_report": json.dumps(tc_report)}, - verify=False + timeout=600 ) else: res = RequestFormatter.request("post", - RequestFormatter.form_uri("create_report_log_api/"), - data={ - "execution_report": json.dumps(tc_report), - "processed_tc_id":processed_tc_id - }, - files=[("file",perf_report_html)], - verify=False) + RequestFormatter.form_uri("create_report_log_api/"), + data={ + "execution_report": json.dumps(tc_report), + "processed_tc_id": processed_tc_id + }, + files=[("file", perf_report_html)], + timeout=600 + ) if res.status_code == 200: CommonUtil.ExecLog(sModuleInfo, f"Successfully uploaded the execution report of run_id {run_id}", 1) @@ -1582,10 +1583,11 @@ def upload_reports_and_zips(temp_ini_file, run_id): for zips in opened_zips: files_list.append(("file",zips)) res = RequestFormatter.request("post", - RequestFormatter.form_uri("save_log_and_attachment_api/"), - files=files_list, - data={"machine_name": Userid}, - verify=False) + RequestFormatter.form_uri("save_log_and_attachment_api/"), + files=files_list, + data={"machine_name": Userid}, + timeout=600 + ) if res.status_code == 200: try: res_json = res.json() @@ -1629,19 +1631,21 @@ def retry_failed_report_upload(): report_json_path = failed_report_dir / folder / 'report.json' report_json = json.load(open(report_json_path)) if not report_json.get('perf_filepath'): - res = RequestFormatter.request("post", + res = RequestFormatter.request( + "post", RequestFormatter.form_uri("create_report_log_api/"), data={"execution_report": report_json.get('execution_report')}, - verify=False) + timeout=600 + ) else: res = RequestFormatter.request("post", RequestFormatter.form_uri("create_report_log_api/"), data={"execution_report": report_json.get('execution_report'), "processed_tc_id":report_json.get('processed_tc_id') - - }, + }, files=[("file",open(failed_report_dir / folder / 'files' /report_json.get('perf_filepath'),'rb'))], - verify=False) + timeout=600 + ) if res.status_code == 200: CommonUtil.ExecLog(sModuleInfo, f"Successfully uploaded the execution report of run_id {report_json.get('run_id')}", 1) @@ -1705,7 +1709,7 @@ def download_attachment(attachment_info: Dict[str, Any]): file_name = url[file_name_start_pos:] file_path = attachment_info["download_dir"] / file_name - r = RequestFormatter.request("get", url, stream=True) + r = RequestFormatter.request("get", url, stream=True, timeout=600) if r.status_code == requests.codes.ok: with open(file_path, 'wb') as f: for data in r.iter_content(chunk_size=512*1024): From 8eebe34e11351fbbcbca3341a8649bf622ef2bbc Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 13:30:52 +0600 Subject: [PATCH 7/9] global attachment download request timeout is set to `10 min` --- Framework/attachment_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/attachment_db.py b/Framework/attachment_db.py index 974d23e1..36dcb733 100644 --- a/Framework/attachment_db.py +++ b/Framework/attachment_db.py @@ -134,7 +134,7 @@ def download_attachment(self, url: str): headers = RequestFormatter.add_api_key_to_headers({}) - with RequestFormatter.request("get", url, stream=True, verify=False,**headers) as r: + with RequestFormatter.request("get", url, stream=True, timeout=600,**headers) as r: r.raise_for_status() with open(path_to_downloaded_attachment, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): From 16e02a5bb1d7290bda0f91242ae68aec8af7a05f Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 13:47:09 +0600 Subject: [PATCH 8/9] node-intasller api adjusted with the zeuz request abstraction --- Framework/deploy_handler/long_poll_handler.py | 7 +- .../install_handler/long_poll_handler.py | 87 ++++++++----------- Framework/install_handler/utils.py | 27 +++--- 3 files changed, 56 insertions(+), 65 deletions(-) diff --git a/Framework/deploy_handler/long_poll_handler.py b/Framework/deploy_handler/long_poll_handler.py index de22be2d..8c865df1 100644 --- a/Framework/deploy_handler/long_poll_handler.py +++ b/Framework/deploy_handler/long_poll_handler.py @@ -238,7 +238,6 @@ def respond_to_key_request(self, request_id: str, private_key_pem: str) -> None: "donor_node_id": node_id, "private_key": private_key_pem }, - verify=False ) if response.ok: @@ -266,7 +265,7 @@ async def run(self, host: str) -> None: try: reconnect = True - resp = RequestFormatter.request("get", host, verify=False, timeout=70) + resp = RequestFormatter.request("get", host, timeout=70) if resp is None: break @@ -290,9 +289,9 @@ async def run(self, host: str) -> None: if not resp.ok: print( - "[deploy] facing difficulty communicating with the server, status code:", + "[deploy] Request Error, status code:", resp.status_code, - " | reconnecting", + "| reconnecting", ) # Encountered a server error, retry. diff --git a/Framework/install_handler/long_poll_handler.py b/Framework/install_handler/long_poll_handler.py index cc6738b4..a6ce444f 100644 --- a/Framework/install_handler/long_poll_handler.py +++ b/Framework/install_handler/long_poll_handler.py @@ -324,62 +324,49 @@ async def run(self) -> None: return if debug: print(f"[installer] Started running") - async with httpx.AsyncClient( - timeout=httpx.Timeout(70.0), verify=False - ) as client: - self.client = client - while not self.cancel_: - if STATE.reconnect_with_credentials is not None: - if debug: - print("[installer] Reconnection requested, stopping...") - break + + while not self.cancel_: + if STATE.reconnect_with_credentials is not None: + if debug: + print("[installer] Reconnection requested, stopping...") + break - self.running = True - try: - if debug: - print("[installer] Active") - api_key = ConfigModule.get_config_value("Authentication", "api-key") - url = RequestFormatter.form_uri( - f"d/nodes/install/node/listen?node_id={read_node_id()}" - ) + self.running = True + try: + if debug: + print("[installer] Active") + host = RequestFormatter.form_uri( + f"d/nodes/install/node/listen?node_id={read_node_id()}" + ) - resp = await client.get(url, headers={"X-API-KEY": api_key}) - if resp.status_code == httpx.codes.NO_CONTENT: - continue + resp = resp = RequestFormatter.request("get", host, timeout=70) - if not resp.is_success: - if debug: - print( - "[installer] facing difficulty communicating with the server, status code:", - resp.status_code, - " | reconnecting", - ) - print(Fore.YELLOW + str(resp.content)) + if not resp.ok: + if debug: + print( + "[installer] Request Error, status code:", + resp.status_code, + "| retrying after 15 sec", + ) + print(Fore.YELLOW + str(resp.content)) - await asyncio.sleep(random.randint(1, 3)) - continue + await asyncio.sleep(15) + continue - try: - data = resp.json() - if data: - validated_data = Response(**data) - await self.on_message(validated_data) - except Exception as e: - print(f"[installer] Error parsing response: {e}") - continue + try: + data = resp.json() + if data: + validated_data = Response(**data) + await self.on_message(validated_data) + except Exception as e: + print(f"[installer] Type Error in parsing response: {e}") + continue - except httpx.ReadTimeout: - pass - except httpx.ConnectError: - if debug: - print("[installer] Connection error, retrying...") - await asyncio.sleep(random.randint(3, 5)) - except Exception: - if debug: - traceback.print_exc() - if debug: - print("[installer] RETRYING...") - await asyncio.sleep(random.randint(1, 3)) + except Exception: + if debug: + traceback.print_exc() + print("[installer] RETRYING...") + await asyncio.sleep(random.randint(1, 3)) self.running = False print("[installer] Stopped running") diff --git a/Framework/install_handler/utils.py b/Framework/install_handler/utils.py index d1ea521f..c20f0cfb 100644 --- a/Framework/install_handler/utils.py +++ b/Framework/install_handler/utils.py @@ -1,5 +1,5 @@ import datetime -import httpx +import asyncio import platform from Framework.Utilities import RequestFormatter, ConfigModule, CommonUtil @@ -42,9 +42,7 @@ def generate_services_list(services): async def send_response(data=None) -> None: try: from Framework.install_handler.route import services - - api_key = ConfigModule.get_config_value("Authentication", "api-key") - url = RequestFormatter.form_uri("d/nodes/install/server/push") + host = RequestFormatter.form_uri("d/nodes/install/server/push") data['last_updated'] = datetime.datetime.now(datetime.timezone.utc).timestamp() data['version'] = version data['node_id'] = read_node_id() @@ -60,13 +58,20 @@ async def send_response(data=None) -> None: if debug: print(f"[installer] Sending response to server: {data}") - async with httpx.AsyncClient(timeout=30.0, verify=False) as client: - resp = await client.post(url, json=data, headers={"X-API-KEY": api_key}) - if debug: - print(f"[installer] Response status: {resp.status_code}") - print(f"[installer] Response content: {resp.content}") - if not resp.is_success: + for _ in range(3): + try: + resp = RequestFormatter.request("post", host, json=data, timeout=70) if debug: - print(f"[installer] Failed to send response: {resp.status_code}") + print(f"[installer] Response status: {resp.status_code}") + print(f"[installer] Response content: {resp.content}") + if not resp.ok: + if debug: + print(f"[installer] Failed to send response: {resp.status_code}") + await asyncio.sleep(3,5) + else: + break + except Exception as e: + if debug: print(e) + await asyncio.sleep(3,5) except Exception as e: print(f"[installer] Error sending response: {e}") From c7110120d4783b9fb414b8fb261da12f4fe00c83 Mon Sep 17 00:00:00 2001 From: test Date: Sun, 28 Dec 2025 15:31:41 +0600 Subject: [PATCH 9/9] async fix for sync requests call in abstraction --- Framework/Utilities/RequestFormatter.py | 10 +++++++++- Framework/deploy_handler/long_poll_handler.py | 2 +- Framework/install_handler/long_poll_handler.py | 6 +++--- Framework/install_handler/utils.py | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Framework/Utilities/RequestFormatter.py b/Framework/Utilities/RequestFormatter.py index fd681f1b..2c979e1e 100644 --- a/Framework/Utilities/RequestFormatter.py +++ b/Framework/Utilities/RequestFormatter.py @@ -1,6 +1,6 @@ # -- coding: utf-8 -- # -- coding: cp1252 -- - +import asyncio from . import ConfigModule import os import requests @@ -182,6 +182,14 @@ def request(*args, **kwargs): return session.request(*args, **kwargs) +# async wrapper +async def async_request(*args, **kwargs): + """ + Runs the blocking request() in a worker thread + so the event loop is not blocked. + """ + return await asyncio.to_thread(request, *args, **kwargs) + def Post(resource_path, payload=None, **kwargs): renew_token_with_expiry_check() diff --git a/Framework/deploy_handler/long_poll_handler.py b/Framework/deploy_handler/long_poll_handler.py index 8c865df1..1a6de8c6 100644 --- a/Framework/deploy_handler/long_poll_handler.py +++ b/Framework/deploy_handler/long_poll_handler.py @@ -265,7 +265,7 @@ async def run(self, host: str) -> None: try: reconnect = True - resp = RequestFormatter.request("get", host, timeout=70) + resp = await RequestFormatter.async_request("get", host, timeout=70) if resp is None: break diff --git a/Framework/install_handler/long_poll_handler.py b/Framework/install_handler/long_poll_handler.py index a6ce444f..6a2432f4 100644 --- a/Framework/install_handler/long_poll_handler.py +++ b/Framework/install_handler/long_poll_handler.py @@ -339,7 +339,7 @@ async def run(self) -> None: f"d/nodes/install/node/listen?node_id={read_node_id()}" ) - resp = resp = RequestFormatter.request("get", host, timeout=70) + resp = await RequestFormatter.async_request("get", host, timeout=70) if not resp.ok: if debug: @@ -368,5 +368,5 @@ async def run(self) -> None: print("[installer] RETRYING...") await asyncio.sleep(random.randint(1, 3)) - self.running = False - print("[installer] Stopped running") + self.running = False + print("[installer] Stopped running") diff --git a/Framework/install_handler/utils.py b/Framework/install_handler/utils.py index c20f0cfb..b268404a 100644 --- a/Framework/install_handler/utils.py +++ b/Framework/install_handler/utils.py @@ -60,7 +60,7 @@ async def send_response(data=None) -> None: for _ in range(3): try: - resp = RequestFormatter.request("post", host, json=data, timeout=70) + resp = await RequestFormatter.request("post", host, json=data, timeout=70) if debug: print(f"[installer] Response status: {resp.status_code}") print(f"[installer] Response content: {resp.content}")