gitarsenal-cli 1.6.5 → 1.6.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.6.5",
3
+ "version": "1.6.6",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Alternative CUDA image options for GitArsenal CLI
4
+ These images are more stable and less likely to cause segmentation faults
5
+ """
6
+
7
+ import modal
8
+
9
+ def get_stable_cuda_image():
10
+ """
11
+ Get a stable CUDA image that's less likely to cause segmentation faults
12
+ """
13
+ return modal.Image.from_registry("nvidia/cuda:11.8.0-runtime-ubuntu22.04", add_python="3.11")
14
+
15
+ def get_lightweight_cuda_image():
16
+ """
17
+ Get a lightweight CUDA image for basic GPU operations
18
+ """
19
+ return modal.Image.from_registry("nvidia/cuda:11.8.0-base-ubuntu22.04", add_python="3.11")
20
+
21
+ def get_latest_stable_cuda_image():
22
+ """
23
+ Get the latest stable CUDA image (12.1 instead of 12.4)
24
+ """
25
+ return modal.Image.from_registry("nvidia/cuda:12.1.0-runtime-ubuntu22.04", add_python="3.11")
26
+
27
+ def get_minimal_cuda_image():
28
+ """
29
+ Get a minimal CUDA image with just the essentials
30
+ """
31
+ return modal.Image.from_registry("nvidia/cuda:11.8.0-minimal-ubuntu22.04", add_python="3.11")
32
+
33
+ def get_custom_cuda_image():
34
+ """
35
+ Create a custom CUDA image with specific optimizations
36
+ """
37
+ return (
38
+ modal.Image.from_registry("nvidia/cuda:11.8.0-runtime-ubuntu22.04", add_python="3.11")
39
+ .apt_install(
40
+ "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
41
+ "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
42
+ "gpg", "ca-certificates", "software-properties-common"
43
+ )
44
+ .pip_install("uv", "modal", "requests", "openai")
45
+ .run_commands(
46
+ # SSH setup
47
+ "mkdir -p /var/run/sshd",
48
+ "mkdir -p /root/.ssh",
49
+ "chmod 700 /root/.ssh",
50
+ "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
51
+ "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
52
+ "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
53
+ "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
54
+ "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
55
+ "ssh-keygen -A",
56
+
57
+ # GPU compatibility
58
+ "echo 'export CUDA_VISIBLE_DEVICES=0' >> /root/.bashrc",
59
+ "echo 'export NVIDIA_VISIBLE_DEVICES=all' >> /root/.bashrc",
60
+ "echo 'export NVIDIA_DRIVER_CAPABILITIES=compute,utility' >> /root/.bashrc",
61
+
62
+ # Bash prompt
63
+ "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
64
+ )
65
+ )
66
+
67
+ # Image selection based on use case
68
+ CUDA_IMAGE_OPTIONS = {
69
+ "stable": get_stable_cuda_image,
70
+ "lightweight": get_lightweight_cuda_image,
71
+ "latest": get_latest_stable_cuda_image,
72
+ "minimal": get_minimal_cuda_image,
73
+ "custom": get_custom_cuda_image,
74
+ "default": lambda: modal.Image.debian_slim() # No CUDA, most stable
75
+ }
76
+
77
+ def get_cuda_image(option="default"):
78
+ """
79
+ Get a CUDA image based on the specified option
80
+
81
+ Args:
82
+ option (str): One of "stable", "lightweight", "latest", "minimal", "custom", "default"
83
+
84
+ Returns:
85
+ modal.Image: The selected CUDA image
86
+ """
87
+ if option not in CUDA_IMAGE_OPTIONS:
88
+ print(f"⚠️ Unknown CUDA image option: {option}. Using default.")
89
+ option = "default"
90
+
91
+ return CUDA_IMAGE_OPTIONS[option]()
92
+
93
+ def test_cuda_image_stability(image_func, name):
94
+ """
95
+ Test the stability of a CUDA image
96
+
97
+ Args:
98
+ image_func: Function that returns a modal.Image
99
+ name (str): Name of the image for logging
100
+
101
+ Returns:
102
+ bool: True if image builds successfully
103
+ """
104
+ try:
105
+ print(f"🧪 Testing {name} CUDA image...")
106
+ image = image_func()
107
+ print(f"✅ {name} image created successfully")
108
+ return True
109
+ except Exception as e:
110
+ print(f"❌ {name} image failed: {e}")
111
+ return False
112
+
113
+ if __name__ == "__main__":
114
+ print("🧪 Testing CUDA image stability...")
115
+
116
+ for name, image_func in CUDA_IMAGE_OPTIONS.items():
117
+ test_cuda_image_stability(image_func, name)
118
+
119
+ print("\n📋 CUDA Image Recommendations:")
120
+ print("• For maximum stability: Use 'default' (no CUDA)")
121
+ print("• For basic GPU operations: Use 'stable' (CUDA 11.8 runtime)")
122
+ print("• For minimal GPU support: Use 'minimal' (CUDA 11.8 minimal)")
123
+ print("• For latest features: Use 'latest' (CUDA 12.1 runtime)")
124
+ print("• For custom setup: Use 'custom' (CUDA 11.8 with SSH)")
@@ -51,28 +51,28 @@ try:
51
51
  print(f"✅ Using tokens from proxy server or defaults")
52
52
  except (ImportError, ValueError) as e:
53
53
  # If the module is not available or tokens are invalid, use hardcoded tokens
54
- # print(f"⚠️ Using default tokens")
54
+ print("")
55
55
 
56
56
  # print("🔧 Fixing Modal token (basic implementation)...")
57
57
 
58
58
  # Set environment variables
59
59
  # os.environ["MODAL_TOKEN_ID"] = TOKEN_ID
60
- os.environ["MODAL_TOKEN_SECRET"] = TOKEN_SECRET
61
- print(f"✅ Set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables")
60
+ # os.environ["MODAL_TOKEN_SECRET"] = TOKEN_SECRET
61
+ # print(f"✅ Set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables")
62
62
 
63
- # Create token file
64
- modal_dir = Path.home() / ".modal"
65
- modal_dir.mkdir(exist_ok=True)
66
- token_file = modal_dir / "token.json"
67
- with open(token_file, 'w') as f:
68
- f.write(f'{{"token_id": "{TOKEN_ID}", "token_secret": "{TOKEN_SECRET}"}}')
69
- print(f"✅ Created token file at {token_file}")
63
+ # # Create token file
64
+ # modal_dir = Path.home() / ".modal"
65
+ # modal_dir.mkdir(exist_ok=True)
66
+ # token_file = modal_dir / "token.json"
67
+ # with open(token_file, 'w') as f:
68
+ # f.write(f'{{"token_id": "{TOKEN_ID}", "token_secret": "{TOKEN_SECRET}"}}')
69
+ # print(f"✅ Created token file at {token_file}")
70
70
 
71
- # Create .modalconfig file
72
- modalconfig_file = Path.home() / ".modalconfig"
73
- with open(modalconfig_file, 'w') as f:
74
- f.write(f"token_id = {TOKEN_ID}\n")
75
- f.write(f"token_secret = {TOKEN_SECRET}\n")
76
- print(f"✅ Created .modalconfig file at {modalconfig_file}")
71
+ # # Create .modalconfig file
72
+ # modalconfig_file = Path.home() / ".modalconfig"
73
+ # with open(modalconfig_file, 'w') as f:
74
+ # f.write(f"token_id = {TOKEN_ID}\n")
75
+ # f.write(f"token_secret = {TOKEN_SECRET}\n")
76
+ # print(f"✅ Created .modalconfig file at {modalconfig_file}")
77
77
 
78
- print("\n✅ Done fixing Modal token. Please try your command again.")
78
+ # print("\n✅ Done fixing Modal token. Please try your command again.")
@@ -28,6 +28,7 @@ try:
28
28
  except ImportError:
29
29
  # If the module is not available, use hardcoded tokens
30
30
  # print(f"⚠️ Using default tokens")
31
+ print("")
31
32
 
32
33
  # print("🔧 Advanced Modal Token Fixer")
33
34
 
@@ -118,6 +119,7 @@ try:
118
119
  # print(f"✅ Set tokens via _auth_config")
119
120
  except Exception as e:
120
121
  # print(f"❌ Error setting tokens via _auth_config: {e}")
122
+ print("")
121
123
 
122
124
  try:
123
125
  # Approach 4.2: Set token via set_token() if it exists
@@ -126,7 +128,7 @@ try:
126
128
  # print(f"✅ Set tokens via set_token()")
127
129
  except Exception as e:
128
130
  # print(f"❌ Error setting tokens via set_token(): {e}")
129
-
131
+ print("")
130
132
  try:
131
133
  # Approach 4.3: Set token via Config
132
134
  if hasattr(modal.config, 'Config'):
@@ -135,6 +137,7 @@ try:
135
137
  # print(f"✅ Set tokens via Config")
136
138
  except Exception as e:
137
139
  # print(f"❌ Error setting tokens via Config: {e}")
140
+ print("")
138
141
 
139
142
  # Approach 4.4: Inspect modal.config and try to find token-related attributes
140
143
  # print("\n🔍 Inspecting modal.config for token-related attributes...")
@@ -151,8 +154,10 @@ try:
151
154
  setattr(attr, "token_secret", TOKEN_SECRET)
152
155
  except Exception as e:
153
156
  # print(f" - Error setting tokens in {name}: {e}")
157
+ print("")
154
158
  except Exception as e:
155
159
  # print(f"❌ Error setting tokens in Modal config: {e}")
160
+ print("")
156
161
  except Exception as e:
157
162
  print(f"❌ Error importing Modal: {e}")
158
163
 
@@ -192,6 +197,7 @@ try:
192
197
  setattr(module, func_name, get_token_id)
193
198
  except Exception as e:
194
199
  # print(f" - Error patching {name}.{func_name}: {e}")
200
+ print("")
195
201
  elif "token_secret" in func_name.lower() or "token" in func_name.lower():
196
202
  try:
197
203
  original_func = getattr(module, func_name)
@@ -200,11 +206,12 @@ try:
200
206
  setattr(module, func_name, get_token_secret)
201
207
  except Exception as e:
202
208
  # print(f" - Error patching {name}.{func_name}: {e}")
209
+ print("")
203
210
 
204
211
  # print(f"✅ Monkey-patching completed")
205
212
  except Exception as e:
206
213
  # print(f"❌ Error during monkey-patching: {e}")
207
-
214
+ print("")
208
215
  # Approach 6: Test Modal authentication
209
216
  # print("\n📋 Approach 6: Testing Modal authentication")
210
217
  try:
@@ -968,218 +968,6 @@ def generate_random_password(length=16):
968
968
  password = ''.join(secrets.choice(alphabet) for i in range(length))
969
969
  return password
970
970
 
971
- # First, add the standalone ssh_container function at the module level, before the create_modal_ssh_container function
972
-
973
- # Define a module-level ssh container function
974
- ssh_app = modal.App("ssh-container-app")
975
-
976
- @ssh_app.function(
977
- image=modal.Image.debian_slim()
978
- .apt_install(
979
- "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
980
- "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
981
- "gpg", "ca-certificates", "software-properties-common"
982
- )
983
- .pip_install("uv", "modal", "requests", "openai") # Fast Python package installer and Modal
984
- .run_commands(
985
- # Create SSH directory
986
- "mkdir -p /var/run/sshd",
987
- "mkdir -p /root/.ssh",
988
- "chmod 700 /root/.ssh",
989
-
990
- # Configure SSH server
991
- "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
992
- "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
993
- "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
994
-
995
- # SSH keep-alive settings
996
- "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
997
- "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
998
-
999
- # Generate SSH host keys
1000
- "ssh-keygen -A",
1001
-
1002
- # Install Modal CLI
1003
- "pip install modal",
1004
-
1005
- # Set up a nice bash prompt
1006
- "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
1007
- ),
1008
- timeout=3600, # Default 1 hour timeout
1009
- gpu="a10g", # Default GPU - this will be overridden when called
1010
- cpu=2,
1011
- memory=8192,
1012
- serialized=True,
1013
- )
1014
- def ssh_container_function(ssh_password, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
1015
- import subprocess
1016
- import time
1017
- import os
1018
-
1019
- # Set root password
1020
- subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
1021
-
1022
- # Start SSH service
1023
- subprocess.run(["service", "ssh", "start"], check=True)
1024
-
1025
- # Setup environment
1026
- os.environ['PS1'] = r'\[\e[1;32m\]modal:\[\e[1;34m\]\w\[\e[0m\]$ '
1027
-
1028
- # Set OpenAI API key if provided
1029
- if openai_api_key:
1030
- os.environ['OPENAI_API_KEY'] = openai_api_key
1031
- print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1032
- else:
1033
- print("⚠️ No OpenAI API key provided to container")
1034
-
1035
- # Clone repository if provided
1036
- if repo_url:
1037
- repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
1038
- print(f"📥 Cloning repository: {repo_url}")
1039
-
1040
- try:
1041
- subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
1042
- print(f"✅ Repository cloned successfully: {repo_name_from_url}")
1043
-
1044
- # Change to repository directory
1045
- repo_dir = f"/root/{repo_name_from_url}"
1046
- if os.path.exists(repo_dir):
1047
- os.chdir(repo_dir)
1048
- print(f"📂 Changed to repository directory: {repo_dir}")
1049
-
1050
- except subprocess.CalledProcessError as e:
1051
- print(f"❌ Failed to clone repository: {e}")
1052
-
1053
- # Run setup commands if provided
1054
- if setup_commands:
1055
- print(f"⚙️ Running {len(setup_commands)} setup commands...")
1056
-
1057
- # First, let's check the current directory structure
1058
- print("🔍 Checking current directory structure before running setup commands...")
1059
- try:
1060
- result = subprocess.run("pwd && ls -la", shell=True, check=True,
1061
- capture_output=True, text=True)
1062
- print(f"📂 Current directory: {result.stdout}")
1063
- except subprocess.CalledProcessError as e:
1064
- print(f"⚠️ Could not check directory structure: {e}")
1065
-
1066
- # Define a simple run_command function for SSH container
1067
- def run_command_with_llm_debug(cmd, show_output=True, retry_count=0, max_retries=3):
1068
- """Execute a command with LLM debugging enabled"""
1069
- print(f"🔧 Executing: {cmd}")
1070
- try:
1071
- # Handle special case for source command which doesn't work with subprocess.run
1072
- if cmd.strip().startswith("source ") or " source " in cmd:
1073
- print("⚠️ Detected 'source' command which doesn't work with subprocess.run")
1074
- print("🔄 Converting to bash -c with dot (.) instead of source")
1075
- # Replace source with . (dot) which is the same as source but works in sh
1076
- modified_cmd = cmd.replace("source ", ". ")
1077
- # Wrap in bash -c to ensure it runs in bash
1078
- bash_cmd = f"bash -c '{modified_cmd}'"
1079
- print(f"🔄 Modified command: {bash_cmd}")
1080
- result = subprocess.run(bash_cmd, shell=True, check=True,
1081
- capture_output=True, text=True)
1082
- else:
1083
- result = subprocess.run(cmd, shell=True, check=True,
1084
- capture_output=True, text=True)
1085
-
1086
- if result.stdout and show_output:
1087
- print(f"✅ Output: {result.stdout}")
1088
- return True, result.stdout, ""
1089
- except subprocess.CalledProcessError as e:
1090
- error_output = e.stderr if e.stderr else str(e)
1091
- print(f"❌ Command failed: {e}")
1092
- print(f"❌ Error: {error_output}")
1093
-
1094
- # Call OpenAI for debugging
1095
- print("🔍 Attempting to debug the failed command with OpenAI...")
1096
- try:
1097
- # Get the current directory for context
1098
- current_dir = os.getcwd()
1099
-
1100
- # Call OpenAI for debugging
1101
- print(f"🔍 DEBUG: About to call call_openai_for_debug...")
1102
- print(f"🔍 DEBUG: Command: {cmd}")
1103
- print(f"🔍 DEBUG: Error output length: {len(error_output)}")
1104
- print(f"🔍 DEBUG: Current directory: {current_dir}")
1105
-
1106
- # Get the API key from environment or use the one that was fetched earlier
1107
- api_key = os.environ.get("OPENAI_API_KEY")
1108
- fix_command = call_openai_for_debug(cmd, error_output, api_key=api_key, current_dir=current_dir)
1109
-
1110
- print(f"🔍 DEBUG: call_openai_for_debug returned: {fix_command}")
1111
-
1112
- if fix_command:
1113
- print(f"🔧 OpenAI suggested fix command: {fix_command}")
1114
-
1115
- # Run the fix command
1116
- print(f"🔄 Running suggested fix command: {fix_command}")
1117
- try:
1118
- fix_result = subprocess.run(fix_command, shell=True, check=True,
1119
- capture_output=True, text=True)
1120
- if fix_result.stdout:
1121
- print(f"✅ Fix command output: {fix_result.stdout}")
1122
-
1123
- # Retry the original command
1124
- print(f"🔄 Retrying original command: {cmd}")
1125
- return run_command_with_llm_debug(cmd, show_output, retry_count + 1, max_retries)
1126
- except subprocess.CalledProcessError as fix_e:
1127
- print(f"❌ Fix command also failed: {fix_e}")
1128
- return False, "", error_output
1129
- else:
1130
- print("❌ No fix suggested by OpenAI")
1131
- return False, "", error_output
1132
-
1133
- except Exception as debug_e:
1134
- print(f"❌ LLM debugging failed: {debug_e}")
1135
- return False, "", error_output
1136
-
1137
- for i, cmd in enumerate(setup_commands, 1):
1138
- print(f"📋 Executing command {i}/{len(setup_commands)}: {cmd}")
1139
-
1140
- # Check if this is a cd command and if the directory exists
1141
- if cmd.strip().startswith("cd "):
1142
- cd_parts = cmd.split(None, 1)
1143
- if len(cd_parts) >= 2:
1144
- target_dir = cd_parts[1].strip('"\'')
1145
- print(f"🔍 Checking if directory exists: {target_dir}")
1146
- try:
1147
- check_result = subprocess.run(f"test -d '{target_dir}'", shell=True,
1148
- capture_output=True, text=True)
1149
- if check_result.returncode != 0:
1150
- print(f"⚠️ Directory does not exist: {target_dir}")
1151
- print(f"🔍 Current directory contents:")
1152
- subprocess.run("pwd && ls -la", shell=True, check=False)
1153
-
1154
- # Try to find similar directories
1155
- print(f"🔍 Looking for similar directories...")
1156
- subprocess.run("find . -type d -name '*llama*' -o -name '*nano*' 2>/dev/null | head -10", shell=True, check=False)
1157
- except Exception as e:
1158
- print(f"⚠️ Could not check directory: {e}")
1159
-
1160
- success, stdout, stderr = run_command_with_llm_debug(cmd, show_output=True)
1161
- if not success:
1162
- print(f"⚠️ Command {i} failed, but continuing with remaining commands...")
1163
-
1164
- # If this was a cd command that failed, try to understand the directory structure
1165
- if cmd.strip().startswith("cd ") and "No such file or directory" in stderr:
1166
- print(f"🔍 Analyzing directory structure after failed cd command...")
1167
- subprocess.run("pwd && ls -la && echo '--- Parent directory ---' && ls -la ..", shell=True, check=False)
1168
-
1169
- # Get container info
1170
- print("🔍 Container started successfully!")
1171
- print(f"🆔 Container ID: {os.environ.get('MODAL_TASK_ID', 'unknown')}")
1172
-
1173
- # Keep the container running
1174
- while True:
1175
- time.sleep(30)
1176
- # Check if SSH service is still running
1177
- try:
1178
- subprocess.run(["service", "ssh", "status"], check=True,
1179
- capture_output=True)
1180
- except subprocess.CalledProcessError:
1181
- print("⚠️ SSH service stopped, restarting...")
1182
- subprocess.run(["service", "ssh", "start"], check=True)
1183
971
 
1184
972
  # Now modify the create_modal_ssh_container function to use the standalone ssh_container_function
1185
973
  def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_commands=None,
@@ -1396,7 +1184,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1396
1184
  try:
1397
1185
  print("📦 Building SSH-enabled image...")
1398
1186
  ssh_image = (
1399
- modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1187
+ # modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1188
+ modal.Image.debian_slim()
1400
1189
  .apt_install(
1401
1190
  "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
1402
1191
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify CUDA setup in GitArsenal containers
4
+ """
5
+
6
+ import subprocess
7
+ import sys
8
+ import os
9
+
10
+ def test_cuda_basic():
11
+ """Test basic CUDA functionality"""
12
+ print("🧪 Testing basic CUDA functionality...")
13
+
14
+ try:
15
+ # Test nvidia-smi
16
+ result = subprocess.run(["nvidia-smi"], capture_output=True, text=True, timeout=30)
17
+ if result.returncode == 0:
18
+ print("✅ nvidia-smi working")
19
+ print(f"Output: {result.stdout[:200]}...")
20
+ else:
21
+ print(f"❌ nvidia-smi failed: {result.stderr}")
22
+ return False
23
+
24
+ # Test nvcc
25
+ result = subprocess.run(["nvcc", "--version"], capture_output=True, text=True, timeout=30)
26
+ if result.returncode == 0:
27
+ print("✅ nvcc available")
28
+ print(f"Version: {result.stdout.split('release')[0].strip()}")
29
+ else:
30
+ print(f"❌ nvcc failed: {result.stderr}")
31
+ return False
32
+
33
+ return True
34
+
35
+ except subprocess.TimeoutExpired:
36
+ print("❌ CUDA test timed out")
37
+ return False
38
+ except Exception as e:
39
+ print(f"❌ CUDA test error: {e}")
40
+ return False
41
+
42
+ def test_cupy_import():
43
+ """Test cupy import"""
44
+ print("🧪 Testing cupy import...")
45
+
46
+ try:
47
+ import cupy as cp
48
+ print("✅ cupy imported successfully")
49
+
50
+ # Test basic cupy functionality
51
+ x = cp.array([1, 2, 3, 4, 5])
52
+ y = cp.square(x)
53
+ print(f"✅ cupy basic operation: {y}")
54
+
55
+ return True
56
+
57
+ except ImportError as e:
58
+ print(f"❌ cupy import failed: {e}")
59
+ return False
60
+ except Exception as e:
61
+ print(f"❌ cupy test error: {e}")
62
+ return False
63
+
64
+ def test_gpu_environment():
65
+ """Test GPU environment variables"""
66
+ print("🧪 Testing GPU environment variables...")
67
+
68
+ gpu_vars = {
69
+ 'CUDA_VISIBLE_DEVICES': '0',
70
+ 'NVIDIA_VISIBLE_DEVICES': 'all',
71
+ 'NVIDIA_DRIVER_CAPABILITIES': 'compute,utility'
72
+ }
73
+
74
+ for var, value in gpu_vars.items():
75
+ os.environ[var] = value
76
+ print(f"✅ Set {var}={value}")
77
+
78
+ # Verify they're set
79
+ for var, expected_value in gpu_vars.items():
80
+ actual_value = os.environ.get(var)
81
+ if actual_value == expected_value:
82
+ print(f"✅ {var} correctly set to {actual_value}")
83
+ else:
84
+ print(f"❌ {var} not set correctly. Expected: {expected_value}, Got: {actual_value}")
85
+ return False
86
+
87
+ return True
88
+
89
+ def test_modal_cuda_image():
90
+ """Test Modal CUDA image creation"""
91
+ print("🧪 Testing Modal CUDA image creation...")
92
+
93
+ try:
94
+ import modal
95
+
96
+ # Test the same image configuration as the SSH container
97
+ image = (
98
+ modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
99
+ .pip_install("cupy-cuda12x", "setuptools", "uv", "modal", "requests", "openai")
100
+ )
101
+
102
+ print("✅ Modal CUDA image created successfully")
103
+ return True
104
+
105
+ except ImportError as e:
106
+ print(f"❌ Modal import failed: {e}")
107
+ return False
108
+ except Exception as e:
109
+ print(f"❌ Modal CUDA image creation failed: {e}")
110
+ return False
111
+
112
+ def main():
113
+ """Run all CUDA tests"""
114
+ print("🧪 GitArsenal CUDA Setup Tests")
115
+ print("=" * 50)
116
+
117
+ tests = [
118
+ ("GPU Environment", test_gpu_environment),
119
+ ("Modal CUDA Image", test_modal_cuda_image),
120
+ ("Basic CUDA", test_cuda_basic),
121
+ ("Cupy Import", test_cupy_import),
122
+ ]
123
+
124
+ passed = 0
125
+ total = len(tests)
126
+
127
+ for test_name, test_func in tests:
128
+ print(f"\n🔍 Running: {test_name}")
129
+ try:
130
+ if test_func():
131
+ passed += 1
132
+ print(f"✅ {test_name} PASSED")
133
+ else:
134
+ print(f"❌ {test_name} FAILED")
135
+ except Exception as e:
136
+ print(f"❌ {test_name} ERROR: {e}")
137
+
138
+ print(f"\n📊 Test Results: {passed}/{total} tests passed")
139
+
140
+ if passed == total:
141
+ print("🎉 All CUDA tests passed! The SSH container should work correctly.")
142
+ return 0
143
+ else:
144
+ print("⚠️ Some CUDA tests failed. The SSH container may have issues.")
145
+ return 1
146
+
147
+ if __name__ == "__main__":
148
+ sys.exit(main())
@@ -968,218 +968,6 @@ def generate_random_password(length=16):
968
968
  password = ''.join(secrets.choice(alphabet) for i in range(length))
