From c9a709783a268761e96757d152c73a0429346724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 20 Aug 2025 17:13:38 +0200 Subject: [PATCH 1/9] BLE001: fix --- pkgs/clan-app/clan_app/api/api_bridge.py | 2 +- .../clan_app/deps/http/http_bridge.py | 8 +-- .../clan-app/clan_app/deps/webview/webview.py | 4 +- pkgs/clan-cli/clan_cli/network/list.py | 3 +- pkgs/clan-cli/clan_cli/secrets/sops.py | 2 +- pkgs/clan-cli/clan_lib/async_run/__init__.py | 2 +- pkgs/clan-cli/clan_lib/flake/flake.py | 2 +- pkgs/clan-cli/clan_lib/machines/update.py | 2 +- pkgs/clan-cli/clan_lib/network/network.py | 58 +++++++------------ pkgs/clan-cli/clan_lib/network/qr_code.py | 10 +++- pkgs/clan-cli/clan_lib/ssh/remote_test.py | 4 +- .../clan_lib/ssh/sudo_askpass_proxy.py | 2 +- .../clan_vm_manager/components/executor.py | 2 +- .../clan_vm_manager/components/vmobj.py | 2 +- .../clan_vm_manager/singletons/use_vms.py | 2 +- pkgs/classgen/main.py | 7 +-- 16 files changed, 49 insertions(+), 63 deletions(-) diff --git a/pkgs/clan-app/clan_app/api/api_bridge.py b/pkgs/clan-app/clan_app/api/api_bridge.py index e95ed6153..f93a7e127 100644 --- a/pkgs/clan-app/clan_app/api/api_bridge.py +++ b/pkgs/clan-app/clan_app/api/api_bridge.py @@ -59,7 +59,7 @@ class ApiBridge(ABC): f"{middleware.__class__.__name__} => {request.method_name}", ) middleware.process(context) - except Exception as e: + except Exception as e: # noqa: BLE001 # If middleware fails, handle error self.send_api_error_response( request.op_key or "unknown", diff --git a/pkgs/clan-app/clan_app/deps/http/http_bridge.py b/pkgs/clan-app/clan_app/deps/http/http_bridge.py index e631c1372..6df1bd537 100644 --- a/pkgs/clan-app/clan_app/deps/http/http_bridge.py +++ b/pkgs/clan-app/clan_app/deps/http/http_bridge.py @@ -148,7 +148,7 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler): self.send_header("Content-Type", content_type) self.end_headers() self.wfile.write(file_data) - except Exception as e: + except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e: log.error(f"Error reading Swagger file: {e!s}") self.send_error(500, "Internal Server Error") @@ -252,7 +252,7 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler): gen_op_key = str(uuid.uuid4()) try: self._handle_api_request(method_name, request_data, gen_op_key) - except Exception as e: + except RuntimeError as e: log.exception(f"Error processing API request {method_name}") self.send_api_error_response( gen_op_key, @@ -275,7 +275,7 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler): ["http_bridge", "POST", method_name], ) return None - except Exception as e: + except (OSError, ValueError, UnicodeDecodeError) as e: self.send_api_error_response( "post", f"Error reading request: {e!s}", @@ -305,7 +305,7 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler): op_key=op_key, ) - except Exception as e: + except (KeyError, TypeError, ValueError) as e: self.send_api_error_response( gen_op_key, str(e), diff --git a/pkgs/clan-app/clan_app/deps/webview/webview.py b/pkgs/clan-app/clan_app/deps/webview/webview.py index 2646a0140..7a369a19a 100644 --- a/pkgs/clan-app/clan_app/deps/webview/webview.py +++ b/pkgs/clan-app/clan_app/deps/webview/webview.py @@ -81,7 +81,7 @@ class Webview: msg = message_queue.get() # Blocks until available js_code = f"window.notifyBus({json.dumps(msg)});" self.eval(js_code) - except Exception as e: + except (json.JSONDecodeError, RuntimeError, AttributeError) as e: print("Bridge notify error:", e) sleep(0.01) # avoid busy loop @@ -211,7 +211,7 @@ class Webview: try: result = callback(*args) success = True - except Exception as e: + except Exception as e: # noqa: BLE001 result = str(e) success = False self.return_(seq.decode(), 0 if success else 1, json.dumps(result)) diff --git a/pkgs/clan-cli/clan_cli/network/list.py b/pkgs/clan-cli/clan_cli/network/list.py index ac3afb744..a6850b05c 100644 --- a/pkgs/clan-cli/clan_cli/network/list.py +++ b/pkgs/clan-cli/clan_cli/network/list.py @@ -1,6 +1,7 @@ import argparse import logging +from clan_lib.errors import ClanError from clan_lib.flake import require_flake from clan_lib.network.network import networks_from_flake @@ -54,7 +55,7 @@ def list_command(args: argparse.Namespace) -> None: try: is_running = network.is_running() running_status = "Yes" if is_running else "No" - except Exception: + except ClanError: running_status = "Error" print( diff --git a/pkgs/clan-cli/clan_cli/secrets/sops.py b/pkgs/clan-cli/clan_cli/secrets/sops.py index c66a97ad2..bdab5a318 100644 --- a/pkgs/clan-cli/clan_cli/secrets/sops.py +++ b/pkgs/clan-cli/clan_cli/secrets/sops.py @@ -83,7 +83,7 @@ class KeyType(enum.Enum): except FileNotFoundError: return - except Exception as ex: + except OSError as ex: log.warning(f"Could not read age keys from {key_path}", exc_info=ex) if keys := os.environ.get("SOPS_AGE_KEY"): diff --git a/pkgs/clan-cli/clan_lib/async_run/__init__.py b/pkgs/clan-cli/clan_lib/async_run/__init__.py index 0cec16006..133d04d02 100644 --- a/pkgs/clan-cli/clan_lib/async_run/__init__.py +++ b/pkgs/clan-cli/clan_lib/async_run/__init__.py @@ -155,7 +155,7 @@ class AsyncThread[**P, R](threading.Thread): set_should_cancel(lambda: self.stop_event.is_set()) # Arguments for ParamSpec "P@AsyncThread" are missing self.result = AsyncResult(_result=self.function(*self.args, **self.kwargs)) - except Exception as ex: + except Exception as ex: # noqa: BLE001 self.result = AsyncResult(_result=ex) finally: self.finished = True diff --git a/pkgs/clan-cli/clan_lib/flake/flake.py b/pkgs/clan-cli/clan_lib/flake/flake.py index 7f8ef9a5e..915ffd23e 100644 --- a/pkgs/clan-cli/clan_lib/flake/flake.py +++ b/pkgs/clan-cli/clan_lib/flake/flake.py @@ -846,7 +846,7 @@ class Flake: return try: self._cache.load_from_file(path) - except Exception as e: + except (OSError, json.JSONDecodeError, KeyError, ValueError) as e: log.warning(f"Failed load eval cache: {e}. Continue without cache") def prefetch(self) -> None: diff --git a/pkgs/clan-cli/clan_lib/machines/update.py b/pkgs/clan-cli/clan_lib/machines/update.py index 539e6908a..d0c84f91b 100644 --- a/pkgs/clan-cli/clan_lib/machines/update.py +++ b/pkgs/clan-cli/clan_lib/machines/update.py @@ -234,7 +234,7 @@ def run_machine_update( is_mobile = machine.select( "config.system.clan.deployment.nixosMobileWorkaround", ) - except Exception: + except ClanError: is_mobile = False # if the machine is mobile, we retry to deploy with the mobile workaround method if is_mobile: diff --git a/pkgs/clan-cli/clan_lib/network/network.py b/pkgs/clan-cli/clan_lib/network/network.py index 71b6db9f3..63f6ab4b6 100644 --- a/pkgs/clan-cli/clan_lib/network/network.py +++ b/pkgs/clan-cli/clan_lib/network/network.py @@ -161,12 +161,9 @@ def get_best_remote(machine: "Machine") -> Iterator["Remote"]: if target_host: log.debug(f"Using targetHost from inventory for {machine.name}: {target_host}") # Create a direct network with just this machine - try: - remote = Remote.from_ssh_uri(machine_name=machine.name, address=target_host) - yield remote - return - except Exception as e: - log.debug(f"Inventory targetHost not reachable for {machine.name}: {e}") + remote = Remote.from_ssh_uri(machine_name=machine.name, address=target_host) + yield remote + return # Step 2: Try existing networks by priority try: @@ -189,7 +186,7 @@ def get_best_remote(machine: "Machine") -> Iterator["Remote"]: ) yield network.remote(machine.name) return - except Exception as e: + except ClanError as e: log.debug(f"Failed to reach {machine.name} via {network_name}: {e}") else: try: @@ -202,34 +199,26 @@ def get_best_remote(machine: "Machine") -> Iterator["Remote"]: ) yield connected_network.remote(machine.name) return - except Exception as e: + except ClanError as e: log.debug( f"Failed to establish connection to {machine.name} via {network_name}: {e}", ) - except Exception as e: + except (ImportError, AttributeError, KeyError) as e: log.debug(f"Failed to use networking modules to determine machines remote: {e}") # Step 3: Try targetHost from machine nixos config - try: - target_host = machine.select('config.clan.core.networking."targetHost"') - if target_host: - log.debug( - f"Using targetHost from machine config for {machine.name}: {target_host}", - ) - # Check if reachable - try: - remote = Remote.from_ssh_uri( - machine_name=machine.name, - address=target_host, - ) - yield remote - return - except Exception as e: - log.debug( - f"Machine config targetHost not reachable for {machine.name}: {e}", - ) - except Exception as e: - log.debug(f"Could not get targetHost from machine config: {e}") + target_host = machine.select('config.clan.core.networking."targetHost"') + if target_host: + log.debug( + f"Using targetHost from machine config for {machine.name}: {target_host}", + ) + # Check if reachable + remote = Remote.from_ssh_uri( + machine_name=machine.name, + address=target_host, + ) + yield remote + return # No connection method found msg = f"Could not find any way to connect to machine '{machine.name}'. No targetHost configured and machine not reachable via any network." @@ -249,12 +238,7 @@ def get_network_overview(networks: dict[str, Network]) -> dict: else: with module.connection(network) as conn: for peer_name in conn.peers: - try: - result[network_name]["peers"][peer_name] = conn.ping( - peer_name, - ) - except ClanError: - log.warning( - f"getting host for machine: {peer_name} in network: {network_name} failed", - ) + result[network_name]["peers"][peer_name] = conn.ping( + peer_name, + ) return result diff --git a/pkgs/clan-cli/clan_lib/network/qr_code.py b/pkgs/clan-cli/clan_lib/network/qr_code.py index 44fa71150..6d97954ab 100644 --- a/pkgs/clan-cli/clan_lib/network/qr_code.py +++ b/pkgs/clan-cli/clan_lib/network/qr_code.py @@ -29,6 +29,7 @@ class QRCodeData: @contextmanager def get_best_remote(self) -> Iterator[Remote]: + errors = [] for address in self.addresses: try: log.debug(f"Establishing connection via {address}") @@ -39,8 +40,13 @@ class QRCodeData: if ping_time is not None: log.info(f"reachable via {address} after connection") yield address.remote - except Exception as e: - log.debug(f"Failed to establish connection via {address}: {e}") + except ClanError as e: + errors.append((address, e)) + if not errors: + msg = "No reachable remote found in QR code data: " + ", ".join( + f"{addr.remote} ({err})" for addr, err in errors + ) + raise ClanError(msg) def read_qr_json(qr_data: dict[str, Any], flake: Flake) -> QRCodeData: diff --git a/pkgs/clan-cli/clan_lib/ssh/remote_test.py b/pkgs/clan-cli/clan_lib/ssh/remote_test.py index 08a21679b..beebc711b 100644 --- a/pkgs/clan-cli/clan_lib/ssh/remote_test.py +++ b/pkgs/clan-cli/clan_lib/ssh/remote_test.py @@ -239,7 +239,7 @@ def test_run_exception(hosts: list[Remote], runtime: AsyncRuntime) -> None: runtime.async_run(None, host.run_local, ["exit 1"], RunOpts(shell=True)) # noqa: S604 runtime.join_all() runtime.check_all() - except Exception: # noqa: S110 + except ClanError: pass else: msg = "should have raised Exception" @@ -255,7 +255,7 @@ def test_run_function_exception(hosts: list[Remote], runtime: AsyncRuntime) -> N runtime.async_run(None, some_func, host) runtime.join_all() runtime.check_all() - except Exception: # noqa: S110 + except ClanError: pass else: msg = "should have raised Exception" diff --git a/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py b/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py index c4e8f5747..dfdaae9ac 100644 --- a/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py +++ b/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py @@ -90,7 +90,7 @@ class SudoAskpassProxy: ssh_process.stdin.flush() else: print(stripped_line) - except Exception as e: + except (OSError, ClanError) as e: logger.error(f"Error processing passwords requests output: {e}") def run(self) -> str: diff --git a/pkgs/clan-vm-manager/clan_vm_manager/components/executor.py b/pkgs/clan-vm-manager/clan_vm_manager/components/executor.py index 350f29135..296f02f7c 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/components/executor.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/components/executor.py @@ -84,7 +84,7 @@ def _init_proc( print(linebreak + f" {func.__name__}:{pid} " + linebreak, file=sys.stderr) try: func(**kwargs) - except Exception as ex: + except Exception as ex: # noqa: BLE001 traceback.print_exc() if on_except is not None: on_except(ex, mp.current_process()) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py b/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py index 3edc1637e..7fb7c27e0 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py @@ -345,7 +345,7 @@ class VMObject(GObject.Object): raise ClanError(msg) with self.qmp_wrap.qmp_ctx() as qmp: qmp.command("system_powerdown") - except Exception as ex: + except (ClanError, OSError, ConnectionError) as ex: log.debug(f"QMP command 'system_powerdown' ignored. Error: {ex}") # Try 20 times to stop the VM diff --git a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py index 0d7486674..3c528b885 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py @@ -139,7 +139,7 @@ class ClanStore: # Convert the byte array to a string and print it logs_view.set_message(contents.decode("utf-8")) - except Exception as e: + except (GLib.Error, UnicodeDecodeError) as e: print(f"Error reading file: {e}") # only one vm can output logs at a time diff --git a/pkgs/classgen/main.py b/pkgs/classgen/main.py index db6cb96e6..b2a737f8e 100644 --- a/pkgs/classgen/main.py +++ b/pkgs/classgen/main.py @@ -1,7 +1,6 @@ import argparse import json import logging -import traceback from collections.abc import Callable, Iterable from functools import partial from pathlib import Path @@ -454,8 +453,4 @@ def main() -> None: if __name__ == "__main__": - try: - main() - except Exception: - print("An error occurred:") - traceback.print_exc() + main() From 61a647b4364347c8989cf8f32ab576a9d8600f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 20 Aug 2025 20:00:31 +0200 Subject: [PATCH 2/9] PLR1704: fix --- pkgs/clan-cli/clan_cli/facts/check.py | 18 +++++++++--------- pkgs/clan-cli/clan_cli/facts/generate.py | 4 ++-- pkgs/clan-cli/clan_cli/state/list.py | 8 ++++---- pkgs/clan-cli/clan_lib/backups/create.py | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/facts/check.py b/pkgs/clan-cli/clan_cli/facts/check.py index f6233ea7d..ee2d71ea5 100644 --- a/pkgs/clan-cli/clan_cli/facts/check.py +++ b/pkgs/clan-cli/clan_cli/facts/check.py @@ -13,24 +13,24 @@ def check_secrets(machine: Machine, service: None | str = None) -> bool: missing_secret_facts = [] missing_public_facts = [] services = [service] if service else list(machine.facts_data.keys()) - for service in services: - for secret_fact in machine.facts_data[service]["secret"]: + for svc in services: + for secret_fact in machine.facts_data[svc]["secret"]: if isinstance(secret_fact, str): secret_name = secret_fact else: secret_name = secret_fact["name"] - if not machine.secret_facts_store.exists(service, secret_name): + if not machine.secret_facts_store.exists(svc, secret_name): machine.info( - f"Secret fact '{secret_fact}' for service '{service}' is missing.", + f"Secret fact '{secret_fact}' for service '{svc}' is missing.", ) - missing_secret_facts.append((service, secret_name)) + missing_secret_facts.append((svc, secret_name)) - for public_fact in machine.facts_data[service]["public"]: - if not machine.public_facts_store.exists(service, public_fact): + for public_fact in machine.facts_data[svc]["public"]: + if not machine.public_facts_store.exists(svc, public_fact): machine.info( - f"Public fact '{public_fact}' for service '{service}' is missing.", + f"Public fact '{public_fact}' for service '{svc}' is missing.", ) - missing_public_facts.append((service, public_fact)) + missing_public_facts.append((svc, public_fact)) machine.debug(f"missing_secret_facts: {missing_secret_facts}") machine.debug(f"missing_public_facts: {missing_public_facts}") diff --git a/pkgs/clan-cli/clan_cli/facts/generate.py b/pkgs/clan-cli/clan_cli/facts/generate.py index c25297fc3..efe97cb0a 100644 --- a/pkgs/clan-cli/clan_cli/facts/generate.py +++ b/pkgs/clan-cli/clan_cli/facts/generate.py @@ -178,10 +178,10 @@ def _generate_facts_for_machine( else: machine_service_facts = machine.facts_data - for service in machine_service_facts: + for svc in machine_service_facts: machine_updated |= generate_service_facts( machine=machine, - service=service, + service=svc, regenerate=regenerate, secret_facts_store=machine.secret_facts_store, public_facts_store=machine.public_facts_store, diff --git a/pkgs/clan-cli/clan_cli/state/list.py b/pkgs/clan-cli/clan_cli/state/list.py index 986383d49..c2fe64f61 100644 --- a/pkgs/clan-cli/clan_cli/state/list.py +++ b/pkgs/clan-cli/clan_cli/state/list.py @@ -52,12 +52,12 @@ def list_state_folders(machine: Machine, service: None | str = None) -> None: description=f"The service: {service} needs to be configured for the machine.", ) - for service in state: - if not service: + for svc in state: + if not svc: continue - print(f"· service: {service}") - service_cfg = state.get(service) + print(f"· service: {svc}") + service_cfg = state.get(svc) if not service_cfg: continue # or handle missing config diff --git a/pkgs/clan-cli/clan_lib/backups/create.py b/pkgs/clan-cli/clan_lib/backups/create.py index 428744bdf..844f2e154 100644 --- a/pkgs/clan-cli/clan_lib/backups/create.py +++ b/pkgs/clan-cli/clan_lib/backups/create.py @@ -11,9 +11,9 @@ def create_backup(machine: Machine, provider: str | None = None) -> None: msg = "No providers specified" raise ClanError(msg) with host.host_connection() as ssh: - for provider in backup_scripts["providers"]: + for prov in backup_scripts["providers"]: proc = ssh.run( - [backup_scripts["providers"][provider]["create"]], + [backup_scripts["providers"][prov]["create"]], ) if proc.returncode != 0: msg = "failed to start backup" From 79c4e73a15f549c185183b8a288365976fdb12a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 20 Aug 2025 20:57:29 +0200 Subject: [PATCH 3/9] test_http_api: remove unused logging middleware --- .../clan_app/deps/http/test_http_api.py | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/pkgs/clan-app/clan_app/deps/http/test_http_api.py b/pkgs/clan-app/clan_app/deps/http/test_http_api.py index b68a6c2ee..db64fd9f5 100644 --- a/pkgs/clan-app/clan_app/deps/http/test_http_api.py +++ b/pkgs/clan-app/clan_app/deps/http/test_http_api.py @@ -4,13 +4,11 @@ import json import logging import threading import time -from unittest.mock import Mock from urllib.request import Request, urlopen import pytest from clan_lib.api import MethodRegistry, tasks from clan_lib.async_run import is_async_cancelled -from clan_lib.log_manager import LogManager from clan_app.api.middleware import ( ArgumentParsingMiddleware, @@ -53,31 +51,20 @@ def mock_api() -> MethodRegistry: return api -@pytest.fixture -def mock_log_manager() -> Mock: - """Create a mock log manager.""" - log_manager = Mock(spec=LogManager) - log_manager.create_log_file.return_value.get_file_path.return_value = Mock() - log_manager.create_log_file.return_value.get_file_path.return_value.open.return_value = Mock() - return log_manager - - @pytest.fixture def http_bridge( mock_api: MethodRegistry, - mock_log_manager: Mock, ) -> tuple[MethodRegistry, tuple]: """Create HTTP bridge dependencies for testing.""" middleware_chain = ( ArgumentParsingMiddleware(api=mock_api), - # LoggingMiddleware(log_manager=mock_log_manager), MethodExecutionMiddleware(api=mock_api), ) return mock_api, middleware_chain @pytest.fixture -def http_server(mock_api: MethodRegistry, mock_log_manager: Mock) -> HttpApiServer: +def http_server(mock_api: MethodRegistry) -> HttpApiServer: """Create HTTP server with mock dependencies.""" server = HttpApiServer( api=mock_api, @@ -87,7 +74,6 @@ def http_server(mock_api: MethodRegistry, mock_log_manager: Mock) -> HttpApiServ # Add middleware server.add_middleware(ArgumentParsingMiddleware(api=mock_api)) - # server.add_middleware(LoggingMiddleware(log_manager=mock_log_manager)) server.add_middleware(MethodExecutionMiddleware(api=mock_api)) # Bridge will be created automatically when accessed @@ -114,7 +100,6 @@ class TestHttpBridge: # The actual HTTP handling will be tested through the server integration tests assert len(middleware_chain) == 2 assert isinstance(middleware_chain[0], ArgumentParsingMiddleware) - # assert isinstance(middleware_chain[1], LoggingMiddleware) assert isinstance(middleware_chain[1], MethodExecutionMiddleware) @@ -259,7 +244,6 @@ class TestIntegration: def test_full_request_flow( self, mock_api: MethodRegistry, - mock_log_manager: Mock, ) -> None: """Test complete request flow from server to bridge to middleware.""" server: HttpApiServer = HttpApiServer( @@ -270,7 +254,6 @@ class TestIntegration: # Add middleware server.add_middleware(ArgumentParsingMiddleware(api=mock_api)) - # server.add_middleware(LoggingMiddleware(log_manager=mock_log_manager)) server.add_middleware(MethodExecutionMiddleware(api=mock_api)) # Bridge will be created automatically when accessed @@ -306,7 +289,6 @@ class TestIntegration: def test_blocking_task( self, mock_api: MethodRegistry, - mock_log_manager: Mock, ) -> None: shared_threads: dict[str, tasks.WebThread] = {} tasks.BAKEND_THREADS = shared_threads @@ -321,7 +303,6 @@ class TestIntegration: # Add middleware server.add_middleware(ArgumentParsingMiddleware(api=mock_api)) - # server.add_middleware(LoggingMiddleware(log_manager=mock_log_manager)) server.add_middleware(MethodExecutionMiddleware(api=mock_api)) # Start server From ac20514a8e307001ae95572ce8a41ae4ba148d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 20 Aug 2025 22:18:45 +0200 Subject: [PATCH 4/9] EXE001: fix --- nixosModules/clanCore/zerotier/genmoon.py | 0 pkgs/clan-cli/clan_cli/tests/ports.py | 2 -- pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py | 2 -- pkgs/clan-vm-manager/clan_vm_manager/app.py | 0 4 files changed, 4 deletions(-) mode change 100644 => 100755 nixosModules/clanCore/zerotier/genmoon.py mode change 100644 => 100755 pkgs/clan-vm-manager/clan_vm_manager/app.py diff --git a/nixosModules/clanCore/zerotier/genmoon.py b/nixosModules/clanCore/zerotier/genmoon.py old mode 100644 new mode 100755 diff --git a/pkgs/clan-cli/clan_cli/tests/ports.py b/pkgs/clan-cli/clan_cli/tests/ports.py index 730e13fd8..fa4c7c089 100644 --- a/pkgs/clan-cli/clan_cli/tests/ports.py +++ b/pkgs/clan-cli/clan_cli/tests/ports.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import contextlib import socket from collections.abc import Callable diff --git a/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py b/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py index dfdaae9ac..443344b40 100644 --- a/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py +++ b/pkgs/clan-cli/clan_lib/ssh/sudo_askpass_proxy.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import logging import subprocess import sys diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py old mode 100644 new mode 100755 From d1421bb5340e0d13b0f1103dc292d1c578be1a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 20 Aug 2025 22:19:12 +0200 Subject: [PATCH 5/9] EXE002: fix --- pkgs/merge-after-ci/merge-after-ci.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/merge-after-ci/merge-after-ci.py b/pkgs/merge-after-ci/merge-after-ci.py index acbffbea8..a53406bf5 100755 --- a/pkgs/merge-after-ci/merge-after-ci.py +++ b/pkgs/merge-after-ci/merge-after-ci.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import argparse import shlex import subprocess From 64217e128180176a221d9df3d2751bfddad4bd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 20 Aug 2025 22:19:50 +0200 Subject: [PATCH 6/9] G001: fix --- pkgs/clan-cli/clan_cli/vms/virtiofsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/clan-cli/clan_cli/vms/virtiofsd.py b/pkgs/clan-cli/clan_cli/vms/virtiofsd.py index 9a5dccfdd..1da43fbb7 100644 --- a/pkgs/clan-cli/clan_cli/vms/virtiofsd.py +++ b/pkgs/clan-cli/clan_cli/vms/virtiofsd.py @@ -34,7 +34,7 @@ def start_virtiofsd(socket_path: Path) -> Iterator[None]: str(store), ], ) - log.debug("$ {}".format(" ".join(virtiofsd))) + log.debug("$ %s", " ".join(virtiofsd)) with subprocess.Popen(virtiofsd) as proc: try: while not socket_path.exists(): From c45d4cfec99fd95fd7e83440a91598d2f0453c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 25 Aug 2025 15:09:51 +0200 Subject: [PATCH 7/9] D413/D212: fix --- pkgs/clan-cli/clan_lib/log_manager/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/clan-cli/clan_lib/log_manager/api.py b/pkgs/clan-cli/clan_lib/log_manager/api.py index 22c5eca64..dcd4b37e0 100644 --- a/pkgs/clan-cli/clan_lib/log_manager/api.py +++ b/pkgs/clan-cli/clan_lib/log_manager/api.py @@ -14,6 +14,7 @@ def list_log_days() -> list[str]: Raises: ClanError: If LOG_MANAGER_INSTANCE is not initialized. + """ if LOG_MANAGER_INSTANCE is None: msg = "LOG_MANAGER_INSTANCE is not initialized" @@ -63,6 +64,7 @@ def get_log_file( Raises: ClanError: If the log file is not found or LOG_MANAGER_INSTANCE is not initialized. + """ if LOG_MANAGER_INSTANCE is None: msg = "LOG_MANAGER_INSTANCE is not initialized" From e6066a6cb167ed60a251f85e295f33a2f8a16d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 26 Aug 2025 12:12:29 +0200 Subject: [PATCH 8/9] spawn_tor: catch OSError and wrap as ClanError --- pkgs/clan-cli/clan_lib/network/tor/lib.py | 26 +++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pkgs/clan-cli/clan_lib/network/tor/lib.py b/pkgs/clan-cli/clan_lib/network/tor/lib.py index c885ba660..177301693 100755 --- a/pkgs/clan-cli/clan_lib/network/tor/lib.py +++ b/pkgs/clan-cli/clan_lib/network/tor/lib.py @@ -43,18 +43,22 @@ def spawn_tor() -> Iterator[None]: cmd_args = ["tor", "--HardwareAccel", "1"] packages = ["tor"] cmd = nix_shell(packages, cmd_args) - process = Popen(cmd) try: - while not is_tor_running(): - log.debug("Waiting for Tor to start...") - time.sleep(0.2) - log.info("Tor is now running") - yield - finally: - log.info("Terminating Tor process...") - process.terminate() - process.wait() - log.info("Tor process terminated") + process = Popen(cmd) + try: + while not is_tor_running(): + log.debug("Waiting for Tor to start...") + time.sleep(0.2) + log.info("Tor is now running") + yield + finally: + log.info("Terminating Tor process...") + process.terminate() + process.wait() + log.info("Tor process terminated") + except OSError as e: + msg = f"Failed to spawn tor process with command: {cmd}" + raise ClanError(msg) from e @dataclass(frozen=True) From f634b8f1fbc68b6bf65ece1d0091f12fe1f70330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 26 Aug 2025 12:33:27 +0200 Subject: [PATCH 9/9] merge-after-ci: move away from writePython3Bin this is one is doing checks we don't want because we already have ruff. --- pkgs/merge-after-ci/default.nix | 57 +++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/pkgs/merge-after-ci/default.nix b/pkgs/merge-after-ci/default.nix index 732c720d4..cc731f27f 100644 --- a/pkgs/merge-after-ci/default.nix +++ b/pkgs/merge-after-ci/default.nix @@ -1,6 +1,8 @@ { + stdenv, + makeWrapper, + python3, bash, - callPackage, coreutils, git, lib, @@ -10,22 +12,37 @@ tea-create-pr, ... }: -let - writers = callPackage ../builders/script-writers.nix { }; -in -writers.writePython3Bin "merge-after-ci" { - makeWrapperArgs = [ - "--prefix" - "PATH" - ":" - (lib.makeBinPath [ - bash - coreutils - git - nix - openssh - tea - tea-create-pr - ]) - ]; -} ./merge-after-ci.py +stdenv.mkDerivation { + pname = "merge-after-ci"; + version = "0.1.0"; + + src = ./.; + + nativeBuildInputs = [ makeWrapper ]; + + buildInputs = [ python3 ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin + cp merge-after-ci.py $out/bin/merge-after-ci + chmod +x $out/bin/merge-after-ci + + wrapProgram $out/bin/merge-after-ci \ + --prefix PATH : ${ + lib.makeBinPath [ + bash + coreutils + git + nix + openssh + tea + tea-create-pr + python3 + ] + } + + runHook postInstall + ''; +}