From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 23AB621C16A for ; Sun, 31 Aug 2025 04:00:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=198.137.202.133 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756612808; cv=none; b=p2k5idxgr+Q1bEUb3PO8jnAm5iV4a+jcWvePsyiLHfxligmu3eXjA43xbFjVcOGdM/R2tqJRWrYtSeAjNM5QK4Qz6GWv6XPF1kuHZr4gfhScOjWjDDxQZZgKHi62VKXcKcGhB/Kb/d5kKuLe8w10ulX+ym+DGUHBIezAMRR3tS4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756612808; c=relaxed/simple; bh=JD+0cQxnHCGpFvXVIanJvvwHuaMOv2JPbJaTlwaI2Bg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=bmm+NeMKgAkoRAZsbGrY7OPoQV7bJjbEIeV+CKgE0xsqL4YchYVMA2zgg2EnAv4SqUsK4Ugm4jIXSC1P/ohegdpEmeXrsAFizK/BaoMD75UPg+IRZ6TMlfOuWMXZNWOAyFDrr/1r0uOo9EwznkWQTlbxWoHfgUU7SWS4UMpJTXs= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=kernel.org; spf=none smtp.mailfrom=infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=uZTdT2wX; arc=none smtp.client-ip=198.137.202.133 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=kernel.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b="uZTdT2wX" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=bombadil.20210309; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=RZwN6hfFu6RCc7kBV5iiwZ4GNZ5sNu0luu0gUCnEsGQ=; b=uZTdT2wXrYqDnJwr75Bg5y+IDq zZqWpV7GVrNvUwFDK2uMP01l44sgdlnDuQYEIaAYKQVF9jxs41aZqQeoYUdpih4kZGZsxxcVQ1O90 Ba4RM1lSCoDL3evEIttJEyb3momuXqE1lD2hOt2KM2LesfDMIfl2L6dFBHZ1PGLz614mJuzPHZxce x+FrECXlpgFWqPPohFtcNW+CDLGt98XGBv2BtY9KtEiugaVhMvhRRiXlfbRf+ivnwl4vAMN8KbDw+ ZesIYy0hBM9/ZlnhX0Lm/cTfOSdBe7Ud/PRPaIDZHwbZULPrr7reILGqaB86F+Sbg6LrjIw9qhbR7 EKXoKzlw==; Received: from mcgrof by bombadil.infradead.org with local (Exim 4.98.2 #2 (Red Hat Linux)) id 1usZEb-000000093rl-3570; Sun, 31 Aug 2025 04:00:05 +0000 From: Luis Chamberlain To: Chuck Lever , Daniel Gomez , kdevops@lists.linux.dev Cc: Luis Chamberlain , Your Name Subject: [PATCH v3 05/10] scripts: add Lambda Labs SSH key management utilities Date: Sat, 30 Aug 2025 20:59:59 -0700 Message-ID: <20250831040004.2159779-6-mcgrof@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250831040004.2159779-1-mcgrof@kernel.org> References: <20250831040004.2159779-1-mcgrof@kernel.org> Precedence: bulk X-Mailing-List: kdevops@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: Luis Chamberlain Add comprehensive SSH key management for Lambda Labs: - lambdalabs_ssh_keys.py: Core SSH key operations (list, upload, delete) - lambdalabs_ssh_key_name.py: Smart SSH key name resolution - ssh_config_file_name.py: SSH config filename inference - update_ssh_config_lambdalabs.py: Automatic SSH configuration Features: - Automatic local SSH key discovery and upload - Smart name generation from key content and hostnames - SSH config file management for seamless connectivity - Integration with kdevops SSH workflow patterns These utilities enable seamless SSH key provisioning and management for Lambda Labs instances. Generated-by: Claude AI Signed-off-by: Your Name --- scripts/lambdalabs_ssh_key_name.py | 135 +++++++++ scripts/lambdalabs_ssh_keys.py | 358 ++++++++++++++++++++++++ scripts/ssh_config_file_name.py | 79 ++++++ scripts/update_ssh_config_lambdalabs.py | 110 ++++++++ 4 files changed, 682 insertions(+) create mode 100755 scripts/lambdalabs_ssh_key_name.py create mode 100755 scripts/lambdalabs_ssh_keys.py create mode 100755 scripts/ssh_config_file_name.py create mode 100755 scripts/update_ssh_config_lambdalabs.py diff --git a/scripts/lambdalabs_ssh_key_name.py b/scripts/lambdalabs_ssh_key_name.py new file mode 100755 index 0000000..131ac3a --- /dev/null +++ b/scripts/lambdalabs_ssh_key_name.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: copyleft-next-0.3.1 + +""" +Generate a unique SSH key name for Lambda Labs based on the current directory. +This ensures each kdevops instance uses its own SSH key for security. +""" + +import hashlib +import os +import sys + + +def get_directory_hash(path: str, length: int = 8) -> str: + """ + Generate a short hash of the directory path. + + Args: + path: Directory path to hash + length: Number of hex characters to use (default 8) + + Returns: + Hex string of specified length + """ + # Get the absolute path to ensure consistency + abs_path = os.path.abspath(path) + + # Create SHA256 hash of the path + hash_obj = hashlib.sha256(abs_path.encode("utf-8")) + + # Return first N characters of the hex digest + return hash_obj.hexdigest()[:length] + + +def get_project_name(path: str) -> str: + """ + Extract a meaningful project name from the path. + + Args: + path: Directory path + + Returns: + Project name derived from directory + """ + abs_path = os.path.abspath(path) + + # Get the last two directory components for context + # e.g., /home/user/projects/kdevops -> projects-kdevops + parts = abs_path.rstrip("/").split("/") + + if len(parts) >= 2: + # Use last two directories + project_parts = parts[-2:] + # Filter out generic names + filtered = [ + p + for p in project_parts + if p not in ["data", "home", "root", "usr", "var", "tmp"] + ] + if filtered: + return "-".join(filtered) + + # Fallback to just the last directory + return parts[-1] if parts else "kdevops" + + +def generate_ssh_key_name(prefix: str = "kdevops", include_project: bool = True) -> str: + """ + Generate a unique SSH key name for the current directory. + + Args: + prefix: Prefix for the key name (default "kdevops") + include_project: Include project name in the key (default True) + + Returns: + Unique SSH key name like "kdevops-lambda-kdevops-a1b2c3d4" + """ + cwd = os.getcwd() + dir_hash = get_directory_hash(cwd) + + parts = [prefix] + + if include_project: + project = get_project_name(cwd) + # Limit project name length and sanitize + project = project.replace("_", "-").replace(".", "-")[:20] + parts.append(project) + + parts.append(dir_hash) + + # Create the key name + key_name = "-".join(parts) + + # Ensure it's a valid name (alphanumeric and hyphens only) + key_name = "".join(c if c.isalnum() or c == "-" else "-" for c in key_name) + + # Remove multiple consecutive hyphens + while "--" in key_name: + key_name = key_name.replace("--", "-") + + # Trim to reasonable length (Lambda Labs might have limits) + if len(key_name) > 50: + # Keep prefix, partial project, and full hash + key_name = f"{prefix}-{dir_hash}" + + return key_name.strip("-") + + +def main(): + """Main entry point.""" + if len(sys.argv) > 1: + if sys.argv[1] == "--help" or sys.argv[1] == "-h": + print("Usage: lambdalabs_ssh_key_name.py [--simple]") + print() + print("Generate a unique SSH key name based on current directory.") + print() + print("Options:") + print(" --simple Generate simple name without project context") + print(" --help Show this help message") + print() + print("Examples:") + print(" Default: kdevops-lambda-kdevops-a1b2c3d4") + print(" Simple: kdevops-a1b2c3d4") + sys.exit(0) + elif sys.argv[1] == "--simple": + print(generate_ssh_key_name(include_project=False)) + else: + print(f"Unknown option: {sys.argv[1]}", file=sys.stderr) + sys.exit(1) + else: + print(generate_ssh_key_name()) + + +if __name__ == "__main__": + main() diff --git a/scripts/lambdalabs_ssh_keys.py b/scripts/lambdalabs_ssh_keys.py new file mode 100755 index 0000000..2fa9880 --- /dev/null +++ b/scripts/lambdalabs_ssh_keys.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: copyleft-next-0.3.1 + +""" +Lambda Labs SSH Key Management via API. +Provides functions to list, add, and delete SSH keys through the Lambda Labs API. +""" + +import json +import os +import sys +import urllib.request +import urllib.error +from typing import Dict, List, Optional, Tuple + +# Import our credentials module +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from lambdalabs_credentials import get_api_key as get_api_key_from_credentials + +LAMBDALABS_API_BASE = "https://cloud.lambdalabs.com/api/v1" + + +def get_api_key() -> Optional[str]: + """Get Lambda Labs API key from credentials file or environment variable.""" + return get_api_key_from_credentials() + + +def make_api_request( + endpoint: str, api_key: str, method: str = "GET", data: Optional[Dict] = None +) -> Optional[Dict]: + """Make a request to Lambda Labs API.""" + url = f"{LAMBDALABS_API_BASE}{endpoint}" + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + "User-Agent": "kdevops/1.0", + } + + try: + req_data = None + if data and method in ["POST", "PUT", "PATCH"]: + req_data = json.dumps(data).encode("utf-8") + + req = urllib.request.Request(url, headers=headers, data=req_data, method=method) + with urllib.request.urlopen(req) as response: + return json.loads(response.read().decode()) + except urllib.error.HTTPError as e: + print(f"HTTP Error {e.code}: {e.reason}", file=sys.stderr) + if e.code == 404: + print(f"Endpoint not found: {endpoint}", file=sys.stderr) + try: + error_body = e.read().decode() + print(f"Error details: {error_body}", file=sys.stderr) + except: + pass + return None + except Exception as e: + print(f"Error making API request: {e}", file=sys.stderr) + return None + + +def list_ssh_keys(api_key: str) -> Optional[List[Dict]]: + """ + List all SSH keys associated with the Lambda Labs account. + + Returns: + List of SSH key dictionaries with 'name', 'id', and 'public_key' fields + """ + response = make_api_request("/ssh-keys", api_key) + if response: + # The API returns {"data": [{name, id, public_key}, ...]} + if "data" in response: + return response["data"] + # Fallback for other response formats + elif isinstance(response, list): + return response + return None + + +def add_ssh_key(api_key: str, name: str, public_key: str) -> bool: + """ + Add a new SSH key to the Lambda Labs account. + + Args: + api_key: Lambda Labs API key + name: Name for the SSH key + public_key: The public key content + + Returns: + True if successful, False otherwise + """ + # Based on the API response structure, the endpoint is /ssh-keys + # and the format is likely {"name": name, "public_key": public_key} + endpoint = "/ssh-keys" + data = {"name": name, "public_key": public_key.strip()} + + print(f"Adding SSH key '{name}' via POST {endpoint}", file=sys.stderr) + response = make_api_request(endpoint, api_key, method="POST", data=data) + if response: + print(f"Successfully added SSH key '{name}'", file=sys.stderr) + return True + + # Try alternative format if the first one fails + data = {"name": name, "key": public_key.strip()} + print(f"Trying alternative format with 'key' field", file=sys.stderr) + response = make_api_request(endpoint, api_key, method="POST", data=data) + if response: + print(f"Successfully added SSH key '{name}'", file=sys.stderr) + return True + + return False + + +def delete_ssh_key(api_key: str, key_name_or_id: str) -> bool: + """ + Delete an SSH key from the Lambda Labs account. + + Args: + api_key: Lambda Labs API key + key_name_or_id: Name or ID of the SSH key to delete + + Returns: + True if successful, False otherwise + """ + # Check if input looks like an ID (32 character hex string) + is_id = len(key_name_or_id) == 32 and all( + c in "0123456789abcdef" for c in key_name_or_id.lower() + ) + + if not is_id: + # If we have a name, we need to find the ID + keys = list_ssh_keys(api_key) + if keys: + for key in keys: + if key.get("name") == key_name_or_id: + key_id = key.get("id") + if key_id: + print( + f"Found ID {key_id} for key '{key_name_or_id}'", + file=sys.stderr, + ) + key_name_or_id = key_id + break + else: + print(f"SSH key '{key_name_or_id}' not found", file=sys.stderr) + return False + + # Delete using the ID + endpoint = f"/ssh-keys/{key_name_or_id}" + print(f"Deleting SSH key via DELETE {endpoint}", file=sys.stderr) + response = make_api_request(endpoint, api_key, method="DELETE") + if response is not None: + print(f"Successfully deleted SSH key", file=sys.stderr) + return True + + return False + + +def read_public_key_file(filepath: str) -> Optional[str]: + """Read SSH public key from file.""" + expanded_path = os.path.expanduser(filepath) + if not os.path.exists(expanded_path): + print(f"SSH public key file not found: {expanded_path}", file=sys.stderr) + return None + + try: + with open(expanded_path, "r") as f: + return f.read().strip() + except Exception as e: + print(f"Error reading SSH public key: {e}", file=sys.stderr) + return None + + +def check_ssh_key_exists(api_key: str, key_name: str) -> bool: + """ + Check if an SSH key with the given name exists. + + Args: + api_key: Lambda Labs API key + key_name: Name of the SSH key to check + + Returns: + True if key exists, False otherwise + """ + keys = list_ssh_keys(api_key) + if not keys: + return False + + for key in keys: + # Try different possible field names + if key.get("name") == key_name or key.get("key_name") == key_name: + return True + + return False + + +def validate_ssh_setup( + api_key: str, expected_key_name: str = "kdevops-lambdalabs" +) -> Tuple[bool, str]: + """ + Validate that SSH keys are properly configured for Lambda Labs. + + Args: + api_key: Lambda Labs API key + expected_key_name: The SSH key name we expect to use + + Returns: + Tuple of (success, message) + """ + # First, try to list SSH keys + keys = list_ssh_keys(api_key) + + if keys is None: + # API doesn't support SSH key management + return ( + False, + "Lambda Labs API does not appear to support SSH key management.\n" + "You must manually add your SSH key through the Lambda Labs web console:\n" + "1. Go to https://cloud.lambdalabs.com/ssh-keys\n" + "2. Click 'Add SSH key'\n" + f"3. Name it '{expected_key_name}'\n" + "4. Paste your public key from ~/.ssh/kdevops_terraform.pub", + ) + + if not keys: + # No keys found + return ( + False, + "No SSH keys found in your Lambda Labs account.\n" + "Please add an SSH key through the web console or API before proceeding.", + ) + + # Check if expected key exists + key_names = [] + for key in keys: + name = key.get("name") or key.get("key_name") + if name: + key_names.append(name) + if name == expected_key_name: + return (True, f"SSH key '{expected_key_name}' found and ready to use.") + + # Key not found but other keys exist + key_list = "\n - ".join(key_names) + return ( + False, + f"SSH key '{expected_key_name}' not found in your Lambda Labs account.\n" + f"Available SSH keys:\n - {key_list}\n" + f"Either:\n" + f"1. Add a key named '{expected_key_name}' through the web console\n" + f"2. Or update terraform/lambdalabs/kconfigs/Kconfig.identity to use one of the existing keys", + ) + + +def main(): + """Main entry point for SSH key management.""" + if len(sys.argv) < 2: + print("Usage: lambdalabs_ssh_keys.py [args...]") + print("Commands:") + print(" list - List all SSH keys") + print(" check - Check if a specific key exists") + print(" add - Add a new SSH key") + print(" delete - Delete an SSH key") + print(" validate [key_name] - Validate SSH setup for kdevops") + sys.exit(1) + + command = sys.argv[1] + api_key = get_api_key() + + if not api_key: + print("Error: Lambda Labs API key not found", file=sys.stderr) + print("Please configure your API key:", file=sys.stderr) + print( + " python3 scripts/lambdalabs_credentials.py set 'your-api-key'", + file=sys.stderr, + ) + sys.exit(1) + + if command == "list": + keys = list_ssh_keys(api_key) + if keys is None: + print("Failed to list SSH keys - API may not support this feature") + sys.exit(1) + elif not keys: + print("No SSH keys found") + else: + print("SSH Keys:") + for key in keys: + if isinstance(key, dict): + name = key.get("name") or key.get("key_name") or "Unknown" + key_id = key.get("id", "") + fingerprint = key.get("fingerprint", "") + print(f" - Name: {name}") + if key_id and key_id != name: + print(f" ID: {key_id}") + if fingerprint: + print(f" Fingerprint: {fingerprint}") + # Show all fields for debugging + for k, v in key.items(): + if k not in ["name", "id", "fingerprint", "key_name"]: + print(f" {k}: {v}") + else: + # Key is just a string (name) + print(f" - {key}") + + elif command == "check": + if len(sys.argv) < 3: + print("Usage: lambdalabs_ssh_keys.py check ") + sys.exit(1) + key_name = sys.argv[2] + if check_ssh_key_exists(api_key, key_name): + print(f"SSH key '{key_name}' exists") + else: + print(f"SSH key '{key_name}' not found") + sys.exit(1) + + elif command == "add": + if len(sys.argv) < 4: + print("Usage: lambdalabs_ssh_keys.py add ") + sys.exit(1) + name = sys.argv[2] + key_file = sys.argv[3] + + public_key = read_public_key_file(key_file) + if not public_key: + sys.exit(1) + + if add_ssh_key(api_key, name, public_key): + print(f"Successfully added SSH key '{name}'") + else: + print(f"Failed to add SSH key '{name}'") + sys.exit(1) + + elif command == "delete": + if len(sys.argv) < 3: + print("Usage: lambdalabs_ssh_keys.py delete ") + sys.exit(1) + key_name = sys.argv[2] + + if delete_ssh_key(api_key, key_name): + print(f"Successfully deleted SSH key '{key_name}'") + else: + print(f"Failed to delete SSH key '{key_name}'") + sys.exit(1) + + elif command == "validate": + key_name = sys.argv[2] if len(sys.argv) > 2 else "kdevops-lambdalabs" + success, message = validate_ssh_setup(api_key, key_name) + print(message) + if not success: + sys.exit(1) + + else: + print(f"Unknown command: {command}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/scripts/ssh_config_file_name.py b/scripts/ssh_config_file_name.py new file mode 100755 index 0000000..9363548 --- /dev/null +++ b/scripts/ssh_config_file_name.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: copyleft-next-0.3.1 + +""" +Generate a unique SSH config file name based on the current directory. +This ensures each kdevops instance uses its own SSH config file. +""" + +import hashlib +import os +import sys + + +def get_directory_hash(path: str, length: int = 8) -> str: + """ + Generate a short hash of the directory path. + + Args: + path: Directory path to hash + length: Number of hex characters to use (default 8) + + Returns: + Hex string of specified length + """ + # Get the absolute path to ensure consistency + abs_path = os.path.abspath(path) + + # Create SHA256 hash of the path + hash_obj = hashlib.sha256(abs_path.encode("utf-8")) + + # Return first N characters of the hex digest + return hash_obj.hexdigest()[:length] + + +def generate_ssh_config_filename(base_path: str = "~/.ssh/config_kdevops") -> str: + """ + Generate a unique SSH config filename for the current directory. + + Args: + base_path: Base path for the SSH config file (default ~/.ssh/config_kdevops) + + Returns: + Unique SSH config filename like "~/.ssh/config_kdevops_a1b2c3d4" + """ + cwd = os.getcwd() + dir_hash = get_directory_hash(cwd) + + # Create the unique filename + config_file = f"{base_path}_{dir_hash}" + + return config_file + + +def main(): + """Main entry point.""" + if len(sys.argv) > 1: + if sys.argv[1] == "--help" or sys.argv[1] == "-h": + print("Usage: ssh_config_file_name.py [base_path]") + print() + print("Generate a unique SSH config filename based on current directory.") + print() + print("Options:") + print( + " base_path Base path for SSH config (default: ~/.ssh/config_kdevops)" + ) + print() + print("Examples:") + print(" Default: ~/.ssh/config_kdevops_a1b2c3d4") + print(" Custom: /tmp/ssh_config_a1b2c3d4") + sys.exit(0) + else: + # Use provided base path + print(generate_ssh_config_filename(sys.argv[1])) + else: + print(generate_ssh_config_filename()) + + +if __name__ == "__main__": + main() diff --git a/scripts/update_ssh_config_lambdalabs.py b/scripts/update_ssh_config_lambdalabs.py new file mode 100755 index 0000000..f944465 --- /dev/null +++ b/scripts/update_ssh_config_lambdalabs.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: copyleft-next-0.3.1 +""" +Update SSH config for Lambda Labs instances. +Creates/updates SSH config entries for Lambda Labs cloud instances. +""" + +import sys +import os +from pathlib import Path + + +def update_ssh_config(action, hostname, ip_address, username, config_file, ssh_key, provider_name): + """ + Update SSH configuration file with Lambda Labs instance details. + + Args: + action: 'update' or 'remove' + hostname: Instance hostname + ip_address: Instance IP address + username: SSH username (usually 'ubuntu') + config_file: SSH config file path + ssh_key: Path to SSH private key + provider_name: Provider name for comments + """ + config_file = os.path.expanduser(config_file) + ssh_key = os.path.expanduser(ssh_key) + + # SSH config template for Lambda Labs + ssh_template = f"""# {provider_name} instance +Host {hostname} {ip_address} +\tHostName {ip_address} +\tUser {username} +\tPort 22 +\tIdentityFile {ssh_key} +\tUserKnownHostsFile /dev/null +\tStrictHostKeyChecking no +\tPasswordAuthentication no +\tIdentitiesOnly yes +\tLogLevel FATAL +""" + + if action == "update": + # Remove existing entry if present + remove_from_config(hostname, config_file) + + # Add new entry + with open(config_file, 'a') as f: + f.write(ssh_template) + print(f"✓ Updated SSH config for {hostname} ({ip_address}) in {config_file}") + + elif action == "remove": + remove_from_config(hostname, config_file) + print(f"✓ Removed SSH config for {hostname} from {config_file}") + + else: + print(f"Unknown action: {action}", file=sys.stderr) + sys.exit(1) + + +def remove_from_config(hostname, config_file): + """Remove an entry from SSH config file.""" + if not os.path.exists(config_file): + return + + with open(config_file, 'r') as f: + lines = f.readlines() + + # Find and remove the host block + new_lines = [] + skip = False + for line in lines: + if line.startswith(f"Host {hostname} ") or line.startswith(f"Host {hostname}\t"): + skip = True + elif skip and line.startswith("Host "): + skip = False + + if not skip: + new_lines.append(line) + + with open(config_file, 'w') as f: + f.writelines(new_lines) + + +def main(): + """Main entry point.""" + if len(sys.argv) < 7: + print(f"Usage: {sys.argv[0]} [provider_name]") + print(" action: 'update' or 'remove'") + print(" hostname: Instance hostname") + print(" ip_address: Instance IP address") + print(" username: SSH username") + print(" config_file: SSH config file path") + print(" ssh_key: Path to SSH private key") + print(" provider_name: Optional provider name (default: 'Lambda Labs')") + sys.exit(1) + + action = sys.argv[1] + hostname = sys.argv[2] + ip_address = sys.argv[3] + username = sys.argv[4] + config_file = sys.argv[5] + ssh_key = sys.argv[6] + provider_name = sys.argv[7] if len(sys.argv) > 7 else "Lambda Labs" + + update_ssh_config(action, hostname, ip_address, username, config_file, ssh_key, provider_name) + + +if __name__ == "__main__": + main() \ No newline at end of file -- 2.50.1