969
969
  return password
970
970
 
971
- # First, add the standalone ssh_container function at the module level, before the create_modal_ssh_container function
972
-
973
- # Define a module-level ssh container function
974
- ssh_app = modal.App("ssh-container-app")
975
-
976
- @ssh_app.function(
977
- image=modal.Image.debian_slim()
978
- .apt_install(
979
- "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
980
- "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
981
- "gpg", "ca-certificates", "software-properties-common"
982
- )
983
- .pip_install("uv", "modal", "requests", "openai") # Fast Python package installer and Modal
984
- .run_commands(
985
- # Create SSH directory
986
- "mkdir -p /var/run/sshd",
987
- "mkdir -p /root/.ssh",
988
- "chmod 700 /root/.ssh",
989
-
990
- # Configure SSH server
991
- "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
992
- "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
993
- "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
994
-
995
- # SSH keep-alive settings
996
- "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
997
- "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
998
-
999
- # Generate SSH host keys
1000
- "ssh-keygen -A",
1001
-
1002
- # Install Modal CLI
1003
- "pip install modal",
1004
-
1005
- # Set up a nice bash prompt
1006
- "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
1007
- ),
1008
- timeout=3600, # Default 1 hour timeout
1009
- gpu="a10g", # Default GPU - this will be overridden when called
1010
- cpu=2,
1011
- memory=8192,
1012
- serialized=True,
1013
- )
1014
- def ssh_container_function(ssh_password, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
1015
- import subprocess
1016
- import time
1017
- import os
1018
-
1019
- # Set root password
1020
- subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
1021
-
1022
- # Start SSH service
1023
- subprocess.run(["service", "ssh", "start"], check=True)
1024
-
1025
- # Setup environment
1026
- os.environ['PS1'] = r'\[\e[1;32m\]modal:\[\e[1;34m\]\w\[\e[0m\]$ '
1027
-
1028
- # Set OpenAI API key if provided
1029
- if openai_api_key:
1030
- os.environ['OPENAI_API_KEY'] = openai_api_key
1031
- print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1032
- else:
1033
- print("⚠️ No OpenAI API key provided to container")
1034
-
1035
- # Clone repository if provided
1036
- if repo_url:
1037
- repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
1038
- print(f"📥 Cloning repository: {repo_url}")
1039
-
1040
- try:
1041
- subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
1042
- print(f"✅ Repository cloned successfully: {repo_name_from_url}")
1043
-
1044
- # Change to repository directory
1045
- repo_dir = f"/root/{repo_name_from_url}"
1046
- if os.path.exists(repo_dir):
1047
- os.chdir(repo_dir)
1048
- print(f"📂 Changed to repository directory: {repo_dir}")
1049
-
1050
- except subprocess.CalledProcessError as e:
1051
- print(f"❌ Failed to clone repository: {e}")
1052
-
1053
- # Run setup commands if provided
1054
- if setup_commands:
1055
- print(f"⚙️ Running {len(setup_commands)} setup commands...")
1056
-
1057
- # First, let's check the current directory structure
1058
- print("🔍 Checking current directory structure before running setup commands...")
1059
- try:
1060
- result = subprocess.run("pwd && ls -la", shell=True, check=True,
1061
- capture_output=True, text=True)
1062
- print(f"📂 Current directory: {result.stdout}")
1063
- except subprocess.CalledProcessError as e:
1064
- print(f"⚠️ Could not check directory structure: {e}")
1065
-
1066
- # Define a simple run_command function for SSH container
1067
- def run_command_with_llm_debug(cmd, show_output=True, retry_count=0, max_retries=3):
1068
- """Execute a command with LLM debugging enabled"""
1069
- print(f"🔧 Executing: {cmd}")
1070
- try:
1071
- # Handle special case for source command which doesn't work with subprocess.run
1072
- if cmd.strip().startswith("source ") or " source " in cmd:
1073
- print("⚠️ Detected 'source' command which doesn't work with subprocess.run")
1074
- print("🔄 Converting to bash -c with dot (.) instead of source")
1075
- # Replace source with . (dot) which is the same as source but works in sh
1076
- modified_cmd = cmd.replace("source ", ". ")
1077
- # Wrap in bash -c to ensure it runs in bash
1078
- bash_cmd = f"bash -c '{modified_cmd}'"
1079
- print(f"🔄 Modified command: {bash_cmd}")
1080
- result = subprocess.run(bash_cmd, shell=True, check=True,
1081
- capture_output=True, text=True)
1082
- else:
1083
- result = subprocess.run(cmd, shell=True, check=True,
1084
- capture_output=True, text=True)
1085
-
1086
- if result.stdout and show_output:
1087
- print(f"✅ Output: {result.stdout}")
1088
- return True, result.stdout, ""
1089
- except subprocess.CalledProcessError as e:
1090
- error_output = e.stderr if e.stderr else str(e)
1091
- print(f"❌ Command failed: {e}")
1092
- print(f"❌ Error: {error_output}")
1093
-
1094
- # Call OpenAI for debugging
1095
- print("🔍 Attempting to debug the failed command with OpenAI...")
1096
- try:
1097
- # Get the current directory for context
1098
- current_dir = os.getcwd()
1099
-
1100
- # Call OpenAI for debugging
1101
- print(f"🔍 DEBUG: About to call call_openai_for_debug...")
1102
- print(f"🔍 DEBUG: Command: {cmd}")
1103
- print(f"🔍 DEBUG: Error output length: {len(error_output)}")
1104
- print(f"🔍 DEBUG: Current directory: {current_dir}")
1105
-
1106
- # Get the API key from environment or use the one that was fetched earlier
1107
- api_key = os.environ.get("OPENAI_API_KEY")
1108
- fix_command = call_openai_for_debug(cmd, error_output, api_key=api_key, current_dir=current_dir)
1109
-
1110
- print(f"🔍 DEBUG: call_openai_for_debug returned: {fix_command}")
1111
-
1112
- if fix_command:
1113
- print(f"🔧 OpenAI suggested fix command: {fix_command}")
1114
-
1115
- # Run the fix command
1116
- print(f"🔄 Running suggested fix command: {fix_command}")
1117
- try:
1118
- fix_result = subprocess.run(fix_command, shell=True, check=True,
1119
- capture_output=True, text=True)
1120
- if fix_result.stdout:
1121
- print(f"✅ Fix command output: {fix_result.stdout}")
1122
-
1123
- # Retry the original command
1124
- print(f"🔄 Retrying original command: {cmd}")
1125
- return run_command_with_llm_debug(cmd, show_output, retry_count + 1, max_retries)
1126
- except subprocess.CalledProcessError as fix_e:
1127
- print(f"❌ Fix command also failed: {fix_e}")
1128
- return False, "", error_output
1129
- else:
1130
- print("❌ No fix suggested by OpenAI")
1131
- return False, "", error_output
1132
-
1133
- except Exception as debug_e:
1134
- print(f"❌ LLM debugging failed: {debug_e}")
1135
- return False, "", error_output
1136
-
1137
- for i, cmd in enumerate(setup_commands, 1):
1138
- print(f"📋 Executing command {i}/{len(setup_commands)}: {cmd}")
1139
-
1140
- # Check if this is a cd command and if the directory exists
1141
- if cmd.strip().startswith("cd "):
1142
- cd_parts = cmd.split(None, 1)
1143
- if len(cd_parts) >= 2:
1144
- target_dir = cd_parts[1].strip('"\'')
1145
- print(f"🔍 Checking if directory exists: {target_dir}")
1146
- try:
1147
- check_result = subprocess.run(f"test -d '{target_dir}'", shell=True,
1148
- capture_output=True, text=True)
1149
- if check_result.returncode != 0:
1150
- print(f"⚠️ Directory does not exist: {target_dir}")
1151
- print(f"🔍 Current directory contents:")
1152
- subprocess.run("pwd && ls -la", shell=True, check=False)
1153
-
1154
- # Try to find similar directories
1155
- print(f"🔍 Looking for similar directories...")
1156
- subprocess.run("find . -type d -name '*llama*' -o -name '*nano*' 2>/dev/null | head -10", shell=True, check=False)
1157
- except Exception as e:
1158
- print(f"⚠️ Could not check directory: {e}")
1159
-
1160
- success, stdout, stderr = run_command_with_llm_debug(cmd, show_output=True)
1161
- if not success:
1162
- print(f"⚠️ Command {i} failed, but continuing with remaining commands...")
1163
-
1164
- # If this was a cd command that failed, try to understand the directory structure
1165
- if cmd.strip().startswith("cd ") and "No such file or directory" in stderr:
1166
- print(f"🔍 Analyzing directory structure after failed cd command...")
1167
- subprocess.run("pwd && ls -la && echo '--- Parent directory ---' && ls -la ..", shell=True, check=False)
1168
-
1169
- # Get container info
1170
- print("🔍 Container started successfully!")
1171
- print(f"🆔 Container ID: {os.environ.get('MODAL_TASK_ID', 'unknown')}")
1172
-
1173
- # Keep the container running
1174
- while True:
1175
- time.sleep(30)
1176
- # Check if SSH service is still running
1177
- try:
1178
- subprocess.run(["service", "ssh", "status"], check=True,
1179
- capture_output=True)
1180
- except subprocess.CalledProcessError:
1181
- print("⚠️ SSH service stopped, restarting...")
1182
- subprocess.run(["service", "ssh", "start"], check=True)
1183
971
 
1184
972
  # Now modify the create_modal_ssh_container function to use the standalone ssh_container_function
1185
973
  def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_commands=None,
@@ -1396,7 +1184,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1396
1184
  try:
1397
1185
  print("📦 Building SSH-enabled image...")
1398
1186
  ssh_image = (
1399
- modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1187
+ # modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1188
+ modal.Image.debian_slim()
1400
1189
  .apt_install(
1401
1190
  "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
1402
1191
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",